node-ctypes 0.1.5 → 1.0.0

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.

Potentially problematic release.


This version of node-ctypes might be problematic. Click here for more details.

package/README.md CHANGED
@@ -38,7 +38,7 @@ npm install node-ctypes
38
38
  Prebuilt binaries are available for:
39
39
  - Windows x64, ARM64
40
40
  - Linux x64, ARM64
41
- - macOS ARM64
41
+ - macOS x64, ARM64
42
42
 
43
43
  ### From source
44
44
 
@@ -659,10 +659,12 @@ console.log(p.x, p.y); // 1 2
659
659
  - `get_errno()` / `set_errno(value)` : access to errno (platform-specific implementation).
660
660
  - `_initWinError()` internals; public helpers: `GetLastError()`, `SetLastError(code)`, `FormatError(code)`, `WinError(code)`.
661
661
 
662
- **Type aliases**
663
- The following aliases are exposed (mapped from `native.types`):
662
+ **Type aliases (Python-compatible)**
663
+ The following type classes are exported (identical to Python ctypes):
664
664
  `c_int, c_uint, c_int8, c_uint8, c_int16, c_uint16, c_int32, c_uint32, c_int64, c_uint64, c_float, c_double, c_char, c_char_p, c_wchar, c_wchar_p, c_void_p, c_bool, c_size_t, c_long, c_ulong`.
665
665
 
666
+ **Note**: Only type classes (e.g., `c_int32`) are supported, not string literals (e.g., `"int32"`). This matches Python ctypes behavior exactly.
667
+
666
668
  **Constants**
667
669
  - `POINTER_SIZE` - pointer size (from `native.POINTER_SIZE`).
668
670
  - `WCHAR_SIZE` - wchar size (from `native.WCHAR_SIZE`).
@@ -720,7 +722,7 @@ Base class for Python-like union definitions. Subclasses should define `static _
720
722
  | **Arrays** | `c_int * 5` | `array(c_int, 5)` |
721
723
  | **Bit fields** | `("flags", c_uint, 3)` | `bitfield(c_uint32, 3)` |
722
724
  | **Callbacks** | `CFUNCTYPE(c_int, c_int)` | `callback(fn, c_int, [c_int])` |
723
- | **Strings** | `c_char_p(b"hello")` | `create_string_buffer("hello")`<br>**or**<br>`c_char_p(b"hello")` |
725
+ | **Strings** | `c_char_p(b"hello")` | `create_string_buffer("hello")`<br>**or**<br>`new c_char_p(b"hello")` |
724
726
  | **Pointers** | `POINTER(c_int)` | `c_void_p` |
725
727
  | **Variadic** | `sprintf(buf, b"%d", 42)` | `sprintf(buf, fmt, 42)` (auto) |
726
728
  | **Sizeof** | `sizeof(c_int)` | `sizeof(c_int)` |
@@ -742,19 +744,22 @@ Base class for Python-like union definitions. Subclasses should define `static _
742
744
  - Windows API (__stdcall via WinDLL)
743
745
 
744
746
  ⚠️ **Differences from Python ctypes**:
745
- - Structs use `.toObject()` for property access (eager loading for performance)
746
747
  - Callbacks must be manually released with `.release()`
747
748
  - **Function definition supports both syntaxes**: `func(name, returnType, argTypes)` **or** `func.argtypes = [...]; func.restype = ...`
748
- - No `POINTER()` type - use `c_void_p`
749
749
 
