koffi 2.5.21-beta.1 → 2.5.21-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/koffi/darwin_arm64/koffi.node +0 -0
- package/build/koffi/darwin_x64/koffi.node +0 -0
- package/build/koffi/freebsd_arm64/koffi.node +0 -0
- package/build/koffi/freebsd_ia32/koffi.node +0 -0
- package/build/koffi/freebsd_x64/koffi.node +0 -0
- package/build/koffi/linux_arm32hf/koffi.node +0 -0
- package/build/koffi/linux_arm64/koffi.node +0 -0
- package/build/koffi/linux_ia32/koffi.node +0 -0
- package/build/koffi/linux_riscv64hf64/koffi.node +0 -0
- package/build/koffi/linux_x64/koffi.node +0 -0
- package/build/koffi/openbsd_ia32/koffi.node +0 -0
- package/build/koffi/openbsd_x64/koffi.node +0 -0
- package/build/koffi/win32_arm64/koffi.node +0 -0
- package/build/koffi/win32_ia32/koffi.node +0 -0
- package/build/koffi/win32_x64/koffi.node +0 -0
- package/doc/contribute.md +10 -2
- package/package.json +1 -1
- package/src/core/libcc/libcc.hh +8 -0
- package/src/index.d.ts +9 -1
- package/src/index.js +1 -1
- package/src/koffi/src/call.cc +55 -55
- package/src/koffi/src/call.hh +10 -8
- package/src/koffi/src/ffi.cc +84 -3
- package/src/koffi/src/ffi.hh +5 -0
- package/src/koffi/src/util.cc +210 -0
- package/src/koffi/src/util.hh +3 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/doc/contribute.md
CHANGED
|
@@ -129,16 +129,24 @@ node qemu.js info debian_x64
|
|
|
129
129
|
|
|
130
130
|
## Making a release
|
|
131
131
|
|
|
132
|
-
First,
|
|
132
|
+
First, you must update the code in three steps:
|
|
133
|
+
|
|
134
|
+
- Change the version numbers in `package.json` (version and stable for stable releases)
|
|
135
|
+
- Add an entry to `CHANGELOG.md` to summarize the changes since last release
|
|
136
|
+
- Commit theses changes with the message *Bump Koffi to X.Y.Z*
|
|
137
|
+
|
|
138
|
+
Once this is done, you can publish a new release with the following commands:
|
|
133
139
|
|
|
134
140
|
```sh
|
|
135
|
-
node qemu.js test
|
|
141
|
+
node qemu.js test # If not done before
|
|
136
142
|
node qemu.js dist
|
|
137
143
|
|
|
138
144
|
cd build/dist
|
|
139
145
|
npm publish
|
|
140
146
|
```
|
|
141
147
|
|
|
148
|
+
Some platforms are emulated so this can take a few minutes until the pre-built binaries are ready. Go grab a cup of coffee, come back and execute the `npm publish` command!
|
|
149
|
+
|
|
142
150
|
## Code style
|
|
143
151
|
|
|
144
152
|
Koffi is programmed in a mix of C++ and assembly code (architecture-specific code). It uses [node-addon-api](https://github.com/nodejs/node-addon-api) (C++ N-API wrapper) to interact with Node.js.
|
package/package.json
CHANGED
package/src/core/libcc/libcc.hh
CHANGED
|
@@ -976,6 +976,8 @@ public:
|
|
|
976
976
|
void *Resize(void *ptr, Size old_size, Size new_size, unsigned int flags = 0) override;
|
|
977
977
|
void Release(const void *ptr, Size size) override;
|
|
978
978
|
|
|
979
|
+
bool IsUsed() const { return list.next; }
|
|
980
|
+
|
|
979
981
|
private:
|
|
980
982
|
static Bucket *PointerToBucket(void *ptr);
|
|
981
983
|
};
|
|
@@ -1002,6 +1004,12 @@ public:
|
|
|
1002
1004
|
void *Resize(void *ptr, Size old_size, Size new_size, unsigned int flags = 0) override;
|
|
1003
1005
|
void Release(const void *ptr, Size size) override;
|
|
1004
1006
|
|
|
1007
|
+
bool IsUsed() const
|
|
1008
|
+
{
|
|
1009
|
+
LinkedAllocator *alloc = ((BlockAllocatorBase *)this)->GetAllocator();
|
|
1010
|
+
return alloc->IsUsed();
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1005
1013
|
protected:
|
|
1006
1014
|
void CopyFrom(BlockAllocatorBase *other);
|
|
1007
1015
|
void ForgetCurrentBlock();
|
package/src/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ declare module 'koffi' {
|
|
|
28
28
|
|
|
29
29
|
type PrimitiveKind = 'Void' | 'Bool' | 'Int8' | 'UInt8' | 'Int16' | 'Int16S' | 'UInt16' | 'UInt16S' |
|
|
30
30
|
'Int32' | 'Int32S' | 'UInt32' | 'UInt32S' | 'Int64' | 'Int64S' | 'UInt64' | 'UInt64S' |
|
|
31
|
-
'String' | 'String16' | 'Pointer' | 'Record'
|
|
31
|
+
'String' | 'String16' | 'Pointer' | 'Record' | 'Union' | 'Array' | 'Float32' | 'Float64' |
|
|
32
32
|
'Prototype' | 'Callback';
|
|
33
33
|
type ArrayHint = 'Array' | 'Typed' | 'String';
|
|
34
34
|
|
|
@@ -80,6 +80,8 @@ declare module 'koffi' {
|
|
|
80
80
|
thiscall(definition: string): KoffiFunction;
|
|
81
81
|
thiscall(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
|
|
82
82
|
|
|
83
|
+
symbol(name: string, type: TypeSpec): any;
|
|
84
|
+
|
|
83
85
|
unload(): void;
|
|
84
86
|
}
|
|
85
87
|
|
|
@@ -129,6 +131,12 @@ declare module 'koffi' {
|
|
|
129
131
|
export function decode(value: any, offset: number, type: TypeSpec): any;
|
|
130
132
|
export function decode(value: any, offset: number, type: TypeSpec, len: number): any;
|
|
131
133
|
export function address(value: any): bigint;
|
|
134
|
+
export function encode(ref: any, type: TypeSpec): void;
|
|
135
|
+
export function encode(ref: any, type: TypeSpec, value: any): void;
|
|
136
|
+
export function encode(ref: any, type: TypeSpec, value: any, len: number): void;
|
|
137
|
+
export function encode(ref: any, offset: number, type: TypeSpec): void;
|
|
138
|
+
export function encode(ref: any, offset: number, type: TypeSpec, value: any): void;
|
|
139
|
+
export function encode(ref: any, offset: number, type: TypeSpec, value: any, len: number): void;
|
|
132
140
|
|
|
133
141
|
export function sizeof(type: TypeSpec): number;
|
|
134
142
|
export function alignof(type: TypeSpec): number;
|
package/src/index.js
CHANGED
|
@@ -378,7 +378,7 @@ var require_package = __commonJS({
|
|
|
378
378
|
"build/dist/src/koffi/package.json"(exports2, module2) {
|
|
379
379
|
module2.exports = {
|
|
380
380
|
name: "koffi",
|
|
381
|
-
version: "2.5.21-beta.
|
|
381
|
+
version: "2.5.21-beta.3",
|
|
382
382
|
stable: "2.5.20",
|
|
383
383
|
description: "Fast and simple C FFI (foreign function interface) for Node.js",
|
|
384
384
|
keywords: [
|
package/src/koffi/src/call.cc
CHANGED
|
@@ -1165,6 +1165,61 @@ Size CallData::PushIndirectString(Napi::Array array, const TypeInfo *ref, uint8_
|
|
|
1165
1165
|
}
|
|
1166
1166
|
}
|
|
1167
1167
|
|
|
1168
|
+
void *CallData::ReserveTrampoline(const FunctionInfo *proto, Napi::Function func)
|
|
1169
|
+
{
|
|
1170
|
+
if (!InitAsyncBroker(env, instance)) [[unlikely]]
|
|
1171
|
+
return nullptr;
|
|
1172
|
+
|
|
1173
|
+
int16_t idx;
|
|
1174
|
+
{
|
|
1175
|
+
std::lock_guard<std::mutex> lock(shared.mutex);
|
|
1176
|
+
|
|
1177
|
+
if (!shared.available.len) [[unlikely]] {
|
|
1178
|
+
ThrowError<Napi::Error>(env, "Too many callbacks are in use (max = %1)", MaxTrampolines);
|
|
1179
|
+
return env.Null();
|
|
1180
|
+
}
|
|
1181
|
+
if (!used_trampolines.Available()) [[unlikely]] {
|
|
1182
|
+
ThrowError<Napi::Error>(env, "This call uses too many temporary callbacks (max = %1)", RG_LEN(used_trampolines.data));
|
|
1183
|
+
return env.Null();
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
idx = shared.available.data[--shared.available.len];
|
|
1187
|
+
used_trampolines.Append(idx);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
TrampolineInfo *trampoline = &shared.trampolines[idx];
|
|
1191
|
+
|
|
1192
|
+
trampoline->instance = instance;
|
|
1193
|
+
trampoline->proto = proto;
|
|
1194
|
+
trampoline->func.Reset(func, 1);
|
|
1195
|
+
trampoline->recv.Reset();
|
|
1196
|
+
trampoline->generation = (int32_t)mem->generation;
|
|
1197
|
+
|
|
1198
|
+
void *ptr = GetTrampoline(idx, proto);
|
|
1199
|
+
|
|
1200
|
+
return ptr;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
void CallData::DumpForward(const FunctionInfo *func) const
|
|
1204
|
+
{
|
|
1205
|
+
PrintLn(stderr, "%!..+---- %1 (%2) ----%!0", func->name, CallConventionNames[(int)func->convention]);
|
|
1206
|
+
|
|
1207
|
+
if (func->parameters.len) {
|
|
1208
|
+
PrintLn(stderr, "Parameters:");
|
|
1209
|
+
for (Size i = 0; i < func->parameters.len; i++) {
|
|
1210
|
+
const ParameterInfo ¶m = func->parameters[i];
|
|
1211
|
+
PrintLn(stderr, " %1 = %2 (%3)", i, param.type->name, FmtMemSize(param.type->size));
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
PrintLn(stderr, "Return: %1 (%2)", func->ret.type->name, FmtMemSize(func->ret.type->size));
|
|
1215
|
+
|
|
1216
|
+
Span<const uint8_t> stack = MakeSpan(mem->stack.end(), old_stack_mem.end() - mem->stack.end());
|
|
1217
|
+
Span<const uint8_t> heap = MakeSpan(old_heap_mem.ptr, mem->heap.ptr - old_heap_mem.ptr);
|
|
1218
|
+
|
|
1219
|
+
DumpMemory("Stack", stack);
|
|
1220
|
+
DumpMemory("Heap", heap);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1168
1223
|
static inline Napi::Value GetReferenceValue(Napi::Env env, napi_ref ref)
|
|
1169
1224
|
{
|
|
1170
1225
|
napi_value value;
|
|
@@ -1234,59 +1289,4 @@ void CallData::PopOutArguments()
|
|
|
1234
1289
|
}
|
|
1235
1290
|
}
|
|
1236
1291
|
|
|
1237
|
-
void *CallData::ReserveTrampoline(const FunctionInfo *proto, Napi::Function func)
|
|
1238
|
-
{
|
|
1239
|
-
if (!InitAsyncBroker(env, instance)) [[unlikely]]
|
|
1240
|
-
return nullptr;
|
|
1241
|
-
|
|
1242
|
-
int16_t idx;
|
|
1243
|
-
{
|
|
1244
|
-
std::lock_guard<std::mutex> lock(shared.mutex);
|
|
1245
|
-
|
|
1246
|
-
if (!shared.available.len) [[unlikely]] {
|
|
1247
|
-
ThrowError<Napi::Error>(env, "Too many callbacks are in use (max = %1)", MaxTrampolines);
|
|
1248
|
-
return env.Null();
|
|
1249
|
-
}
|
|
1250
|
-
if (!used_trampolines.Available()) [[unlikely]] {
|
|
1251
|
-
ThrowError<Napi::Error>(env, "This call uses too many temporary callbacks (max = %1)", RG_LEN(used_trampolines.data));
|
|
1252
|
-
return env.Null();
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
idx = shared.available.data[--shared.available.len];
|
|
1256
|
-
used_trampolines.Append(idx);
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
TrampolineInfo *trampoline = &shared.trampolines[idx];
|
|
1260
|
-
|
|
1261
|
-
trampoline->instance = instance;
|
|
1262
|
-
trampoline->proto = proto;
|
|
1263
|
-
trampoline->func.Reset(func, 1);
|
|
1264
|
-
trampoline->recv.Reset();
|
|
1265
|
-
trampoline->generation = (int32_t)mem->generation;
|
|
1266
|
-
|
|
1267
|
-
void *ptr = GetTrampoline(idx, proto);
|
|
1268
|
-
|
|
1269
|
-
return ptr;
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
void CallData::DumpForward(const FunctionInfo *func) const
|
|
1273
|
-
{
|
|
1274
|
-
PrintLn(stderr, "%!..+---- %1 (%2) ----%!0", func->name, CallConventionNames[(int)func->convention]);
|
|
1275
|
-
|
|
1276
|
-
if (func->parameters.len) {
|
|
1277
|
-
PrintLn(stderr, "Parameters:");
|
|
1278
|
-
for (Size i = 0; i < func->parameters.len; i++) {
|
|
1279
|
-
const ParameterInfo ¶m = func->parameters[i];
|
|
1280
|
-
PrintLn(stderr, " %1 = %2 (%3)", i, param.type->name, FmtMemSize(param.type->size));
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
PrintLn(stderr, "Return: %1 (%2)", func->ret.type->name, FmtMemSize(func->ret.type->size));
|
|
1284
|
-
|
|
1285
|
-
Span<const uint8_t> stack = MakeSpan(mem->stack.end(), old_stack_mem.end() - mem->stack.end());
|
|
1286
|
-
Span<const uint8_t> heap = MakeSpan(old_heap_mem.ptr, mem->heap.ptr - old_heap_mem.ptr);
|
|
1287
|
-
|
|
1288
|
-
DumpMemory("Stack", stack);
|
|
1289
|
-
DumpMemory("Heap", heap);
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
1292
|
}
|
package/src/koffi/src/call.hh
CHANGED
|
@@ -113,12 +113,6 @@ public:
|
|
|
113
113
|
|
|
114
114
|
void DumpForward(const FunctionInfo *func) const;
|
|
115
115
|
|
|
116
|
-
private:
|
|
117
|
-
template <typename T>
|
|
118
|
-
bool AllocStack(Size size, Size align, T **out_ptr);
|
|
119
|
-
template <typename T = uint8_t>
|
|
120
|
-
T *AllocHeap(Size size, Size align);
|
|
121
|
-
|
|
122
116
|
bool PushString(Napi::Value value, int directions, const char **out_str);
|
|
123
117
|
Size PushStringValue(Napi::Value value, const char **out_str);
|
|
124
118
|
bool PushString16(Napi::Value value, int directions, const char16_t **out_str16);
|
|
@@ -130,9 +124,17 @@ private:
|
|
|
130
124
|
bool PushPointer(Napi::Value value, const TypeInfo *type, int directions, void **out_ptr);
|
|
131
125
|
Size PushIndirectString(Napi::Array array, const TypeInfo *ref, uint8_t **out_ptr);
|
|
132
126
|
|
|
133
|
-
void PopOutArguments();
|
|
134
|
-
|
|
135
127
|
void *ReserveTrampoline(const FunctionInfo *proto, Napi::Function func);
|
|
128
|
+
|
|
129
|
+
BlockAllocator *GetAllocator() { return &call_alloc; }
|
|
130
|
+
|
|
131
|
+
private:
|
|
132
|
+
template <typename T>
|
|
133
|
+
bool AllocStack(Size size, Size align, T **out_ptr);
|
|
134
|
+
template <typename T = uint8_t>
|
|
135
|
+
T *AllocHeap(Size size, Size align);
|
|
136
|
+
|
|
137
|
+
void PopOutArguments();
|
|
136
138
|
};
|
|
137
139
|
|
|
138
140
|
template <typename T>
|
package/src/koffi/src/ffi.cc
CHANGED
|
@@ -1558,6 +1558,43 @@ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConve
|
|
|
1558
1558
|
return wrapper;
|
|
1559
1559
|
}
|
|
1560
1560
|
|
|
1561
|
+
static Napi::Value FindSymbol(const Napi::CallbackInfo &info)
|
|
1562
|
+
{
|
|
1563
|
+
Napi::Env env = info.Env();
|
|
1564
|
+
InstanceData *instance = env.GetInstanceData<InstanceData>();
|
|
1565
|
+
LibraryHolder *lib = (LibraryHolder *)info.Data();
|
|
1566
|
+
|
|
1567
|
+
if (info.Length() < 2) {
|
|
1568
|
+
ThrowError<Napi::TypeError>(env, "Expected 2, got %1", info.Length());
|
|
1569
|
+
return env.Null();
|
|
1570
|
+
}
|
|
1571
|
+
if (!info[0].IsString()) {
|
|
1572
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for name, expected string", GetValueType(instance, info[0]));
|
|
1573
|
+
return env.Null();
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
std::string name = info[0].As<Napi::String>();
|
|
1577
|
+
|
|
1578
|
+
const TypeInfo *type = ResolveType(info[1]);
|
|
1579
|
+
if (!type)
|
|
1580
|
+
return env.Null();
|
|
1581
|
+
|
|
1582
|
+
#ifdef _WIN32
|
|
1583
|
+
void *ptr = (void *)GetProcAddress((HMODULE)lib->module, name.c_str());
|
|
1584
|
+
#else
|
|
1585
|
+
void *ptr = (void *)dlsym(lib->module, name.c_str());
|
|
1586
|
+
#endif
|
|
1587
|
+
if (!ptr) {
|
|
1588
|
+
ThrowError<Napi::Error>(env, "Cannot find symbol '%1' in shared library", name.c_str());
|
|
1589
|
+
return env.Null();
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
Napi::External<void> external = Napi::External<void>::New(env, ptr);
|
|
1593
|
+
SetValueTag(instance, external, &type);
|
|
1594
|
+
|
|
1595
|
+
return external;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1561
1598
|
static Napi::Value UnloadLibrary(const Napi::CallbackInfo &info)
|
|
1562
1599
|
{
|
|
1563
1600
|
Napi::Env env = info.Env();
|
|
@@ -1654,13 +1691,14 @@ static Napi::Value LoadSharedLibrary(const Napi::CallbackInfo &info)
|
|
|
1654
1691
|
|
|
1655
1692
|
Napi::Object obj = Napi::Object::New(env);
|
|
1656
1693
|
|
|
1657
|
-
#define
|
|
1694
|
+
#define ADD_METHOD(Name, Call) \
|
|
1658
1695
|
do { \
|
|
1659
|
-
const auto wrapper = [](const Napi::CallbackInfo &info) { return
|
|
1696
|
+
const auto wrapper = [](const Napi::CallbackInfo &info) { return Call; }; \
|
|
1660
1697
|
Napi::Function func = Napi::Function::New(env, wrapper, (Name), (void *)lib->Ref()); \
|
|
1661
1698
|
func.AddFinalizer([](Napi::Env, LibraryHolder *lib) { lib->Unref(); }, lib); \
|
|
1662
1699
|
obj.Set((Name), func); \
|
|
1663
1700
|
} while (false)
|
|
1701
|
+
#define ADD_CONVENTION(Name, Value) ADD_METHOD((Name), FindLibraryFunction(info, Value))
|
|
1664
1702
|
|
|
1665
1703
|
ADD_CONVENTION("func", CallConvention::Cdecl);
|
|
1666
1704
|
ADD_CONVENTION("cdecl", CallConvention::Cdecl);
|
|
@@ -1668,10 +1706,14 @@ static Napi::Value LoadSharedLibrary(const Napi::CallbackInfo &info)
|
|
|
1668
1706
|
ADD_CONVENTION("fastcall", CallConvention::Fastcall);
|
|
1669
1707
|
ADD_CONVENTION("thiscall", CallConvention::Thiscall);
|
|
1670
1708
|
|
|
1671
|
-
|
|
1709
|
+
ADD_METHOD("symbol", FindSymbol(info));
|
|
1672
1710
|
|
|
1711
|
+
// We can't unref the library after unload, obviously
|
|
1673
1712
|
obj.Set("unload", Napi::Function::New(env, UnloadLibrary, "unload", (void *)lib->Ref()));
|
|
1674
1713
|
|
|
1714
|
+
#undef ADD_CONVENTION
|
|
1715
|
+
#undef ADD_METHOD
|
|
1716
|
+
|
|
1675
1717
|
return obj;
|
|
1676
1718
|
}
|
|
1677
1719
|
|
|
@@ -1897,6 +1939,39 @@ static Napi::Value CallPointerSync(const Napi::CallbackInfo &info)
|
|
|
1897
1939
|
: TranslateNormalCall(proto, ptr, info);
|
|
1898
1940
|
}
|
|
1899
1941
|
|
|
1942
|
+
static Napi::Value EncodeValue(const Napi::CallbackInfo &info)
|
|
1943
|
+
{
|
|
1944
|
+
Napi::Env env = info.Env();
|
|
1945
|
+
|
|
1946
|
+
bool has_offset = (info.Length() >= 2 && info[1].IsNumber());
|
|
1947
|
+
bool has_len = (info.Length() >= 4u + has_offset && info[2u + has_offset].IsNumber());
|
|
1948
|
+
|
|
1949
|
+
if (info.Length() < 3u + has_offset) [[unlikely]] {
|
|
1950
|
+
ThrowError<Napi::TypeError>(env, "Expected %1 to 5 arguments, got %2", 3 + has_offset, info.Length());
|
|
1951
|
+
return env.Null();
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
const TypeInfo *type = ResolveType(info[1u + has_offset]);
|
|
1955
|
+
if (!type) [[unlikely]]
|
|
1956
|
+
return env.Null();
|
|
1957
|
+
|
|
1958
|
+
Napi::Value ref = info[0];
|
|
1959
|
+
int64_t offset = has_offset ? info[1].As<Napi::Number>().Int64Value() : 0;
|
|
1960
|
+
Napi::Value value = info[2u + has_offset + has_len];
|
|
1961
|
+
|
|
1962
|
+
if (has_len) {
|
|
1963
|
+
Size len = info[2u + has_offset].As<Napi::Number>();
|
|
1964
|
+
|
|
1965
|
+
if (!Encode(ref, offset, value, type, &len))
|
|
1966
|
+
return env.Null();
|
|
1967
|
+
} else {
|
|
1968
|
+
if (!Encode(ref, offset, value, type))
|
|
1969
|
+
return env.Null();
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
return env.Undefined();
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1900
1975
|
void LibraryHolder::Unload()
|
|
1901
1976
|
{
|
|
1902
1977
|
#ifdef _WIN32
|
|
@@ -1980,6 +2055,11 @@ bool InitAsyncBroker(Napi::Env env, InstanceData *instance)
|
|
|
1980
2055
|
return true;
|
|
1981
2056
|
}
|
|
1982
2057
|
|
|
2058
|
+
CallData *GetThreadCall()
|
|
2059
|
+
{
|
|
2060
|
+
return exec_call;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
1983
2063
|
static void RegisterPrimitiveType(Napi::Env env, Napi::Object map, std::initializer_list<const char *> names,
|
|
1984
2064
|
PrimitiveKind primitive, int32_t size, int16_t align, const char *ref = nullptr)
|
|
1985
2065
|
{
|
|
@@ -2193,6 +2273,7 @@ static Napi::Object InitModule(Napi::Env env, Napi::Object exports)
|
|
|
2193
2273
|
exports.Set("decode", Napi::Function::New(env, DecodeValue));
|
|
2194
2274
|
exports.Set("address", Napi::Function::New(env, GetPointerAddress));
|
|
2195
2275
|
exports.Set("call", Napi::Function::New(env, CallPointerSync));
|
|
2276
|
+
exports.Set("encode", Napi::Function::New(env, EncodeValue));
|
|
2196
2277
|
|
|
2197
2278
|
exports.Set("reset", Napi::Function::New(env, ResetKoffi));
|
|
2198
2279
|
|
package/src/koffi/src/ffi.hh
CHANGED
|
@@ -99,6 +99,7 @@ static const char *const PrimitiveKindNames[] = {
|
|
|
99
99
|
struct TypeInfo;
|
|
100
100
|
struct RecordMember;
|
|
101
101
|
struct FunctionInfo;
|
|
102
|
+
class CallData;
|
|
102
103
|
|
|
103
104
|
typedef void DisposeFunc (Napi::Env env, const TypeInfo *type, const void *ptr);
|
|
104
105
|
|
|
@@ -286,6 +287,9 @@ struct InstanceData {
|
|
|
286
287
|
void *main_stack_min;
|
|
287
288
|
#endif
|
|
288
289
|
|
|
290
|
+
BucketArray<BlockAllocator> encode_allocators;
|
|
291
|
+
HashMap<void *, BlockAllocator *> encode_map;
|
|
292
|
+
|
|
289
293
|
HashMap<void *, int16_t> trampolines_map;
|
|
290
294
|
|
|
291
295
|
BlockAllocator str_alloc;
|
|
@@ -342,5 +346,6 @@ Napi::Value TranslateVariadicCall(const Napi::CallbackInfo &info);
|
|
|
342
346
|
Napi::Value TranslateAsyncCall(const Napi::CallbackInfo &info);
|
|
343
347
|
|
|
344
348
|
bool InitAsyncBroker(Napi::Env env, InstanceData *instance);
|
|
349
|
+
CallData *GetThreadCall();
|
|
345
350
|
|
|
346
351
|
}
|
package/src/koffi/src/util.cc
CHANGED
|
@@ -1193,6 +1193,216 @@ Napi::Value Decode(Napi::Env env, const uint8_t *ptr, const TypeInfo *type, cons
|
|
|
1193
1193
|
return env.Null();
|
|
1194
1194
|
}
|
|
1195
1195
|
|
|
1196
|
+
bool Encode(Napi::Value ref, Size offset, Napi::Value value, const TypeInfo *type, const Size *len)
|
|
1197
|
+
{
|
|
1198
|
+
Napi::Env env = ref.Env();
|
|
1199
|
+
InstanceData *instance = env.GetInstanceData<InstanceData>();
|
|
1200
|
+
|
|
1201
|
+
uint8_t *ptr = nullptr;
|
|
1202
|
+
|
|
1203
|
+
if (ref.IsExternal()) {
|
|
1204
|
+
Napi::External<void> external = ref.As<Napi::External<void>>();
|
|
1205
|
+
ptr = (uint8_t *)external.Data();
|
|
1206
|
+
} else if (IsRawBuffer(ref)) {
|
|
1207
|
+
Span<uint8_t> buffer = GetRawBuffer(ref);
|
|
1208
|
+
|
|
1209
|
+
if (buffer.len - offset < type->size) [[unlikely]] {
|
|
1210
|
+
ThrowError<Napi::Error>(env, "Expected buffer with size superior or equal to type %1 (%2 bytes)",
|
|
1211
|
+
type->name, type->size + offset);
|
|
1212
|
+
return env.Null();
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
ptr = (uint8_t *)buffer.ptr;
|
|
1216
|
+
} else {
|
|
1217
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for reference, expected external or TypedArray", GetValueType(instance, value));
|
|
1218
|
+
return env.Null();
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
if (!ptr) [[unlikely]] {
|
|
1222
|
+
ThrowError<Napi::Error>(env, "Cannot encode data in NULL pointer");
|
|
1223
|
+
return env.Null();
|
|
1224
|
+
}
|
|
1225
|
+
ptr += offset;
|
|
1226
|
+
|
|
1227
|
+
return Encode(env, ptr, value, type, len);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
bool Encode(Napi::Env env, uint8_t *origin, Napi::Value value, const TypeInfo *type, const Size *len)
|
|
1231
|
+
{
|
|
1232
|
+
InstanceData *instance = env.GetInstanceData<InstanceData>();
|
|
1233
|
+
|
|
1234
|
+
if (len && type->primitive != PrimitiveKind::String &&
|
|
1235
|
+
type->primitive != PrimitiveKind::String16 &&
|
|
1236
|
+
type->primitive != PrimitiveKind::Prototype) {
|
|
1237
|
+
if (*len < 0) [[unlikely]] {
|
|
1238
|
+
ThrowError<Napi::TypeError>(env, "Automatic (negative) length is only supported when decoding");
|
|
1239
|
+
return env.Null();
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
type = MakeArrayType(instance, type, *len);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
InstanceMemory mem = {};
|
|
1246
|
+
CallData call(env, instance, &mem);
|
|
1247
|
+
|
|
1248
|
+
#define PUSH_INTEGER(CType) \
|
|
1249
|
+
do { \
|
|
1250
|
+
if (!value.IsNumber() && !value.IsBigInt()) [[unlikely]] { \
|
|
1251
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected number", GetValueType(instance, value)); \
|
|
1252
|
+
return false; \
|
|
1253
|
+
} \
|
|
1254
|
+
\
|
|
1255
|
+
CType v = GetNumber<CType>(value); \
|
|
1256
|
+
*(CType *)origin = v; \
|
|
1257
|
+
} while (false)
|
|
1258
|
+
#define PUSH_INTEGER_SWAP(CType) \
|
|
1259
|
+
do { \
|
|
1260
|
+
if (!value.IsNumber() && !value.IsBigInt()) [[unlikely]] { \
|
|
1261
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected number", GetValueType(instance, value)); \
|
|
1262
|
+
return false; \
|
|
1263
|
+
} \
|
|
1264
|
+
\
|
|
1265
|
+
CType v = GetNumber<CType>(value); \
|
|
1266
|
+
*(CType *)origin = ReverseBytes(v); \
|
|
1267
|
+
} while (false)
|
|
1268
|
+
|
|
1269
|
+
switch (type->primitive) {
|
|
1270
|
+
case PrimitiveKind::Void: { RG_UNREACHABLE(); } break;
|
|
1271
|
+
|
|
1272
|
+
case PrimitiveKind::Bool: {
|
|
1273
|
+
if (!value.IsBoolean()) [[unlikely]] {
|
|
1274
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected boolean", GetValueType(instance, value));
|
|
1275
|
+
return false;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
bool b = value.As<Napi::Boolean>();
|
|
1279
|
+
*(bool *)origin = b;
|
|
1280
|
+
} break;
|
|
1281
|
+
case PrimitiveKind::Int8: { PUSH_INTEGER(int8_t); } break;
|
|
1282
|
+
case PrimitiveKind::UInt8: { PUSH_INTEGER(uint8_t); } break;
|
|
1283
|
+
case PrimitiveKind::Int16: { PUSH_INTEGER(int16_t); } break;
|
|
1284
|
+
case PrimitiveKind::Int16S: { PUSH_INTEGER_SWAP(int16_t); } break;
|
|
1285
|
+
case PrimitiveKind::UInt16: { PUSH_INTEGER(uint16_t); } break;
|
|
1286
|
+
case PrimitiveKind::UInt16S: { PUSH_INTEGER_SWAP(uint16_t); } break;
|
|
1287
|
+
case PrimitiveKind::Int32: { PUSH_INTEGER(int32_t); } break;
|
|
1288
|
+
case PrimitiveKind::Int32S: { PUSH_INTEGER_SWAP(int32_t); } break;
|
|
1289
|
+
case PrimitiveKind::UInt32: { PUSH_INTEGER(uint32_t); } break;
|
|
1290
|
+
case PrimitiveKind::UInt32S: { PUSH_INTEGER_SWAP(uint32_t); } break;
|
|
1291
|
+
case PrimitiveKind::Int64: { PUSH_INTEGER(int64_t); } break;
|
|
1292
|
+
case PrimitiveKind::Int64S: { PUSH_INTEGER_SWAP(int64_t); } break;
|
|
1293
|
+
case PrimitiveKind::UInt64: { PUSH_INTEGER(uint64_t); } break;
|
|
1294
|
+
case PrimitiveKind::UInt64S: { PUSH_INTEGER_SWAP(uint64_t); } break;
|
|
1295
|
+
case PrimitiveKind::String: {
|
|
1296
|
+
const char *str;
|
|
1297
|
+
if (!call.PushString(value, 1, &str)) [[unlikely]]
|
|
1298
|
+
return false;
|
|
1299
|
+
*(const char **)origin = str;
|
|
1300
|
+
} break;
|
|
1301
|
+
case PrimitiveKind::String16: {
|
|
1302
|
+
const char16_t *str16;
|
|
1303
|
+
if (!call.PushString16(value, 1, &str16)) [[unlikely]]
|
|
1304
|
+
return false;
|
|
1305
|
+
*(const char16_t **)origin = str16;
|
|
1306
|
+
} break;
|
|
1307
|
+
case PrimitiveKind::Pointer: {
|
|
1308
|
+
void *ptr;
|
|
1309
|
+
if (!call.PushPointer(value, type, 1, &ptr)) [[unlikely]]
|
|
1310
|
+
return false;
|
|
1311
|
+
*(void **)origin = ptr;
|
|
1312
|
+
} break;
|
|
1313
|
+
case PrimitiveKind::Record:
|
|
1314
|
+
case PrimitiveKind::Union: {
|
|
1315
|
+
if (!IsObject(value)) [[unlikely]] {
|
|
1316
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected object", GetValueType(instance, value));
|
|
1317
|
+
return false;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
Napi::Object obj = value.As<Napi::Object>();
|
|
1321
|
+
|
|
1322
|
+
if (!call.PushObject(obj, type, origin))
|
|
1323
|
+
return false;
|
|
1324
|
+
} break;
|
|
1325
|
+
case PrimitiveKind::Array: {
|
|
1326
|
+
if (value.IsArray()) {
|
|
1327
|
+
Napi::Array array = value.As<Napi::Array>();
|
|
1328
|
+
Size len = (Size)type->size / type->ref.type->size;
|
|
1329
|
+
|
|
1330
|
+
if (!call.PushNormalArray(array, len, type, origin))
|
|
1331
|
+
return false;
|
|
1332
|
+
} else if (IsRawBuffer(value)) {
|
|
1333
|
+
Span<const uint8_t> buffer = GetRawBuffer(value);
|
|
1334
|
+
|
|
1335
|
+
if (!call.PushBuffer(buffer, type->size, type, origin))
|
|
1336
|
+
return false;
|
|
1337
|
+
} else if (value.IsString()) {
|
|
1338
|
+
if (!call.PushStringArray(value, type, origin))
|
|
1339
|
+
return false;
|
|
1340
|
+
} else {
|
|
1341
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected array", GetValueType(instance, value));
|
|
1342
|
+
return false;
|
|
1343
|
+
}
|
|
1344
|
+
} break;
|
|
1345
|
+
case PrimitiveKind::Float32: {
|
|
1346
|
+
if (!value.IsNumber() && !value.IsBigInt()) [[unlikely]] {
|
|
1347
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected number", GetValueType(instance, value));
|
|
1348
|
+
return false;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
float f = GetNumber<float>(value);
|
|
1352
|
+
*(float *)origin = f;
|
|
1353
|
+
} break;
|
|
1354
|
+
case PrimitiveKind::Float64: {
|
|
1355
|
+
if (!value.IsNumber() && !value.IsBigInt()) [[unlikely]] {
|
|
1356
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected number", GetValueType(instance, value));
|
|
1357
|
+
return false;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
double d = GetNumber<double>(value);
|
|
1361
|
+
*(double *)origin = d;
|
|
1362
|
+
} break;
|
|
1363
|
+
case PrimitiveKind::Callback: {
|
|
1364
|
+
void *ptr;
|
|
1365
|
+
|
|
1366
|
+
if (value.IsFunction()) {
|
|
1367
|
+
ThrowError<Napi::Error>(env, "Cannot encode non-registered callback");
|
|
1368
|
+
return false;
|
|
1369
|
+
} else if (CheckValueTag(instance, value, type->ref.marker)) {
|
|
1370
|
+
ptr = value.As<Napi::External<void>>().Data();
|
|
1371
|
+
} else if (IsNullOrUndefined(value)) {
|
|
1372
|
+
ptr = nullptr;
|
|
1373
|
+
} else {
|
|
1374
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected %2", GetValueType(instance, value), type->name);
|
|
1375
|
+
return false;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
*(void **)origin = ptr;
|
|
1379
|
+
} break;
|
|
1380
|
+
|
|
1381
|
+
case PrimitiveKind::Prototype: { RG_UNREACHABLE(); } break;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
#undef PUSH_INTEGER_SWAP
|
|
1385
|
+
#undef PUSH_INTEGER
|
|
1386
|
+
|
|
1387
|
+
// Keep memory around if any was allocated
|
|
1388
|
+
{
|
|
1389
|
+
BlockAllocator *alloc = call.GetAllocator();
|
|
1390
|
+
|
|
1391
|
+
if (alloc->IsUsed()) {
|
|
1392
|
+
BlockAllocator *copy = instance->encode_map.FindValue(origin, nullptr);
|
|
1393
|
+
|
|
1394
|
+
if (!copy) {
|
|
1395
|
+
copy = instance->encode_allocators.AppendDefault();
|
|
1396
|
+
instance->encode_map.Set(origin, copy);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
std::swap(*alloc, *copy);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
return true;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1196
1406
|
Napi::Function WrapFunction(Napi::Env env, const FunctionInfo *func)
|
|
1197
1407
|
{
|
|
1198
1408
|
InstanceData *instance = env.GetInstanceData<InstanceData>();
|
package/src/koffi/src/util.hh
CHANGED
|
@@ -170,6 +170,9 @@ void DecodeBuffer(Span<uint8_t> buffer, const uint8_t *origin, const TypeInfo *r
|
|
|
170
170
|
Napi::Value Decode(Napi::Value value, Size offset, const TypeInfo *type, const Size *len = nullptr);
|
|
171
171
|
Napi::Value Decode(Napi::Env env, const uint8_t *ptr, const TypeInfo *type, const Size *len = nullptr);
|
|
172
172
|
|
|
173
|
+
bool Encode(Napi::Value ref, Size offset, Napi::Value value, const TypeInfo *type, const Size *len = nullptr);
|
|
174
|
+
bool Encode(Napi::Env env, uint8_t *ptr, Napi::Value value, const TypeInfo *type, const Size *len = nullptr);
|
|
175
|
+
|
|
173
176
|
static inline Napi::Value NewBigInt(Napi::Env env, int64_t value)
|
|
174
177
|
{
|
|
175
178
|
if (value <= 9007199254740992ll && value >= -9007199254740992ll) {
|