node-ctypes 0.1.7 → 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 +13 -8
- package/build/Release/ctypes-darwin-arm64.node +0 -0
- package/build/Release/ctypes-darwin-x64.node +0 -0
- package/build/Release/ctypes-linux-arm64.node +0 -0
- package/build/Release/ctypes-linux-x64.node +0 -0
- package/build/Release/ctypes-win32-arm64.node +0 -0
- package/build/Release/ctypes-win32-x64.node +0 -0
- package/lib/core/Library.js +312 -0
- package/lib/core/callback.js +275 -0
- package/lib/core/types.js +140 -0
- package/lib/index.d.ts +71 -124
- package/lib/index.js +215 -3096
- package/lib/memory/buffer.js +404 -0
- package/lib/memory/operations.js +295 -0
- package/lib/memory/pointer.js +358 -0
- package/lib/platform/constants.js +110 -0
- package/lib/platform/errors.js +403 -0
- package/lib/structures/Structure.js +414 -0
- package/lib/structures/Union.js +102 -0
- package/lib/structures/helpers/array.js +261 -0
- package/lib/structures/helpers/bitfield.js +147 -0
- package/lib/structures/helpers/common.js +129 -0
- package/lib/structures/helpers/struct.js +925 -0
- package/lib/structures/helpers/union.js +465 -0
- package/lib/types/SimpleCData.js +193 -0
- package/lib/types/primitives.js +392 -0
- package/lib/utils/cache.js +142 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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
|
|
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
|
-
-
|
|
756
|
-
-
|
|
757
|
-
-
|
|
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
|
|
Binary file
|
|
Binary file
|
|
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
|
+
}
|