koffi 0.9.16 → 0.9.17
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/README.md +25 -11
- package/package.json +1 -1
- package/src/call_x86.cc +2 -9
- package/src/ffi.cc +24 -40
- package/src/ffi.hh +0 -1
- package/test/tests/misc.js +2 -2
- package/test/tests/raylib.js +8 -8
- package/test/tests/sqlite.js +9 -9
package/README.md
CHANGED
|
@@ -63,6 +63,8 @@ npm install koffi
|
|
|
63
63
|
|
|
64
64
|
This section assumes you know how to build C shared libraries.
|
|
65
65
|
|
|
66
|
+
## Raylib example
|
|
67
|
+
|
|
66
68
|
This examples illustrates how to use Koffi with a Raylib shared library:
|
|
67
69
|
|
|
68
70
|
```js
|
|
@@ -123,17 +125,17 @@ const Font = koffi.struct('Font', {
|
|
|
123
125
|
// Fix the path to Raylib DLL if needed
|
|
124
126
|
let lib = koffi.load('build/raylib' + (process.platform == 'win32' ? '.dll' : '.so'));
|
|
125
127
|
|
|
126
|
-
const InitWindow = lib.
|
|
127
|
-
const SetTargetFPS = lib.
|
|
128
|
-
const GetScreenWidth = lib.
|
|
129
|
-
const GetScreenHeight = lib.
|
|
130
|
-
const ClearBackground = lib.
|
|
131
|
-
const BeginDrawing = lib.
|
|
132
|
-
const EndDrawing = lib.
|
|
133
|
-
const WindowShouldClose = lib.
|
|
134
|
-
const GetFontDefault = lib.
|
|
135
|
-
const MeasureTextEx = lib.
|
|
136
|
-
const DrawTextEx = lib.
|
|
128
|
+
const InitWindow = lib.cdecl('InitWindow', 'void', ['int', 'int', 'string']);
|
|
129
|
+
const SetTargetFPS = lib.cdecl('SetTargetFPS', 'void', ['int']);
|
|
130
|
+
const GetScreenWidth = lib.cdecl('GetScreenWidth', 'int', []);
|
|
131
|
+
const GetScreenHeight = lib.cdecl('GetScreenHeight', 'int', []);
|
|
132
|
+
const ClearBackground = lib.cdecl('ClearBackground', 'void', [Color]);
|
|
133
|
+
const BeginDrawing = lib.cdecl('BeginDrawing', 'void', []);
|
|
134
|
+
const EndDrawing = lib.cdecl('EndDrawing', 'void', []);
|
|
135
|
+
const WindowShouldClose = lib.cdecl('WindowShouldClose', 'bool', []);
|
|
136
|
+
const GetFontDefault = lib.cdecl('GetFontDefault', Font, []);
|
|
137
|
+
const MeasureTextEx = lib.cdecl('MeasureTextEx', Vector2, [Font, 'string', 'float', 'float']);
|
|
138
|
+
const DrawTextEx = lib.cdecl('DrawTextEx', 'void', [Font, 'string', Vector2, 'float', 'float', Color]);
|
|
137
139
|
|
|
138
140
|
InitWindow(800, 600, 'Test Raylib');
|
|
139
141
|
SetTargetFPS(60);
|
|
@@ -170,6 +172,18 @@ while (!WindowShouldClose()) {
|
|
|
170
172
|
|
|
171
173
|
```
|
|
172
174
|
|
|
175
|
+
## Win32 (\__stdcall) example
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
const koffi = require('koffi');
|
|
179
|
+
|
|
180
|
+
let lib = koffi.load('user32.dll');
|
|
181
|
+
|
|
182
|
+
const MessageBoxA = lib.stdcall('MessageBoxA', 'int', ['void *', 'string', 'string', 'uint']);
|
|
183
|
+
|
|
184
|
+
MessageBoxA(null, 'Hello', 'Foobar', 0x40); // MB_ICONINFORMATION
|
|
185
|
+
```
|
|
186
|
+
|
|
173
187
|
# Tests
|
|
174
188
|
|
|
175
189
|
Koffi is tested on multiple architectures using emulated (accelerated when possible) QEMU machines. First, you need to install qemu packages, such as `qemu-system` (or even `qemu-system-gui`) on Ubuntu.
|
package/package.json
CHANGED
package/src/call_x86.cc
CHANGED
|
@@ -39,17 +39,10 @@ bool AnalyseFunction(InstanceData *instance, FunctionInfo *func)
|
|
|
39
39
|
#endif
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
Size params_size = 0;
|
|
43
42
|
for (const ParameterInfo ¶m: func->parameters) {
|
|
44
|
-
|
|
43
|
+
func->args_size += std::max((int16_t)4, param.type->size);
|
|
45
44
|
}
|
|
46
|
-
func->args_size
|
|
47
|
-
|
|
48
|
-
#ifdef _WIN32
|
|
49
|
-
if (func->convention == CallConvention::Stdcall) {
|
|
50
|
-
func->decorated_name = Fmt(&instance->str_alloc, "_%1@%2", func->name, params_size).ptr;
|
|
51
|
-
}
|
|
52
|
-
#endif
|
|
45
|
+
func->args_size += 4 * !func->ret.trivial;
|
|
53
46
|
|
|
54
47
|
return true;
|
|
55
48
|
}
|
package/src/ffi.cc
CHANGED
|
@@ -273,7 +273,7 @@ static Napi::Value MarkInOut(const Napi::CallbackInfo &info)
|
|
|
273
273
|
return EncodePointerDirection(info, 3);
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info)
|
|
276
|
+
static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConvention convention)
|
|
277
277
|
{
|
|
278
278
|
Napi::Env env = info.Env();
|
|
279
279
|
InstanceData *instance = env.GetInstanceData<InstanceData>();
|
|
@@ -293,42 +293,17 @@ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info)
|
|
|
293
293
|
|
|
294
294
|
std::string name = ((Napi::Value)info[0u]).As<Napi::String>();
|
|
295
295
|
func->name = DuplicateString(name.c_str(), &instance->str_alloc).ptr;
|
|
296
|
-
func->decorated_name = func->name;
|
|
297
296
|
func->lib = lib->Ref();
|
|
298
297
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
func->convention = CallConvention::Default;
|
|
306
|
-
} else if (conv == "stdcall" || conv == "__stdcall") {
|
|
307
|
-
func->convention = CallConvention::Stdcall;
|
|
308
|
-
} else {
|
|
309
|
-
ThrowError<Napi::Error>(env, "Unknown calling convention '%1'", conv.c_str());
|
|
310
|
-
return env.Null();
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
func->ret.type = ResolveType(instance, info[2u]);
|
|
314
|
-
if (!func->ret.type)
|
|
315
|
-
return env.Null();
|
|
316
|
-
if (!((Napi::Value)info[3u]).IsArray()) {
|
|
317
|
-
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for parameters of '%2', expected an array", GetValueType(instance, (Napi::Value)info[1u]), func->name);
|
|
318
|
-
return env.Null();
|
|
319
|
-
}
|
|
320
|
-
parameters = ((Napi::Value)info[3u]).As<Napi::Array>();
|
|
321
|
-
} else {
|
|
322
|
-
func->ret.type = ResolveType(instance, info[1u]);
|
|
323
|
-
if (!func->ret.type)
|
|
324
|
-
return env.Null();
|
|
325
|
-
if (!((Napi::Value)info[2u]).IsArray()) {
|
|
326
|
-
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for parameters of '%2', expected an array", GetValueType(instance, (Napi::Value)info[1u]), func->name);
|
|
327
|
-
return env.Null();
|
|
328
|
-
}
|
|
329
|
-
parameters = ((Napi::Value)info[2u]).As<Napi::Array>();
|
|
298
|
+
func->ret.type = ResolveType(instance, info[1u]);
|
|
299
|
+
if (!func->ret.type)
|
|
300
|
+
return env.Null();
|
|
301
|
+
if (!((Napi::Value)info[2u]).IsArray()) {
|
|
302
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for parameters of '%2', expected an array", GetValueType(instance, (Napi::Value)info[1u]), func->name);
|
|
303
|
+
return env.Null();
|
|
330
304
|
}
|
|
331
305
|
|
|
306
|
+
Napi::Array parameters = ((Napi::Value)info[2u]).As<Napi::Array>();
|
|
332
307
|
Size out_counter = 0;
|
|
333
308
|
|
|
334
309
|
for (uint32_t j = 0; j < parameters.Length(); j++) {
|
|
@@ -358,15 +333,15 @@ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info)
|
|
|
358
333
|
return env.Null();
|
|
359
334
|
|
|
360
335
|
#ifdef _WIN32
|
|
361
|
-
func->func = (void *)GetProcAddress((HMODULE)lib->module, func->
|
|
336
|
+
func->func = (void *)GetProcAddress((HMODULE)lib->module, func->name);
|
|
362
337
|
#else
|
|
363
|
-
func->func = dlsym(lib->module, func->
|
|
338
|
+
func->func = dlsym(lib->module, func->name);
|
|
364
339
|
#endif
|
|
365
340
|
if (!func->func) {
|
|
366
|
-
|
|
367
|
-
ThrowError<Napi::Error>(env, "Cannot find function '%1' in shared library", name.c_str());
|
|
341
|
+
ThrowError<Napi::Error>(env, "Cannot find function '%1' in shared library", func->name);
|
|
368
342
|
return env.Null();
|
|
369
343
|
}
|
|
344
|
+
func->convention = convention;
|
|
370
345
|
|
|
371
346
|
Napi::Function wrapper = Napi::Function::New(env, TranslateCall, name.c_str(), (void *)func);
|
|
372
347
|
wrapper.AddFinalizer([](Napi::Env, FunctionInfo *func) { delete func; }, func);
|
|
@@ -432,9 +407,18 @@ static Napi::Value LoadSharedLibrary(const Napi::CallbackInfo &info)
|
|
|
432
407
|
|
|
433
408
|
Napi::Object obj = Napi::Object::New(env);
|
|
434
409
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
410
|
+
#define ADD_CONVENTION(Name, Value) \
|
|
411
|
+
do { \
|
|
412
|
+
const auto wrapper = [](const Napi::CallbackInfo &info) { return FindLibraryFunction(info, (Value)); }; \
|
|
413
|
+
Napi::Function func = Napi::Function::New(env, wrapper, (Name), (void *)lib->Ref()); \
|
|
414
|
+
func.AddFinalizer([](Napi::Env, LibraryHolder *lib) { lib->Unref(); }, lib); \
|
|
415
|
+
obj.Set((Name), func); \
|
|
416
|
+
} while (false)
|
|
417
|
+
|
|
418
|
+
ADD_CONVENTION("cdecl", CallConvention::Default);
|
|
419
|
+
ADD_CONVENTION("stdcall", CallConvention::Stdcall);
|
|
420
|
+
|
|
421
|
+
#undef ADD_CONVENTION
|
|
438
422
|
|
|
439
423
|
return obj;
|
|
440
424
|
}
|
package/src/ffi.hh
CHANGED
package/test/tests/misc.js
CHANGED
|
@@ -40,8 +40,8 @@ async function test() {
|
|
|
40
40
|
(process.platform == 'win32' ? '.dll' : '.so');
|
|
41
41
|
let lib = koffi.load(lib_filename);
|
|
42
42
|
|
|
43
|
-
const FillPack3 = lib.
|
|
44
|
-
const AddPack3 = lib.
|
|
43
|
+
const FillPack3 = lib.cdecl('FillPack3', 'void', ['int', 'int', 'int', koffi.out(koffi.pointer(Pack3))]);
|
|
44
|
+
const AddPack3 = lib.cdecl('AddPack3', 'void', ['int', 'int', 'int', koffi.inout(koffi.pointer(Pack3))]);
|
|
45
45
|
|
|
46
46
|
let p = {};
|
|
47
47
|
|
package/test/tests/raylib.js
CHANGED
|
@@ -87,14 +87,14 @@ async function test() {
|
|
|
87
87
|
(process.platform == 'win32' ? '.dll' : '.so');
|
|
88
88
|
let lib = koffi.load(lib_filename);
|
|
89
89
|
|
|
90
|
-
const InitWindow = lib.
|
|
91
|
-
const SetTraceLogLevel = lib.
|
|
92
|
-
const SetWindowState = lib.
|
|
93
|
-
const GenImageColor = lib.
|
|
94
|
-
const GetFontDefault = lib.
|
|
95
|
-
const MeasureTextEx = lib.
|
|
96
|
-
const ImageDrawTextEx = lib.
|
|
97
|
-
const ExportImage = lib.
|
|
90
|
+
const InitWindow = lib.cdecl('InitWindow', 'void', ['int', 'int', 'string']);
|
|
91
|
+
const SetTraceLogLevel = lib.cdecl('SetTraceLogLevel', 'void', ['int']);
|
|
92
|
+
const SetWindowState = lib.cdecl('SetWindowState', 'void', ['uint']);
|
|
93
|
+
const GenImageColor = lib.cdecl('GenImageColor', Image, ['int', 'int', Color]);
|
|
94
|
+
const GetFontDefault = lib.cdecl('GetFontDefault', Font, []);
|
|
95
|
+
const MeasureTextEx = lib.cdecl('MeasureTextEx', Vector2, [Font, 'string', 'float', 'float']);
|
|
96
|
+
const ImageDrawTextEx = lib.cdecl('ImageDrawTextEx', 'void', [koffi.pointer(Image), Font, 'string', Vector2, 'float', 'float', Color]);
|
|
97
|
+
const ExportImage = lib.cdecl('ExportImage', 'bool', [Image, 'string']);
|
|
98
98
|
|
|
99
99
|
// We need to call InitWindow before using anything else (such as fonts)
|
|
100
100
|
SetTraceLogLevel(4); // Warnings
|
package/test/tests/sqlite.js
CHANGED
|
@@ -40,15 +40,15 @@ async function test() {
|
|
|
40
40
|
(process.platform == 'win32' ? '.dll' : '.so');
|
|
41
41
|
let lib = koffi.load(lib_filename);
|
|
42
42
|
|
|
43
|
-
const sqlite3_open_v2 = lib.
|
|
44
|
-
const sqlite3_exec = lib.
|
|
45
|
-
const sqlite3_prepare_v2 = lib.
|
|
46
|
-
const sqlite3_reset = lib.
|
|
47
|
-
const sqlite3_bind_text = lib.
|
|
48
|
-
const sqlite3_bind_int = lib.
|
|
49
|
-
const sqlite3_step = lib.
|
|
50
|
-
const sqlite3_finalize = lib.
|
|
51
|
-
const sqlite3_close_v2 = lib.
|
|
43
|
+
const sqlite3_open_v2 = lib.cdecl('sqlite3_open_v2', 'int', ['string', koffi.out(koffi.pointer(sqlite3_db)), 'int', 'string']);
|
|
44
|
+
const sqlite3_exec = lib.cdecl('sqlite3_exec', 'int', [sqlite3_db, 'string', 'void *', 'void *', 'void *']);
|
|
45
|
+
const sqlite3_prepare_v2 = lib.cdecl('sqlite3_prepare_v2', 'int', [sqlite3_db, 'string', 'int', koffi.out(koffi.pointer(sqlite3_stmt)), 'string']);
|
|
46
|
+
const sqlite3_reset = lib.cdecl('sqlite3_reset', 'int', [sqlite3_stmt]);
|
|
47
|
+
const sqlite3_bind_text = lib.cdecl('sqlite3_bind_text', 'int', [sqlite3_stmt, 'int', 'string', 'int', 'void *']);
|
|
48
|
+
const sqlite3_bind_int = lib.cdecl('sqlite3_bind_int', 'int', [sqlite3_stmt, 'int', 'int']);
|
|
49
|
+
const sqlite3_step = lib.cdecl('sqlite3_step', 'int', [sqlite3_stmt]);
|
|
50
|
+
const sqlite3_finalize = lib.cdecl('sqlite3_finalize', 'int', [sqlite3_stmt]);
|
|
51
|
+
const sqlite3_close_v2 = lib.cdecl('sqlite3_close_v2', 'int', [sqlite3_db]);
|
|
52
52
|
|
|
53
53
|
let filename = create_temporary_file(path.join(os.tmpdir(), 'test_sqlite'));
|
|
54
54
|
let db = {};
|