750
+ ✨ **100% Python-compatible**:
751
+ - **Struct property access**: Direct access (`p.x`, `p.y`) works with `class X extends Structure` - identical to Python!
752
+ - **Type system**: Only type classes (`c_int32`, `c_char_p`) are accepted, exactly like Python ctypes
753
+ - **No string literals**: `"int32"`, `"string"` are NOT supported (Python doesn't use them either)
750
754
 
751
755
  ## Limitations & Known Issues
752
756
 
753
757
  - ⚠️ Callbacks must be released manually with `.release()` to prevent memory leaks
754
758
  - ⚠️ No automatic memory management for returned pointers (manual `free()` required)
755
- - ⚠️ Struct alignment follows platform defaults (not customizable per-field)
756
- - ℹ️ Nested union access uses getter caching (slight behavior difference from Python)
757
- - ℹ️ Struct property access via `.toObject()` instead of direct field access (performance optimization)
759
+ - ℹ️ Struct property access:
760
+ - **Python-style classes** (`class X extends Structure`): Direct property access (`p.x`, `p.y`) - fully compatible!
761
+ - **Plain struct definitions** (`struct({...})`): Use `.get()` / `.set()` methods for property access
762
+ - ℹ️ Struct alignment: Platform defaults are used, but `packed: true` option is available for packed structs
758
763
 
759
764
  ## Examples in Test Suite
760
765
 
Binary file
Binary file
@@ -0,0 +1,312 @@
1
+ /**
2
+ * @file Library.js
3
+ * @module core/Library
4
+ * @description Library loading and FFI function wrapping for CDLL and WinDLL.
5
+ *
6
+ * This module provides the core library loading functionality including:
7
+ * - FunctionWrapper for Python-like function attribute syntax
8
+ * - CDLL class for loading C libraries (cdecl calling convention)
9
+ * - WinDLL class for loading Windows libraries (stdcall calling convention)
10
+ *
11
+ * **Python ctypes Compatibility**:
12
+ * - `CDLL` ≈ Python's `ctypes.CDLL`
13
+ * - `WinDLL` ≈ Python's `ctypes.WinDLL`
14
+ *
15
+ * @example Loading a library
16
+ * ```javascript
17
+ * import { CDLL } from 'node-ctypes';
18
+ *
19
+ * const libc = new CDLL(null); // Load C standard library
20
+ * const printf = libc.func('printf', c_int, [c_char_p]);
21
+ * printf('Hello from Node.js!\n');
22
+ * ```
23
+ *
24
+ * @example Python-like syntax
25
+ * ```javascript
26
+ * const libc = new CDLL(null);
27
+ * const abs = libc.abs;
28
+ * abs.argtypes = [c_int];
29
+ * abs.restype = c_int;
30
+ * console.log(abs(-42)); // 42
31
+ * ```
32
+ *
33
+ * @example Windows DLL
34
+ * ```javascript
35
+ * import { WinDLL } from 'node-ctypes';
36
+ *
37
+ * const user32 = new WinDLL('user32.dll');
38
+ * const MessageBoxW = user32.func('MessageBoxW', c_int, [c_void_p, c_wchar_p, c_wchar_p, c_uint]);
39
+ * ```
40
+ */
41
+
42
+ /**
43
+ * Creates the Library-related classes with required dependencies injected.
44
+ *
45
+ * @param {Class} Library - Native Library class
46
+ * @param {Class} LRUCache - LRU cache class
47
+ * @param {Function} _toNativeType - Type conversion function
48
+ * @param {Function} _toNativeTypes - Type array conversion function
49
+ * @param {Object} native - Native module reference
50
+ * @returns {Object} Object containing FunctionWrapper, CDLL, WinDLL classes
51
+ * @private
52
+ */
53
+ export function createLibraryClasses(Library, LRUCache, _toNativeType, _toNativeTypes, native) {
54
+ class FunctionWrapper {
55
+ constructor(cdll, name) {
56
+ const argtypes = [];
57
+ let restype = undefined;
58
+ let cachedFunc = null;
59
+
60
+ // Create a function and proxy it to make it callable
61
+ const func = (...args) => {
62
+ if (restype === undefined) {
63
+ throw new Error(`Function ${name}: restype not set`);
64
+ }
65
+ if (!cachedFunc) {
66
+ cachedFunc = cdll.func(name, restype, argtypes);
67
+ }
68
+ return cachedFunc(...args);
69
+ };
70
+
71
+ // Proxy the function to intercept property access
72
+ return new Proxy(func, {
73
+ get(target, prop) {
74
+ if (prop === "argtypes") return argtypes;
75
+ if (prop === "restype") return restype;
76
+ if (prop === "errcheck") return cachedFunc ? cachedFunc.errcheck : undefined;
77
+ return target[prop];
78
+ },
79
+ set(target, prop, value) {
80
+ if (prop === "argtypes") {
81
+ argtypes.splice(0, argtypes.length, ...value);
82
+ cachedFunc = null; // Invalidate cache
83
+ return true;
84
+ }
85
+ if (prop === "restype") {
86
+ restype = value;
87
+ cachedFunc = null; // Invalidate cache
88
+ return true;
89
+ }
90
+ if (prop === "errcheck") {
91
+ if (!cachedFunc) {
92
+ if (restype === undefined) {
93
+ throw new Error(`Function ${name}: restype not set`);
94
+ }
95
+ cachedFunc = cdll.func(name, restype, argtypes);
96
+ }
97
+ cachedFunc.errcheck = value;
98
+ return true;
99
+ }
100
+ target[prop] = value;
101
+ return true;
102
+ },
103
+ });
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Wrapper conveniente per CDLL (come in Python ctypes)
109
+ */
110
+ class CDLL {
111
+ constructor(libPath, options = {}) {
112
+ this._lib = new Library(libPath);
113
+ // Use LRU cache to prevent unbounded memory growth
114
+ const cacheSize = options.cacheSize ?? 1000;
115
+ this._cache = new LRUCache(cacheSize);
116
+
117
+ // Return a proxy to enable Python-like syntax: libc.abs.argtypes = [...]; libc.abs.restype = ...; libc.abs(...)
118
+ return new Proxy(this, {
119
+ get(target, prop, receiver) {
120
+ // Allow access to existing properties/methods
121
+ if (prop in target || typeof prop !== "string") {
122
+ return Reflect.get(target, prop, receiver);
123
+ }
124
+ // Assume it's a function name from the library
125
+ return new FunctionWrapper(target, prop);
126
+ },
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Ottiene una funzione dalla libreria
132
+ * @param {string} name - Nome della funzione
133
+ * @param {string|CType} returnType - Tipo di ritorno
134
+ * @param {Array<string|CType>} argTypes - Tipi degli argomenti
135
+ * @param {Object} [options] - Opzioni aggiuntive (es. { abi: 'stdcall' })
136
+ * @returns {Function} Funzione callable
137
+ */
138
+ func(name, returnType, argTypes = [], options = {}) {
139
+ const cacheKey = `${name}:${returnType}:${argTypes.join(",")}`;
140
+
141
+ if (this._cache.has(cacheKey)) {
142
+ return this._cache.get(cacheKey);
143
+ }
144
+
145
+ const ffiFunc = this._lib.func(name, _toNativeType(returnType, native), _toNativeTypes(argTypes, native), options);
146
+
147
+ // =========================================================================
148
+ // OPTIMIZATION: Create specialized wrapper based on argument types
149
+ // For primitive-only args, bypass the processing loop entirely
150
+ // =========================================================================
151
+
152
+ const argCount = argTypes.length;
153
+
154
+ // Check if all args are primitive types (no structs/buffers that need _buffer extraction)
155
+ const allPrimitive = argTypes.every((t) => {
156
+ if (typeof t === "function" && t._isSimpleCData) {
157
+ // SimpleCData types that are NOT pointers are primitive
158
+ const typeName = t._type;
159
+ return typeName !== "pointer" && typeName !== "char_p" && typeName !== "wchar_p";
160
+ }
161
+ if (typeof t === "string") {
162
+ return t !== "pointer" && t !== "char_p" && t !== "wchar_p" && t !== "void_p";
163
+ }
164
+ // CType objects from native - check if it's a pointer type
165
+ if (t && typeof t === "object" && t.name) {
166
+ return !t.name.includes("pointer") && !t.name.includes("char_p");
167
+ }
168
+ return false;
169
+ });
170
+
171
+ let callMethod;
172
+
173
+ if (argCount === 0) {
174
+ // FAST PATH: No arguments - direct call
175
+ callMethod = function () {
176
+ return ffiFunc.call();
177
+ };
178
+ } else if (allPrimitive) {
179
+ // FAST PATH: All primitive args - direct call without processing
180
+ switch (argCount) {
181
+ case 1:
182
+ callMethod = function (a0) {
183
+ return ffiFunc.call(a0);
184
+ };
185
+ break;
186
+ case 2:
187
+ callMethod = function (a0, a1) {
188
+ return ffiFunc.call(a0, a1);
189
+ };
190
+ break;
191
+ case 3:
192
+ callMethod = function (a0, a1, a2) {
193
+ return ffiFunc.call(a0, a1, a2);
194
+ };
195
+ break;
196
+ case 4:
197
+ callMethod = function (a0, a1, a2, a3) {
198
+ return ffiFunc.call(a0, a1, a2, a3);
199
+ };
200
+ break;
201
+ default:
202
+ // Fallback for many args
203
+ callMethod = function (...args) {
204
+ return ffiFunc.call(...args);
205
+ };
206
+ }
207
+ } else {
208
+ // SLOW PATH: Has buffer/struct args - need to extract _buffer
209
+ callMethod = function (...args) {
210
+ const processedArgs = [];
211
+
212
+ for (let i = 0; i < args.length; i++) {
213
+ const arg = args[i];
214
+ // Check for _buffer FIRST (handles Proxy-wrapped structs)
215
+ // This avoids calling Buffer.isBuffer() on Proxy which can cause issues
216
+ if (arg && typeof arg === "object") {
217
+ // Check for struct proxy FIRST - accessing _buffer on Proxy is safe
218
+ // but Buffer.isBuffer(proxy) can cause hangs
219
+ if (arg._buffer !== undefined && Buffer.isBuffer(arg._buffer)) {
220
+ // Struct proxy or object with _buffer
221
+ processedArgs.push(arg._buffer);
222
+ } else if (Buffer.isBuffer(arg)) {
223
+ // Already a buffer, use directly
224
+ processedArgs.push(arg);
225
+ } else {
226
+ // Other object, pass as-is
227
+ processedArgs.push(arg);
228
+ }
229
+ } else {
230
+ // Primitive value (number, string, bigint, null, undefined)
231
+ processedArgs.push(arg);
232
+ }
233
+ }
234
+
235
+ return ffiFunc.call(...processedArgs);
236
+ };
237
+ }
238
+
239
+ // Aggiungi metadata come proprietà non-enumerable per non interferire
240
+ Object.defineProperties(callMethod, {
241
+ funcName: { value: name },
242
+ address: { value: ffiFunc.address },
243
+ _ffi: { value: ffiFunc },
244
+ // Esponi errcheck come setter/getter
245
+ errcheck: {
246
+ get() {
247
+ return ffiFunc._errcheck;
248
+ },
249
+ set(callback) {
250
+ ffiFunc._errcheck = callback;
251
+ ffiFunc.setErrcheck(callback);
252
+ },
253
+ enumerable: false,
254
+ configurable: true,
255
+ },
256
+ });
257
+
258
+ this._cache.set(cacheKey, callMethod);
259
+ return callMethod;
260
+ }
261
+
262
+ /**
263
+ * Ottiene l'indirizzo di un simbolo
264
+ * @param {string} name - Nome del simbolo
265
+ * @returns {BigInt} Indirizzo del simbolo
266
+ */
267
+ symbol(name) {
268
+ return this._lib.symbol(name);
269
+ }
270
+
271
+ /**
272
+ * Crea un callback JS chiamabile da C
273
+ * @param {Function} fn - Funzione JavaScript
274
+ * @param {string|CType} returnType - Tipo di ritorno
275
+ * @param {Array<string|CType>} argTypes - Tipi degli argomenti
276
+ * @returns {Object} Oggetto callback con proprietà pointer e metodi
277
+ */
278
+ callback(fn, returnType, argTypes = []) {
279
+ return this._lib.callback(_toNativeType(returnType, native), _toNativeTypes(argTypes, native), fn);
280
+ }
281
+
282
+ /**
283
+ * Chiude la libreria
284
+ */
285
+ close() {
286
+ this._lib.close();
287
+ this._cache.clear();
288
+ }
289
+
290
+ get path() {
291
+ return this._lib.path;
292
+ }
293
+
294
+ get loaded() {
295
+ return this._lib.loaded;
296
+ }
297
+ }
298
+
299
+ /**
300
+ * WinDLL - come CDLL ma con stdcall di default (per Windows)
301
+ */
302
+ class WinDLL extends CDLL {
303
+ func(name, returnType, argTypes = [], options = {}) {
304
+ return super.func(name, returnType, argTypes, {
305
+ abi: "stdcall",
306
+ ...options,
307
+ });
308
+ }
309
+ }
310
+
311
+ return { FunctionWrapper, CDLL, WinDLL };
312
+ }
@@ -0,0 +1,275 @@
1
+ /**
2
+ * @file callback.js
3
+ * @module core/callback
4
+ * @description Callback wrapper functions for creating C-callable JavaScript functions.
5
+ *
6
+ * This module provides two types of callbacks for different threading scenarios:
7
+ * 1. **callback()**: Fast, main-thread-only callbacks (e.g., qsort, EnumWindows)
8
+ * 2. **threadSafeCallback()**: Thread-safe callbacks for external threads (e.g., CreateThread)
9
+ *
10
+ * **Python ctypes Compatibility**:
11
+ * Equivalent to Python's `ctypes.CFUNCTYPE` and `ctypes.WINFUNCTYPE`.
12
+ *
13
+ * @example Main-thread callback (fast)
14
+ * ```javascript
15
+ * import { callback, c_int } from 'node-ctypes';
16
+ *
17
+ * // Comparison function for qsort
18
+ * const compareInts = callback(
19
+ * (a, b) => {
20
+ * const aVal = a.readInt32LE(0);
21
+ * const bVal = b.readInt32LE(0);
22
+ * return aVal - bVal;
23
+ * },
24
+ * c_int, // return type
25
+ * [c_void_p, c_void_p] // arg types
26
+ * );
27
+ *
28
+ * // Use callback.pointer to pass to C
29
+ * libc.func('qsort', c_void, [c_void_p, c_size_t, c_size_t, c_void_p])
30
+ * (arrayBuf, count, sizeof(c_int), compareInts.pointer);
31
+ *
32
+ * // CRITICAL: Release when done to prevent memory leak
33
+ * compareInts.release();
34
+ * ```
35
+ *
36
+ * @example Thread-safe callback (for external threads)
37
+ * ```javascript
38
+ * import { threadSafeCallback, c_int, c_void_p } from 'node-ctypes';
39
+ *
40
+ * // Callback that will be called from a C thread pool
41
+ * const threadWorker = threadSafeCallback(
42
+ * (dataPtr) => {
43
+ * console.log('Called from C thread!');
44
+ * return 0;
45
+ * },
46
+ * c_int,
47
+ * [c_void_p]
48
+ * );
49
+ *
50
+ * // Pass to CreateThread or similar
51
+ * kernel32.func('CreateThread', c_void_p, [...])
52
+ * (null, 0, threadWorker.pointer, null, 0, null);
53
+ *
54
+ * // Release when done
55
+ * threadWorker.release();
56
+ * ```
57
+ */
58
+
59
+ import { _toNativeType, _toNativeTypes } from "./types.js";
60
+
61
+ /**
62
+ * Creates a C-callable callback from a JavaScript function (main-thread only).
63
+ *
64
+ * This callback is optimized for performance with minimal overhead, but it **MUST**
65
+ * only be called from Node.js's main thread. Use this for standard C APIs that
66
+ * invoke callbacks synchronously on the same thread (e.g., qsort, bsearch,
67
+ * EnumWindows, etc.).
68
+ *
69
+ * **When to Use**:
70
+ * - Callbacks invoked by C functions on the same thread (qsort, bsearch, etc.)
71
+ * - Synchronous callbacks in event loops (Windows message processing)
72
+ * - Callbacks that don't involve threading
73
+ *
74
+ * **When NOT to Use**:
75
+ * - Callbacks invoked from external threads (CreateThread, pthread_create)
76
+ * - Asynchronous callbacks from thread pools
77
+ * - Any callback that might be called from a non-Node.js thread
78
+ * → Use `threadSafeCallback()` instead
79
+ *
80
+ * **Python ctypes Compatibility**:
81
+ * Equivalent to Python's `ctypes.CFUNCTYPE(restype, *argtypes)`.
82
+ *
83
+ * **Memory Management**:
84
+ * - Callbacks must be manually released by calling `.release()` when done
85
+ * - Failing to release callbacks will leak native memory
86
+ * - Keep a reference to the callback object while it's in use to prevent GC
87
+ *
88
+ * @param {Function} fn - JavaScript function to wrap
89
+ * The function will receive converted arguments based on argTypes
90
+ * @param {Function|Object} returnType - Return type (SimpleCData class or CType)
91
+ * @param {Array<Function|Object>} argTypes - Argument types array
92
+ * @param {Object} native - Native module reference (internal use)
93
+ * @returns {Object} Callback wrapper with properties:
94
+ * - `pointer` (BigInt): Function pointer to pass to C
95
+ * - `release()`: Release native resources (MUST be called)
96
+ * - `_callback`: Internal callback object
97
+ *
98
+ * @example Basic callback usage
99
+ * ```javascript
100
+ * import { CDLL, callback, c_int, c_char_p } from 'node-ctypes';
101
+ *
102
+ * const libc = new CDLL(null);
103
+ *
104
+ * // Create callback for qsort comparison
105
+ * const compare = callback(
106
+ * (aPtr, bPtr) => {
107
+ * const a = aPtr.readInt32LE(0);
108
+ * const b = bPtr.readInt32LE(0);
109
+ * return a - b;
110
+ * },
111
+ * c_int,
112
+ * [c_void_p, c_void_p]
113
+ * );
114
+ *
115
+ * // Sort array of integers
116
+ * const arr = Buffer.from([5, 2, 8, 1].flatMap(n => [n, 0, 0, 0]));
117
+ * const qsort = libc.func('qsort', c_void, [c_void_p, c_size_t, c_size_t, c_void_p]);
118
+ * qsort(arr, 4, 4, compare.pointer);
119
+ *
120
+ * console.log([...arr].filter((_, i) => i % 4 === 0)); // [1, 2, 5, 8]
121
+ *
122
+ * // CRITICAL: Release callback
123
+ * compare.release();
124
+ * ```
125
+ *
126
+ * @example Windows API callback
127
+ * ```javascript
128
+ * import { WinDLL, callback, c_bool, c_void_p } from 'node-ctypes';
129
+ *
130
+ * const user32 = new WinDLL('user32.dll');
131
+ *
132
+ * // EnumWindows callback
133
+ * const windowList = [];
134
+ * const enumProc = callback(
135
+ * (hwnd, lParam) => {
136
+ * windowList.push(hwnd);
137
+ * return 1; // Continue enumeration
138
+ * },
139
+ * c_bool,
140
+ * [c_void_p, c_void_p]
141
+ * );
142
+ *
143
+ * user32.func('EnumWindows', c_bool, [c_void_p, c_void_p])
144
+ * (enumProc.pointer, 0n);
145
+ *
146
+ * console.log(`Found ${windowList.length} windows`);
147
+ * enumProc.release();
148
+ * ```
149
+ *
150
+ * @throws {Error} If callback creation fails or type conversion fails
151
+ * @see {@link threadSafeCallback} for thread-safe callbacks
152
+ */
153
+ export function callback(fn, returnType, argTypes = [], native) {
154
+ const cb = new native.Callback(fn, _toNativeType(returnType, native), _toNativeTypes(argTypes, native));
155
+
156
+ return {
157
+ get pointer() {
158
+ return cb.pointer;
159
+ },
160
+ release() {
161
+ cb.release();
162
+ },
163
+ _callback: cb,
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Creates a thread-safe C-callable callback from a JavaScript function.
169
+ *
170
+ * This callback can be invoked from **any thread**, including threads created
171
+ * by C code (e.g., CreateThread, pthread_create, thread pools). It uses Node.js's
172
+ * thread-safe function mechanism to safely execute JavaScript on the main thread.
173
+ *
174
+ * **Performance Trade-off**:
175
+ * - Thread-safe callbacks have higher overhead than regular callbacks
176
+ * - They involve cross-thread synchronization and event loop scheduling
177
+ * - Use regular `callback()` if you know the callback is main-thread-only
178
+ *
179
+ * **When to Use**:
180
+ * - Callbacks invoked from C threads (CreateThread, pthread_create)
181
+ * - Asynchronous callbacks from thread pools
182
+ * - Any callback where you're unsure about the calling thread
183
+ *
184
+ * **When NOT to Use**:
185
+ * - Main-thread-only callbacks (qsort, bsearch, etc.)
186
+ * → Use `callback()` instead for better performance
187
+ *
188
+ * **Python ctypes Compatibility**:
189
+ * Python's ctypes doesn't distinguish between thread-safe and regular callbacks.
190
+ * This is a Node.js-specific requirement due to V8's threading model.
191
+ *
192
+ * **Memory Management**:
193
+ * - Must be manually released by calling `.release()` when done
194
+ * - Failing to release will leak native memory
195
+ * - Keep a reference while in use to prevent GC
196
+ *
197
+ * @param {Function} fn - JavaScript function to wrap
198
+ * The function will be executed on Node.js's main thread regardless of
199
+ * which thread invokes the callback
200
+ * @param {Function|Object} returnType - Return type (SimpleCData class or CType)
201
+ * @param {Array<Function|Object>} argTypes - Argument types array
202
+ * @param {Object} native - Native module reference (internal use)
203
+ * @returns {Object} Callback wrapper with properties:
204
+ * - `pointer` (BigInt): Function pointer to pass to C
205
+ * - `release()`: Release native resources (MUST be called)
206
+ * - `_callback`: Internal callback object
207
+ *
208
+ * @example Windows thread callback
209
+ * ```javascript
210
+ * import { WinDLL, threadSafeCallback, c_int, c_void_p } from 'node-ctypes';
211
+ *
212
+ * const kernel32 = new WinDLL('kernel32.dll');
213
+ *
214
+ * // Thread function that will run in a C thread
215
+ * const threadFunc = threadSafeCallback(
216
+ * (param) => {
217
+ * console.log('Running in C thread, but executed on Node.js main thread');
218
+ * return 0;
219
+ * },
220
+ * c_int,
221
+ * [c_void_p]
222
+ * );
223
+ *
224
+ * // Create Windows thread
225
+ * const CreateThread = kernel32.func('CreateThread', c_void_p,
226
+ * [c_void_p, c_size_t, c_void_p, c_void_p, c_uint32, c_void_p]);
227
+ *
228
+ * const threadHandle = CreateThread(null, 0, threadFunc.pointer, null, 0, null);
229
+ *
230
+ * // Wait for thread to finish...
231
+ * // Then release callback
232
+ * threadFunc.release();
233
+ * ```
234
+ *
235
+ * @example POSIX thread callback
236
+ * ```javascript
237
+ * import { CDLL, threadSafeCallback, c_void_p } from 'node-ctypes';
238
+ *
239
+ * const pthread = new CDLL('libpthread.so.0');
240
+ *
241
+ * const threadFunc = threadSafeCallback(
242
+ * (arg) => {
243
+ * console.log('POSIX thread callback');
244
+ * return null;
245
+ * },
246
+ * c_void_p,
247
+ * [c_void_p]
248
+ * );
249
+ *
250
+ * const pthread_create = pthread.func('pthread_create', c_int,
251
+ * [c_void_p, c_void_p, c_void_p, c_void_p]);
252
+ *
253
+ * const tidBuf = Buffer.alloc(8);
254
+ * pthread_create(tidBuf, null, threadFunc.pointer, null);
255
+ *
256
+ * // Wait for thread...
257
+ * threadFunc.release();
258
+ * ```
259
+ *
260
+ * @throws {Error} If callback creation fails or type conversion fails
261
+ * @see {@link callback} for main-thread-only callbacks
262
+ */
263
+ export function threadSafeCallback(fn, returnType, argTypes = [], native) {
264
+ const cb = new native.ThreadSafeCallback(fn, _toNativeType(returnType, native), _toNativeTypes(argTypes, native));
265
+
266
+ return {
267
+ get pointer() {
268
+ return cb.pointer;
269
+ },
270
+ release() {
271
+ cb.release();
272
+ },
273
+ _callback: cb,
274
+ };
275
+ }