node-ctypes 0.1.3
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/LICENSE +21 -0
- package/README.md +768 -0
- package/build/Release/ctypes-darwin-arm64.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/index.d.ts +527 -0
- package/lib/index.js +3501 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
# node-ctypes
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/node-ctypes)
|
|
4
|
+
[](https://www.npmjs.com/package/node-ctypes)
|
|
5
|
+
[](https://github.com/dmazzella/node-ctypes/actions/workflows/build.yml)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
**Python ctypes for Node.js** - A high-performance FFI (Foreign Function Interface) library with full Python ctypes compatibility, built on [libffi](https://github.com/libffi/libffi) and [N-API](https://nodejs.org/api/n-api.html).
|
|
9
|
+
|
|
10
|
+
## Why node-ctypes?
|
|
11
|
+
|
|
12
|
+
- ⨠**Python ctypes API Compatibility** - If you know Python ctypes, you already know node-ctypes! Same syntax, same patterns.
|
|
13
|
+
- đ **High Performance** - Up to **50x faster** than ffi-napi, with struct operations matching or exceeding koffi performance.
|
|
14
|
+
- đ§ **Complete FFI Support** - Structs, unions, bit fields, nested structures, arrays, callbacks, variadic functions, and more.
|
|
15
|
+
- đ **Cross-platform** - Works seamlessly on Linux, macOS, and Windows with identical API.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- đ **Full Python ctypes compatibility** - Struct definitions, unions, bit fields, anonymous fields
|
|
20
|
+
- đ **Load shared libraries** (.so, .dll, .dylib) with CDLL and WinDLL
|
|
21
|
+
- đ **Variadic functions** (printf, sprintf) with automatic detection
|
|
22
|
+
- đ **Callbacks** - JavaScript functions callable from C code
|
|
23
|
+
- đī¸ **Complex data structures** - Nested structs, unions in structs, arrays in structs
|
|
24
|
+
- ⥠**High performance** - Eager loading for struct properties, optimized FFI wrapper
|
|
25
|
+
- đ **Transparent API** - Pass struct objects directly to FFI functions
|
|
26
|
+
- đ§ **Extended type support** - All ctypes types (int8, uint8, int16, uint16, etc.)
|
|
27
|
+
- đ **Memory utilities** - readValue/writeValue for direct memory access, enhanced sizeof
|
|
28
|
+
- đī¸ **Advanced array support** - String initialization, improved proxy behavior
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
### From npm (recommended)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install node-ctypes
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Prebuilt binaries are available for:
|
|
39
|
+
- Windows x64, ARM64
|
|
40
|
+
- Linux x64, ARM64
|
|
41
|
+
- macOS ARM64
|
|
42
|
+
|
|
43
|
+
### From source
|
|
44
|
+
|
|
45
|
+
#### Prerequisites
|
|
46
|
+
|
|
47
|
+
- Node.js >= 16
|
|
48
|
+
- CMake >= 3.15
|
|
49
|
+
- C++ compiler (GCC, Clang, or MSVC)
|
|
50
|
+
|
|
51
|
+
#### Ubuntu/Debian
|
|
52
|
+
```bash
|
|
53
|
+
sudo apt install build-essential cmake
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### macOS
|
|
57
|
+
```bash
|
|
58
|
+
brew install cmake
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### Windows
|
|
62
|
+
- Install Visual Studio Build Tools
|
|
63
|
+
- Install CMake
|
|
64
|
+
|
|
65
|
+
#### Build
|
|
66
|
+
```bash
|
|
67
|
+
npm install
|
|
68
|
+
npm run build
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Quick Start - Python ctypes Users
|
|
72
|
+
|
|
73
|
+
If you're familiar with Python ctypes, you'll feel right at home:
|
|
74
|
+
|
|
75
|
+
**Python ctypes:**
|
|
76
|
+
```python
|
|
77
|
+
from ctypes import CDLL, c_int, Structure
|
|
78
|
+
|
|
79
|
+
libc = CDLL("libc.so.6")
|
|
80
|
+
abs_func = libc.abs
|
|
81
|
+
abs_func.argtypes = [c_int]
|
|
82
|
+
abs_func.restype = c_int
|
|
83
|
+
print(abs_func(-42)) # 42
|
|
84
|
+
|
|
85
|
+
class Point(Structure):
|
|
86
|
+
_fields_ = [("x", c_int), ("y", c_int)]
|
|
87
|
+
|
|
88
|
+
p = Point(10, 20)
|
|
89
|
+
print(p.x, p.y) # 10 20
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**node-ctypes (identical patterns!):**
|
|
93
|
+
```javascript
|
|
94
|
+
import { CDLL, c_int, Structure } from 'node-ctypes';
|
|
95
|
+
|
|
96
|
+
const libc = new CDLL("libc.so.6");
|
|
97
|
+
const abs = libc.func("abs", c_int, [c_int]);
|
|
98
|
+
console.log(abs(-42)); // 42
|
|
99
|
+
|
|
100
|
+
class Point extends Structure {
|
|
101
|
+
static _fields_ = [
|
|
102
|
+
["x", c_int],
|
|
103
|
+
["y", c_int]
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const p = new Point(10, 20);
|
|
108
|
+
console.log(p.x, p.y); // 10 20
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Usage
|
|
112
|
+
|
|
113
|
+
### Basic FFI - Calling C Functions
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
import { CDLL, c_int, c_double, c_char_p } from 'node-ctypes';
|
|
117
|
+
|
|
118
|
+
// Load libc
|
|
119
|
+
const libc = new CDLL('libc.so.6'); // Linux
|
|
120
|
+
// const libc = new CDLL('msvcrt.dll'); // Windows
|
|
121
|
+
// const libc = new CDLL('libc.dylib'); // macOS
|
|
122
|
+
|
|
123
|
+
// Call abs() - integer absolute value
|
|
124
|
+
const abs = libc.func('abs', c_int, [c_int]);
|
|
125
|
+
console.log(abs(-42)); // 42
|
|
126
|
+
|
|
127
|
+
// Call strlen() - string length
|
|
128
|
+
const strlen = libc.func('strlen', 'size_t', [c_char_p]);
|
|
129
|
+
console.log(strlen('Hello')); // 5n (BigInt)
|
|
130
|
+
|
|
131
|
+
// Load libm for math functions
|
|
132
|
+
const libm = new CDLL('libm.so.6');
|
|
133
|
+
const sqrt = libm.func('sqrt', c_double, [c_double]);
|
|
134
|
+
console.log(sqrt(16.0)); // 4.0
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Structs - Full Python ctypes Compatibility
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
import { Structure, c_int, c_uint32 } from 'node-ctypes';
|
|
141
|
+
|
|
142
|
+
// Simple struct - Python-like class syntax
|
|
143
|
+
class Point extends Structure {
|
|
144
|
+
static _fields_ = [
|
|
145
|
+
["x", c_int],
|
|
146
|
+
["y", c_int]
|
|
147
|
+
];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Create and initialize - direct property access!
|
|
151
|
+
const p = new Point(10, 20);
|
|
152
|
+
console.log(p.x, p.y); // 10 20
|
|
153
|
+
|
|
154
|
+
// Modify properties directly
|
|
155
|
+
p.x = 100;
|
|
156
|
+
console.log(p.x); // 100
|
|
157
|
+
|
|
158
|
+
// Get struct size
|
|
159
|
+
console.log(Point.size); // 8
|
|
160
|
+
|
|
161
|
+
// Nested structs
|
|
162
|
+
class Rectangle extends Structure {
|
|
163
|
+
static _fields_ = [
|
|
164
|
+
["topLeft", Point],
|
|
165
|
+
["bottomRight", Point],
|
|
166
|
+
["color", c_uint32]
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const rect = new Rectangle({
|
|
171
|
+
topLeft: { x: 0, y: 0 },
|
|
172
|
+
bottomRight: { x: 100, y: 200 },
|
|
173
|
+
color: 0xff0000
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
console.log(rect.topLeft.x); // 0
|
|
177
|
+
console.log(rect.bottomRight.x); // 100
|
|
178
|
+
console.log(rect.color); // 16711680
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Unions - Shared Memory Regions
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
import { Union, c_int, c_float } from 'node-ctypes';
|
|
185
|
+
|
|
186
|
+
// Union - all fields share the same memory
|
|
187
|
+
class IntOrFloat extends Union {
|
|
188
|
+
static _fields_ = [
|
|
189
|
+
["i", c_int],
|
|
190
|
+
["f", c_float]
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const u = new IntOrFloat();
|
|
195
|
+
u.f = 3.14159;
|
|
196
|
+
console.log(u.i); // Bit pattern of float as integer
|
|
197
|
+
|
|
198
|
+
u.i = 42;
|
|
199
|
+
console.log(u.f); // 42 reinterpreted as float
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Bit Fields - Compact Data Structures
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
import { Structure, bitfield, c_uint32 } from 'node-ctypes';
|
|
206
|
+
|
|
207
|
+
// Bit fields for flags and compact data
|
|
208
|
+
class Flags extends Structure {
|
|
209
|
+
static _fields_ = [
|
|
210
|
+
["enabled", bitfield(c_uint32, 1)], // 1 bit
|
|
211
|
+
["mode", bitfield(c_uint32, 3)], // 3 bits
|
|
212
|
+
["priority", bitfield(c_uint32, 4)], // 4 bits
|
|
213
|
+
["reserved", bitfield(c_uint32, 24)] // 24 bits
|
|
214
|
+
];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const flags = new Flags();
|
|
218
|
+
|
|
219
|
+
flags.enabled = 1;
|
|
220
|
+
flags.mode = 5;
|
|
221
|
+
flags.priority = 12;
|
|
222
|
+
|
|
223
|
+
console.log(flags.enabled); // 1
|
|
224
|
+
console.log(flags.mode); // 5
|
|
225
|
+
console.log(flags.priority); // 12
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Arrays - Fixed-size and Dynamic
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
import { c_int, c_uint8, array } from 'node-ctypes';
|
|
232
|
+
|
|
233
|
+
// Fixed-size array
|
|
234
|
+
const IntArray = array('int32', 5);
|
|
235
|
+
const arr = IntArray.create([1, 2, 3, 4, 5]);
|
|
236
|
+
|
|
237
|
+
// Array access
|
|
238
|
+
console.log(arr[0]); // 1
|
|
239
|
+
console.log(arr[4]); // 5
|
|
240
|
+
|
|
241
|
+
// Iterate
|
|
242
|
+
for (const val of arr) {
|
|
243
|
+
console.log(val);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Arrays in structs
|
|
247
|
+
import { Structure, array } from 'node-ctypes';
|
|
248
|
+
|
|
249
|
+
class Packet extends Structure {
|
|
250
|
+
static _fields_ = [
|
|
251
|
+
["header", array("c_uint8", 8)],
|
|
252
|
+
["data", array("c_uint8", 256)]
|
|
253
|
+
];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const pkt = new Packet({
|
|
257
|
+
header: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
|
|
258
|
+
data: new Array(256).fill(0)
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
console.log(pkt.header); // [1, 2, 3, 4, 5, 6, 7, 8]
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Complex Nested Structures
|
|
265
|
+
|
|
266
|
+
Real-world example from our test suite:
|
|
267
|
+
|
|
268
|
+
```javascript
|
|
269
|
+
import { Structure, Union, c_uint8, c_uint16, c_int32, array } from 'node-ctypes';
|
|
270
|
+
|
|
271
|
+
// RGB color components
|
|
272
|
+
class RGB extends Structure {
|
|
273
|
+
static _fields_ = [
|
|
274
|
+
["r", c_uint8],
|
|
275
|
+
["g", c_uint8],
|
|
276
|
+
["b", c_uint8],
|
|
277
|
+
["a", c_uint8]
|
|
278
|
+
];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Union for color access (as RGB or as 32-bit value)
|
|
282
|
+
class Color extends Union {
|
|
283
|
+
static _fields_ = [
|
|
284
|
+
["rgb", RGB],
|
|
285
|
+
["value", c_int32]
|
|
286
|
+
];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Pixel with position and color
|
|
290
|
+
class Pixel extends Structure {
|
|
291
|
+
static _fields_ = [
|
|
292
|
+
["x", c_uint16],
|
|
293
|
+
["y", c_uint16],
|
|
294
|
+
["color", Color]
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Image with array of pixels
|
|
299
|
+
class Image extends Structure {
|
|
300
|
+
static _fields_ = [
|
|
301
|
+
["width", c_uint16],
|
|
302
|
+
["height", c_uint16],
|
|
303
|
+
["pixels", array(Pixel, 2)]
|
|
304
|
+
];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Create and manipulate
|
|
308
|
+
const img = new Image({
|
|
309
|
+
width: 640,
|
|
310
|
+
height: 480,
|
|
311
|
+
pixels: [
|
|
312
|
+
{ x: 10, y: 20, color: { rgb: { r: 255, g: 0, b: 0, a: 255 } } },
|
|
313
|
+
{ x: 30, y: 40, color: { value: 0xFF00FF00 } }
|
|
314
|
+
]
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
console.log(img.pixels[0].color.rgb.r); // 255
|
|
318
|
+
console.log(img.pixels[1].color.value); // -16711936 (0xFF00FF00 as signed)
|
|
319
|
+
|
|
320
|
+
// Union nested in struct - direct property access!
|
|
321
|
+
img.pixels[0].color.rgb.g = 128; // Works correctly!
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Callbacks - JavaScript Functions in C
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
import { CDLL, callback, c_int, c_void_p, readValue, writeValue, create_string_buffer } from 'node-ctypes';
|
|
328
|
+
|
|
329
|
+
const libc = new CDLL('msvcrt.dll'); // or libc.so.6 on Linux
|
|
330
|
+
|
|
331
|
+
// Create a comparison callback for qsort
|
|
332
|
+
const compare = callback(
|
|
333
|
+
(a, b) => {
|
|
334
|
+
// a and b are pointers to int32 values
|
|
335
|
+
const aVal = readValue(a, 'int32');
|
|
336
|
+
const bVal = readValue(b, 'int32');
|
|
337
|
+
return aVal - bVal;
|
|
338
|
+
},
|
|
339
|
+
c_int, // return type
|
|
340
|
+
[c_void_p, c_void_p] // argument types: two pointers
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// Sort an array using qsort
|
|
344
|
+
const qsort = libc.func('qsort', 'void', [
|
|
345
|
+
c_void_p, // array pointer
|
|
346
|
+
'size_t', // number of elements
|
|
347
|
+
'size_t', // element size
|
|
348
|
+
c_void_p // comparison function
|
|
349
|
+
]);
|
|
350
|
+
|
|
351
|
+
const arr = create_string_buffer(5 * 4);
|
|
352
|
+
const values = [5, 2, 8, 1, 9];
|
|
353
|
+
values.forEach((v, i) => writeValue(arr, 'int32', v, i * 4));
|
|
354
|
+
qsort(arr, 5, 4, compare.pointer);
|
|
355
|
+
|
|
356
|
+
// Array is now sorted: [1, 2, 5, 8, 9]
|
|
357
|
+
console.log(readValue(arr, 'int32', 0)); // 1
|
|
358
|
+
console.log(readValue(arr, 'int32', 4)); // 2
|
|
359
|
+
|
|
360
|
+
// IMPORTANT: Release callback when done
|
|
361
|
+
compare.release();
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Variadic Functions - printf, sprintf
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
import { CDLL, create_string_buffer, string_at, c_int, c_void_p, c_char_p } from 'node-ctypes';
|
|
368
|
+
|
|
369
|
+
const libc = new CDLL('msvcrt.dll'); // Windows
|
|
370
|
+
// const libc = new CDLL('libc.so.6'); // Linux
|
|
371
|
+
|
|
372
|
+
// Define only the fixed parameters - variadic args detected automatically!
|
|
373
|
+
const sprintf = libc.func('sprintf', c_int, [c_void_p, c_char_p]);
|
|
374
|
+
|
|
375
|
+
const buffer = Buffer.alloc(256);
|
|
376
|
+
|
|
377
|
+
// Pass extra arguments - automatically handled as variadic
|
|
378
|
+
sprintf(buffer, create_string_buffer('Hello %s!'), create_string_buffer('World'));
|
|
379
|
+
console.log(string_at(buffer)); // "Hello World!"
|
|
380
|
+
|
|
381
|
+
sprintf(buffer, create_string_buffer('Number: %d'), 42);
|
|
382
|
+
console.log(string_at(buffer)); // "Number: 42"
|
|
383
|
+
|
|
384
|
+
sprintf(buffer, create_string_buffer('%s: %d + %d = %d'), create_string_buffer('Sum'), 10, 20, 30);
|
|
385
|
+
console.log(string_at(buffer)); // "Sum: 10 + 20 = 30"
|
|
386
|
+
|
|
387
|
+
sprintf(buffer, create_string_buffer('Pi â %.2f'), 3.14159);
|
|
388
|
+
console.log(string_at(buffer)); // "Pi â 3.14"
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Automatic variadic detection** - When you pass more arguments than specified, node-ctypes:
|
|
392
|
+
- â
Detects the extra arguments
|
|
393
|
+
- â
Infers their types (string â char*, number â int32/double, Buffer â pointer)
|
|
394
|
+
- â
Uses `ffi_prep_cif_var` for variadic call preparation
|
|
395
|
+
- â
Calls the function with correct argument marshalling
|
|
396
|
+
|
|
397
|
+
**This matches Python ctypes behavior exactly!**
|
|
398
|
+
|
|
399
|
+
### Windows API - Full Support
|
|
400
|
+
|
|
401
|
+
```javascript
|
|
402
|
+
import { WinDLL, Structure, c_uint16, c_void_p, c_wchar_p, c_int } from 'node-ctypes';
|
|
403
|
+
|
|
404
|
+
// WinDLL uses __stdcall convention (default for Windows API)
|
|
405
|
+
const kernel32 = new WinDLL('kernel32.dll');
|
|
406
|
+
|
|
407
|
+
// SYSTEMTIME structure (from tests/windows/test_winapi.js)
|
|
408
|
+
class SYSTEMTIME extends Structure {
|
|
409
|
+
static _fields_ = [
|
|
410
|
+
["wYear", c_uint16],
|
|
411
|
+
["wMonth", c_uint16],
|
|
412
|
+
["wDayOfWeek", c_uint16],
|
|
413
|
+
["wDay", c_uint16],
|
|
414
|
+
["wHour", c_uint16],
|
|
415
|
+
["wMinute", c_uint16],
|
|
416
|
+
["wSecond", c_uint16],
|
|
417
|
+
["wMilliseconds", c_uint16]
|
|
418
|
+
];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Get local time
|
|
422
|
+
const GetLocalTime = kernel32.func('GetLocalTime', 'void', [c_void_p]);
|
|
423
|
+
|
|
424
|
+
const st = new SYSTEMTIME();
|
|
425
|
+
GetLocalTime(st); // Pass struct directly - automatic _buffer extraction!
|
|
426
|
+
|
|
427
|
+
console.log(`${st.wYear}-${st.wMonth}-${st.wDay}`);
|
|
428
|
+
console.log(`${st.wHour}:${st.wMinute}:${st.wSecond}`);
|
|
429
|
+
|
|
430
|
+
// MessageBox (wide string version)
|
|
431
|
+
const user32 = new WinDLL('user32.dll');
|
|
432
|
+
const MessageBoxW = user32.func('MessageBoxW', c_int, [
|
|
433
|
+
c_void_p, // hWnd
|
|
434
|
+
c_wchar_p, // lpText
|
|
435
|
+
c_wchar_p, // lpCaption
|
|
436
|
+
'uint32' // uType
|
|
437
|
+
]);
|
|
438
|
+
|
|
439
|
+
// Create UTF-16 buffers for wide strings
|
|
440
|
+
const text = Buffer.from('Hello from node-ctypes!\0', 'utf16le');
|
|
441
|
+
const caption = Buffer.from('node-ctypes\0', 'utf16le');
|
|
442
|
+
|
|
443
|
+
MessageBoxW(null, text, caption, 0);
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Memory Operations - Low-level Control
|
|
447
|
+
|
|
448
|
+
```javascript
|
|
449
|
+
import { readValue, writeValue, sizeof, create_string_buffer, string_at, c_int, c_double, c_void_p } from 'node-ctypes';
|
|
450
|
+
|
|
451
|
+
// Allocate memory
|
|
452
|
+
const buf = Buffer.alloc(16);
|
|
453
|
+
|
|
454
|
+
// Write values at specific offsets
|
|
455
|
+
writeValue(buf, c_int, 12345, 0);
|
|
456
|
+
writeValue(buf, c_double, 3.14159, 8);
|
|
457
|
+
|
|
458
|
+
// Read values back
|
|
459
|
+
console.log(readValue(buf, c_int, 0)); // 12345
|
|
460
|
+
console.log(readValue(buf, c_double, 8)); // 3.14159
|
|
461
|
+
|
|
462
|
+
// Get type sizes
|
|
463
|
+
console.log(sizeof(c_int)); // 4
|
|
464
|
+
console.log(sizeof(c_double)); // 8
|
|
465
|
+
console.log(sizeof(c_void_p)); // 8 (on 64-bit)
|
|
466
|
+
|
|
467
|
+
// String handling
|
|
468
|
+
const str = create_string_buffer('Hello, World!');
|
|
469
|
+
console.log(string_at(str)); // "Hello, World!"
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## Performance Benchmarks
|
|
473
|
+
|
|
474
|
+
Benchmarked on Windows with Node.js v24.11.0:
|
|
475
|
+
|
|
476
|
+
**vs koffi** (comprehensive 10-benchmark comparison, geometric mean: **3.27x slower**):
|
|
477
|
+
- Simple int32 function: 1.74x slower
|
|
478
|
+
- String parameter: 1.95x slower
|
|
479
|
+
- Floating point: 1.83x slower
|
|
480
|
+
- No arguments: 2.11x slower
|
|
481
|
+
- Multiple arguments: **1.40x faster**
|
|
482
|
+
- Variadic function: 1.28x slower
|
|
483
|
+
- **Struct read/write: 14.91x slower**
|
|
484
|
+
- Buffer allocation: 40.5% overhead
|
|
485
|
+
- Raw vs CDLL wrapper: 7.3% overhead
|
|
486
|
+
- **Callback creation: 1.51x slower**
|
|
487
|
+
|
|
488
|
+
**Key Insights:**
|
|
489
|
+
- koffi excels at simple operations and struct access
|
|
490
|
+
- node-ctypes competitive on complex argument handling
|
|
491
|
+
- **Struct performance gap**: koffi 15x faster due to direct object manipulation
|
|
492
|
+
- **Callback overhead**: koffi 1.5x faster at callback creation
|
|
493
|
+
|
|
494
|
+
**Transparent API overhead**: Only **3.5%** for auto `._buffer` extraction!
|
|
495
|
+
|
|
496
|
+
*See `tests/benchmarks/` for full benchmark suite.*
|
|
497
|
+
|
|
498
|
+
## Supported Types
|
|
499
|
+
|
|
500
|
+
| Type Name | Aliases | Size |
|
|
501
|
+
|-----------|---------|------|
|
|
502
|
+
| `void` | - | 0 |
|
|
503
|
+
| `int8` | `c_int8`, `char` | 1 |
|
|
504
|
+
| `uint8` | `c_uint8`, `uchar` | 1 |
|
|
505
|
+
| `int16` | `c_int16`, `short` | 2 |
|
|
506
|
+
| `uint16` | `c_uint16`, `ushort` | 2 |
|
|
507
|
+
| `int32` | `c_int32`, `int`, `c_int` | 4 |
|
|
508
|
+
| `uint32` | `c_uint32`, `uint`, `c_uint` | 4 |
|
|
509
|
+
| `int64` | `c_int64`, `long long` | 8 |
|
|
510
|
+
| `uint64` | `c_uint64` | 8 |
|
|
511
|
+
| `float` | `c_float` | 4 |
|
|
512
|
+
| `double` | `c_double` | 8 |
|
|
513
|
+
| `pointer` | `c_void_p`, `void*`, `ptr` | 8 (64-bit) |
|
|
514
|
+
| `string` | `c_char_p`, `char*` | pointer |
|
|
515
|
+
| `bool` | `c_bool` | 1 |
|
|
516
|
+
| `size_t` | `c_size_t` | pointer |
|
|
517
|
+
|
|
518
|
+
## API Reference
|
|
519
|
+
|
|
520
|
+
### Classes
|
|
521
|
+
|
|
522
|
+
#### `CDLL(libPath)`
|
|
523
|
+
Load a shared library using default (cdecl) calling convention.
|
|
524
|
+
|
|
525
|
+
#### `WinDLL(libPath)`
|
|
526
|
+
Load a shared library using stdcall calling convention (Windows).
|
|
527
|
+
|
|
528
|
+
#### `Library(libPath)`
|
|
529
|
+
Low-level library wrapper.
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## Detailed API Reference (from lib/index.js)
|
|
534
|
+
|
|
535
|
+
This section provides a more complete description of the APIs exported from `lib/index.js`, with quick examples and usage notes.
|
|
536
|
+
|
|
537
|
+
**Native classes and exports**
|
|
538
|
+
- `Version` - Version information exposed by the native module.
|
|
539
|
+
- `Library` - Represents a loaded native library and exposes low-level functions for symbols and library management.
|
|
540
|
+
- `FFIFunction` - Low-level object representing an FFI function (has properties like `address` and internal methods).
|
|
541
|
+
- `Callback` - Builds JS callbacks callable from C (main thread).
|
|
542
|
+
- `ThreadSafeCallback` - Builds thread-safe JS callbacks (can be called from external threads).
|
|
543
|
+
- `CType`, `StructType`, `ArrayType` - Types and helpers exposed by the native layer.
|
|
544
|
+
|
|
545
|
+
**Library loading and wrappers**
|
|
546
|
+
- `load(libPath)` â `Library` : loads a native library; `libPath` can be `null` for the current executable.
|
|
547
|
+
- `CDLL(libPath)` : common-use wrapper for C calls with cdecl convention; maintains a function cache and provides more convenient `func()`.
|
|
548
|
+
- `WinDLL(libPath)` : like `CDLL` but with `abi: 'stdcall'` by default (useful for WinAPI).
|
|
549
|
+
|
|
550
|
+
Example:
|
|
551
|
+
```js
|
|
552
|
+
import { CDLL } from './lib/index.js';
|
|
553
|
+
const libc = new CDLL(null);
|
|
554
|
+
const abs = libc.func('abs', 'int32', ['int32']);
|
|
555
|
+
console.log(abs(-5));
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
**Detailed CDLL API**
|
|
559
|
+
- `func(name, returnType, argTypes = [], options = {})` â `Function` : gets a callable function. The returned function is optimized and:
|
|
560
|
+
- automatically extracts `._buffer` from struct objects passed as arguments;
|
|
561
|
+
- exposes non-enumerable metadata: `funcName`, `address`, `_ffi`;
|
|
562
|
+
- provides the `errcheck` property as getter/setter to intercept return errors.
|
|
563
|
+
- `symbol(name)` â `BigInt` : address of a symbol.
|
|
564
|
+
- `close()` : closes the library and clears the cache.
|
|
565
|
+
- `path` (getter) : library path.
|
|
566
|
+
- `loaded` (getter) : loading status.
|
|
567
|
+
|
|
568
|
+
**Callback**
|
|
569
|
+
- `callback(fn, returnType, argTypes = [])` â `{ pointer, release(), _callback }` : fast callback, main thread only.
|
|
570
|
+
- `threadSafeCallback(fn, returnType, argTypes = [])` â `{ pointer, release(), _callback }` : thread-safe callback for external threads.
|
|
571
|
+
|
|
572
|
+
Note: always call `release()` when a callback is no longer needed.
|
|
573
|
+
|
|
574
|
+
**Allocation and strings**
|
|
575
|
+
|
|
576
|
+
- `Buffer.alloc(size)` â `Buffer` : allocates native memory.
|
|
577
|
+
- `create_string_buffer(init)` â `Buffer` : creates null-terminated C string (init: size|string|Buffer).
|
|
578
|
+
- `create_unicode_buffer(init)` â `Buffer` : creates null-terminated wide string (wchar_t).
|
|
579
|
+
- `ptrToBuffer(address, size)` â `Buffer` : view on native address (use with caution).
|
|
580
|
+
- `addressof(ptr)` â `BigInt` : get the address as BigInt.
|
|
581
|
+
|
|
582
|
+
Example string creation and passing to function:
|
|
583
|
+
```js
|
|
584
|
+
import { create_string_buffer, CDLL } from './lib/index.js';
|
|
585
|
+
const libc = new CDLL(null);
|
|
586
|
+
const puts = libc.func('puts', 'int32', ['pointer']);
|
|
587
|
+
const s = create_string_buffer('hello');
|
|
588
|
+
puts(s);
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**Reading and writing values**
|
|
592
|
+
- `readValue(ptr, type, offset = 0)` : supports fast-path for Buffer + basic types (`int8`, `uint8`, `int16`, `int32`, `int64`, `float`, `double`, `bool`).
|
|
593
|
+
- `writeValue(ptr, type, value, offset = 0)` : writes values with fast-path for Buffer.
|
|
594
|
+
|
|
595
|
+
**Types and helpers**
|
|
596
|
+
- `sizeof(type)` â `number` : size in bytes of a type.
|
|
597
|
+
- `POINTER(baseType)` : creates a pointer type with helpers `create()`, `fromBuffer()`, `deref()`, `set()`.
|
|
598
|
+
- `byref(buffer)` : passes a buffer by reference (Python ctypes compatibility).
|
|
599
|
+
- `cast(ptr, targetType)` : interprets a pointer as another type (returns wrapper for struct).
|
|
600
|
+
|
|
601
|
+
**Struct / Union / Array / Bitfield**
|
|
602
|
+
- `struct(fields, options)` : defines struct with support for nested, bitfields, anonymous fields, packed option. Returns object with `create()`, `get()`, `set()`, `toObject()`, `getNestedBuffer()`.
|
|
603
|
+
- `union(fields)` : defines union; provides `create()`, `get()`, `set()`, `toObject()` and returns plain objects with properties.
|
|
604
|
+
- `array(elementType, count)` : defines ArrayType; `wrap(buffer)` returns Proxy with indexing.
|
|
605
|
+
- `bitfield(baseType, bits)` : bitfield definition.
|
|
606
|
+
|
|
607
|
+
Struct example:
|
|
608
|
+
```js
|
|
609
|
+
class Point extends Structure {
|
|
610
|
+
static _fields_ = [
|
|
611
|
+
["x", c_int32],
|
|
612
|
+
["y", c_int32]
|
|
613
|
+
];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const p = new Point(1, 2);
|
|
617
|
+
console.log(p.x, p.y); // 1 2
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
**Python-compatible conveniences**
|
|
621
|
+
- `create_string_buffer(init)` : create string buffer from number/string/Buffer.
|
|
622
|
+
- `create_unicode_buffer(init)` : create wide string buffer.
|
|
623
|
+
- `string_at(address, size)` / `wstring_at(address, size)` : read strings from address.
|
|
624
|
+
|
|
625
|
+
**Memory: utilities**
|
|
626
|
+
- `memmove(dst, src, count)` : copy memory.
|
|
627
|
+
- `memset(dst, value, count)` : set memory.
|
|
628
|
+
|
|
629
|
+
**Error handling and WinAPI helpers**
|
|
630
|
+
- `get_errno()` / `set_errno(value)` : access to errno (platform-specific implementation).
|
|
631
|
+
- `_initWinError()` internals; public helpers: `GetLastError()`, `SetLastError(code)`, `FormatError(code)`, `WinError(code)`.
|
|
632
|
+
|
|
633
|
+
**Type aliases**
|
|
634
|
+
The following aliases are exposed (mapped from `native.types`):
|
|
635
|
+
`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`.
|
|
636
|
+
|
|
637
|
+
**Constants**
|
|
638
|
+
- `POINTER_SIZE` - pointer size (from `native.POINTER_SIZE`).
|
|
639
|
+
- `WCHAR_SIZE` - wchar size (from `native.WCHAR_SIZE`).
|
|
640
|
+
- `NULL` - exported null value.
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
If you want, I can generate additional Windows or Linux-specific snippets, or integrate examples in the `tests/` directory.
|
|
645
|
+
|
|
646
|
+
### Functions
|
|
647
|
+
|
|
648
|
+
#### `load(libPath)` â `Library`
|
|
649
|
+
Load a shared library.
|
|
650
|
+
|
|
651
|
+
#### `callback(fn, returnType, argTypes)` â `{pointer, release()}`
|
|
652
|
+
Create a callback from a JavaScript function.
|
|
653
|
+
|
|
654
|
+
#### `create_string_buffer(init)` â `Buffer`
|
|
655
|
+
Create a null-terminated C string buffer (like Python ctypes). `init` can be a size, a string, or an existing `Buffer`.
|
|
656
|
+
|
|
657
|
+
#### `create_unicode_buffer(init)` â `Buffer`
|
|
658
|
+
Create a wide (wchar_t) null-terminated buffer (UTF-16LE on Windows).
|
|
659
|
+
|
|
660
|
+
#### `string_at(address, [size])` â `string`
|
|
661
|
+
Read a C string from an address or buffer.
|
|
662
|
+
|
|
663
|
+
#### `readValue(ptr, type, [offset])` â `value`
|
|
664
|
+
Read a value from memory.
|
|
665
|
+
|
|
666
|
+
#### `writeValue(ptr, type, value, [offset])` â `bytesWritten`
|
|
667
|
+
Write a value to memory.
|
|
668
|
+
|
|
669
|
+
#### `sizeof(type)` â `number`
|
|
670
|
+
Get the size of a type in bytes.
|
|
671
|
+
|
|
672
|
+
#### `struct(fields)` â `StructDefinition`
|
|
673
|
+
Create a simple struct definition.
|
|
674
|
+
|
|
675
|
+
#### `Structure` (base class)
|
|
676
|
+
Base class for Python-like struct definitions. Subclasses should define `static _fields_`.
|
|
677
|
+
|
|
678
|
+
#### `Union` (base class)
|
|
679
|
+
Base class for Python-like union definitions. Subclasses should define `static _fields_`.
|
|
680
|
+
|
|
681
|
+
## Python ctypes Compatibility Reference
|
|
682
|
+
|
|
683
|
+
### API Comparison
|
|
684
|
+
|
|
685
|
+
| Feature | Python ctypes | node-ctypes |
|
|
686
|
+
|---------|---------------|-------------|
|
|
687
|
+
| **Load library** | `CDLL("lib.so")` | `new CDLL("lib.so")` |
|
|
688
|
+
| **Define function** | `lib.func.argtypes = [c_int]`<br>`lib.func.restype = c_int` | `lib.func("func", c_int, [c_int])` |
|
|
689
|
+
| **Structs** | `class Point(Structure):`<br> `_fields_ = [("x", c_int)]` | `class Point extends Structure`<br> `{ static _fields_ = [["x", c_int]] }` |
|
|
690
|
+
| **Unions** | `class U(Union):`<br> `_fields_ = [("i", c_int)]` | `class U extends Union`<br> `{ static _fields_ = [["i", c_int]] }` |
|
|
691
|
+
| **Arrays** | `c_int * 5` | `array(c_int, 5)` |
|
|
692
|
+
| **Bit fields** | `("flags", c_uint, 3)` | `bitfield(c_uint32, 3)` |
|
|
693
|
+
| **Callbacks** | `CFUNCTYPE(c_int, c_int)` | `callback(fn, c_int, [c_int])` |
|
|
694
|
+
| **Strings** | `c_char_p(b"hello")` | `create_string_buffer("hello")` |
|
|
695
|
+
| **Pointers** | `POINTER(c_int)` | `c_void_p` or `"pointer"` |
|
|
696
|
+
| **Variadic** | `sprintf(buf, b"%d", 42)` | `sprintf(buf, fmt, 42)` (auto) |
|
|
697
|
+
| **Sizeof** | `sizeof(c_int)` | `sizeof(c_int)` |
|
|
698
|
+
|
|
699
|
+
### What's Supported
|
|
700
|
+
|
|
701
|
+
â
**Fully Compatible**:
|
|
702
|
+
- All basic types (int8-64, uint8-64, float, double, bool, pointer, string)
|
|
703
|
+
- Structs with nested structures
|
|
704
|
+
- Unions (including nested in structs)
|
|
705
|
+
- Bit fields
|
|
706
|
+
- Arrays (fixed-size)
|
|
707
|
+
- Callbacks (with manual release)
|
|
708
|
+
- Variadic functions (automatic detection)
|
|
709
|
+
- Anonymous fields in structs/unions
|
|
710
|
+
- **Class-based struct/union definitions** (`class MyStruct extends Structure`)
|
|
711
|
+
- Platform-specific types (c_long, c_ulong, c_size_t)
|
|
712
|
+
- Memory operations (alloc, read, write)
|
|
713
|
+
- Windows API (__stdcall via WinDLL)
|
|
714
|
+
|
|
715
|
+
â ī¸ **Differences from Python ctypes**:
|
|
716
|
+
- Structs use `.toObject()` for property access (eager loading for performance)
|
|
717
|
+
- Callbacks must be manually released with `.release()`
|
|
718
|
+
- Function definition is combined: `func(name, returnType, argTypes)` vs separate argtypes/restype
|
|
719
|
+
- No `POINTER()` type - use `c_void_p` or type name string
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
## Limitations & Known Issues
|
|
723
|
+
|
|
724
|
+
- â ī¸ Callbacks must be released manually with `.release()` to prevent memory leaks
|
|
725
|
+
- â ī¸ No automatic memory management for returned pointers (manual `free()` required)
|
|
726
|
+
- â ī¸ Struct alignment follows platform defaults (not customizable per-field)
|
|
727
|
+
- âšī¸ Nested union access uses getter caching (slight behavior difference from Python)
|
|
728
|
+
- âšī¸ Struct property access via `.toObject()` instead of direct field access (performance optimization)
|
|
729
|
+
|
|
730
|
+
## Examples in Test Suite
|
|
731
|
+
|
|
732
|
+
For complete, working examples, see the test suite:
|
|
733
|
+
|
|
734
|
+
- **Basic types**: [tests/common/test_basic_types.js](tests/common/test_basic_types.js)
|
|
735
|
+
- **Structs & unions**: [tests/common/test_structs.js](tests/common/test_structs.js)
|
|
736
|
+
- **Nested structures**: [tests/common/test_nested_structs.js](tests/common/test_nested_structs.js)
|
|
737
|
+
- **Arrays**: [tests/common/test_arrays.js](tests/common/test_arrays.js)
|
|
738
|
+
- **Functions**: [tests/common/test_functions.js](tests/common/test_functions.js)
|
|
739
|
+
- **Callbacks**: [tests/common/test_callbacks.js](tests/common/test_callbacks.js)
|
|
740
|
+
- **Version info**: [tests/common/test_version.js](tests/common/test_version.js)
|
|
741
|
+
- **Windows API**: [tests/windows/test_winapi.js](tests/windows/test_winapi.js)
|
|
742
|
+
- **Python compatibility**: [tests/common/*.py](tests/common/) (parallel Python implementations)
|
|
743
|
+
|
|
744
|
+
Run tests:
|
|
745
|
+
```bash
|
|
746
|
+
cd tests
|
|
747
|
+
npm install
|
|
748
|
+
npm run test # All tests
|
|
749
|
+
npm run bench:koffi # Benchmark vs koffi
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
## Examples
|
|
753
|
+
|
|
754
|
+
For practical GUI application examples using the Windows API:
|
|
755
|
+
|
|
756
|
+
- **Simple GUI Demo**: [examples/windows/simple.js](examples/windows/simple.js) - Message boxes and basic Windows API GUI elements
|
|
757
|
+
- **Windows Controls Showcase Demo**: [examples/windows/windows_controls.js](examples/windows/windows_controls.js) - Comprehensive demo with a wide set of common Win32 controls
|
|
758
|
+
|
|
759
|
+
## License
|
|
760
|
+
|
|
761
|
+
MIT
|
|
762
|
+
|
|
763
|
+
## Credits
|
|
764
|
+
|
|
765
|
+
Built with:
|
|
766
|
+
- [libffi](https://github.com/libffi/libffi) - Foreign Function Interface library
|
|
767
|
+
- [node-addon-api](https://github.com/nodejs/node-addon-api) - N-API C++ wrapper
|
|
768
|
+
- [cmake-js](https://github.com/cmake-js/cmake-js) - CMake-based build system for Node.js addons
|