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 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.func('InitWindow', 'void', ['int', 'int', 'string']);
127
- const SetTargetFPS = lib.func('SetTargetFPS', 'void', ['int']);
128
- const GetScreenWidth = lib.func('GetScreenWidth', 'int', []);
129
- const GetScreenHeight = lib.func('GetScreenHeight', 'int', []);
130
- const ClearBackground = lib.func('ClearBackground', 'void', [Color]);
131
- const BeginDrawing = lib.func('BeginDrawing', 'void', []);
132
- const EndDrawing = lib.func('EndDrawing', 'void', []);
133
- const WindowShouldClose = lib.func('WindowShouldClose', 'bool', []);
134
- const GetFontDefault = lib.func('GetFontDefault', Font, []);
135
- const MeasureTextEx = lib.func('MeasureTextEx', Vector2, [Font, 'string', 'float', 'float']);
136
- const DrawTextEx = lib.func('DrawTextEx', 'void', [Font, 'string', Vector2, 'float', 'float', Color]);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "0.9.16",
3
+ "version": "0.9.17",
4
4
  "description": "Fast and simple FFI (foreign function interface) for Node.js",
5
5
  "keywords": [
6
6
  "foreign",
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 &param: func->parameters) {
44
- params_size += std::max((int16_t)4, param.type->size);
43
+ func->args_size += std::max((int16_t)4, param.type->size);
45
44
  }
46
- func->args_size = params_size + 4 * !func->ret.trivial;
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
- Napi::Array parameters;
300
-
301
- if (info.Length() >= 4 && info[3u].IsArray()) {
302
- std::string conv = ((Napi::Value)info[1u]).As<Napi::String>();
303
-
304
- if (conv == "cdecl" || conv == "__cdecl") {
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->decorated_name);
336
+ func->func = (void *)GetProcAddress((HMODULE)lib->module, func->name);
362
337
  #else
363
- func->func = dlsym(lib->module, func->decorated_name);
338
+ func->func = dlsym(lib->module, func->name);
364
339
  #endif
365
340
  if (!func->func) {
366
- RG_DEBUG_BREAK();
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
- Napi::Function func = Napi::Function::New(env, FindLibraryFunction, "func", (void *)lib->Ref());
436
- func.AddFinalizer([](Napi::Env, LibraryHolder *lib) { lib->Unref(); }, lib);
437
- obj.Set("func", func);
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
@@ -133,7 +133,6 @@ struct FunctionInfo {
133
133
  ~FunctionInfo();
134
134
 
135
135
  const char *name;
136
- const char *decorated_name;
137
136
  LibraryHolder *lib = nullptr;
138
137
 
139
138
  void *func;
@@ -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.func('FillPack3', 'void', ['int', 'int', 'int', koffi.out(koffi.pointer(Pack3))]);
44
- const AddPack3 = lib.func('AddPack3', 'void', ['int', 'int', 'int', koffi.inout(koffi.pointer(Pack3))]);
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
 
@@ -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.func('InitWindow', 'void', ['int', 'int', 'string']);
91
- const SetTraceLogLevel = lib.func('SetTraceLogLevel', 'void', ['int']);
92
- const SetWindowState = lib.func('SetWindowState', 'void', ['uint']);
93
- const GenImageColor = lib.func('GenImageColor', Image, ['int', 'int', Color]);
94
- const GetFontDefault = lib.func('GetFontDefault', Font, []);
95
- const MeasureTextEx = lib.func('MeasureTextEx', Vector2, [Font, 'string', 'float', 'float']);
96
- const ImageDrawTextEx = lib.func('ImageDrawTextEx', 'void', [koffi.pointer(Image), Font, 'string', Vector2, 'float', 'float', Color]);
97
- const ExportImage = lib.func('ExportImage', 'bool', [Image, 'string']);
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
@@ -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.func('sqlite3_open_v2', 'int', ['string', koffi.out(koffi.pointer(sqlite3_db)), 'int', 'string']);
44
- const sqlite3_exec = lib.func('sqlite3_exec', 'int', [sqlite3_db, 'string', 'void *', 'void *', 'void *']);
45
- const sqlite3_prepare_v2 = lib.func('sqlite3_prepare_v2', 'int', [sqlite3_db, 'string', 'int', koffi.out(koffi.pointer(sqlite3_stmt)), 'string']);
46
- const sqlite3_reset = lib.func('sqlite3_reset', 'int', [sqlite3_stmt]);
47
- const sqlite3_bind_text = lib.func('sqlite3_bind_text', 'int', [sqlite3_stmt, 'int', 'string', 'int', 'void *']);
48
- const sqlite3_bind_int = lib.func('sqlite3_bind_int', 'int', [sqlite3_stmt, 'int', 'int']);
49
- const sqlite3_step = lib.func('sqlite3_step', 'int', [sqlite3_stmt]);
50
- const sqlite3_finalize = lib.func('sqlite3_finalize', 'int', [sqlite3_stmt]);
51
- const sqlite3_close_v2 = lib.func('sqlite3_close_v2', 'int', [sqlite3_db]);
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 = {};