node-ctypes 1.3.0 → 1.5.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.
package/README.md CHANGED
@@ -835,7 +835,7 @@ Base class for Python-like union definitions. Subclasses should define `static _
835
835
  | **Unions** | `class U(Union):`<br>&nbsp;&nbsp;`_fields_ = [("i", c_int)]` | `class U extends Union`<br>&nbsp;&nbsp;`{ static _fields_ = [["i", c_int]] }` |
836
836
  | **Arrays** | `c_int * 5` | `array(c_int, 5)` |
837
837
  | **Bit fields** | `("flags", c_uint, 3)` | `["flags", c_uint32, 3]`<br>**or** `bitfield(c_uint32, 3)` |
838
- | **Callbacks** | `CFUNCTYPE(c_int, c_int)` | `callback(fn, c_int, [c_int])` |
838
+ | **Callbacks** | `CFUNCTYPE(c_int, c_int)` | `CFUNCTYPE(c_int, c_int)`<br>**or** ` callback(fn, c_int, [c_int])` |
839
839
  | **Strings** | `c_char_p(b"hello")` | `create_string_buffer("hello")`<br>**or**<br>`new c_char_p(b"hello")` |
840
840
  | **Pointers** | `POINTER(c_int)`<br>`p.contents`<br>`p[0]` | `POINTER(c_int)`<br>`p.contents`<br>`p[0]` |
841
841
  | **pointer()** | `pointer(obj)` | `pointer(obj)` |
@@ -902,8 +902,10 @@ npm run bench:koffi # Benchmark vs koffi
902
902
 
903
903
  For practical GUI application examples using the Windows API:
904
904
 
905
- - **Simple GUI Demo**: [examples/windows/simple.js](examples/windows/simple.js) - Message boxes and basic Windows API GUI elements
906
- - **Windows Controls Showcase Demo**: [examples/windows/windows_controls.js](examples/windows/windows_controls.js) - Comprehensive demo with a wide set of common Win32 controls
905
+ - **Windows Controls Showcase Demo**: [examples/windows/demo_controls.js](examples/windows/demo_controls.js) - Comprehensive demo with a wide set of common Win32 controls
906
+ - **Windows Registry Demo**: [examples/windows/demo_registry](examples/windows/demo_registry) - Comprehensive demo with setValue, getValue, openKey, deleteValue, deleteKey
907
+ - **Windows Tray Demo**: [examples/windows/demo_tray.js](examples/windows/demo_tray.js) - Comprehensive demo for tray menu inspired by [pit-ray/fluent-tray](https://github.com/pit-ray/fluent-tray)
908
+ - **WinScard - PC/SC Smart Card**: [examples/demo_scard.js](examples/demo_scard.js) - Comprehensive demo for Windows Smart Card API on Windows and PC/SC Lite (pcsclite) on macOS and Linux
907
909
 
908
910
  ## License
909
911
 
Binary file
Binary file
@@ -74,6 +74,15 @@ export function createLibraryClasses(Library, LRUCache, _toNativeType, _toNative
74
74
  if (prop === "argtypes") return argtypes;
75
75
  if (prop === "restype") return restype;
76
76
  if (prop === "errcheck") return cachedFunc ? cachedFunc.errcheck : undefined;
77
+ if (prop === "callAsync") {
78
+ if (restype === undefined) {
79
+ throw new Error(`Function ${name}: restype not set`);
80
+ }
81
+ if (!cachedFunc) {
82
+ cachedFunc = cdll.func(name, restype, argtypes);
83
+ }
84
+ return cachedFunc.callAsync;
85
+ }
77
86
  return target[prop];
78
87
  },
79
88
  set(target, prop, value) {
@@ -172,39 +181,19 @@ export function createLibraryClasses(Library, LRUCache, _toNativeType, _toNative
172
181
  let callMethod;
173
182
 
174
183
  if (argCount === 0) {
175
- // FAST PATH: No arguments - direct call
176
- callMethod = function () {
177
- return ffiFunc.call();
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);
178
191
  };
179
192
  } else if (allPrimitive) {
180
- // FAST PATH: All primitive args - direct call without processing
181
- switch (argCount) {
182
- case 1:
183
- callMethod = function (a0) {
184
- return ffiFunc.call(a0);
185
- };
186
- break;
187
- case 2:
188
- callMethod = function (a0, a1) {
189
- return ffiFunc.call(a0, a1);
190
- };
191
- break;
192
- case 3:
193
- callMethod = function (a0, a1, a2) {
194
- return ffiFunc.call(a0, a1, a2);
195
- };
196
- break;
197
- case 4:
198
- callMethod = function (a0, a1, a2, a3) {
199
- return ffiFunc.call(a0, a1, a2, a3);
200
- };
201
- break;
202
- default:
203
- // Fallback for many args
204
- callMethod = function (...args) {
205
- return ffiFunc.call(...args);
206
- };
207
- }
193
+ // FAST PATH: All primitive args - use rest args to support variadic
194
+ callMethod = function (...args) {
195
+ return ffiFunc.call(...args);
196
+ };
208
197
  } else {
209
198
  // SLOW PATH: Has buffer/struct args - need to extract _buffer
210
199
  callMethod = function (...args) {
@@ -237,11 +226,54 @@ export function createLibraryClasses(Library, LRUCache, _toNativeType, _toNative
237
226
  };
238
227
  }
239
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
+ }
262
+ } else {
263
+ processedArgs.push(arg);
264
+ }
265
+ }
266
+
267
+ return ffiFunc.callAsync(...processedArgs);
268
+ };
269
+ }
270
+
240
271
  // Aggiungi metadata come proprietà non-enumerable per non interferire
241
272
  Object.defineProperties(callMethod, {
242
273
  funcName: { value: name },
243
274
  address: { value: ffiFunc.address },
244
275
  _ffi: { value: ffiFunc },
276
+ callAsync: { value: asyncCallMethod, writable: false, enumerable: false, configurable: false },
245
277
  // Esponi errcheck come setter/getter
246
278
  errcheck: {
247
279
  get() {
@@ -150,8 +150,10 @@ import { _toNativeType, _toNativeTypes } from "./types.js";
150
150
  * @throws {Error} If callback creation fails or type conversion fails
151
151
  * @see {@link threadSafeCallback} for thread-safe callbacks
152
152
  */
153
- export function callback(fn, returnType, argTypes = [], native) {
154
- const cb = new native.Callback(fn, _toNativeType(returnType, native), _toNativeTypes(argTypes, native));
153
+ export function callback(fn, returnType, argTypes = [], native, abi = undefined) {
154
+ const args = [fn, _toNativeType(returnType, native), _toNativeTypes(argTypes, native)];
155
+ if (abi) args.push(abi);
156
+ const cb = new native.Callback(...args);
155
157
 
156
158
  return {
157
159
  get pointer() {
@@ -260,8 +262,10 @@ export function callback(fn, returnType, argTypes = [], native) {
260
262
  * @throws {Error} If callback creation fails or type conversion fails
261
263
  * @see {@link callback} for main-thread-only callbacks
262
264
  */
263
- export function threadSafeCallback(fn, returnType, argTypes = [], native) {
264
- const cb = new native.ThreadSafeCallback(fn, _toNativeType(returnType, native), _toNativeTypes(argTypes, native));
265
+ export function threadSafeCallback(fn, returnType, argTypes = [], native, abi = undefined) {
266
+ const args = [fn, _toNativeType(returnType, native), _toNativeTypes(argTypes, native)];
267
+ if (abi) args.push(abi);
268
+ const cb = new native.ThreadSafeCallback(...args);
265
269
 
266
270
  return {
267
271
  get pointer() {
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @file funcptr.js
3
+ * @module core/funcptr
4
+ * @description Function pointer type factories for calling raw function pointers.
5
+ *
6
+ * Provides CFUNCTYPE and WINFUNCTYPE factories that create callable function
7
+ * types from raw memory addresses (BigInt). This enables COM vtable dispatch
8
+ * and other scenarios where function pointers are obtained at runtime.
9
+ *
10
+ * **Python ctypes Compatibility**:
11
+ * - `CFUNCTYPE(restype, ...argtypes)` ≈ Python's `ctypes.CFUNCTYPE`
12
+ * - `WINFUNCTYPE(restype, ...argtypes)` ≈ Python's `ctypes.WINFUNCTYPE`
13
+ *
14
+ * @example Calling a function by address
15
+ * ```javascript
16
+ * import { WinDLL, WINFUNCTYPE, c_int32, c_void_p, c_wchar_p, c_uint32 } from 'node-ctypes';
17
+ *
18
+ * const user32 = new WinDLL('user32.dll');
19
+ * const msgBoxAddr = user32.symbol('MessageBoxW');
20
+ *
21
+ * const MsgBoxProto = WINFUNCTYPE(c_int32, c_void_p, c_wchar_p, c_wchar_p, c_uint32);
22
+ * const msgBox = MsgBoxProto(msgBoxAddr);
23
+ * msgBox(null, 'Hello from FuncPtr!', 'Test', 0);
24
+ * ```
25
+ *
26
+ * @example COM vtable dispatch
27
+ * ```javascript
28
+ * import { WINFUNCTYPE, c_int32, c_void_p, ptrToBuffer } from 'node-ctypes';
29
+ *
30
+ * // Read vtable pointer from COM object
31
+ * const vtableBuf = ptrToBuffer(objPtr, 8);
32
+ * const vtableAddr = vtableBuf.readBigUInt64LE(0);
33
+ * const vtable = ptrToBuffer(vtableAddr, 24); // 3 IUnknown methods * 8 bytes
34
+ *
35
+ * // QueryInterface is the first vtable entry
36
+ * const QueryInterface = WINFUNCTYPE(c_int32, c_void_p, c_void_p, c_void_p);
37
+ * const qi = QueryInterface(vtable.readBigUInt64LE(0));
38
+ * ```
39
+ */
40
+
41
+ import { _toNativeType, _toNativeTypes } from "./types.js";
42
+
43
+ /**
44
+ * Creates a function pointer type factory with cdecl calling convention.
45
+ *
46
+ * @param {Function|Object|number} restype - Return type
47
+ * @param {...(Function|Object|number)} argtypes - Argument types
48
+ * @returns {Function} Factory that accepts a BigInt address and returns a callable
49
+ */
50
+ export function createCFUNCTYPE(native) {
51
+ return function CFUNCTYPE(restype, ...argtypes) {
52
+ const nativeRestype = _toNativeType(restype, native);
53
+ const nativeArgtypes = _toNativeTypes(argtypes, native);
54
+
55
+ function createFromAddress(address) {
56
+ if (typeof address !== "bigint") {
57
+ throw new TypeError("CFUNCTYPE: address must be a BigInt");
58
+ }
59
+
60
+ const ffiFunc = new native.FFIFunction(address, null, nativeRestype, nativeArgtypes);
61
+
62
+ const callMethod = function (...args) {
63
+ const processedArgs = processArgs(args);
64
+ return ffiFunc.call(...processedArgs);
65
+ };
66
+
67
+ Object.defineProperties(callMethod, {
68
+ funcName: { value: `0x${address.toString(16)}` },
69
+ address: { value: ffiFunc.address },
70
+ _ffi: { value: ffiFunc },
71
+ callAsync: {
72
+ value: function (...args) {
73
+ const processedArgs = processArgs(args);
74
+ return ffiFunc.callAsync(...processedArgs);
75
+ },
76
+ writable: false,
77
+ enumerable: false,
78
+ configurable: false,
79
+ },
80
+ errcheck: {
81
+ get() {
82
+ return ffiFunc._errcheck;
83
+ },
84
+ set(callback) {
85
+ ffiFunc._errcheck = callback;
86
+ ffiFunc.setErrcheck(callback);
87
+ },
88
+ enumerable: false,
89
+ configurable: true,
90
+ },
91
+ });
92
+
93
+ return callMethod;
94
+ }
95
+
96
+ // Attach metadata to the factory
97
+ createFromAddress.restype = restype;
98
+ createFromAddress.argtypes = argtypes;
99
+ createFromAddress._isFuncPtr = true;
100
+
101
+ return createFromAddress;
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Creates a function pointer type factory with stdcall calling convention.
107
+ *
108
+ * @param {Function|Object|number} restype - Return type
109
+ * @param {...(Function|Object|number)} argtypes - Argument types
110
+ * @returns {Function} Factory that accepts a BigInt address and returns a callable
111
+ */
112
+ export function createWINFUNCTYPE(native) {
113
+ return function WINFUNCTYPE(restype, ...argtypes) {
114
+ const nativeRestype = _toNativeType(restype, native);
115
+ const nativeArgtypes = _toNativeTypes(argtypes, native);
116
+
117
+ function createFromAddress(address) {
118
+ if (typeof address !== "bigint") {
119
+ throw new TypeError("WINFUNCTYPE: address must be a BigInt");
120
+ }
121
+
122
+ const ffiFunc = new native.FFIFunction(address, null, nativeRestype, nativeArgtypes, { abi: "stdcall" });
123
+
124
+ const callMethod = function (...args) {
125
+ const processedArgs = processArgs(args);
126
+ return ffiFunc.call(...processedArgs);
127
+ };
128
+
129
+ Object.defineProperties(callMethod, {
130
+ funcName: { value: `0x${address.toString(16)}` },
131
+ address: { value: ffiFunc.address },
132
+ _ffi: { value: ffiFunc },
133
+ callAsync: {
134
+ value: function (...args) {
135
+ const processedArgs = processArgs(args);
136
+ return ffiFunc.callAsync(...processedArgs);
137
+ },
138
+ writable: false,
139
+ enumerable: false,
140
+ configurable: false,
141
+ },
142
+ errcheck: {
143
+ get() {
144
+ return ffiFunc._errcheck;
145
+ },
146
+ set(callback) {
147
+ ffiFunc._errcheck = callback;
148
+ ffiFunc.setErrcheck(callback);
149
+ },
150
+ enumerable: false,
151
+ configurable: true,
152
+ },
153
+ });
154
+
155
+ return callMethod;
156
+ }
157
+
158
+ // Attach metadata to the factory
159
+ createFromAddress.restype = restype;
160
+ createFromAddress.argtypes = argtypes;
161
+ createFromAddress._isFuncPtr = true;
162
+
163
+ return createFromAddress;
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Process arguments, extracting _buffer from struct/union proxies.
169
+ * @private
170
+ */
171
+ function processArgs(args) {
172
+ const processedArgs = [];
173
+ for (let i = 0; i < args.length; i++) {
174
+ const arg = args[i];
175
+ if (arg && typeof arg === "object") {
176
+ if (arg._buffer !== undefined && Buffer.isBuffer(arg._buffer)) {
177
+ processedArgs.push(arg._buffer);
178
+ } else if (Buffer.isBuffer(arg)) {
179
+ processedArgs.push(arg);
180
+ } else {
181
+ processedArgs.push(arg);
182
+ }
183
+ } else {
184
+ processedArgs.push(arg);
185
+ }
186
+ }
187
+ return processedArgs;
188
+ }
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,7 +89,7 @@ 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;
@@ -99,6 +101,8 @@ export class CDLL {
99
101
  /** FunctionWrapper for Python-like argtypes/restype syntax */
100
102
  export interface FunctionWrapper {
101
103
  (...args: any[]): any;
104
+ /** Async version: executes the native call on a worker thread, returns a Promise */
105
+ callAsync(...args: any[]): Promise<any>;
102
106
  argtypes: AnyType[];
103
107
  restype: AnyType;
104
108
  errcheck: ErrcheckCallback | null;
package/lib/index.js CHANGED
@@ -23,6 +23,7 @@ import { LRUCache } from "./utils/cache.js";
23
23
  import { _initConstants } from "./platform/constants.js";
24
24
  import { _toNativeType, _toNativeTypes } from "./core/types.js";
25
25
  import { callback as createCallback, threadSafeCallback as createThreadSafeCallback } from "./core/callback.js";
26
+ import { createCFUNCTYPE, createWINFUNCTYPE } from "./core/funcptr.js";
26
27
  import { createLibraryClasses } from "./core/Library.js";
27
28
  import {
28
29
  alloc as _alloc,
@@ -138,6 +139,10 @@ function load(libPath) {
138
139
  // Create Library classes (FunctionWrapper, CDLL, WinDLL)
139
140
  const { FunctionWrapper, CDLL, WinDLL } = createLibraryClasses(Library, LRUCache, _toNativeType, _toNativeTypes, native);
140
141
 
142
+ // Function pointer type factories (Python ctypes CFUNCTYPE/WINFUNCTYPE)
143
+ const CFUNCTYPE = createCFUNCTYPE(native);
144
+ const WINFUNCTYPE = createWINFUNCTYPE(native);
145
+
141
146
  // ============================================================================
142
147
  // Callback Functions
143
148
  // Now imported from ./core/callback.js - see that file for implementation details
@@ -147,16 +152,16 @@ const { FunctionWrapper, CDLL, WinDLL } = createLibraryClasses(Library, LRUCache
147
152
  * Crea un callback JS chiamabile da C (solo main thread)
148
153
  * @see ./core/callback.js for full documentation
149
154
  */
150
- function callback(fn, returnType, argTypes = []) {
151
- return createCallback(fn, returnType, argTypes, native);
155
+ function callback(fn, returnType, argTypes = [], abi = undefined) {
156
+ return createCallback(fn, returnType, argTypes, native, abi);
152
157
  }
153
158
 
154
159
  /**
155
160
  * Crea un callback JS chiamabile da qualsiasi thread
156
161
  * @see ./core/callback.js for full documentation
157
162
  */
158
- function threadSafeCallback(fn, returnType, argTypes = []) {
159
- return createThreadSafeCallback(fn, returnType, argTypes, native);
163
+ function threadSafeCallback(fn, returnType, argTypes = [], abi = undefined) {
164
+ return createThreadSafeCallback(fn, returnType, argTypes, native, abi);
160
165
  }
161
166
 
162
167
  // ============================================================================
@@ -484,6 +489,8 @@ export {
484
489
  load,
485
490
  callback,
486
491
  threadSafeCallback,
492
+ CFUNCTYPE,
493
+ WINFUNCTYPE,
487
494
 
488
495
  // Memory Management - Python-compatibili
489
496
  create_string_buffer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-ctypes",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "Python ctypes-like FFI for Node.js using libffi",
5
5
  "author": "Damiano Mazzella",
6
6
  "license": "MIT",