koffi 2.6.12 → 2.7.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## Version history
4
4
 
5
+ ### Koffi 2.7
6
+
7
+ #### Koffi 2.7.1 (2024-01-02)
8
+
9
+ - Support C-like `int[3]` syntax for [fixed array types](input.md#fixed-size-c-arrays)
10
+ - Refuse type specifiers with invalid tokens at the end (previously ignored)
11
+
12
+ #### Koffi 2.7.0 (2023-12-21)
13
+
14
+ - Support alternative [callback calling convention](callbacks.md#callback-types) in classic syntax
15
+ - Change syntax for [calling conventions](functions.md#calling-conventions) with classic syntax
16
+ - Drop unused "internal" property from Koffi
17
+
5
18
  ### Koffi 2.6
6
19
 
7
20
  #### Koffi 2.6.12 (2023-12-11)
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/callbacks.md CHANGED
@@ -8,6 +8,8 @@ The function `koffi.proto()` was introduced in Koffi 2.4, it was called `koffi.c
8
8
 
9
9
  ## Callback types
10
10
 
11
+ *Changed in Koffi 2.7*
12
+
11
13
  In order to pass a JS function to a C function expecting a callback, you must first create a callback type with the expected return type and parameters. The syntax is similar to the one used to load functions from a shared library.
12
14
 
13
15
  ```js
@@ -21,6 +23,23 @@ const ExampleCallback = koffi.proto('ExampleCallback', 'void', ['int']);
21
23
  const AddDoubleFloat = koffi.proto('double AddDoubleFloat(double d, float f)');
22
24
  ```
23
25
 
26
+ For alternative [calling conventions](functions.md#calling-conventions) (such as `stdcall` on Windows x86 32-bit), you can specify as the first argument with the classic syntax, or after the return type in prototype strings, like this:
27
+
28
+ ```js
29
+ const HANDLE = koffi.pointer('HANDLE', koffi.opaque());
30
+ const HWND = koffi.alias('HWND', HANDLE);
31
+
32
+ // These two declarations work the same, and use the __stdcall convention on Windows x86
33
+ const EnumWindowsProc = koffi.proto('bool __stdcall EnumWindowsProc (HWND hwnd, long lParam)');
34
+ const EnumWindowsProc = koffi.proto('__stdcall', 'EnumWindowsProc', 'bool', ['HWND', 'long']);
35
+ ```
36
+
37
+ ```{warning}
38
+ You have to make sure you **get the calling convention right** (such as specifying __stdcall for a Windows API callback), or your code will crash on Windows 32-bit.
39
+
40
+ Before Koffi 2.7, it was *impossible to use an alternative callback calling convention with the classic syntax*. Use a prototype string or *upgrade to Koffi 2.7* to solve this limitation.
41
+ ```
42
+
24
43
  Once your callback type is declared, you can use a pointer to it in struct definitions, as function parameters and/or return types, or to call/decode function pointers.
25
44
 
26
45
  ```{note}
package/doc/functions.md CHANGED
@@ -9,7 +9,7 @@ To declare functions, start by loading the shared library with `koffi.load(filen
9
9
  const koffi = require('koffi');
10
10
 
11
11
  const lib = koffi.load('/path/to/shared/library'); // File extension depends on platforms: .so, .dll, .dylib, etc.
12
- ````
12
+ ```
13
13
 
14
14
  This library will be automatically unloaded once all references to it are gone (including all the functions that use it, as described below).
15
15
 
@@ -83,19 +83,27 @@ On x86 platforms, only the Cdecl convention can be used for variadic functions.
83
83
 
84
84
  ### Calling conventions
85
85
 
86
+ *Changed in Koffi 2.7*
87
+
86
88
  By default, calling a C function happens synchronously.
87
89
 
88
90
  Most architectures only support one procedure call standard per process. The 32-bit x86 platform is an exception to this, and Koffi supports several x86 conventions:
89
91
 
90
- Convention | Classic form | Prototype form | Description
91
- ------------- | ----------------------------- | -------------- | -------------------------------------------------------------------
92
- **Cdecl** | `koffi.cdecl` or `koffi.func` | _(default)_ | This is the default convention, and the only one on other platforms
93
- **Stdcall** | `koffi.stdcall` | __stdcall | This convention is used extensively within the Win32 API
94
- **Fastcall** | `koffi.fastcall` | __fastcall | Rarely used, uses ECX and EDX for first two parameters
95
- **Thiscall** | `koffi.thiscall` | __thiscall | Rarely used, uses ECX for first parameter
92
+ Convention | Classic form | Prototype form | Description
93
+ ------------- | --------------------------------------------- | -------------- | -------------------------------------------------------------------
94
+ **Cdecl** | `koffi.func(name, ret, params)` | _(default)_ | This is the default convention, and the only one on other platforms
95
+ **Stdcall** | `koffi.func('__stdcall', name, ret, params)` | __stdcall | This convention is used extensively within the Win32 API
96
+ **Fastcall** | `koffi.func('__fastcall', name, ret, params)` | __fastcall | Rarely used, uses ECX and EDX for first two parameters
97
+ **Thiscall** | `koffi.func('__thiscall', name, ret, params)` | __thiscall | Rarely used, uses ECX for first parameter
96
98
 
97
99
  You can safely use these on non-x86 platforms, they are simply ignored.
98
100
 
101
+ ```{note}
102
+ Support for specifying the convention as the first argument of the classic form was introduced in Koffi 2.7.
103
+
104
+ In earlier versions, you had to use `koffi.stdcall()` and similar functions. These functions are still supported but deprecated, and will be removed in Koffi 3.0.
105
+ ```
106
+
99
107
  Below you can find a small example showing how to use a non-default calling convention, with the two syntaxes:
100
108
 
101
109
  ```js
@@ -105,7 +113,7 @@ const koffi = require('koffi');
105
113
  const lib = koffi.load('user32.dll');
106
114
 
107
115
  // The following two declarations are equivalent, and use stdcall on x86 (and the default ABI on other platforms)
108
- const MessageBoxA_1 = lib.stdcall('MessageBoxA', 'int', ['void *', 'str', 'str', 'uint']);
116
+ const MessageBoxA_1 = lib.func('__stdcall', 'MessageBoxA', 'int', ['void *', 'str', 'str', 'uint']);
109
117
  const MessageBoxA_2 = lib.func('int __stdcall MessageBoxA(void *hwnd, str text, str caption, uint type)');
110
118
  ```
111
119
 
package/doc/input.md CHANGED
@@ -336,6 +336,8 @@ try {
336
336
 
337
337
  ### Fixed-size C arrays
338
338
 
339
+ *Changed in Koffi 2.7.1*
340
+
339
341
  Fixed-size arrays are declared with `koffi.array(type, length)`. Just like in C, they cannot be passed as functions parameters (they degenerate to pointers), or returned by value. You can however embed them in struct types.
340
342
 
341
343
  Koffi applies the following conversion rules when passing arrays to/from C:
@@ -350,13 +352,13 @@ See the example below:
350
352
  const koffi = require('koffi');
351
353
 
352
354
  // Those two structs are exactly the same, only the array conversion hint is different
353
- const Foo1 = koffi.struct('Foo', {
355
+ const Foo1 = koffi.struct('Foo1', {
354
356
  i: 'int',
355
- a16: koffi.array('int16_t', 8)
357
+ a16: koffi.array('int16_t', 2)
356
358
  });
357
- const Foo2 = koffi.struct('Foo', {
359
+ const Foo2 = koffi.struct('Foo2', {
358
360
  i: 'int',
359
- a16: koffi.array('int16_t', 8, 'Array')
361
+ a16: koffi.array('int16_t', 2, 'Array')
360
362
  });
361
363
 
362
364
  // Uses an hypothetical C function that just returns the struct passed as a parameter
@@ -367,6 +369,19 @@ console.log(ReturnFoo1({ i: 5, a16: [6, 8] })) // Prints { i: 5, a16: Int16Array
367
369
  console.log(ReturnFoo2({ i: 5, a16: [6, 8] })) // Prints { i: 5, a16: [6, 8] }
368
370
  ```
369
371
 
372
+ You can also declare arrays with the C-like short syntax in type declarations, as shown below:
373
+
374
+ ```js
375
+ const StructType = koffi.struct('StructType', {
376
+ f8: 'float [8]',
377
+ self4: 'StructType *[4]'
378
+ });
379
+ ```
380
+
381
+ ```{note}
382
+ The short C-like syntax was introduced in Koffi 2.7.1, use `koffi.array()` for older versions.
383
+ ```
384
+
370
385
  ### Fixed-size string buffers
371
386
 
372
387
  Koffi can also convert JS strings to fixed-sized arrays in the following cases:
package/doc/start.md CHANGED
@@ -99,8 +99,8 @@ const IDYES = 6;
99
99
  const IDNO = 7;
100
100
 
101
101
  // Find functions
102
- const MessageBoxA = lib.stdcall('MessageBoxA', 'int', ['void *', 'str', 'str', 'uint']);
103
- const MessageBoxW = lib.stdcall('MessageBoxW', 'int', ['void *', 'str16', 'str16', 'uint']);
102
+ const MessageBoxA = lib.func('__stdcall', 'MessageBoxA', 'int', ['void *', 'str', 'str', 'uint']);
103
+ const MessageBoxW = lib.func('__stdcall', 'MessageBoxW', 'int', ['void *', 'str16', 'str16', 'uint']);
104
104
 
105
105
  let ret = MessageBoxA(null, 'Do you want another message box?', 'Koffi', MB_YESNO | MB_ICONQUESTION);
106
106
  if (ret == IDYES)
package/index.d.ts CHANGED
@@ -67,18 +67,16 @@ declare module 'koffi' {
67
67
  export interface IKoffiLib {
68
68
  func(definition: string): KoffiFunction;
69
69
  func(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
70
+ func(convention: string, name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
70
71
 
71
- cdecl(definition: string): KoffiFunction;
72
- cdecl(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
73
-
74
- stdcall(definition: string): KoffiFunction;
75
- stdcall(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
76
-
77
- fastcall(definition: string): KoffiFunction;
78
- fastcall(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
79
-
80
- thiscall(definition: string): KoffiFunction;
81
- thiscall(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
72
+ /** @deprecated */ cdecl(definition: string): KoffiFunction;
73
+ /** @deprecated */ cdecl(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
74
+ /** @deprecated */ stdcall(definition: string): KoffiFunction;
75
+ /** @deprecated */ stdcall(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
76
+ /** @deprecated */ fastcall(definition: string): KoffiFunction;
77
+ /** @deprecated */ fastcall(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
78
+ /** @deprecated */ thiscall(definition: string): KoffiFunction;
79
+ /** @deprecated */ thiscall(name: string, result: TypeSpec, arguments: TypeSpec[]): KoffiFunction;
82
80
 
83
81
  symbol(name: string, type: TypeSpec): any;
84
82
 
@@ -106,8 +104,8 @@ declare module 'koffi' {
106
104
  /** @deprecated */ export function handle(): IKoffiCType;
107
105
 
108
106
  export function pointer(ref: TypeSpec): IKoffiCType;
109
- export function pointer(ref: TypeSpec, asteriskCount: number): IKoffiCType;
110
- export function pointer(name: string, ref: TypeSpec, asteriskCount: number): IKoffiCType;
107
+ export function pointer(ref: TypeSpec, asteriskCount?: number): IKoffiCType;
108
+ export function pointer(name: string, ref: TypeSpec, asteriskCount?: number): IKoffiCType;
111
109
 
112
110
  export function out(type: TypeSpec): IKoffiCType;
113
111
  export function inout(type: TypeSpec): IKoffiCType;
@@ -118,8 +116,10 @@ declare module 'koffi' {
118
116
 
119
117
  export function proto(definition: string): IKoffiCType;
120
118
  export function proto(name: string, result: TypeSpec, arguments: TypeSpec[]): IKoffiCType;
119
+ export function proto(convention: string, name: string, result: TypeSpec, arguments: TypeSpec[]): IKoffiCType;
121
120
  /** @deprecated */ export function callback(definition: string): IKoffiCType;
122
121
  /** @deprecated */ export function callback(name: string, result: TypeSpec, arguments: TypeSpec[]): IKoffiCType;
122
+ /** @deprecated */ export function callback(convention: string, name: string, result: TypeSpec, arguments: TypeSpec[]): IKoffiCType;
123
123
 
124
124
  export function register(callback: Function, type: TypeSpec): IKoffiRegisteredCallback;
125
125
  export function register(thisValue: any, callback: Function, type: TypeSpec): IKoffiRegisteredCallback;
@@ -131,6 +131,7 @@ declare module 'koffi' {
131
131
  export function decode(value: any, offset: number, type: TypeSpec): any;
132
132
  export function decode(value: any, offset: number, type: TypeSpec, len: number): any;
133
133
  export function address(value: any): bigint;
134
+ export function call(value: any, type: TypeSpec, ...args: any[]): any;
134
135
  export function encode(ref: any, type: TypeSpec): void;
135
136
  export function encode(ref: any, type: TypeSpec, value: any): void;
136
137
  export function encode(ref: any, type: TypeSpec, value: any, len: number): void;
package/index.js CHANGED
@@ -378,8 +378,8 @@ 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.6.12",
382
- stable: "2.6.12",
381
+ version: "2.7.1",
382
+ stable: "2.7.1",
383
383
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
384
384
  keywords: [
385
385
  "foreign",
@@ -557,3 +557,12 @@ module.exports = {
557
557
  handle: util.deprecate(native.opaque, "The koffi.handle() function was deprecated in Koffi 2.1, use koffi.opaque() instead", "KOFFI001"),
558
558
  callback: util.deprecate(native.proto, "The koffi.callback() function was deprecated in Koffi 2.4, use koffi.proto() instead", "KOFFI002")
559
559
  };
560
+ var load = module.exports.load;
561
+ module.exports.load = (...args) => {
562
+ let lib = load(...args);
563
+ lib.cdecl = util.deprecate((...args2) => lib.func("__cdecl", ...args2), "The koffi.stdcall() function was deprecated in Koffi 2.7, use koffi.func(...) instead", "KOFFI003");
564
+ lib.stdcall = util.deprecate((...args2) => lib.func("__stdcall", ...args2), 'The koffi.stdcall() function was deprecated in Koffi 2.7, use koffi.func("__stdcall", ...) instead', "KOFFI004");
565
+ lib.fastcall = util.deprecate((...args2) => lib.func("__fastcall", ...args2), 'The koffi.fastcall() function was deprecated in Koffi 2.7, use koffi.func("__fastcall", ...) instead', "KOFFI005");
566
+ lib.thiscall = util.deprecate((...args2) => lib.func("__thiscall", ...args2), 'The koffi.thiscall() function was deprecated in Koffi 2.7, use koffi.func("__thiscall", ...) instead', "KOFFI006");
567
+ return lib;
568
+ };
package/indirect.js CHANGED
@@ -378,8 +378,8 @@ 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.6.12",
382
- stable: "2.6.12",
381
+ version: "2.7.1",
382
+ stable: "2.7.1",
383
383
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
384
384
  keywords: [
385
385
  "foreign",
@@ -477,3 +477,12 @@ module.exports = {
477
477
  handle: util.deprecate(native.opaque, "The koffi.handle() function was deprecated in Koffi 2.1, use koffi.opaque() instead", "KOFFI001"),
478
478
  callback: util.deprecate(native.proto, "The koffi.callback() function was deprecated in Koffi 2.4, use koffi.proto() instead", "KOFFI002")
479
479
  };
480
+ var load = module.exports.load;
481
+ module.exports.load = (...args) => {
482
+ let lib = load(...args);
483
+ lib.cdecl = util.deprecate((...args2) => lib.func("__cdecl", ...args2), "The koffi.stdcall() function was deprecated in Koffi 2.7, use koffi.func(...) instead", "KOFFI003");
484
+ lib.stdcall = util.deprecate((...args2) => lib.func("__stdcall", ...args2), 'The koffi.stdcall() function was deprecated in Koffi 2.7, use koffi.func("__stdcall", ...) instead', "KOFFI004");
485
+ lib.fastcall = util.deprecate((...args2) => lib.func("__fastcall", ...args2), 'The koffi.fastcall() function was deprecated in Koffi 2.7, use koffi.func("__fastcall", ...) instead', "KOFFI005");
486
+ lib.thiscall = util.deprecate((...args2) => lib.func("__thiscall", ...args2), 'The koffi.thiscall() function was deprecated in Koffi 2.7, use koffi.func("__thiscall", ...) instead', "KOFFI006");
487
+ return lib;
488
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "2.6.12",
4
- "stable": "2.6.12",
3
+ "version": "2.7.1",
4
+ "stable": "2.7.1",
5
5
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
6
6
  "keywords": [
7
7
  "foreign",
@@ -3881,8 +3881,11 @@ bool CreateOverlappedPipe(bool overlap0, bool overlap1, PipeMode mode, HANDLE ou
3881
3881
 
3882
3882
  void CloseHandleSafe(HANDLE *handle_ptr)
3883
3883
  {
3884
- if (*handle_ptr && *handle_ptr != INVALID_HANDLE_VALUE) {
3885
- CloseHandle(*handle_ptr);
3884
+ HANDLE h = *handle_ptr;
3885
+
3886
+ if (h && h != INVALID_HANDLE_VALUE) {
3887
+ CancelIo(h);
3888
+ CloseHandle(h);
3886
3889
  }
3887
3890
 
3888
3891
  *handle_ptr = nullptr;
@@ -4021,6 +4024,7 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
4021
4024
  CloseHandleSafe(&si.hStdOutput);
4022
4025
  CloseHandleSafe(&si.hStdError);
4023
4026
  };
4027
+
4024
4028
  if (in_func.IsValid() || out_func.IsValid()) {
4025
4029
  if (!DuplicateHandle(GetCurrentProcess(), in_func.IsValid() ? in_pipe[0] : GetStdHandle(STD_INPUT_HANDLE),
4026
4030
  GetCurrentProcess(), &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS)) {
@@ -4095,6 +4099,7 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
4095
4099
  if (proc_in.err && proc_in.err != ERROR_BROKEN_PIPE && proc_in.err != ERROR_NO_DATA) {
4096
4100
  LogError("Failed to write to process: %1", GetWin32ErrorString(proc_in.err));
4097
4101
  }
4102
+
4098
4103
  proc_in.pending = true;
4099
4104
  }
4100
4105
 
@@ -4111,30 +4116,23 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
4111
4116
  }
4112
4117
  }
4113
4118
 
4114
- if (proc_out.err && proc_out.err != ERROR_BROKEN_PIPE && proc_out.err != ERROR_NO_DATA) {
4115
- LogError("Failed to read process output: %1", GetWin32ErrorString(proc_out.err));
4119
+ if (proc_out.err) {
4120
+ if (proc_out.err != ERROR_BROKEN_PIPE && proc_out.err != ERROR_NO_DATA) {
4121
+ LogError("Failed to read process output: %1", GetWin32ErrorString(proc_out.err));
4122
+ }
4123
+ break;
4116
4124
  }
4125
+
4117
4126
  proc_out.pending = true;
4118
4127
  }
4119
4128
 
4120
- HANDLE events[2] = {
4121
- process_handle,
4122
- console_ctrl_event
4123
- };
4124
-
4125
- running = (WaitForMultipleObjectsEx(RG_LEN(events), events, FALSE, INFINITE, TRUE) > WAIT_OBJECT_0 + 1);
4129
+ running = (WaitForSingleObjectEx(console_ctrl_event, INFINITE, TRUE) != WAIT_OBJECT_0);
4126
4130
  }
4127
4131
  }
4128
4132
 
4129
4133
  // Terminate any remaining I/O
4130
- if (in_pipe[1]) {
4131
- CancelIo(in_pipe[1]);
4132
- CloseHandleSafe(&in_pipe[1]);
4133
- }
4134
- if (out_pipe[0]) {
4135
- CancelIo(out_pipe[0]);
4136
- CloseHandleSafe(&out_pipe[0]);
4137
- }
4134
+ CloseHandleSafe(&in_pipe[1]);
4135
+ CloseHandleSafe(&out_pipe[0]);
4138
4136
 
4139
4137
  // Wait for process exit
4140
4138
  {
@@ -5599,7 +5597,6 @@ public:
5599
5597
  AsyncPool(int threads, bool leak);
5600
5598
 
5601
5599
  int GetWorkerCount() const { return (int)workers.len; }
5602
- int CountPendingTasks() const { return pending_tasks; }
5603
5600
 
5604
5601
  void RegisterAsync();
5605
5602
  void UnregisterAsync();
@@ -5673,11 +5670,6 @@ bool Async::Sync()
5673
5670
  return success;
5674
5671
  }
5675
5672
 
5676
- int Async::CountPendingTasks()
5677
- {
5678
- return pool->CountPendingTasks();
5679
- }
5680
-
5681
5673
  bool Async::IsTaskRunning()
5682
5674
  {
5683
5675
  return async_running_task;
@@ -5806,6 +5798,11 @@ void AsyncPool::AddTask(Async *async, const std::function<bool()> &func)
5806
5798
  pending_cv.notify_all();
5807
5799
  sync_cv.notify_all();
5808
5800
  }
5801
+
5802
+ // Limit queue size (back pressure)
5803
+ while (pending_tasks >= RG_ASYNC_MAX_PENDING_TASKS) {
5804
+ RunTasks(0);
5805
+ }
5809
5806
  }
5810
5807
 
5811
5808
  void AsyncPool::RunWorker(int worker_idx)
@@ -7519,6 +7516,13 @@ void OptionParser::LogUnknownError() const
7519
7516
  }
7520
7517
  }
7521
7518
 
7519
+ void OptionParser::LogUnusedArguments() const
7520
+ {
7521
+ if (pos < args.len) {
7522
+ LogWarning("Unused command-line arguments");
7523
+ }
7524
+ }
7525
+
7522
7526
  // ------------------------------------------------------------------------
7523
7527
  // Console prompter (simplified readline)
7524
7528
  // ------------------------------------------------------------------------
@@ -93,6 +93,8 @@ namespace RG {
93
93
 
94
94
  #define RG_ASYNC_MAX_THREADS 2048
95
95
  #define RG_ASYNC_MAX_IDLE_TIME 10000
96
+ #define RG_ASYNC_MAX_PENDING_TASKS 1024
97
+
96
98
  #define RG_FIBER_DEFAULT_STACK_SIZE Kibibytes(128)
97
99
 
98
100
  // ------------------------------------------------------------------------
@@ -1218,11 +1220,22 @@ public:
1218
1220
  {
1219
1221
  RG_ASSERT(len <= N - count);
1220
1222
 
1221
- T *it = data + len;
1222
- *it = {};
1223
- len += count;
1223
+ T *first = data + len;
1224
+ #if __cplusplus >= 201703L
1225
+ if constexpr(!std::is_trivial<T>::value) {
1226
+ #else
1227
+ if (true) {
1228
+ #endif
1229
+ for (Size i = 0; i < count; i++) {
1230
+ new (data + len) T();
1231
+ len++;
1232
+ }
1233
+ } else {
1234
+ memset_safe(first, 0, count * RG_SIZE(T));
1235
+ len += count;
1236
+ }
1224
1237
 
1225
- return it;
1238
+ return first;
1226
1239
  }
1227
1240
 
1228
1241
  T *Append(const T &value)
@@ -1432,6 +1445,7 @@ public:
1432
1445
  memset_safe(first, 0, count * RG_SIZE(T));
1433
1446
  len += count;
1434
1447
  }
1448
+
1435
1449
  return first;
1436
1450
  }
1437
1451
 
@@ -4265,8 +4279,6 @@ public:
4265
4279
  void Run(const std::function<bool()> &f);
4266
4280
  bool Sync();
4267
4281
 
4268
- int CountPendingTasks();
4269
-
4270
4282
  static bool IsTaskRunning();
4271
4283
  static int GetWorkerIdx();
4272
4284
  static int GetWorkerCount();
@@ -4887,7 +4899,9 @@ public:
4887
4899
  { return Test(test1, nullptr, type); }
4888
4900
 
4889
4901
  bool TestHasFailed() const { return test_failed; }
4902
+
4890
4903
  void LogUnknownError() const;
4904
+ void LogUnusedArguments() const;
4891
4905
  };
4892
4906
 
4893
4907
  template <typename T>
@@ -880,35 +880,50 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
880
880
  return WrapType(env, instance, type);
881
881
  }
882
882
 
883
- static bool ParseClassicFunction(Napi::Env env, Napi::String name, Napi::Value ret,
884
- Napi::Array parameters, FunctionInfo *func)
883
+ static bool ParseClassicFunction(const Napi::CallbackInfo &info, FunctionInfo *out_func)
885
884
  {
885
+ Napi::Env env = info.Env();
886
886
  InstanceData *instance = env.GetInstanceData<InstanceData>();
887
887
 
888
+ Napi::String name = info[0u].As<Napi::String>();
889
+ Napi::Value ret = info[1u];
890
+ Napi::Array parameters = info[2u].As<Napi::Array>();
891
+
892
+ // Detect optional call convention
893
+ if (name.IsString() && DetectCallConvention(name.Utf8Value().c_str(), &out_func->convention)) {
894
+ if (info.Length() < 4) {
895
+ ThrowError<Napi::TypeError>(env, "Expected 4 arguments, got %1", info.Length());
896
+ return false;
897
+ }
898
+
899
+ name = info[1u].As<Napi::String>();
900
+ ret = info[2u];
901
+ parameters = info[3u].As<Napi::Array>();
902
+ }
903
+
888
904
  #ifdef _WIN32
889
- if (!name.IsString() && !name.IsNumber()) {
890
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value for name, expected string or integer", GetValueType(instance, name));
891
- return false;
905
+ if (name.IsNumber()) {
906
+ out_func->ordinal_name = name.As<Napi::Number>().Int32Value();
907
+ name = name.ToString();
892
908
  }
893
- #else
909
+ #endif
894
910
  if (!name.IsString()) {
895
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value for name, expected string", GetValueType(instance, name));
911
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for name, expected string or integer", GetValueType(instance, name));
896
912
  return false;
897
913
  }
898
- #endif
899
914
 
900
- func->name = DuplicateString(name.ToString().Utf8Value().c_str(), &instance->str_alloc).ptr;
915
+ out_func->name = DuplicateString(name.ToString().Utf8Value().c_str(), &instance->str_alloc).ptr;
901
916
 
902
- func->ret.type = ResolveType(ret);
903
- if (!func->ret.type)
917
+ out_func->ret.type = ResolveType(ret);
918
+ if (!out_func->ret.type)
904
919
  return false;
905
- if (!CanReturnType(func->ret.type)) {
906
- ThrowError<Napi::TypeError>(env, "You are not allowed to directly return %1 values (maybe try %1 *)", func->ret.type->name);
920
+ if (!CanReturnType(out_func->ret.type)) {
921
+ ThrowError<Napi::TypeError>(env, "You are not allowed to directly return %1 values (maybe try %1 *)", out_func->ret.type->name);
907
922
  return false;
908
923
  }
909
924
 
910
925
  if (!parameters.IsArray()) {
911
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value for parameters of '%2', expected an array", GetValueType(instance, parameters), func->name);
926
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for parameters of '%2', expected an array", GetValueType(instance, parameters), out_func->name);
912
927
  return false;
913
928
  }
914
929
 
@@ -918,7 +933,7 @@ static bool ParseClassicFunction(Napi::Env env, Napi::String name, Napi::Value r
918
933
  Napi::String str = parameters.Get(parameters_len - 1).As<Napi::String>();
919
934
 
920
935
  if (str.IsString() && str.Utf8Value() == "...") {
921
- func->variadic = true;
936
+ out_func->variadic = true;
922
937
  parameters_len--;
923
938
  }
924
939
  }
@@ -934,21 +949,21 @@ static bool ParseClassicFunction(Napi::Env env, Napi::String name, Napi::Value r
934
949
  ThrowError<Napi::TypeError>(env, "Type %1 cannot be used as a parameter", param.type->name);
935
950
  return false;
936
951
  }
937
- if (func->parameters.len >= MaxParameters) {
952
+ if (out_func->parameters.len >= MaxParameters) {
938
953
  ThrowError<Napi::TypeError>(env, "Functions cannot have more than %1 parameters", MaxParameters);
939
954
  return false;
940
955
  }
941
- if ((param.directions & 2) && ++func->out_parameters >= MaxParameters) {
956
+ if ((param.directions & 2) && ++out_func->out_parameters >= MaxParameters) {
942
957
  ThrowError<Napi::TypeError>(env, "Functions cannot have more than %1 output parameters", MaxParameters);
943
958
  return false;
944
959
  }
945
960
 
946
961
  param.offset = (int8_t)j;
947
962
 
948
- func->parameters.Append(param);
963
+ out_func->parameters.Append(param);
949
964
  }
950
965
 
951
- func->required_parameters = (int8_t)func->parameters.len;
966
+ out_func->required_parameters = (int8_t)out_func->parameters.len;
952
967
 
953
968
  return true;
954
969
  }
@@ -962,7 +977,7 @@ static Napi::Value CreateFunctionType(const Napi::CallbackInfo &info)
962
977
  RG_DEFER_N(err_guard) { instance->callbacks.RemoveLast(1); };
963
978
 
964
979
  if (info.Length() >= 3) {
965
- if (!ParseClassicFunction(env, info[0u].As<Napi::String>(), info[1u], info[2u].As<Napi::Array>(), func))
980
+ if (!ParseClassicFunction(info, func))
966
981
  return env.Null();
967
982
  } else if (info.Length() >= 1) {
968
983
  if (!info[0].IsString()) {
@@ -1519,7 +1534,7 @@ extern "C" void RelayCallback(Size idx, uint8_t *own_sp, uint8_t *caller_sp, Bac
1519
1534
  }
1520
1535
  }
1521
1536
 
1522
- static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConvention convention)
1537
+ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info)
1523
1538
  {
1524
1539
  Napi::Env env = info.Env();
1525
1540
  InstanceData *instance = env.GetInstanceData<InstanceData>();
@@ -1529,10 +1544,9 @@ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConve
1529
1544
  RG_DEFER { func->Unref(); };
1530
1545
 
1531
1546
  func->lib = lib->Ref();
1532
- func->convention = convention;
1533
1547
 
1534
1548
  if (info.Length() >= 3) {
1535
- if (!ParseClassicFunction(env, info[0u].As<Napi::String>(), info[1u], info[2u].As<Napi::Array>(), func))
1549
+ if (!ParseClassicFunction(info, func))
1536
1550
  return env.Null();
1537
1551
  } else if (info.Length() >= 1) {
1538
1552
  if (!info[0].IsString()) {
@@ -1562,7 +1576,7 @@ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConve
1562
1576
  }
1563
1577
 
1564
1578
  #ifdef _WIN32
1565
- if (info[0].IsString()) {
1579
+ if (func->ordinal_name < 0) {
1566
1580
  if (func->decorated_name) {
1567
1581
  func->native = (void *)GetProcAddress((HMODULE)lib->module, func->decorated_name);
1568
1582
  }
@@ -1570,7 +1584,7 @@ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConve
1570
1584
  func->native = (void *)GetProcAddress((HMODULE)lib->module, func->name);
1571
1585
  }
1572
1586
  } else {
1573
- uint16_t ordinal = (uint16_t)info[0].As<Napi::Number>().Uint32Value();
1587
+ uint16_t ordinal = (uint16_t)func->ordinal_name;
1574
1588
 
1575
1589
  func->decorated_name = nullptr;
1576
1590
  func->native = (void *)GetProcAddress((HMODULE)lib->module, (LPCSTR)(size_t)ordinal);
@@ -1720,20 +1734,13 @@ static Napi::Value LoadSharedLibrary(const Napi::CallbackInfo &info)
1720
1734
  func.AddFinalizer([](Napi::Env, LibraryHolder *lib) { lib->Unref(); }, lib); \
1721
1735
  obj.Set((Name), func); \
1722
1736
  } while (false)
1723
- #define ADD_CONVENTION(Name, Value) ADD_METHOD((Name), FindLibraryFunction(info, Value))
1724
-
1725
- ADD_CONVENTION("func", CallConvention::Cdecl);
1726
- ADD_CONVENTION("cdecl", CallConvention::Cdecl);
1727
- ADD_CONVENTION("stdcall", CallConvention::Stdcall);
1728
- ADD_CONVENTION("fastcall", CallConvention::Fastcall);
1729
- ADD_CONVENTION("thiscall", CallConvention::Thiscall);
1730
1737
 
1738
+ ADD_METHOD("func", FindLibraryFunction(info));
1731
1739
  ADD_METHOD("symbol", FindSymbol(info));
1732
1740
 
1733
1741
  // We can't unref the library after unload, obviously
1734
1742
  obj.Set("unload", Napi::Function::New(env, UnloadLibrary, "unload", (void *)lib->Ref()));
1735
1743
 
1736
- #undef ADD_CONVENTION
1737
1744
  #undef ADD_METHOD
1738
1745
 
1739
1746
  return obj;
@@ -2326,7 +2333,6 @@ static Napi::Object InitModule(Napi::Env env, Napi::Object exports)
2326
2333
  Napi::Object types = InitBaseTypes(env);
2327
2334
  exports.Set("types", types);
2328
2335
 
2329
- exports.Set("internal", Napi::Boolean::New(env, false));
2330
2336
  exports.Set("version", Napi::String::New(env, RG_STRINGIFY(VERSION)));
2331
2337
 
2332
2338
  return exports;
@@ -220,6 +220,9 @@ struct FunctionInfo {
220
220
 
221
221
  const char *name;
222
222
  const char *decorated_name; // Only set for some platforms/calling conventions
223
+ #ifdef _WIN32
224
+ int ordinal_name = -1;
225
+ #endif
223
226
  const LibraryHolder *lib = nullptr;
224
227
 
225
228
  void *native;
@@ -40,15 +40,7 @@ bool PrototypeParser::Parse(const char *str, FunctionInfo *out_func)
40
40
  MarkError("You are not allowed to directly return %1 values (maybe try %1 *)", out_func->ret.type->name);
41
41
  return false;
42
42
  }
43
- if (Match("__cdecl")) {
44
- out_func->convention = CallConvention::Cdecl;
45
- } else if (Match("__stdcall")) {
46
- out_func->convention = CallConvention::Stdcall;
47
- } else if (Match("__fastcall")) {
48
- out_func->convention = CallConvention::Fastcall;
49
- } else if (Match("__thiscall")) {
50
- out_func->convention = CallConvention::Thiscall;
51
- }
43
+ offset += (offset < tokens.len && DetectCallConvention(tokens[offset], &out_func->convention));
52
44
  out_func->name = ParseIdentifier();
53
45
 
54
46
  Consume("(");
@@ -152,6 +144,10 @@ const TypeInfo *PrototypeParser::ParseType(int *out_directions)
152
144
  while (++offset < tokens.len && (tokens[offset] == '*' ||
153
145
  tokens[offset] == '!' ||
154
146
  tokens[offset] == "const"));
147
+ if (offset < tokens.len && tokens[offset] == "[") [[unlikely]] {
148
+ MarkError("Array types decay to pointers in prototypes (C standard), use pointers");
149
+ return instance->void_type;
150
+ }
155
151
  offset--;
156
152
 
157
153
  while (offset >= start) {
@@ -192,7 +192,8 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
192
192
  {
193
193
  InstanceData *instance = env.GetInstanceData<InstanceData>();
194
194
 
195
- int indirect = 0;
195
+ // Each item can be > 0 for array or 0 for a pointer
196
+ LocalArray<Size, 8> arrays;
196
197
  uint8_t disposables = 0;
197
198
 
198
199
  // Consume parameter direction qualifier
@@ -223,18 +224,18 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
223
224
  Span<const char> remain = str;
224
225
 
225
226
  // Skip initial const qualifiers
226
- remain = TrimStr(remain);
227
+ remain = TrimStrLeft(remain);
227
228
  while (SplitIdentifier(remain) == "const") {
228
229
  remain = remain.Take(6, remain.len - 6);
229
- remain = TrimStr(remain);
230
+ remain = TrimStrLeft(remain);
230
231
  }
231
- remain = TrimStr(remain);
232
+ remain = TrimStrLeft(remain);
232
233
 
233
234
  after = remain;
234
235
 
235
236
  // Consume one or more identifiers (e.g. unsigned int)
236
237
  for (;;) {
237
- after = TrimStr(after);
238
+ after = TrimStrLeft(after);
238
239
 
239
240
  Span<const char> token = SplitIdentifier(after);
240
241
  if (!token.len)
@@ -245,26 +246,57 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
245
246
  name = TrimStr(MakeSpan(remain.ptr, after.ptr - remain.ptr));
246
247
  }
247
248
 
248
- // Consume pointer indirections
249
+ // Consume type indirections (pointer, array, etc.)
249
250
  while (after.len) {
250
251
  if (after[0] == '*') {
251
252
  after = after.Take(1, after.len - 1);
252
- indirect++;
253
253
 
254
- if (indirect >= RG_SIZE(disposables) * 8) [[unlikely]] {
255
- ThrowError<Napi::Error>(env, "Too many pointer indirections");
254
+ if (!arrays.Available()) [[unlikely]] {
255
+ ThrowError<Napi::Error>(env, "Too many type indirections");
256
256
  return nullptr;
257
257
  }
258
+
259
+ arrays.Append(0);
258
260
  } else if (after[0] == '!') {
259
261
  after = after.Take(1, after.len - 1);
260
- disposables |= (1u << indirect);
262
+ disposables |= (1u << arrays.len);
263
+ } else if (after[0] == '[') {
264
+ after = after.Take(1, after.len - 1);
265
+
266
+ Size len = 0;
267
+
268
+ after = TrimStrLeft(after);
269
+ if (!ParseInt(after, &len, 0, &after) || len < 0) [[unlikely]] {
270
+ ThrowError<Napi::Error>(env, "Invalid array length");
271
+ return nullptr;
272
+ }
273
+ after = TrimStrLeft(after);
274
+ if (!after.len || after[0] != ']') [[unlikely]] {
275
+ ThrowError<Napi::Error>(env, "Expected ']' after array length");
276
+ return nullptr;
277
+ }
278
+ after = after.Take(1, after.len - 1);
279
+
280
+ if (!arrays.Available()) [[unlikely]] {
281
+ ThrowError<Napi::Error>(env, "Too many type indirections");
282
+ return nullptr;
283
+ }
284
+
285
+ arrays.Append(len);
261
286
  } else if (SplitIdentifier(after) == "const") {
262
287
  after = after.Take(6, after.len - 6);
263
288
  } else {
289
+ after = TrimStrRight(after);
290
+
291
+ if (after.len) [[unlikely]] {
292
+ ThrowError<Napi::Error>(env, "Unexpected character '%1' in type specifier", after[0]);
293
+ return nullptr;
294
+ }
295
+
264
296
  break;
265
297
  }
266
298
 
267
- after = TrimStr(after);
299
+ after = TrimStrLeft(after);
268
300
  }
269
301
 
270
302
  const TypeInfo *type = instance->types_map.FindValue(name, nullptr);
@@ -317,11 +349,29 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
317
349
  type = copy;
318
350
  }
319
351
 
320
- if (i >= indirect)
352
+ if (i >= arrays.len)
321
353
  break;
354
+ Size len = arrays[i];
322
355
 
323
- type = MakePointerType(instance, type);
324
- RG_ASSERT(type);
356
+ if (len > 0) {
357
+ if (type->flags & (int)TypeFlag::IsIncomplete) [[unlikely]] {
358
+ ThrowError<Napi::TypeError>(env, "Cannot make array of incomplete type");
359
+ return nullptr;
360
+ }
361
+
362
+ if (len > instance->config.max_type_size / type->size) {
363
+ ThrowError<Napi::TypeError>(env, "Array length is too high (max = %1)", instance->config.max_type_size / type->size);
364
+ return nullptr;
365
+ }
366
+
367
+ type = MakeArrayType(instance, type, len);
368
+ RG_ASSERT(type);
369
+ } else {
370
+ RG_ASSERT(!len);
371
+
372
+ type = MakePointerType(instance, type);
373
+ RG_ASSERT(type);
374
+ }
325
375
  }
326
376
 
327
377
  if (type->flags & (int)TypeFlag::IsIncomplete) [[unlikely]] {
@@ -1508,6 +1558,25 @@ Napi::Function WrapFunction(Napi::Env env, const FunctionInfo *func)
1508
1558
  return wrapper;
1509
1559
  }
1510
1560
 
1561
+ bool DetectCallConvention(Span<const char> name, CallConvention *out_convention)
1562
+ {
1563
+ if (name == "__cdecl") {
1564
+ *out_convention = CallConvention::Cdecl;
1565
+ return true;
1566
+ } else if (name == "__stdcall") {
1567
+ *out_convention = CallConvention::Stdcall;
1568
+ return true;
1569
+ } else if (name == "__fastcall") {
1570
+ *out_convention = CallConvention::Fastcall;
1571
+ return true;
1572
+ } else if (name == "__thiscall") {
1573
+ *out_convention = CallConvention::Thiscall;
1574
+ return true;
1575
+ } else {
1576
+ return false;
1577
+ }
1578
+ }
1579
+
1511
1580
  static int AnalyseFlatRec(const TypeInfo *type, int offset, int count, FunctionRef<void(const TypeInfo *type, int offset, int count)> func)
1512
1581
  {
1513
1582
  if (type->primitive == PrimitiveKind::Record) {
@@ -22,15 +22,12 @@
22
22
  #pragma once
23
23
 
24
24
  #include "src/core/libcc/libcc.hh"
25
+ #include "ffi.hh"
25
26
 
26
27
  #include <napi.h>
27
28
 
28
29
  namespace RG {
29
30
 
30
- struct InstanceData;
31
- struct TypeInfo;
32
- struct FunctionInfo;
33
-
34
31
  extern const int TypeInfoMarker;
35
32
  extern const int CastMarker;
36
33
  extern const int MagicUnionMarker;
@@ -208,6 +205,8 @@ static inline Napi::Array GetOwnPropertyNames(Napi::Object obj)
208
205
 
209
206
  Napi::Function WrapFunction(Napi::Env env, const FunctionInfo *func);
210
207
 
208
+ bool DetectCallConvention(Span<const char> name, CallConvention *out_convention);
209
+
211
210
  int AnalyseFlat(const TypeInfo *type, FunctionRef<void(const TypeInfo *type, int offset, int count)> func);
212
211
 
213
212
  void DumpMemory(const char *type, Span<const uint8_t> bytes);
@@ -21,6 +21,7 @@
21
21
 
22
22
  #ifdef _WIN32
23
23
 
24
+ #include "util.hh"
24
25
  #include "win32.hh"
25
26
 
26
27
  #ifndef NOMINMAX
@@ -24,6 +24,7 @@
24
24
  #include "src/core/libcc/libcc.hh"
25
25
 
26
26
  #include <intrin.h>
27
+ #include <napi.h>
27
28
 
28
29
  namespace RG {
29
30