node-ctypes 1.5.0 → 1.7.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
@@ -5,907 +5,282 @@
5
5
  [![Build](https://github.com/dmazzella/node-ctypes/actions/workflows/build.yml/badge.svg)](https://github.com/dmazzella/node-ctypes/actions/workflows/build.yml)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
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).
8
+ **Python ctypes for Node.js** A foreign function interface library that mirrors the Python ctypes API, built on [libffi](https://github.com/libffi/libffi) and [N-API](https://nodejs.org/api/n-api.html).
9
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
10
+ If you know Python ctypes, you already know node-ctypes.
29
11
 
30
12
  ## Installation
31
13
 
32
- ### From npm (recommended)
33
-
34
14
  ```bash
35
15
  npm install node-ctypes
36
16
  ```
37
17
 
38
- Prebuilt binaries are available for:
39
- - Windows x64, ARM64
40
- - Linux x64, ARM64
41
- - macOS x64, ARM64
18
+ Prebuilt binaries for Windows, Linux, and macOS (x64/ARM64).
42
19
 
43
- ### From source
20
+ <details>
21
+ <summary>Build from source</summary>
44
22
 
45
- #### Prerequisites
23
+ Requires Node.js >= 16, CMake >= 3.15, and a C++ compiler.
46
24
 
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
25
  ```bash
67
26
  npm install
68
27
  npm run build
69
28
  ```
29
+ </details>
70
30
 
71
- ## Quick Start - Python ctypes Users
31
+ ## Python ctypes vs node-ctypes
72
32
 
73
- If you're familiar with Python ctypes, you'll feel right at home:
33
+ ### Loading libraries and calling functions
74
34
 
75
- **Python ctypes:**
35
+ **Python:**
76
36
  ```python
77
- from ctypes import CDLL, c_int, Structure
37
+ from ctypes import CDLL, c_int
78
38
 
79
39
  libc = CDLL("libc.so.6")
80
40
  abs_func = libc.abs
81
41
  abs_func.argtypes = [c_int]
82
42
  abs_func.restype = c_int
83
43
  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
44
  ```
91
45
 
92
- **node-ctypes (identical patterns!):**
46
+ **Node.js:**
93
47
  ```javascript
94
- import { CDLL, c_int, Structure } from 'node-ctypes';
48
+ import { CDLL, c_int } from 'node-ctypes';
95
49
 
96
- // Traditional syntax (always available)
97
- const libc = new CDLL("libc.so.6"); // Linux
50
+ const libc = new CDLL("libc.so.6"); // Linux
98
51
  // const libc = new CDLL('msvcrt.dll'); // Windows
99
52
  // const libc = new CDLL('libc.dylib'); // macOS
100
-
101
- const abs = libc.func("abs", c_int, [c_int]);
102
- console.log(abs(-42)); // 42
103
-
104
- // Python ctypes-like syntax
105
53
  const abs_func = libc.abs;
106
54
  abs_func.argtypes = [c_int];
107
55
  abs_func.restype = c_int;
108
56
  console.log(abs_func(-42)); // 42
109
-
110
- class Point extends Structure {
111
- static _fields_ = [
112
- ["x", c_int],
113
- ["y", c_int]
114
- ];
115
- }
116
-
117
- const p = new Point(10, 20);
118
- console.log(p.x, p.y); // 10 20
119
57
  ```
120
58
 
121
- ## Usage
59
+ A traditional syntax is also available: `libc.func("abs", c_int, [c_int])`.
122
60
 
123
- ### Basic FFI - Calling C Functions
124
-
125
- ```javascript
126
- import { CDLL, c_int, c_double, c_char_p, c_size_t } from 'node-ctypes';
127
-
128
- // Load libc
129
- const libc = new CDLL('libc.so.6'); // Linux
130
- // const libc = new CDLL('msvcrt.dll'); // Windows
131
- // const libc = new CDLL('libc.dylib'); // macOS
132
-
133
- // Traditional syntax
134
- const abs = libc.func('abs', c_int, [c_int]);
135
- console.log(abs(-42)); // 42
136
-
137
- // Python ctypes-like syntax (equivalent!)
138
- const abs_func = libc.abs;
139
- abs_func.argtypes = [c_int];
140
- abs_func.restype = c_int;
141
- console.log(abs_func(-42)); // 42
61
+ ### Structures
142
62
 
143
- // Call strlen() - string length
144
- const strlen = libc.func('strlen', c_size_t, [c_char_p]);
145
- console.log(strlen('Hello')); // 5n (BigInt)
63
+ **Python:**
64
+ ```python
65
+ from ctypes import Structure, c_int, c_uint32
146
66
 
147
- // Load libm for math functions
148
- const libm = new CDLL('libm.so.6'); // Linux
149
- // const libb = new CDLL('ucrtbase.dll'); // Windows
150
- // const libm = new CDLL('libm.dylib'); // macOS
67
+ class Point(Structure):
68
+ _fields_ = [("x", c_int), ("y", c_int)]
151
69
 
152
- const sqrt = libm.func('sqrt', c_double, [c_double]);
153
- console.log(sqrt(16.0)); // 4.0
70
+ p = Point(10, 20)
71
+ print(p.x, p.y) # 10 20
154
72
  ```
155
73
 
156
- ### Structs - Full Python ctypes Compatibility
157
-
74
+ **Node.js:**
158
75
  ```javascript
159
76
  import { Structure, c_int, c_uint32 } from 'node-ctypes';
160
77
 
161
- // Simple struct - Python-like class syntax
162
78
  class Point extends Structure {
163
- static _fields_ = [
164
- ["x", c_int],
165
- ["y", c_int]
166
- ];
79
+ static _fields_ = [["x", c_int], ["y", c_int]];
167
80
  }
168
81
 
169
- // Create and initialize - direct property access!
170
82
  const p = new Point(10, 20);
171
83
  console.log(p.x, p.y); // 10 20
172
-
173
- // Modify properties directly
174
- p.x = 100;
175
- console.log(p.x); // 100
176
-
177
- // Get struct size
178
- console.log(Point.size); // 8
179
-
180
- // Nested structs
181
- class Rectangle extends Structure {
182
- static _fields_ = [
183
- ["topLeft", Point],
184
- ["bottomRight", Point],
185
- ["color", c_uint32]
186
- ];
187
- }
188
-
189
- const rect = new Rectangle({
190
- topLeft: { x: 0, y: 0 },
191
- bottomRight: { x: 100, y: 200 },
192
- color: 0xff0000
193
- });
194
-
195
- console.log(rect.topLeft.x); // 0
196
- console.log(rect.bottomRight.x); // 100
197
- console.log(rect.color); // 16711680
198
84
  ```
199
85
 
200
- ### Unions - Shared Memory Regions
86
+ Nested structs, unions, bit fields, and anonymous fields all work the same way.
87
+
88
+ ### Unions
201
89
 
202
90
  ```javascript
203
91
  import { Union, c_int, c_float } from 'node-ctypes';
204
92
 
205
- // Union - all fields share the same memory
206
93
  class IntOrFloat extends Union {
207
- static _fields_ = [
208
- ["i", c_int],
209
- ["f", c_float]
210
- ];
94
+ static _fields_ = [["i", c_int], ["f", c_float]];
211
95
  }
212
96
 
213
97
  const u = new IntOrFloat();
214
98
  u.f = 3.14159;
215
99
  console.log(u.i); // Bit pattern of float as integer
216
-
217
- u.i = 42;
218
- console.log(u.f); // 42 reinterpreted as float
219
- ```
220
-
221
- ### Bit Fields - Compact Data Structures
222
-
223
- ```javascript
224
- import { Structure, c_uint32 } from 'node-ctypes';
225
-
226
- // Bit fields using Python-style syntax: [name, type, bits]
227
- class Flags extends Structure {
228
- static _fields_ = [
229
- ["enabled", c_uint32, 1], // 1 bit
230
- ["mode", c_uint32, 3], // 3 bits
231
- ["priority", c_uint32, 4], // 4 bits
232
- ["reserved", c_uint32, 24] // 24 bits
233
- ];
234
- }
235
-
236
- const flags = new Flags();
237
-
238
- flags.enabled = 1;
239
- flags.mode = 5;
240
- flags.priority = 12;
241
-
242
- console.log(flags.enabled); // 1
243
- console.log(flags.mode); // 5
244
- console.log(flags.priority); // 12
245
100
  ```
246
101
 
247
- **Alternative syntax with `bitfield()` helper:**
102
+ ### Arrays
248
103
 
249
104
  ```javascript
250
- import { Structure, bitfield, c_uint32 } from 'node-ctypes';
105
+ import { array, c_int32 } from 'node-ctypes';
251
106
 
252
- class Flags extends Structure {
253
- static _fields_ = [
254
- ["enabled", bitfield(c_uint32, 1)],
255
- ["mode", bitfield(c_uint32, 3)],
256
- ];
257
- }
258
- ```
259
-
260
- ### Arrays - Fixed-size and Dynamic
261
-
262
- ```javascript
263
- import { c_int32, c_uint8, array } from 'node-ctypes';
264
-
265
- // Fixed-size array
266
107
  const IntArray = array(c_int32, 5);
267
108
  const arr = IntArray.create([1, 2, 3, 4, 5]);
268
-
269
- // Array access
270
109
  console.log(arr[0]); // 1
271
- console.log(arr[4]); // 5
272
-
273
- // Iterate
274
- for (const val of arr) {
275
- console.log(val);
276
- }
277
-
278
- // Arrays in structs
279
- import { Structure, array, c_uint8 } from 'node-ctypes';
280
-
281
- class Packet extends Structure {
282
- static _fields_ = [
283
- ["header", array(c_uint8, 8)],
284
- ["data", array(c_uint8, 256)]
285
- ];
286
- }
287
-
288
- const pkt = new Packet({
289
- header: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
290
- data: new Array(256).fill(0)
291
- });
292
-
293
- console.log(pkt.header.toString()); // [1, 2, 3, 4, 5, 6, 7, 8]
294
110
  ```
295
111
 
296
- ### Complex Nested Structures
297
-
298
- Real-world example from our test suite:
112
+ ### Pointers
299
113
 
300
114
  ```javascript
301
- import { Structure, Union, c_uint8, c_uint16, c_int32, array } from 'node-ctypes';
115
+ import { POINTER, pointer, c_int32 } from 'node-ctypes';
302
116
 
303
- // RGB color components
304
- class RGB extends Structure {
305
- static _fields_ = [
306
- ["r", c_uint8],
307
- ["g", c_uint8],
308
- ["b", c_uint8],
309
- ["a", c_uint8]
310
- ];
311
- }
117
+ const IntPtr = POINTER(c_int32);
118
+ const buf = Buffer.alloc(4);
119
+ buf.writeInt32LE(42, 0);
312
120
 
313
- // Union for color access (as RGB or as 32-bit value)
314
- class Color extends Union {
315
- static _fields_ = [
316
- ["rgb", RGB],
317
- ["value", c_int32]
318
- ];
319
- }
121
+ const p = IntPtr.fromBuffer(buf);
122
+ console.log(p.contents); // 42 (like *p in C)
123
+ console.log(p[0]); // 42 (like p[0] in C)
320
124
 
321
- // Pixel with position and color
322
- class Pixel extends Structure {
323
- static _fields_ = [
324
- ["x", c_uint16],
325
- ["y", c_uint16],
326
- ["color", Color]
327
- ];
328
- }
125
+ // pointer() function
126
+ const x = new c_int32(42);
127
+ const px = pointer(x);
128
+ console.log(px.contents); // 42
329
129
 
330
- // Image with array of pixels
331
- class Image extends Structure {
332
- static _fields_ = [
333
- ["width", c_uint16],
334
- ["height", c_uint16],
335
- ["pixels", array(Pixel, 2)]
336
- ];
337
- }
130
+ // fromAddress() typed access to native memory
131
+ const pValues = POINTER(MyStruct).fromAddress(nativeAddr);
132
+ console.log(pValues[0].field); // pointer arithmetic (like pValues[0] in C)
133
+ console.log(pValues[5].field); // pValues + 5 * sizeof(MyStruct)
338
134
 
339
- // Create and manipulate
340
- const img = new Image({
341
- width: 640,
342
- height: 480,
343
- pixels: [
344
- { x: 10, y: 20, color: { rgb: { r: 255, g: 0, b: 0, a: 255 } } },
345
- { x: 30, y: 40, color: { value: 0xFF00FF00 } }
346
- ]
347
- });
348
-
349
- console.log(img.pixels[0].color.rgb.r); // 255
350
- console.log(img.pixels[1].color.value); // -16711936 (0xFF00FF00 as signed)
351
-
352
- // Union nested in struct - direct property access!
353
- img.pixels[0].color.rgb.g = 128; // Works correctly!
354
- console.log(img.pixels[0].color.rgb.g); // 128
135
+ // cast() to POINTER — Python: cast(c_void_p(addr), POINTER(MyStruct))
136
+ const pData = cast(rawAddr, POINTER(MyStruct));
137
+ console.log(pData[0].field); // same result as fromAddress()
355
138
  ```
356
139
 
357
- ### Callbacks - JavaScript Functions in C
140
+ ### Callbacks
358
141
 
359
142
  ```javascript
360
- import { CDLL, callback, c_int32, c_void, c_void_p, c_size_t, readValue, writeValue, create_string_buffer } from 'node-ctypes';
143
+ import { callback, c_int32, c_void_p, readValue } from 'node-ctypes';
361
144
 
362
- const libc = new CDLL('msvcrt.dll'); // or libc.so.6 on Linux
363
-
364
- // Create a comparison callback for qsort
365
145
  const compare = callback(
366
- (a, b) => {
367
- // a and b are pointers to int32 values
368
- const aVal = readValue(a, c_int32);
369
- const bVal = readValue(b, c_int32);
370
- return aVal - bVal;
371
- },
372
- c_int32, // return type
373
- [c_void_p, c_void_p] // argument types: two pointers
146
+ (a, b) => readValue(a, c_int32) - readValue(b, c_int32),
147
+ c_int32, [c_void_p, c_void_p]
374
148
  );
375
149
 
376
- // Sort an array using qsort
377
- const qsort = libc.func('qsort', c_void, [
378
- c_void_p, // array pointer
379
- c_size_t, // number of elements
380
- c_size_t, // element size
381
- c_void_p // comparison function
382
- ]);
383
-
384
- const arr = create_string_buffer(5 * 4);
385
- const values = [5, 2, 8, 1, 9];
386
- values.forEach((v, i) => writeValue(arr, c_int32, v, i * 4));
387
- qsort(arr, 5, 4, compare.pointer);
388
-
389
- // Array is now sorted: [1, 2, 5, 8, 9]
390
- console.log(readValue(arr, c_int32, 0)); // 1
391
- console.log(readValue(arr, c_int32, 4)); // 2
392
-
393
- // IMPORTANT: Release callback when done
394
- compare.release();
150
+ // Use with qsort, etc.
151
+ compare.release(); // Release when done
395
152
  ```
396
153
 
397
- ### Variadic Functions - printf, sprintf
154
+ `CFUNCTYPE(restype, ...argtypes)` is also supported for Python-compatible function pointer types.
398
155
 
399
- ```javascript
400
- import { CDLL, create_string_buffer, string_at, c_int, c_void_p, c_char_p } from 'node-ctypes';
156
+ ### Variadic functions
401
157
 
402
- const libc = new CDLL('msvcrt.dll'); // Windows
403
- // const libc = new CDLL('libc.so.6'); // Linux
158
+ ```javascript
159
+ import { CDLL, c_int, c_void_p, c_char_p, string_at } from 'node-ctypes';
404
160
 
405
- // Define only the fixed parameters - variadic args detected automatically!
161
+ const libc = new CDLL('libc.so.6'); // Linux
162
+ // const libc = new CDLL('msvcrt.dll'); // Windows
163
+ // const libc = new CDLL('libc.dylib'); // macOS
406
164
  const sprintf = libc.func('sprintf', c_int, [c_void_p, c_char_p]);
407
165
 
408
- const buffer = Buffer.alloc(256);
409
-
410
- // Pass extra arguments - automatically handled as variadic
411
- sprintf(buffer, 'Hello %s!', 'World');
412
- console.log(string_at(buffer)); // "Hello World!"
413
-
414
- sprintf(buffer, 'Number: %d', 42);
415
- console.log(string_at(buffer)); // "Number: 42"
416
-
417
- sprintf(buffer, '%s: %d + %d = %d', 'Sum', 10, 20, 30);
418
- console.log(string_at(buffer)); // "Sum: 10 + 20 = 30"
419
-
420
- sprintf(buffer, 'Pi ≈ %.2f', 3.14159);
421
- console.log(string_at(buffer)); // "Pi ≈ 3.14"
166
+ const buf = Buffer.alloc(256);
167
+ sprintf(buf, 'Hello %s! %d', 'World', 42); // Extra args auto-detected
168
+ console.log(string_at(buf)); // "Hello World! 42"
422
169
  ```
423
170
 
424
- **Automatic variadic detection** - When you pass more arguments than specified, node-ctypes:
425
- - ✅ Detects the extra arguments
426
- - ✅ Infers their types (string → char*, number → int32/double, Buffer → pointer)
427
- - ✅ Uses `ffi_prep_cif_var` for variadic call preparation
428
- - ✅ Calls the function with correct argument marshalling
429
-
430
- **This matches Python ctypes behavior exactly!**
431
-
432
- ### Windows API - Full Support
171
+ ### Windows API
433
172
 
434
173
  ```javascript
435
- import { WinDLL, Structure, c_uint16, c_uint32, c_void_p, c_wchar_p, c_int } from 'node-ctypes';
174
+ import { WinDLL, Structure, c_uint16, c_void, c_void_p } from 'node-ctypes';
436
175
 
437
- // WinDLL uses __stdcall convention (default for Windows API)
438
- const kernel32 = new WinDLL('kernel32.dll');
176
+ const kernel32 = new WinDLL('kernel32.dll'); // Uses __stdcall
439
177
 
440
- // SYSTEMTIME structure (from tests/windows/test_winapi.js)
441
178
  class SYSTEMTIME extends Structure {
442
179
  static _fields_ = [
443
- ["wYear", c_uint16],
444
- ["wMonth", c_uint16],
445
- ["wDayOfWeek", c_uint16],
446
- ["wDay", c_uint16],
447
- ["wHour", c_uint16],
448
- ["wMinute", c_uint16],
449
- ["wSecond", c_uint16],
450
- ["wMilliseconds", c_uint16]
180
+ ["wYear", c_uint16], ["wMonth", c_uint16],
181
+ ["wDayOfWeek", c_uint16], ["wDay", c_uint16],
182
+ ["wHour", c_uint16], ["wMinute", c_uint16],
183
+ ["wSecond", c_uint16], ["wMilliseconds", c_uint16]
451
184
  ];
452
185
  }
453
186
 
454
- // Get local time
455
187
  const GetLocalTime = kernel32.func('GetLocalTime', c_void, [c_void_p]);
456
-
457
188
  const st = new SYSTEMTIME();
458
- GetLocalTime(st); // Pass struct directly - automatic _buffer extraction!
459
-
189
+ GetLocalTime(st);
460
190
  console.log(`${st.wYear}-${st.wMonth}-${st.wDay}`);
461
- console.log(`${st.wHour}:${st.wMinute}:${st.wSecond}`);
462
-
463
- // MessageBox (wide string version)
464
- const user32 = new WinDLL('user32.dll');
465
- const MessageBoxW = user32.func('MessageBoxW', c_int, [
466
- c_void_p, // hWnd
467
- c_wchar_p, // lpText
468
- c_wchar_p, // lpCaption
469
- c_uint32 // uType
470
- ]);
471
-
472
- // Create UTF-16 buffers for wide strings
473
- const text = Buffer.from('Hello from node-ctypes!\0', 'utf16le');
474
- const caption = Buffer.from('node-ctypes\0', 'utf16le');
475
-
476
- MessageBoxW(null, text, caption, 0);
477
191
  ```
478
192
 
479
- ### Memory Operations - Low-level Control
480
-
481
- ```javascript
482
- import { readValue, writeValue, sizeof, create_string_buffer, string_at, c_int, c_double, c_void_p } from 'node-ctypes';
483
-
484
- // Allocate memory
485
- const buf = Buffer.alloc(16);
486
-
487
- // Write values at specific offsets
488
- writeValue(buf, c_int, 12345, 0);
489
- writeValue(buf, c_double, 3.14159, 8);
490
-
491
- // Read values back
492
- console.log(readValue(buf, c_int, 0)); // 12345
493
- console.log(readValue(buf, c_double, 8)); // 3.14159
494
-
495
- // Get type sizes
496
- console.log(sizeof(c_int)); // 4
497
- console.log(sizeof(c_double)); // 8
498
- console.log(sizeof(c_void_p)); // 8 (on 64-bit)
499
-
500
- // String handling
501
- const str = create_string_buffer('Hello, World!');
502
- console.log(string_at(str)); // "Hello, World!"
503
- ```
504
-
505
- ## Performance Benchmarks
506
-
507
- Benchmarked on Windows with Node.js v24.11.0:
193
+ ## API Compatibility Reference
508
194
 
509
- **vs koffi** (comprehensive 10-benchmark comparison, geometric mean: **3.27x slower**):
510
- - Simple int32 function: 1.74x slower
511
- - String parameter: 1.95x slower
512
- - Floating point: 1.83x slower
513
- - No arguments: 2.11x slower
514
- - Multiple arguments: **1.40x faster**
515
- - Variadic function: 1.28x slower
516
- - **Struct read/write: 14.91x slower**
517
- - Buffer allocation: 40.5% overhead
518
- - Raw vs CDLL wrapper: 7.3% overhead
519
- - **Callback creation: 1.51x slower**
520
-
521
- **Key Insights:**
522
- - koffi excels at simple operations and struct access
523
- - node-ctypes competitive on complex argument handling
524
- - **Struct performance gap**: koffi ~13x faster due to plain object vs Proxy+N-API
525
- - **Workaround**: Use `toObject()` for repeated struct reads (see below)
526
- - **Callback overhead**: koffi 1.5x faster at callback creation
527
-
528
- ### Struct Performance Tip
529
-
530
- When reading struct fields repeatedly (e.g., in a tight loop), use `toObject()` to convert to a plain JavaScript object:
531
-
532
- ```javascript
533
- const point = Point.create({ x: 10, y: 20 });
534
-
535
- // ❌ Slow: Each access goes through Proxy → N-API → buffer read
536
- for (let i = 0; i < 1000000; i++) {
537
- const x = point.x; // ~80ns per access
538
- }
539
-
540
- // ✅ Fast: Convert once, then use plain object access
541
- const obj = point.toObject(); // { x: 10, y: 20 }
542
- for (let i = 0; i < 1000000; i++) {
543
- const x = obj.x; // ~6ns per access (same as koffi!)
544
- }
545
- ```
546
-
547
- **When to use direct access vs toObject():**
548
- - **Direct access (`point.x`)**: Always synchronized with underlying buffer; required when C code modifies the buffer
549
- - **`toObject()`**: Snapshot copy; use for read-only loops or when passing data to JS-only code
550
-
551
- **Transparent API overhead**: Only **3.5%** for auto `._buffer` extraction!
552
-
553
- *See `tests/benchmarks/` for full benchmark suite.*
195
+ | Feature | Python ctypes | node-ctypes |
196
+ |---------|---------------|-------------|
197
+ | Load library | `CDLL("lib.so")` | `new CDLL("lib.so")` |
198
+ | Function setup | `f.argtypes = [c_int]` | `f.argtypes = [c_int]` |
199
+ | Structs | `class P(Structure):` | `class P extends Structure` |
200
+ | Unions | `class U(Union):` | `class U extends Union` |
201
+ | Arrays | `c_int * 5` | `array(c_int, 5)` |
202
+ | Bit fields | `("f", c_uint, 3)` | `["f", c_uint32, 3]` |
203
+ | Callbacks | `CFUNCTYPE(c_int, c_int)` | `CFUNCTYPE(c_int, c_int)` |
204
+ | Pointers | `POINTER(c_int)` / `pointer(obj)` | `POINTER(c_int)` / `pointer(obj)` |
205
+ | Sizeof | `sizeof(c_int)` | `sizeof(c_int)` |
206
+ | Alignment | `alignment(c_int)` | `alignment(c_int)` |
207
+ | Strings | `c_char_p(b"hello")` | `create_string_buffer("hello")` |
208
+ | Variadic | `sprintf(buf, b"%d", 42)` | `sprintf(buf, "%d", 42)` |
209
+ | Errno | `get_errno()` | `get_errno()` |
210
+ | byref | `byref(obj)` | `byref(obj)` |
211
+ | cast | `cast(ptr, type)` | `cast(ptr, type)` (supports `POINTER()` target) |
554
212
 
555
213
  ## Supported Types
556
214
 
557
- | Type Name | Aliases | Size |
558
- |-----------|---------|------|
559
- | `void` | - | 0 |
560
- | `int8` | `c_int8`, `char` | 1 |
561
- | `uint8` | `c_uint8`, `uchar` | 1 |
562
- | `int16` | `c_int16`, `short` | 2 |
563
- | `uint16` | `c_uint16`, `ushort` | 2 |
564
- | `int32` | `c_int32`, `int`, `c_int` | 4 |
565
- | `uint32` | `c_uint32`, `uint`, `c_uint` | 4 |
566
- | `int64` | `c_int64`, `long long` | 8 |
567
- | `uint64` | `c_uint64` | 8 |
568
- | `float` | `c_float` | 4 |
569
- | `double` | `c_double` | 8 |
570
- | `pointer` | `c_void_p`, `void*`, `ptr` | 8 (64-bit) |
571
- | `string` | `c_char_p`, `char*` | pointer |
572
- | `bool` | `c_bool` | 1 |
573
- | `size_t` | `c_size_t` | pointer |
574
-
575
- ## API Reference
576
-
577
- ### Classes
578
-
579
- #### `CDLL(libPath)`
580
- Load a shared library using default (cdecl) calling convention.
581
-
582
- #### `WinDLL(libPath)`
583
- Load a shared library using stdcall calling convention (Windows).
584
-
585
- #### `Library(libPath)`
586
- Low-level library wrapper.
587
-
588
- ---
589
-
590
- ## Detailed API Reference (from lib/index.js)
591
-
592
- This section provides a more complete description of the APIs exported from `lib/index.js`, with quick examples and usage notes.
593
-
594
- **Native classes and exports**
595
- - `Version` - Version information exposed by the native module.
596
- - `Library` - Represents a loaded native library and exposes low-level functions for symbols and library management.
597
- - `FFIFunction` - Low-level object representing an FFI function (has properties like `address` and internal methods).
598
- - `Callback` - Builds JS callbacks callable from C (main thread).
599
- - `ThreadSafeCallback` - Builds thread-safe JS callbacks (can be called from external threads).
600
- - `CType`, `StructType`, `ArrayType` - Types and helpers exposed by the native layer.
601
-
602
- **Library loading and wrappers**
603
- - `load(libPath)` → `Library` : loads a native library; `libPath` can be `null` for the current executable.
604
- - `CDLL(libPath)` : common-use wrapper for C calls with cdecl convention; maintains a function cache and provides more convenient `func()`.
605
- - `WinDLL(libPath)` : like `CDLL` but with `abi: 'stdcall'` by default (useful for WinAPI).
606
-
607
- Example:
608
- ```js
609
- import { CDLL, c_int32 } from './lib/index.js';
610
- const libc = new CDLL(null);
611
-
612
- // Traditional syntax
613
- const abs = libc.func('abs', c_int32, [c_int32]);
614
- console.log(abs(-5));
615
-
616
- // Python ctypes-like syntax
617
- const abs_func = libc.abs;
618
- abs_func.argtypes = [c_int32];
619
- abs_func.restype = c_int32;
620
- console.log(abs_func(-5));
621
- ```
622
-
623
- **Detailed CDLL API**
624
- - `func(name, returnType, argTypes = [], options = {})` → `Function` : gets a callable function. The returned function is optimized and:
625
- - automatically extracts `._buffer` from struct objects passed as arguments;
626
- - exposes non-enumerable metadata: `funcName`, `address`, `_ffi`;
627
- - provides the `errcheck` property as getter/setter to intercept return errors.
628
- - **Python ctypes-like access**: `libc.functionName` returns a wrapper with `argtypes`/`restype`/`errcheck` properties for Python-compatible syntax.
629
- - `symbol(name)` → `BigInt` : address of a symbol.
630
- - `close()` : closes the library and clears the cache.
631
- - `path` (getter) : library path.
632
- - `loaded` (getter) : loading status.
633
-
634
- **Callback**
635
- - `callback(fn, returnType, argTypes = [])` → `{ pointer, release(), _callback }` : fast callback, main thread only.
636
- - `threadSafeCallback(fn, returnType, argTypes = [])` → `{ pointer, release(), _callback }` : thread-safe callback for external threads.
637
-
638
- Note: always call `release()` when a callback is no longer needed.
639
-
640
- **Allocation and strings**
641
-
642
- - `Buffer.alloc(size)` → `Buffer` : allocates native memory.
643
- - `create_string_buffer(init)` → `Buffer` : creates null-terminated C string (init: size|string|Buffer).
644
- - `create_unicode_buffer(init)` → `Buffer` : creates null-terminated wide string (wchar_t).
645
- - `ptrToBuffer(address, size)` → `Buffer` : view on native address (use with caution).
646
- - `addressof(ptr)` → `BigInt` : get the address as BigInt.
647
-
648
- Example string creation and passing to function:
649
- ```js
650
- import { create_string_buffer, CDLL, c_int32, c_void_p } from './lib/index.js';
651
- const libc = new CDLL(null);
652
- const puts = libc.func('puts', c_int32, [c_void_p]);
653
- const s = create_string_buffer('hello');
654
- puts(s);
655
- ```
656
-
657
- **Reading and writing values**
658
- - `readValue(ptr, type, offset = 0)` : supports fast-path for Buffer + basic types (`int8`, `uint8`, `int16`, `int32`, `int64`, `float`, `double`, `bool`).
659
- - `writeValue(ptr, type, value, offset = 0)` : writes values with fast-path for Buffer.
660
-
661
- **Types and helpers**
662
- - `sizeof(type)` → `number` : size in bytes of a type.
663
- - `POINTER(baseType)` : creates a pointer type factory (see [POINTER and pointer() API](#pointer-and-pointer-api) below).
664
- - `pointer(obj)` : creates a pointer to an existing ctypes instance (Python-compatible).
665
- - `byref(buffer)` : passes a buffer by reference (Python ctypes compatibility).
666
- - `cast(ptr, targetType)` : interprets a pointer as another type (returns wrapper for struct).
667
-
668
- **Struct / Union / Array / Bitfield**
669
- - `struct(fields, options)` : defines struct with support for nested, bitfields, anonymous fields, packed option. Returns object with `create()`, `get()`, `set()`, `toObject()`, `getNestedBuffer()`.
670
- - `union(fields)` : defines union; provides `create()`, `get()`, `set()`, `toObject()` and returns plain objects with properties.
671
- - `array(elementType, count)` : defines ArrayType; `wrap(buffer)` returns Proxy with indexing.
672
- - `bitfield(baseType, bits)` : bitfield definition.
673
-
674
- Struct example:
675
- ```js
676
- class Point extends Structure {
677
- static _fields_ = [
678
- ["x", c_int32],
679
- ["y", c_int32]
680
- ];
681
- }
682
-
683
- const p = new Point(1, 2);
684
- console.log(p.x, p.y); // 1 2
685
- ```
686
-
687
- **Python-compatible conveniences**
688
- - `create_string_buffer(init)` : create string buffer from number/string/Buffer.
689
- - `create_unicode_buffer(init)` : create wide string buffer.
690
- - `string_at(address, size)` / `wstring_at(address, size)` : read strings from address.
691
-
692
- **Memory: utilities**
693
- - `memmove(dst, src, count)` : copy memory.
694
- - `memset(dst, value, count)` : set memory.
695
-
696
- **Error handling and WinAPI helpers**
697
- - `get_errno()` / `set_errno(value)` : access to errno (platform-specific implementation).
698
- - `_initWinError()` internals; public helpers: `GetLastError()`, `SetLastError(code)`, `FormatError(code)`, `WinError(code)`.
699
-
700
- **Type aliases (Python-compatible)**
701
- The following type classes are exported (identical to Python ctypes):
702
- `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`.
703
-
704
- **Note**: Only type classes (e.g., `c_int32`) are supported, not string literals (e.g., `"int32"`). This matches Python ctypes behavior exactly.
705
-
706
- **Constants**
707
- - `POINTER_SIZE` - pointer size (from `native.POINTER_SIZE`).
708
- - `WCHAR_SIZE` - wchar size (from `native.WCHAR_SIZE`).
709
- - `NULL` - exported null value.
710
-
711
- ---
712
-
713
- If you want, I can generate additional Windows or Linux-specific snippets, or integrate examples in the `tests/` directory.
714
-
715
- ### Functions
716
-
717
- #### `load(libPath)` → `Library`
718
- Load a shared library.
719
-
720
- #### `callback(fn, returnType, argTypes)` → `{pointer, release()}`
721
- Create a callback from a JavaScript function.
722
-
723
- #### `create_string_buffer(init)` → `Buffer`
724
- Create a null-terminated C string buffer (like Python ctypes). `init` can be a size, a string, or an existing `Buffer`.
725
-
726
- #### `create_unicode_buffer(init)` → `Buffer`
727
- Create a wide (wchar_t) null-terminated buffer (UTF-16LE on Windows).
728
-
729
- #### `string_at(address, [size])` → `string`
730
- Read a C string from an address or buffer.
731
-
732
- #### `readValue(ptr, type, [offset])` → `value`
733
- Read a value from memory.
734
-
735
- #### `writeValue(ptr, type, value, [offset])` → `bytesWritten`
736
- Write a value to memory.
737
-
738
- #### `sizeof(type)` → `number`
739
- Get the size of a type in bytes.
740
-
741
- #### POINTER and pointer() API
742
-
743
- `node-ctypes` provides a Python ctypes-compatible pointer API:
744
-
745
- ```javascript
746
- import { POINTER, pointer, c_int32, Structure } from 'node-ctypes';
747
-
748
- // Create a pointer type
749
- const IntPtr = POINTER(c_int32);
750
-
751
- // Create NULL pointer
752
- const p1 = IntPtr.create();
753
- console.log(p1.isNull); // true
754
-
755
- // Create pointer from buffer
756
- const buf = Buffer.alloc(12);
757
- buf.writeInt32LE(10, 0);
758
- buf.writeInt32LE(20, 4);
759
- buf.writeInt32LE(30, 8);
215
+ | Type | Aliases | Size |
216
+ |------|---------|------|
217
+ | `c_int8` | `c_char` | 1 |
218
+ | `c_uint8` | `c_uchar` | 1 |
219
+ | `c_int16` | `c_short` | 2 |
220
+ | `c_uint16` | `c_ushort` | 2 |
221
+ | `c_int32` | `c_int` | 4 |
222
+ | `c_uint32` | `c_uint` | 4 |
223
+ | `c_int64` | `c_long` (64-bit) | 8 |
224
+ | `c_uint64` | `c_ulong` (64-bit) | 8 |
225
+ | `c_float` | | 4 |
226
+ | `c_double` | | 8 |
227
+ | `c_void_p` | pointer | 8 (64-bit) |
228
+ | `c_char_p` | string | pointer |
229
+ | `c_wchar_p` | wide string | pointer |
230
+ | `c_bool` | | 1 |
231
+ | `c_size_t` | | platform |
232
+ | `c_ssize_t` | | platform |
233
+
234
+ ## Key Differences from Python ctypes
235
+
236
+ - Callbacks must be manually released with `.release()` to prevent memory leaks
237
+ - No automatic memory management for returned pointers
238
+ - Both `class extends Structure` (Python-like) and `struct({...})` (functional) syntaxes available
239
+ - Only type classes (`c_int32`) are accepted, not string literals (`"int32"`) — same as Python
240
+
241
+ ## Utility Functions
242
+
243
+ - `create_string_buffer(init)` / `create_unicode_buffer(init)` — create C string buffers
244
+ - `string_at(address, size)` / `wstring_at(address, size)` — read strings from memory
245
+ - `readValue(ptr, type, offset)` / `writeValue(ptr, type, value, offset)` — direct memory access
246
+ - `sizeof(type)` — type size in bytes
247
+ - `alignment(type)` — type alignment in bytes
248
+ - `addressof(ptr)` get address as BigInt
249
+ - `memmove(dst, src, count)` / `memset(dst, value, count)` — memory operations
250
+ - `GetLastError()` / `FormatError(code)` Windows error helpers
760
251
 
761
- const p2 = IntPtr.fromBuffer(buf);
762
-
763
- // .contents property (Python-compatible)
764
- console.log(p2.contents); // 10
765
- p2.contents = 100;
766
- console.log(buf.readInt32LE(0)); // 100
767
-
768
- // Pointer indexing (Python-compatible)
769
- console.log(p2[0]); // 100
770
- console.log(p2[1]); // 20
771
- console.log(p2[2]); // 30
772
-
773
- p2[1] = 200;
774
- console.log(buf.readInt32LE(4)); // 200
775
-
776
- // pointer() function - create pointer to existing object
777
- const x = new c_int32(42);
778
- const px = pointer(x);
779
- console.log(px.contents); // 42
780
- px.contents = 100;
781
- console.log(x.value); // 100
782
-
783
- // Works with structures too
784
- class Point extends Structure {
785
- static _fields_ = [["x", c_int32], ["y", c_int32]];
786
- }
787
- const pt = new Point({ x: 10, y: 20 });
788
- const ppt = pointer(pt);
789
- ```
252
+ ## Examples
790
253
 
791
- **POINTER type methods:**
792
- - `POINTER(baseType)` - creates a pointer type factory
793
- - `PointerType.create()` - creates a NULL pointer instance
794
- - `PointerType.fromBuffer(buf)` - creates pointer to buffer
795
- - `PointerType.fromAddress(addr)` - creates pointer from address
796
-
797
- **Pointer instance properties:**
798
- - `.contents` - get/set dereferenced value (Python-compatible)
799
- - `.address` - get raw address as BigInt
800
- - `.isNull` - check if pointer is NULL
801
- - `[n]` - array-style indexing with pointer arithmetic
802
- - `.deref()` - alias for `.contents` getter
803
- - `.set(value)` - update pointer target
804
-
805
- **Using POINTER in function definitions:**
806
- ```javascript
807
- // POINTER types can be used as argtypes and restype
808
- const IntPtr = POINTER(c_int32);
254
+ - [Windows Controls Demo](examples/windows/demo_controls.js) — Win32 common controls showcase
255
+ - [Windows Registry Demo](examples/windows/demo_registry.js) setValue, getValue, openKey, deleteValue, deleteKey
256
+ - [Windows Tray Demo](examples/windows/demo_tray.js) System tray menu
257
+ - [Windows COM Automation Demo](examples/windows/demo_comtypes.js) IShellLinkW / IPersistFile COM interfaces
258
+ - [Windows LDAP Demo](examples/windows/demo_ldap.js) LDAP directory queries
259
+ - [Smart Card (PC/SC) Demo](examples/demo_scard.js) — WinSCard on Windows, pcsclite on macOS/Linux
809
260
 
810
- // As argument type
811
- const memset = msvcrt.func("memset", c_void_p, [IntPtr, c_int32, c_size_t]);
261
+ ## Tests
812
262
 
813
- // As return type
814
- const memchr = msvcrt.func("memchr", IntPtr, [c_void_p, c_int32, c_size_t]);
263
+ ```bash
264
+ cd tests
265
+ npm install
266
+ npm run test
815
267
  ```
816
268
 
817
- #### `struct(fields)` `StructDefinition`
818
- Create a simple struct definition.
269
+ See [tests/common/](tests/common/) for working examples including parallel Python implementations.
819
270
 
820
- #### `Structure` (base class)
821
- Base class for Python-like struct definitions. Subclasses should define `static _fields_`.
271
+ ## Documentation
822
272
 
823
- #### `Union` (base class)
824
- Base class for Python-like union definitions. Subclasses should define `static _fields_`.
273
+ Generate the API docs locally:
825
274
 
826
- ## Python ctypes Compatibility Reference
827
-
828
- ### API Comparison
829
-
830
- | Feature | Python ctypes | node-ctypes |
831
- |---------|---------------|-------------|
832
- | **Load library** | `CDLL("lib.so")` | `new CDLL("lib.so")` |
833
- | **Define function** | `lib.func.argtypes = [c_int]`<br>`lib.func.restype = c_int` | `lib.func("func", c_int, [c_int])`<br>**or**<br>`lib.func.argtypes = [c_int]`<br>`lib.func.restype = c_int` |
834
- | **Structs** | `class Point(Structure):`<br>&nbsp;&nbsp;`_fields_ = [("x", c_int)]` | `class Point extends Structure`<br>&nbsp;&nbsp;`{ static _fields_ = [["x", c_int]] }` |
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
- | **Arrays** | `c_int * 5` | `array(c_int, 5)` |
837
- | **Bit fields** | `("flags", c_uint, 3)` | `["flags", c_uint32, 3]`<br>**or** `bitfield(c_uint32, 3)` |
838
- | **Callbacks** | `CFUNCTYPE(c_int, c_int)` | `CFUNCTYPE(c_int, c_int)`<br>**or** ` callback(fn, c_int, [c_int])` |
839
- | **Strings** | `c_char_p(b"hello")` | `create_string_buffer("hello")`<br>**or**<br>`new c_char_p(b"hello")` |
840
- | **Pointers** | `POINTER(c_int)`<br>`p.contents`<br>`p[0]` | `POINTER(c_int)`<br>`p.contents`<br>`p[0]` |
841
- | **pointer()** | `pointer(obj)` | `pointer(obj)` |
842
- | **Variadic** | `sprintf(buf, b"%d", 42)` | `sprintf(buf, fmt, 42)` (auto) |
843
- | **Sizeof** | `sizeof(c_int)` | `sizeof(c_int)` |
844
-
845
- ### What's Supported
846
-
847
- ✅ **Fully Compatible**:
848
- - All basic types (int8-64, uint8-64, float, double, bool, pointer, string)
849
- - Structs with nested structures
850
- - Unions (including nested in structs)
851
- - Bit fields
852
- - Arrays (fixed-size)
853
- - Callbacks (with manual release)
854
- - Variadic functions (automatic detection)
855
- - Anonymous fields in structs/unions
856
- - **Class-based struct/union definitions** (`class MyStruct extends Structure`)
857
- - Platform-specific types (c_long, c_ulong, c_size_t)
858
- - Memory operations (alloc, read, write)
859
- - Windows API (__stdcall via WinDLL)
860
-
861
- ⚠️ **Differences from Python ctypes**:
862
- - Callbacks must be manually released with `.release()`
863
- - **Function definition supports both syntaxes**: `func(name, returnType, argTypes)` **or** `func.argtypes = [...]; func.restype = ...`
864
-
865
- ✨ **100% Python-compatible**:
866
- - **Struct property access**: Direct access (`p.x`, `p.y`) works with `class X extends Structure` - identical to Python!
867
- - **Type system**: Only type classes (`c_int32`, `c_char_p`) are accepted, exactly like Python ctypes
868
- - **No string literals**: `"int32"`, `"string"` are NOT supported (Python doesn't use them either)
869
-
870
- ## Limitations & Known Issues
871
-
872
- - ⚠️ Callbacks must be released manually with `.release()` to prevent memory leaks
873
- - ⚠️ No automatic memory management for returned pointers (manual `free()` required)
874
- - ℹ️ Struct property access:
875
- - **Python-style classes** (`class X extends Structure`): Direct property access (`p.x`, `p.y`) - fully compatible!
876
- - **Plain struct definitions** (`struct({...})`): Use `.get()` / `.set()` methods for property access
877
- - ℹ️ Struct alignment: Platform defaults are used, but `packed: true` option is available for packed structs
878
-
879
- ## Examples in Test Suite
880
-
881
- For complete, working examples, see the test suite:
882
-
883
- - **Basic types**: [tests/common/test_basic_types.js](tests/common/test_basic_types.js)
884
- - **Structs & unions**: [tests/common/test_structs.js](tests/common/test_structs.js)
885
- - **Nested structures**: [tests/common/test_nested_structs.js](tests/common/test_nested_structs.js)
886
- - **Arrays**: [tests/common/test_arrays.js](tests/common/test_arrays.js)
887
- - **Functions**: [tests/common/test_functions.js](tests/common/test_functions.js)
888
- - **Callbacks**: [tests/common/test_callbacks.js](tests/common/test_callbacks.js)
889
- - **Version info**: [tests/common/test_version.js](tests/common/test_version.js)
890
- - **Windows API**: [tests/windows/test_winapi.js](tests/windows/test_winapi.js)
891
- - **Python compatibility**: [tests/common/*.py](tests/common/) (parallel Python implementations)
892
-
893
- Run tests:
894
275
  ```bash
895
- cd tests
896
- npm install
897
- npm run test # All tests
898
- npm run bench:koffi # Benchmark vs koffi
276
+ npm run docs
899
277
  ```
900
278
 
901
- ## Examples
902
-
903
- For practical GUI application examples using the Windows API:
279
+ This produces a browsable site in `docs/` using [TypeDoc](https://typedoc.org/). To preview it:
904
280
 
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
281
+ ```bash
282
+ npm run docs:serve
283
+ ```
909
284
 
910
285
  ## License
911
286
 
@@ -913,7 +288,4 @@ MIT
913
288
 
914
289
  ## Credits
915
290
 
916
- Built with:
917
- - [libffi](https://github.com/libffi/libffi) - Foreign Function Interface library
918
- - [node-addon-api](https://github.com/nodejs/node-addon-api) - N-API C++ wrapper
919
- - [cmake-js](https://github.com/cmake-js/cmake-js) - CMake-based build system for Node.js addons
291
+ Built with [libffi](https://github.com/libffi/libffi), [node-addon-api](https://github.com/nodejs/node-addon-api), and [cmake-js](https://github.com/cmake-js/cmake-js).