node-ctypes 0.1.5 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of node-ctypes might be problematic. Click here for more details.

@@ -0,0 +1,414 @@
1
+ /**
2
+ * @file Structure.js
3
+ * @module structures/Structure
4
+ * @description Structure base class for Python ctypes-compatible structures.
5
+ *
6
+ * This module provides the `Structure` base class that users extend to create
7
+ * custom struct types with automatic field management, memory layout, and
8
+ * proxy-based field access.
9
+ *
10
+ * **Python ctypes Compatibility**:
11
+ * Direct equivalent to Python's `ctypes.Structure` class with `_fields_` attribute.
12
+ *
13
+ * @example Basic Structure subclass
14
+ * ```javascript
15
+ * import { Structure, c_int32, c_float } from 'node-ctypes';
16
+ *
17
+ * class Point extends Structure {
18
+ * static _fields_ = [
19
+ * ['x', c_int32],
20
+ * ['y', c_int32]
21
+ * ];
22
+ * }
23
+ *
24
+ * const pt = new Point({ x: 10, y: 20 });
25
+ * console.log(pt.x, pt.y); // 10 20
26
+ * pt.x = 42;
27
+ * console.log(pt.x); // 42
28
+ * ```
29
+ *
30
+ * @example Positional arguments
31
+ * ```javascript
32
+ * const pt = new Point(10, 20); // x=10, y=20
33
+ * ```
34
+ *
35
+ * @example Wrapping existing buffer
36
+ * ```javascript
37
+ * const buffer = Buffer.alloc(8);
38
+ * const pt = new Point(buffer);
39
+ * pt.x = 100;
40
+ * ```
41
+ *
42
+ * @example Array-style _fields_ (Python ctypes compatible)
43
+ * ```javascript
44
+ * class Rect extends Structure {
45
+ * static _fields_ = [
46
+ * ['x', c_int32],
47
+ * ['y', c_int32],
48
+ * ['width', c_int32],
49
+ * ['height', c_int32]
50
+ * ];
51
+ * }
52
+ * ```
53
+ *
54
+ * @example Object-style _fields_ (alternative syntax)
55
+ * ```javascript
56
+ * class Rect extends Structure {
57
+ * static _fields_ = {
58
+ * x: c_int32,
59
+ * y: c_int32,
60
+ * width: c_int32,
61
+ * height: c_int32
62
+ * };
63
+ * }
64
+ * ```
65
+ */
66
+
67
+ /**
68
+ * Creates the Structure base class with required dependencies injected.
69
+ *
70
+ * @param {Function} alloc - Memory allocation function
71
+ * @param {Function} struct - Struct definition function
72
+ * @returns {Class} Structure base class
73
+ * @private
74
+ */
75
+ export function createStructureClass(alloc, struct) {
76
+ /**
77
+ * Structure base class for creating C-style structures.
78
+ *
79
+ * Users extend this class and define static `_fields_` to specify the struct layout.
80
+ * The constructor handles memory allocation, field initialization, and returns a
81
+ * Proxy for transparent field access.
82
+ *
83
+ * **Key Features**:
84
+ * - Automatic memory layout based on `_fields_`
85
+ * - Proxy-based transparent field access
86
+ * - Support for positional arguments in constructor
87
+ * - Buffer wrapping for zero-copy operations
88
+ * - Anonymous field support via `_anonymous_`
89
+ * - Packed mode via `_pack_`
90
+ *
91
+ * **Python ctypes Compatibility**:
92
+ * Similar to Python's `ctypes.Structure` with:
93
+ * - `_fields_` attribute (array or object)
94
+ * - `_anonymous_` attribute for anonymous fields
95
+ * - `_pack_` attribute for packed structures
96
+ *
97
+ * @class Structure
98
+ *
99
+ * @example Extending Structure
100
+ * ```javascript
101
+ * class Vec3 extends Structure {
102
+ * static _fields_ = [
103
+ * ['x', c_float],
104
+ * ['y', c_float],
105
+ * ['z', c_float]
106
+ * ];
107
+ *
108
+ * // Add custom methods
109
+ * length() {
110
+ * return Math.sqrt(this.x**2 + this.y**2 + this.z**2);
111
+ * }
112
+ * }
113
+ *
114
+ * const v = new Vec3(1.0, 2.0, 3.0);
115
+ * console.log(v.length()); // 3.7416573867739413
116
+ * ```
117
+ *
118
+ * @example Anonymous fields
119
+ * ```javascript
120
+ * class Point extends Structure {
121
+ * static _fields_ = [['x', c_int32], ['y', c_int32]];
122
+ * }
123
+ *
124
+ * class Circle extends Structure {
125
+ * static _fields_ = [
126
+ * ['center', Point],
127
+ * ['radius', c_int32]
128
+ * ];
129
+ * static _anonymous_ = ['center'];
130
+ * }
131
+ *
132
+ * const c = new Circle();
133
+ * c.x = 10; // Access Point.x directly
134
+ * c.y = 20; // Access Point.y directly
135
+ * c.radius = 5;
136
+ * ```
137
+ */
138
+ class Structure {
139
+ constructor(...args) {
140
+ const Ctor = this.constructor;
141
+ const def = Ctor._structDef || Ctor._buildStruct();
142
+ // Allocate buffer for instance
143
+ let buf = alloc(def.size);
144
+ buf.fill(0);
145
+
146
+ // Support positional args: new Point(10,20)
147
+ if (args.length === 1 && typeof args[0] === "object" && !Array.isArray(args[0]) && !Buffer.isBuffer(args[0])) {
148
+ const initial = args[0];
149
+ for (const [k, v] of Object.entries(initial)) {
150
+ def.set(buf, k, v);
151
+ }
152
+ } else if (args.length === 1 && Buffer.isBuffer(args[0])) {
153
+ // new Point(buffer) - wrap existing buffer
154
+ buf = args[0];
155
+ // Validate buffer size matches struct size
156
+ if (buf.length < def.size) {
157
+ throw new RangeError(`Buffer size (${buf.length}) is smaller than struct size (${def.size})`);
158
+ }
159
+ } else if (args.length > 0) {
160
+ // Positional mapping to non-anonymous declared fields order
161
+ const ordered = def.fields.filter((f) => !f.isAnonymous);
162
+ for (let i = 0; i < Math.min(args.length, ordered.length); i++) {
163
+ def.set(buf, ordered[i].name, args[i]);
164
+ }
165
+ }
166
+
167
+ // Store internal references on this (needed for methods like toObject)
168
+ this.__buffer = buf;
169
+ this.__structDef = def;
170
+
171
+ // Build field map for O(1) lookup
172
+ const fieldMap = new Map();
173
+ const anonFieldNames = new Set();
174
+ for (const field of def.fields) {
175
+ fieldMap.set(field.name, field);
176
+ if (field.isAnonymous && field.type && Array.isArray(field.type.fields)) {
177
+ for (const subField of field.type.fields) {
178
+ anonFieldNames.add(subField.name);
179
+ }
180
+ }
181
+ }
182
+
183
+ // Return Proxy instead of using Object.defineProperty for each field
184
+ return new Proxy(this, {
185
+ get(target, prop, receiver) {
186
+ // Special properties that exist on the instance
187
+ if (prop === "_buffer") return buf;
188
+ if (prop === "_structDef") return def;
189
+ if (prop === "__buffer") return buf;
190
+ if (prop === "__structDef") return def;
191
+ if (prop === Symbol.toStringTag) return Ctor.name || "Structure";
192
+ if (prop === Symbol.iterator) return undefined;
193
+
194
+ // Methods defined on the prototype
195
+ if (typeof target[prop] === "function") {
196
+ return target[prop].bind(target);
197
+ }
198
+
199
+ // Check if it's a known field (O(1) lookup)
200
+ if (typeof prop === "string" && fieldMap.has(prop)) {
201
+ return def.get(buf, prop);
202
+ }
203
+
204
+ // Check anonymous fields
205
+ if (typeof prop === "string" && anonFieldNames.has(prop)) {
206
+ return def.get(buf, prop);
207
+ }
208
+
209
+ // Fallback to target properties
210
+ return Reflect.get(target, prop, receiver);
211
+ },
212
+ set(target, prop, value, receiver) {
213
+ // Check if it's a known field (O(1) lookup)
214
+ if (typeof prop === "string" && fieldMap.has(prop)) {
215
+ def.set(buf, prop, value);
216
+ return true;
217
+ }
218
+
219
+ // Check anonymous fields
220
+ if (typeof prop === "string" && anonFieldNames.has(prop)) {
221
+ def.set(buf, prop, value);
222
+ return true;
223
+ }
224
+
225
+ // Fallback
226
+ return Reflect.set(target, prop, value, receiver);
227
+ },
228
+ has(target, prop) {
229
+ if (prop === "_buffer" || prop === "_structDef" || prop === "toObject") return true;
230
+ if (typeof prop === "string" && fieldMap.has(prop)) return true;
231
+ if (typeof prop === "string" && anonFieldNames.has(prop)) return true;
232
+ return Reflect.has(target, prop);
233
+ },
234
+ ownKeys(target) {
235
+ const keys = [];
236
+ for (const f of def.fields) {
237
+ if (f.isAnonymous && f.type && Array.isArray(f.type.fields)) {
238
+ for (const sf of f.type.fields) {
239
+ keys.push(sf.name);
240
+ }
241
+ } else {
242
+ keys.push(f.name);
243
+ }
244
+ }
245
+ return keys;
246
+ },
247
+ getOwnPropertyDescriptor(target, prop) {
248
+ if (typeof prop === "string" && (fieldMap.has(prop) || anonFieldNames.has(prop))) {
249
+ return {
250
+ enumerable: true,
251
+ configurable: true,
252
+ writable: true,
253
+ };
254
+ }
255
+ if (prop === "_buffer" || prop === "_structDef") {
256
+ return {
257
+ enumerable: false,
258
+ configurable: true,
259
+ writable: true,
260
+ };
261
+ }
262
+ return Reflect.getOwnPropertyDescriptor(target, prop);
263
+ },
264
+ });
265
+ }
266
+
267
+ // Build the underlying struct definition and cache it on the constructor
268
+ static _buildStruct() {
269
+ // Accept either array _fields_ (Python style) or object map
270
+ const fields = this._fields_ || {};
271
+ let mapFields = {};
272
+
273
+ if (Array.isArray(fields)) {
274
+ for (const entry of fields) {
275
+ if (Array.isArray(entry) && entry.length >= 2) {
276
+ mapFields[entry[0]] = entry[1];
277
+ }
278
+ }
279
+ } else if (typeof fields === "object" && fields !== null) {
280
+ mapFields = { ...fields };
281
+ }
282
+
283
+ // Handle anonymous fields list: convert to { anonymous: true, type: T }
284
+ if (Array.isArray(this._anonymous_)) {
285
+ for (const anonName of this._anonymous_) {
286
+ if (mapFields[anonName] !== undefined) {
287
+ mapFields[anonName] = {
288
+ anonymous: true,
289
+ type: mapFields[anonName],
290
+ };
291
+ }
292
+ }
293
+ }
294
+
295
+ const options = {};
296
+ if (this._pack_ !== undefined) options.packed = !!this._pack_;
297
+
298
+ const def = struct(mapFields, options);
299
+ this._structDef = def;
300
+ return def;
301
+ }
302
+
303
+ // Create returns an instance of the JS class (not a plain object)
304
+ static create(values = {}) {
305
+ const def = this._structDef || this._buildStruct();
306
+ // If a Buffer provided, wrap it
307
+ if (Buffer.isBuffer(values)) {
308
+ const inst = new this(values);
309
+ return inst;
310
+ }
311
+ // If values is instance of this class, return it
312
+ if (values && values._buffer && values._structDef === def) {
313
+ return values;
314
+ }
315
+ return new this(values);
316
+ }
317
+
318
+ // Create raw plain object without synchronization (for performance)
319
+ static createRaw(values = {}) {
320
+ const def = this._structDef || this._buildStruct();
321
+ return def.toObject(def.create(values)._buffer);
322
+ }
323
+
324
+ // Synchronize plain object into instance buffer
325
+ syncFromObject(obj) {
326
+ const plain = this.__structDef.toObject(this.__buffer);
327
+ Object.assign(plain, obj);
328
+ this.__structDef.fromObject(this.__buffer, plain);
329
+ }
330
+
331
+ // toObject: accept Buffer, instance or plain buffer
332
+ static toObject(bufOrInst) {
333
+ const def = this._structDef || this._buildStruct();
334
+ if (bufOrInst && bufOrInst._buffer) {
335
+ return def.toObject(bufOrInst._buffer);
336
+ }
337
+ return def.toObject(bufOrInst);
338
+ }
339
+
340
+ // fromObject: write plain object into buffer
341
+ static fromObject(bufOrInst, obj) {
342
+ const def = this._structDef || this._buildStruct();
343
+ const buf = bufOrInst && bufOrInst._buffer ? bufOrInst._buffer : bufOrInst;
344
+ return def.fromObject(buf, obj);
345
+ }
346
+
347
+ // Instance convenience
348
+ get(fieldName) {
349
+ return this.__structDef.get(this.__buffer, fieldName);
350
+ }
351
+
352
+ set(fieldName, value) {
353
+ return this.__structDef.set(this.__buffer, fieldName, value);
354
+ }
355
+
356
+ // Bulk operations for better performance
357
+ setFields(fields) {
358
+ for (const [name, value] of Object.entries(fields)) {
359
+ this.__structDef.set(this.__buffer, name, value);
360
+ }
361
+ }
362
+
363
+ getFields(fieldNames) {
364
+ const result = {};
365
+ for (const name of fieldNames) {
366
+ result[name] = this.__structDef.get(this.__buffer, name);
367
+ }
368
+ return result;
369
+ }
370
+
371
+ // bulk operations (Proxy reads directly from buffer - no cache needed)
372
+ withBulkUpdate(callback) {
373
+ return callback(this);
374
+ }
375
+
376
+ // Direct typed array access for numeric fields (maximum performance)
377
+ getInt32Array(offset = 0, length) {
378
+ return new Int32Array(this.__buffer.buffer, this.__buffer.byteOffset + offset, length);
379
+ }
380
+
381
+ getFloat64Array(offset = 0, length) {
382
+ return new Float64Array(this.__buffer.buffer, this.__buffer.byteOffset + offset, length);
383
+ }
384
+
385
+ // Get raw buffer slice for external operations
386
+ getBufferSlice(offset = 0, size = this.__buffer.length - offset) {
387
+ return this.__buffer.subarray(offset, offset + size);
388
+ }
389
+
390
+ // Vectorized operations for arrays of structs
391
+ static createArray(count, initialValues = []) {
392
+ const instances = [];
393
+ for (let i = 0; i < count; i++) {
394
+ instances.push(this.create(initialValues[i] || {}));
395
+ }
396
+ return instances;
397
+ }
398
+
399
+ // Bulk update all instances in array
400
+ static updateArray(instances, updates) {
401
+ for (let i = 0; i < instances.length && i < updates.length; i++) {
402
+ if (updates[i] && typeof updates[i] === "object") {
403
+ instances[i].setFields(updates[i]);
404
+ }
405
+ }
406
+ }
407
+
408
+ toObject() {
409
+ return this.__structDef.toObject(this.__buffer);
410
+ }
411
+ }
412
+
413
+ return Structure;
414
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @file UnionClass.js
3
+ * @module structures/UnionClass
4
+ * @description Union base class for Python ctypes-compatible unions.
5
+ *
6
+ * This module provides the `Union` base class that users extend to create
7
+ * custom union types where all fields share the same memory location.
8
+ *
9
+ * **Python ctypes Compatibility**:
10
+ * Direct equivalent to Python's `ctypes.Union` class with `_fields_` attribute.
11
+ *
12
+ * @example Basic Union subclass
13
+ * ```javascript
14
+ * import { Union, c_int32, c_float } from 'node-ctypes';
15
+ *
16
+ * class Value extends Union {
17
+ * static _fields_ = [
18
+ * ['as_int', c_int32],
19
+ * ['as_float', c_float]
20
+ * ];
21
+ * }
22
+ *
23
+ * const v = new Value();
24
+ * v.as_int = 0x40490FDB; // IEEE 754 for ~3.14159
25
+ * console.log(v.as_float); // ~3.14159
26
+ * ```
27
+ *
28
+ * @example Type reinterpretation
29
+ * ```javascript
30
+ * class ByteWord extends Union {
31
+ * static _fields_ = [
32
+ * ['bytes', array(c_uint8, 4)],
33
+ * ['word', c_uint32]
34
+ * ];
35
+ * }
36
+ *
37
+ * const bw = new ByteWord();
38
+ * bw.word = 0x12345678;
39
+ * console.log([...bw.bytes]); // [0x78, 0x56, 0x34, 0x12] (little-endian)
40
+ * ```
41
+ */
42
+
43
+ /**
44
+ * Creates the Union base class with required dependencies injected.
45
+ *
46
+ * @param {Class} Structure - Structure base class to extend from
47
+ * @param {Function} union - Union definition function
48
+ * @returns {Class} Union base class
49
+ * @private
50
+ */
51
+ export function createUnionClass(Structure, union) {
52
+ /**
53
+ * Union base class for creating C-style unions.
54
+ *
55
+ * Extends `Structure` but uses union layout where all fields share the
56
+ * same memory location (offset 0). The union size is the maximum of all
57
+ * field sizes.
58
+ *
59
+ * **Python ctypes Compatibility**:
60
+ * Similar to Python's `ctypes.Union` with:
61
+ * - `_fields_` attribute (array or object)
62
+ * - All fields at same memory location
63
+ * - Size equals largest field size
64
+ *
65
+ * @class Union
66
+ * @extends Structure
67
+ *
68
+ * @example RGB color union
69
+ * ```javascript
70
+ * class Color extends Union {
71
+ * static _fields_ = [
72
+ * ['rgba', c_uint32],
73
+ * ['components', array(c_uint8, 4)]
74
+ * ];
75
+ * }
76
+ *
77
+ * const color = new Color();
78
+ * color.rgba = 0xFF0080FF; // RGBA: 255, 0, 128, 255
79
+ * console.log([...color.components]); // [255, 128, 0, 255] (little-endian)
80
+ * ```
81
+ */
82
+ class Union extends Structure {
83
+ static _buildStruct() {
84
+ const fields = this._fields_ || {};
85
+ let mapFields = {};
86
+ if (Array.isArray(fields)) {
87
+ for (const entry of fields) {
88
+ if (Array.isArray(entry) && entry.length >= 2) {
89
+ mapFields[entry[0]] = entry[1];
90
+ }
91
+ }
92
+ } else if (typeof fields === "object" && fields !== null) {
93
+ mapFields = fields;
94
+ }
95
+ const def = union(mapFields);
96
+ this._structDef = def;
97
+ return def;
98
+ }
99
+ }
100
+
101
+ return Union;
102
+ }