node-ctypes 1.2.0 → 1.4.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.
@@ -51,255 +51,295 @@
51
51
  * @private
52
52
  */
53
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;
54
+ class FunctionWrapper {
55
+ constructor(cdll, name) {
56
+ const argtypes = [];
57
+ let restype = undefined;
58
+ let cachedFunc = null;
59
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;
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`);
84
64
  }
85
- if (prop === "restype") {
86
- restype = value;
87
- cachedFunc = null; // Invalidate cache
88
- return true;
65
+ if (!cachedFunc) {
66
+ cachedFunc = cdll.func(name, restype, argtypes);
89
67
  }
90
- if (prop === "errcheck") {
91
- if (!cachedFunc) {
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
+ if (prop === "callAsync") {
92
78
  if (restype === undefined) {
93
79
  throw new Error(`Function ${name}: restype not set`);
94
80
  }
95
- cachedFunc = cdll.func(name, restype, argtypes);
81
+ if (!cachedFunc) {
82
+ cachedFunc = cdll.func(name, restype, argtypes);
83
+ }
84
+ return cachedFunc.callAsync;
85
+ }
86
+ return target[prop];
87
+ },
88
+ set(target, prop, value) {
89
+ if (prop === "argtypes") {
90
+ argtypes.splice(0, argtypes.length, ...value);
91
+ cachedFunc = null; // Invalidate cache
92
+ return true;
96
93
  }
97
- cachedFunc.errcheck = value;
94
+ if (prop === "restype") {
95
+ restype = value;
96
+ cachedFunc = null; // Invalidate cache
97
+ return true;
98
+ }
99
+ if (prop === "errcheck") {
100
+ if (!cachedFunc) {
101
+ if (restype === undefined) {
102
+ throw new Error(`Function ${name}: restype not set`);
103
+ }
104
+ cachedFunc = cdll.func(name, restype, argtypes);
105
+ }
106
+ cachedFunc.errcheck = value;
107
+ return true;
108
+ }
109
+ target[prop] = value;
98
110
  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
- });
111
+ },
112
+ });
113
+ }
128
114
  }
129
115
 
130
116
  /**
131
- * Ottiene una funzione dalla libreria
132
- * @param {string} name - Nome della funzione
133
- * @param {Function|number|CType} returnType - Tipo di ritorno (SimpleCData class, CType value, o CType)
134
- * @param {Array<Function|number|CType>} argTypes - Tipi degli argomenti
135
- * @param {Object} [options] - Opzioni aggiuntive (es. { abi: 'stdcall' })
136
- * @returns {Function} Funzione callable
117
+ * Wrapper conveniente per CDLL (come in Python ctypes)
137
118
  */
138
- func(name, returnType, argTypes = [], options = {}) {
139
- const cacheKey = `${name}:${returnType}:${argTypes.join(",")}`;
119
+ class CDLL {
120
+ constructor(libPath, options = {}) {
121
+ this._lib = new Library(libPath);
122
+ // Use LRU cache to prevent unbounded memory growth
123
+ const cacheSize = options.cacheSize ?? 1000;
124
+ this._cache = new LRUCache(cacheSize);
125
+ // Cache for FunctionWrapper instances (Python-like syntax)
126
+ this._funcWrapperCache = new Map();
140
127
 
141
- if (this._cache.has(cacheKey)) {
142
- return this._cache.get(cacheKey);
128
+ // Return a proxy to enable Python-like syntax: libc.abs.argtypes = [...]; libc.abs.restype = ...; libc.abs(...)
129
+ return new Proxy(this, {
130
+ get(target, prop, receiver) {
131
+ // Allow access to existing properties/methods
132
+ if (prop in target || typeof prop !== "string") {
133
+ return Reflect.get(target, prop, receiver);
134
+ }
135
+ // Check if we already have a FunctionWrapper for this function name
136
+ if (target._funcWrapperCache.has(prop)) {
137
+ return target._funcWrapperCache.get(prop);
138
+ }
139
+ // Create and cache a new FunctionWrapper
140
+ const wrapper = new FunctionWrapper(target, prop);
141
+ target._funcWrapperCache.set(prop, wrapper);
142
+ return wrapper;
143
+ },
144
+ });
143
145
  }
144
146
 
145
- const ffiFunc = this._lib.func(name, _toNativeType(returnType, native), _toNativeTypes(argTypes, native), options);
147
+ /**
148
+ * Ottiene una funzione dalla libreria
149
+ * @param {string} name - Nome della funzione
150
+ * @param {Function|number|CType} returnType - Tipo di ritorno (SimpleCData class, CType value, o CType)
151
+ * @param {Array<Function|number|CType>} argTypes - Tipi degli argomenti
152
+ * @param {Object} [options] - Opzioni aggiuntive (es. { abi: 'stdcall' })
153
+ * @returns {Function} Funzione callable
154
+ */
155
+ func(name, returnType, argTypes = [], options = {}) {
156
+ const cacheKey = `${name}:${returnType}:${argTypes.join(",")}`;
146
157
 
147
- // =========================================================================
148
- // OPTIMIZATION: Create specialized wrapper based on argument types
149
- // For primitive-only args, bypass the processing loop entirely
150
- // =========================================================================
158
+ if (this._cache.has(cacheKey)) {
159
+ return this._cache.get(cacheKey);
160
+ }
151
161
 
152
- const argCount = argTypes.length;
162
+ const ffiFunc = this._lib.func(name, _toNativeType(returnType, native), _toNativeTypes(argTypes, native), options);
153
163
 
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 !== native.CType.POINTER && typeName !== native.CType.STRING && typeName !== native.CType.WSTRING;
160
- }
161
- return false;
162
- });
164
+ // =========================================================================
165
+ // OPTIMIZATION: Create specialized wrapper based on argument types
166
+ // For primitive-only args, bypass the processing loop entirely
167
+ // =========================================================================
163
168
 
164
- let callMethod;
169
+ const argCount = argTypes.length;
165
170
 
166
- if (argCount === 0) {
167
- // FAST PATH: No arguments - direct call
168
- callMethod = function () {
169
- return ffiFunc.call();
170
- };
171
- } else if (allPrimitive) {
172
- // FAST PATH: All primitive args - direct call without processing
173
- switch (argCount) {
174
- case 1:
175
- callMethod = function (a0) {
176
- return ffiFunc.call(a0);
177
- };
178
- break;
179
- case 2:
180
- callMethod = function (a0, a1) {
181
- return ffiFunc.call(a0, a1);
182
- };
183
- break;
184
- case 3:
185
- callMethod = function (a0, a1, a2) {
186
- return ffiFunc.call(a0, a1, a2);
187
- };
188
- break;
189
- case 4:
190
- callMethod = function (a0, a1, a2, a3) {
191
- return ffiFunc.call(a0, a1, a2, a3);
192
- };
193
- break;
194
- default:
195
- // Fallback for many args
196
- callMethod = function (...args) {
197
- return ffiFunc.call(...args);
198
- };
199
- }
200
- } else {
201
- // SLOW PATH: Has buffer/struct args - need to extract _buffer
202
- callMethod = function (...args) {
203
- const processedArgs = [];
171
+ // Check if all args are primitive types (no structs/buffers that need _buffer extraction)
172
+ const allPrimitive = argTypes.every((t) => {
173
+ if (typeof t === "function" && t._isSimpleCData) {
174
+ // SimpleCData types that are NOT pointers are primitive
175
+ const typeName = t._type;
176
+ return typeName !== native.CType.POINTER && typeName !== native.CType.STRING && typeName !== native.CType.WSTRING;
177
+ }
178
+ return false;
179
+ });
204
180
 
205
- for (let i = 0; i < args.length; i++) {
206
- const arg = args[i];
207
- // Check for _buffer FIRST (handles Proxy-wrapped structs)
208
- // This avoids calling Buffer.isBuffer() on Proxy which can cause issues
209
- if (arg && typeof arg === "object") {
210
- // Check for struct proxy FIRST - accessing _buffer on Proxy is safe
211
- // but Buffer.isBuffer(proxy) can cause hangs
212
- if (arg._buffer !== undefined && Buffer.isBuffer(arg._buffer)) {
213
- // Struct proxy or object with _buffer
214
- processedArgs.push(arg._buffer);
215
- } else if (Buffer.isBuffer(arg)) {
216
- // Already a buffer, use directly
181
+ let callMethod;
182
+
183
+ if (argCount === 0) {
184
+ // FAST PATH: No declared arguments - but still support variadic
185
+ callMethod = function (...args) {
186
+ if (args.length === 0) {
187
+ return ffiFunc.call();
188
+ }
189
+ // Variadic: pass all args directly
190
+ return ffiFunc.call(...args);
191
+ };
192
+ } else if (allPrimitive) {
193
+ // FAST PATH: All primitive args - use rest args to support variadic
194
+ callMethod = function (...args) {
195
+ return ffiFunc.call(...args);
196
+ };
197
+ } else {
198
+ // SLOW PATH: Has buffer/struct args - need to extract _buffer
199
+ callMethod = function (...args) {
200
+ const processedArgs = [];
201
+
202
+ for (let i = 0; i < args.length; i++) {
203
+ const arg = args[i];
204
+ // Check for _buffer FIRST (handles Proxy-wrapped structs)
205
+ // This avoids calling Buffer.isBuffer() on Proxy which can cause issues
206
+ if (arg && typeof arg === "object") {
207
+ // Check for struct proxy FIRST - accessing _buffer on Proxy is safe
208
+ // but Buffer.isBuffer(proxy) can cause hangs
209
+ if (arg._buffer !== undefined && Buffer.isBuffer(arg._buffer)) {
210
+ // Struct proxy or object with _buffer
211
+ processedArgs.push(arg._buffer);
212
+ } else if (Buffer.isBuffer(arg)) {
213
+ // Already a buffer, use directly
214
+ processedArgs.push(arg);
215
+ } else {
216
+ // Other object, pass as-is
217
+ processedArgs.push(arg);
218
+ }
219
+ } else {
220
+ // Primitive value (number, string, bigint, null, undefined)
217
221
  processedArgs.push(arg);
222
+ }
223
+ }
224
+
225
+ return ffiFunc.call(...processedArgs);
226
+ };
227
+ }
228
+
229
+ // =========================================================================
230
+ // ASYNC WRAPPER: same argument processing, but calls callAsync
231
+ // Returns a Promise instead of the result directly
232
+ // =========================================================================
233
+
234
+ let asyncCallMethod;
235
+
236
+ if (argCount === 0) {
237
+ asyncCallMethod = function (...args) {
238
+ if (args.length === 0) {
239
+ return ffiFunc.callAsync();
240
+ }
241
+ // Variadic: pass all args directly
242
+ return ffiFunc.callAsync(...args);
243
+ };
244
+ } else if (allPrimitive) {
245
+ asyncCallMethod = function (...args) {
246
+ return ffiFunc.callAsync(...args);
247
+ };
248
+ } else {
249
+ asyncCallMethod = function (...args) {
250
+ const processedArgs = [];
251
+
252
+ for (let i = 0; i < args.length; i++) {
253
+ const arg = args[i];
254
+ if (arg && typeof arg === "object") {
255
+ if (arg._buffer !== undefined && Buffer.isBuffer(arg._buffer)) {
256
+ processedArgs.push(arg._buffer);
257
+ } else if (Buffer.isBuffer(arg)) {
258
+ processedArgs.push(arg);
259
+ } else {
260
+ processedArgs.push(arg);
261
+ }
218
262
  } else {
219
- // Other object, pass as-is
220
263
  processedArgs.push(arg);
221
264
  }
222
- } else {
223
- // Primitive value (number, string, bigint, null, undefined)
224
- processedArgs.push(arg);
225
265
  }
226
- }
227
266
 
228
- return ffiFunc.call(...processedArgs);
229
- };
230
- }
267
+ return ffiFunc.callAsync(...processedArgs);
268
+ };
269
+ }
231
270
 
232
- // Aggiungi metadata come proprietà non-enumerable per non interferire
233
- Object.defineProperties(callMethod, {
234
- funcName: { value: name },
235
- address: { value: ffiFunc.address },
236
- _ffi: { value: ffiFunc },
237
- // Esponi errcheck come setter/getter
238
- errcheck: {
239
- get() {
240
- return ffiFunc._errcheck;
241
- },
242
- set(callback) {
243
- ffiFunc._errcheck = callback;
244
- ffiFunc.setErrcheck(callback);
271
+ // Aggiungi metadata come proprietà non-enumerable per non interferire
272
+ Object.defineProperties(callMethod, {
273
+ funcName: { value: name },
274
+ address: { value: ffiFunc.address },
275
+ _ffi: { value: ffiFunc },
276
+ callAsync: { value: asyncCallMethod, writable: false, enumerable: false, configurable: false },
277
+ // Esponi errcheck come setter/getter
278
+ errcheck: {
279
+ get() {
280
+ return ffiFunc._errcheck;
281
+ },
282
+ set(callback) {
283
+ ffiFunc._errcheck = callback;
284
+ ffiFunc.setErrcheck(callback);
285
+ },
286
+ enumerable: false,
287
+ configurable: true,
245
288
  },
246
- enumerable: false,
247
- configurable: true,
248
- },
249
- });
289
+ });
250
290
 
251
- this._cache.set(cacheKey, callMethod);
252
- return callMethod;
253
- }
291
+ this._cache.set(cacheKey, callMethod);
292
+ return callMethod;
293
+ }
254
294
 
255
- /**
256
- * Ottiene l'indirizzo di un simbolo
257
- * @param {string} name - Nome del simbolo
258
- * @returns {BigInt} Indirizzo del simbolo
259
- */
260
- symbol(name) {
261
- return this._lib.symbol(name);
262
- }
295
+ /**
296
+ * Ottiene l'indirizzo di un simbolo
297
+ * @param {string} name - Nome del simbolo
298
+ * @returns {BigInt} Indirizzo del simbolo
299
+ */
300
+ symbol(name) {
301
+ return this._lib.symbol(name);
302
+ }
263
303
 
264
- /**
265
- * Crea un callback JS chiamabile da C
266
- * @param {Function} fn - Funzione JavaScript
267
- * @param {Function|number|CType} returnType - Tipo di ritorno (SimpleCData class, CType value, o CType)
268
- * @param {Array<Function|number|CType>} argTypes - Tipi degli argomenti
269
- * @returns {Object} Oggetto callback con proprietà pointer e metodi
270
- */
271
- callback(fn, returnType, argTypes = []) {
272
- return this._lib.callback(_toNativeType(returnType, native), _toNativeTypes(argTypes, native), fn);
273
- }
304
+ /**
305
+ * Crea un callback JS chiamabile da C
306
+ * @param {Function} fn - Funzione JavaScript
307
+ * @param {Function|number|CType} returnType - Tipo di ritorno (SimpleCData class, CType value, o CType)
308
+ * @param {Array<Function|number|CType>} argTypes - Tipi degli argomenti
309
+ * @returns {Object} Oggetto callback con proprietà pointer e metodi
310
+ */
311
+ callback(fn, returnType, argTypes = []) {
312
+ return this._lib.callback(_toNativeType(returnType, native), _toNativeTypes(argTypes, native), fn);
313
+ }
274
314
 
275
- /**
276
- * Chiude la libreria
277
- */
278
- close() {
279
- this._lib.close();
280
- this._cache.clear();
281
- }
315
+ /**
316
+ * Chiude la libreria
317
+ */
318
+ close() {
319
+ this._lib.close();
320
+ this._cache.clear();
321
+ }
282
322
 
283
- get path() {
284
- return this._lib.path;
285
- }
323
+ get path() {
324
+ return this._lib.path;
325
+ }
286
326
 
287
- get loaded() {
288
- return this._lib.loaded;
327
+ get loaded() {
328
+ return this._lib.loaded;
329
+ }
289
330
  }
290
- }
291
331
 
292
- /**
293
- * WinDLL - come CDLL ma con stdcall di default (per Windows)
294
- */
295
- class WinDLL extends CDLL {
296
- func(name, returnType, argTypes = [], options = {}) {
297
- return super.func(name, returnType, argTypes, {
298
- abi: "stdcall",
299
- ...options,
300
- });
332
+ /**
333
+ * WinDLL - come CDLL ma con stdcall di default (per Windows)
334
+ */
335
+ class WinDLL extends CDLL {
336
+ func(name, returnType, argTypes = [], options = {}) {
337
+ return super.func(name, returnType, argTypes, {
338
+ abi: "stdcall",
339
+ ...options,
340
+ });
341
+ }
301
342
  }
302
- }
303
343
 
304
344
  return { FunctionWrapper, CDLL, WinDLL };
305
345
  }
package/lib/core/types.js CHANGED
@@ -88,6 +88,13 @@ export function _toNativeType(type, native) {
88
88
  return type._type;
89
89
  }
90
90
 
91
+ // POINTER types: return CType.POINTER (11)
92
+ // POINTER(baseType) returns an object with _pointerTo property
93
+ if (type && typeof type === "object" && type._pointerTo !== undefined) {
94
+ // All pointer types are passed as generic pointers to FFI
95
+ return native.CType.POINTER;
96
+ }
97
+
91
98
  // Structure/Union classes: pass through unchanged
92
99
  // These are handled specially by the FFI layer
93
100
  if (typeof type === "function" && type.prototype) {
package/lib/index.d.ts CHANGED
@@ -79,6 +79,8 @@ export type ErrcheckCallback = (result: any, func: CallableFunction, args: any[]
79
79
  /** FFI function wrapper */
80
80
  export interface FFIFunction {
81
81
  (...args: any[]): any;
82
+ /** Async version: executes the native call on a worker thread, returns a Promise */
83
+ callAsync(...args: any[]): Promise<any>;
82
84
  readonly funcName: string;
83
85
  readonly address: bigint;
84
86
  errcheck: ErrcheckCallback | null;
@@ -87,11 +89,23 @@ export interface FFIFunction {
87
89
  /** CDLL - C calling convention library */
88
90
  export class CDLL {
89
91
  constructor(path: string | null);
90
- func(name: string, returnType: AnyType, argTypes?: AnyType[], options?: FunctionOptions): CallableFunction & { errcheck: ErrcheckCallback | null };
92
+ func(name: string, returnType: AnyType, argTypes?: AnyType[], options?: FunctionOptions): CallableFunction & { callAsync(...args: any[]): Promise<any>; errcheck: ErrcheckCallback | null };
91
93
  symbol(name: string): bigint;
92
94
  close(): void;
93
95
  readonly path: string;
94
96
  readonly loaded: boolean;
97
+ /** Python-like function access: lib.FuncName returns a FunctionWrapper */
98
+ [funcName: string]: FunctionWrapper | any;
99
+ }
100
+
101
+ /** FunctionWrapper for Python-like argtypes/restype syntax */
102
+ export interface FunctionWrapper {
103
+ (...args: any[]): any;
104
+ /** Async version: executes the native call on a worker thread, returns a Promise */
105
+ callAsync(...args: any[]): Promise<any>;
106
+ argtypes: AnyType[];
107
+ restype: AnyType;
108
+ errcheck: ErrcheckCallback | null;
95
109
  }
96
110
 
97
111
  /** WinDLL - stdcall calling convention library (Windows) */
@@ -326,6 +340,7 @@ export function bitfield(baseType: AnyType, bits: number): BitFieldDef;
326
340
  export function byref(obj: Buffer | SimpleCDataInstance | { _buffer: Buffer }): Buffer;
327
341
  export function cast(ptr: Buffer | bigint, targetType: AnyType | StructDef): Buffer | { [key: string]: any };
328
342
  export function POINTER(baseType: AnyType | StructDef): PointerTypeDef;
343
+ export function pointer(obj: SimpleCDataInstance | { _buffer: Buffer }): SimpleCDataInstance & { contents: any; [index: number]: any };
329
344
 
330
345
  // Error handling
331
346
  export function get_errno(): number;
@@ -343,7 +358,7 @@ export function WinError(code?: number): Error & { winerror: number };
343
358
  export interface SimpleCDataConstructor {
344
359
  new (value?: any): SimpleCDataInstance;
345
360
  readonly _size: number;
346
- readonly _type: number; // CType numeric value from native module
361
+ readonly _type: number; // CType numeric value from native module
347
362
  readonly _isSimpleCData: true;
348
363
  _reader(buf: Buffer, offset: number): any;
349
364
  _writer(buf: Buffer, offset: number, value: any): void;
@@ -385,6 +400,17 @@ export const c_size_t: SimpleCDataConstructor;
385
400
  export const c_long: SimpleCDataConstructor;
386
401
  export const c_ulong: SimpleCDataConstructor;
387
402
 
403
+ // Python-compatible aliases
404
+ export const c_byte: SimpleCDataConstructor;
405
+ export const c_ubyte: SimpleCDataConstructor;
406
+ export const c_short: SimpleCDataConstructor;
407
+ export const c_ushort: SimpleCDataConstructor;
408
+ export const c_longlong: SimpleCDataConstructor;
409
+ export const c_ulonglong: SimpleCDataConstructor;
410
+
411
+ /** SimpleCData base class for creating custom simple types */
412
+ export const SimpleCData: SimpleCDataConstructor;
413
+
388
414
  // =============================================================================
389
415
  // Constants
390
416
  // =============================================================================