node-ctypes 0.1.7 → 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.
- package/README.md +13 -8
- package/build/Release/ctypes-darwin-arm64.node +0 -0
- package/build/Release/ctypes-darwin-x64.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/core/Library.js +312 -0
- package/lib/core/callback.js +275 -0
- package/lib/core/types.js +140 -0
- package/lib/index.d.ts +71 -124
- package/lib/index.js +215 -3096
- package/lib/memory/buffer.js +404 -0
- package/lib/memory/operations.js +295 -0
- package/lib/memory/pointer.js +358 -0
- package/lib/platform/constants.js +110 -0
- package/lib/platform/errors.js +403 -0
- package/lib/structures/Structure.js +414 -0
- package/lib/structures/Union.js +102 -0
- package/lib/structures/helpers/array.js +261 -0
- package/lib/structures/helpers/bitfield.js +147 -0
- package/lib/structures/helpers/common.js +129 -0
- package/lib/structures/helpers/struct.js +925 -0
- package/lib/structures/helpers/union.js +465 -0
- package/lib/types/SimpleCData.js +193 -0
- package/lib/types/primitives.js +392 -0
- package/lib/utils/cache.js +142 -0
- package/package.json +1 -1
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file union.js
|
|
3
|
+
* @module structures/union
|
|
4
|
+
* @description Union type definition implementation for C-style unions.
|
|
5
|
+
*
|
|
6
|
+
* This module provides Python ctypes-compatible union types where all fields
|
|
7
|
+
* share the same memory location (offset 0). The union size is the maximum
|
|
8
|
+
* size of all fields, with proper alignment.
|
|
9
|
+
*
|
|
10
|
+
* **Python ctypes Compatibility**:
|
|
11
|
+
* Similar to Python's `ctypes.Union` class with `_fields_` definition.
|
|
12
|
+
*
|
|
13
|
+
* @example Basic union
|
|
14
|
+
* ```javascript
|
|
15
|
+
* import { union, c_int32, c_float } from 'node-ctypes';
|
|
16
|
+
*
|
|
17
|
+
* const Value = union({
|
|
18
|
+
* asInt: c_int32,
|
|
19
|
+
* asFloat: c_float
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* const val = Value.create();
|
|
23
|
+
* val.asInt = 0x3F800000;
|
|
24
|
+
* console.log(val.asFloat); // 1.0 (same memory, different interpretation)
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example Union with nested types
|
|
28
|
+
* ```javascript
|
|
29
|
+
* const Data = union({
|
|
30
|
+
* bytes: array(c_uint8, 8),
|
|
31
|
+
* int64: c_int64,
|
|
32
|
+
* floats: array(c_float, 2)
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { _readBitField, _writeBitField } from "./bitfield.js";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a union type definition where all fields share offset 0.
|
|
41
|
+
*
|
|
42
|
+
* Returns a union definition object with methods to create instances,
|
|
43
|
+
* read/write fields, and convert to/from JavaScript objects. All fields
|
|
44
|
+
* in a union share the same memory location.
|
|
45
|
+
*
|
|
46
|
+
* **Python ctypes Compatibility**:
|
|
47
|
+
* Similar to Python's `Union` class with `_fields_` attribute.
|
|
48
|
+
*
|
|
49
|
+
* @param {Object} fields - Field definitions { name: type, ... }
|
|
50
|
+
* @param {Function} sizeof - sizeof function reference
|
|
51
|
+
* @param {Function} alloc - alloc function reference
|
|
52
|
+
* @param {Function} readValue - readValue function reference
|
|
53
|
+
* @param {Function} writeValue - writeValue function reference
|
|
54
|
+
* @param {Function} _isStruct - _isStruct function reference
|
|
55
|
+
* @param {Function} _isArrayType - _isArrayType function reference
|
|
56
|
+
* @param {Function} _isBitField - _isBitField function reference
|
|
57
|
+
* @param {Function} Structure - Structure class reference
|
|
58
|
+
* @param {Function} Union - Union class reference
|
|
59
|
+
* @param {Object} native - Native module reference
|
|
60
|
+
* @returns {Object} Union definition with create, get, set, toObject methods
|
|
61
|
+
* @throws {TypeError} If field types are invalid
|
|
62
|
+
*
|
|
63
|
+
* @example Create union with different interpretations
|
|
64
|
+
* ```javascript
|
|
65
|
+
* const Value = union({
|
|
66
|
+
* asInt: c_int32,
|
|
67
|
+
* asFloat: c_float,
|
|
68
|
+
* bytes: array(c_uint8, 4)
|
|
69
|
+
* });
|
|
70
|
+
*
|
|
71
|
+
* const val = Value.create({ asInt: 1065353216 });
|
|
72
|
+
* console.log(val.asFloat); // 1.0
|
|
73
|
+
* console.log([...val.bytes]); // [0, 0, 128, 63]
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example Union with structs
|
|
77
|
+
* ```javascript
|
|
78
|
+
* class Point extends Structure {
|
|
79
|
+
* static _fields_ = [['x', c_int32], ['y', c_int32]];
|
|
80
|
+
* }
|
|
81
|
+
*
|
|
82
|
+
* const Variant = union({
|
|
83
|
+
* point: Point,
|
|
84
|
+
* int64: c_int64
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function union(fields, sizeof, alloc, readValue, writeValue, _isStruct, _isArrayType, _isBitField, Structure, Union, native) {
|
|
89
|
+
let maxSize = 0;
|
|
90
|
+
let maxAlignment = 1;
|
|
91
|
+
const fieldDefs = [];
|
|
92
|
+
|
|
93
|
+
for (const [name, type] of Object.entries(fields)) {
|
|
94
|
+
let size, alignment;
|
|
95
|
+
let fieldDef = { name, type, offset: 0 };
|
|
96
|
+
|
|
97
|
+
// Nested struct (object form)
|
|
98
|
+
if (_isStruct(type)) {
|
|
99
|
+
size = type.size;
|
|
100
|
+
alignment = type.alignment;
|
|
101
|
+
fieldDef.isNested = true;
|
|
102
|
+
}
|
|
103
|
+
// Struct class (declarata come `class X extends Structure`)
|
|
104
|
+
else if (typeof type === "function" && type.prototype instanceof Structure) {
|
|
105
|
+
const nested = type._structDef || type._buildStruct();
|
|
106
|
+
size = nested.size;
|
|
107
|
+
alignment = nested.alignment;
|
|
108
|
+
fieldDef.type = nested;
|
|
109
|
+
fieldDef.isNested = true;
|
|
110
|
+
}
|
|
111
|
+
// Union class (declarata come `class X extends Union`)
|
|
112
|
+
else if (typeof type === "function" && type.prototype instanceof Union) {
|
|
113
|
+
const nested = type._unionDef || type._buildUnion();
|
|
114
|
+
size = nested.size;
|
|
115
|
+
alignment = nested.alignment;
|
|
116
|
+
fieldDef.type = nested;
|
|
117
|
+
fieldDef.isNested = true;
|
|
118
|
+
}
|
|
119
|
+
// Array type
|
|
120
|
+
else if (_isArrayType(type)) {
|
|
121
|
+
size = type.getSize();
|
|
122
|
+
const elemSize = sizeof(type.elementType);
|
|
123
|
+
alignment = Math.min(elemSize, native.POINTER_SIZE);
|
|
124
|
+
fieldDef.isArray = true;
|
|
125
|
+
}
|
|
126
|
+
// Bit field - in union ogni bitfield occupa l'intero baseType
|
|
127
|
+
else if (_isBitField(type)) {
|
|
128
|
+
size = type.baseSize;
|
|
129
|
+
alignment = Math.min(size, native.POINTER_SIZE);
|
|
130
|
+
fieldDef.isBitField = true;
|
|
131
|
+
fieldDef.bitOffset = 0;
|
|
132
|
+
fieldDef.bitSize = type.bits;
|
|
133
|
+
fieldDef.baseSize = type.baseSize;
|
|
134
|
+
fieldDef.type = type.baseType; // Usa il tipo base per lettura/scrittura
|
|
135
|
+
}
|
|
136
|
+
// Tipo base (SimpleCData)
|
|
137
|
+
else {
|
|
138
|
+
// Validate type is a SimpleCData class
|
|
139
|
+
if (!(typeof type === "function" && type._isSimpleCData)) {
|
|
140
|
+
throw new TypeError(`union field "${name}": type must be a SimpleCData class, struct, union, or array`);
|
|
141
|
+
}
|
|
142
|
+
size = type._size;
|
|
143
|
+
alignment = Math.min(size, native.POINTER_SIZE);
|
|
144
|
+
fieldDef.type = type;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
fieldDef.size = size;
|
|
148
|
+
fieldDef.alignment = alignment;
|
|
149
|
+
fieldDefs.push(fieldDef);
|
|
150
|
+
|
|
151
|
+
if (size > maxSize) {
|
|
152
|
+
maxSize = size;
|
|
153
|
+
}
|
|
154
|
+
if (alignment > maxAlignment) {
|
|
155
|
+
maxAlignment = alignment;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Padding finale per allineamento
|
|
160
|
+
if (maxSize % maxAlignment !== 0) {
|
|
161
|
+
maxSize += maxAlignment - (maxSize % maxAlignment);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Crea Map per lookup O(1)
|
|
165
|
+
const fieldMap = new Map();
|
|
166
|
+
for (const field of fieldDefs) {
|
|
167
|
+
fieldMap.set(field.name, field);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const unionDef = {
|
|
171
|
+
size: maxSize,
|
|
172
|
+
alignment: maxAlignment,
|
|
173
|
+
fields: fieldDefs,
|
|
174
|
+
isUnion: true,
|
|
175
|
+
_isStructType: true, // Per compatibilità con _isStruct()
|
|
176
|
+
|
|
177
|
+
create(values = {}) {
|
|
178
|
+
const buf = alloc(maxSize);
|
|
179
|
+
buf.fill(0);
|
|
180
|
+
|
|
181
|
+
// Prima imposta i campi diretti
|
|
182
|
+
for (const field of fieldDefs) {
|
|
183
|
+
if (values[field.name] !== undefined) {
|
|
184
|
+
unionDef.set(buf, field.name, values[field.name]);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Proxy-based instance (single Proxy instead of N defineProperty calls)
|
|
189
|
+
return new Proxy(buf, {
|
|
190
|
+
get(target, prop, receiver) {
|
|
191
|
+
// Special properties
|
|
192
|
+
if (prop === "_buffer") return target;
|
|
193
|
+
if (prop === "toObject") return () => unionDef.toObject(target);
|
|
194
|
+
if (prop === Symbol.toStringTag) return "UnionInstance";
|
|
195
|
+
if (prop === Symbol.iterator) return undefined;
|
|
196
|
+
|
|
197
|
+
// Handle Buffer/TypedArray properties FIRST before field checks
|
|
198
|
+
if (prop === "length" || prop === "byteLength" || prop === "byteOffset" || prop === "buffer" || prop === "BYTES_PER_ELEMENT") {
|
|
199
|
+
return target[prop];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Check if it's a known field
|
|
203
|
+
if (typeof prop === "string" && fieldMap.has(prop)) {
|
|
204
|
+
return unionDef.get(target, prop);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Fallback to buffer properties and methods
|
|
208
|
+
const value = target[prop];
|
|
209
|
+
if (typeof value === "function") {
|
|
210
|
+
return value.bind(target);
|
|
211
|
+
}
|
|
212
|
+
return value;
|
|
213
|
+
},
|
|
214
|
+
set(target, prop, value, receiver) {
|
|
215
|
+
// Check if it's a known field
|
|
216
|
+
if (typeof prop === "string" && fieldMap.has(prop)) {
|
|
217
|
+
unionDef.set(target, prop, value);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Fallback
|
|
222
|
+
return Reflect.set(target, prop, value, receiver);
|
|
223
|
+
},
|
|
224
|
+
has(target, prop) {
|
|
225
|
+
if (prop === "_buffer" || prop === "toObject") return true;
|
|
226
|
+
if (typeof prop === "string" && fieldMap.has(prop)) return true;
|
|
227
|
+
return Reflect.has(target, prop);
|
|
228
|
+
},
|
|
229
|
+
ownKeys(target) {
|
|
230
|
+
return fieldDefs.map((f) => f.name);
|
|
231
|
+
},
|
|
232
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
233
|
+
if (typeof prop === "string" && (fieldMap.has(prop) || prop === "_buffer" || prop === "toObject")) {
|
|
234
|
+
return {
|
|
235
|
+
enumerable: prop !== "_buffer" && prop !== "toObject",
|
|
236
|
+
configurable: true,
|
|
237
|
+
writable: true,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
get(bufOrObj, fieldName) {
|
|
246
|
+
// NUOVO: supporta sia buffer che object wrapper
|
|
247
|
+
let buf;
|
|
248
|
+
if (Buffer.isBuffer(bufOrObj)) {
|
|
249
|
+
buf = bufOrObj;
|
|
250
|
+
} else if (bufOrObj && bufOrObj._buffer) {
|
|
251
|
+
// Object wrapper - accesso diretto tramite property
|
|
252
|
+
return bufOrObj[fieldName];
|
|
253
|
+
} else {
|
|
254
|
+
throw new TypeError("Expected Buffer or union instance");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// O(1) lookup con Map
|
|
258
|
+
const field = fieldMap.get(fieldName);
|
|
259
|
+
if (!field) throw new Error(`Unknown field: ${fieldName}`);
|
|
260
|
+
|
|
261
|
+
// Bit field
|
|
262
|
+
if (field.isBitField) {
|
|
263
|
+
return _readBitField(buf, field.offset, field.type, field.bitOffset, field.bitSize, sizeof);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Nested struct
|
|
267
|
+
if (field.isNested) {
|
|
268
|
+
const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
269
|
+
// Proxy-based nested instance
|
|
270
|
+
return new Proxy(nestedBuf, {
|
|
271
|
+
get(target, prop, receiver) {
|
|
272
|
+
if (prop === "_buffer") return target;
|
|
273
|
+
if (prop === "toObject") return () => field.type.toObject(target);
|
|
274
|
+
if (prop === Symbol.toStringTag) return "NestedUnionInstance";
|
|
275
|
+
if (prop === Symbol.iterator) return undefined;
|
|
276
|
+
// Handle Buffer/TypedArray properties
|
|
277
|
+
if (prop === "length" || prop === "byteLength" || prop === "byteOffset" || prop === "buffer" || prop === "BYTES_PER_ELEMENT") {
|
|
278
|
+
return target[prop];
|
|
279
|
+
}
|
|
280
|
+
if (typeof prop === "string" && field.type.fields) {
|
|
281
|
+
if (field.type.fields.some((f) => f.name === prop && !f.isAnonymous)) {
|
|
282
|
+
return field.type.get(target, prop);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const value = target[prop];
|
|
286
|
+
if (typeof value === "function") return value.bind(target);
|
|
287
|
+
return value;
|
|
288
|
+
},
|
|
289
|
+
set(target, prop, value, receiver) {
|
|
290
|
+
if (typeof prop === "string" && field.type.fields) {
|
|
291
|
+
if (field.type.fields.some((f) => f.name === prop && !f.isAnonymous)) {
|
|
292
|
+
field.type.set(target, prop, value);
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return Reflect.set(target, prop, value, receiver);
|
|
297
|
+
},
|
|
298
|
+
has(target, prop) {
|
|
299
|
+
if (prop === "_buffer" || prop === "toObject") return true;
|
|
300
|
+
if (typeof prop === "string" && field.type.fields) {
|
|
301
|
+
if (field.type.fields.some((f) => f.name === prop)) return true;
|
|
302
|
+
}
|
|
303
|
+
return Reflect.has(target, prop);
|
|
304
|
+
},
|
|
305
|
+
ownKeys(target) {
|
|
306
|
+
return (field.type.fields || []).filter((f) => !f.isAnonymous).map((f) => f.name);
|
|
307
|
+
},
|
|
308
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
309
|
+
if (typeof prop === "string" && field.type.fields && field.type.fields.some((f) => f.name === prop)) {
|
|
310
|
+
return {
|
|
311
|
+
enumerable: true,
|
|
312
|
+
configurable: true,
|
|
313
|
+
writable: true,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Array
|
|
322
|
+
if (field.isArray) {
|
|
323
|
+
return field.type.wrap(buf.subarray(field.offset, field.offset + field.size));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Tipo base
|
|
327
|
+
return readValue(buf, field.type, field.offset);
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
set(bufOrObj, fieldName, value) {
|
|
331
|
+
// NUOVO: supporta sia buffer che object wrapper
|
|
332
|
+
let buf;
|
|
333
|
+
if (Buffer.isBuffer(bufOrObj)) {
|
|
334
|
+
buf = bufOrObj;
|
|
335
|
+
} else if (bufOrObj && bufOrObj._buffer) {
|
|
336
|
+
// Object wrapper - accesso diretto tramite property
|
|
337
|
+
bufOrObj[fieldName] = value;
|
|
338
|
+
return;
|
|
339
|
+
} else {
|
|
340
|
+
throw new TypeError("Expected Buffer or union instance");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Lookup with Map
|
|
344
|
+
const field = fieldMap.get(fieldName);
|
|
345
|
+
if (!field) throw new Error(`Unknown field: ${fieldName}`);
|
|
346
|
+
|
|
347
|
+
// Bit field
|
|
348
|
+
if (field.isBitField) {
|
|
349
|
+
_writeBitField(buf, field.offset, field.type, field.bitOffset, field.bitSize, value, sizeof);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Nested struct
|
|
354
|
+
if (field.isNested) {
|
|
355
|
+
if (Buffer.isBuffer(value)) {
|
|
356
|
+
value.copy(buf, field.offset, 0, field.size);
|
|
357
|
+
} else if (typeof value === "object") {
|
|
358
|
+
const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
359
|
+
for (const [k, v] of Object.entries(value)) {
|
|
360
|
+
field.type.set(nestedBuf, k, v);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Array
|
|
367
|
+
if (field.isArray) {
|
|
368
|
+
if (Buffer.isBuffer(value)) {
|
|
369
|
+
value.copy(buf, field.offset, 0, field.size);
|
|
370
|
+
} else if (Array.isArray(value)) {
|
|
371
|
+
const wrapped = field.type.wrap(buf.subarray(field.offset, field.offset + field.size));
|
|
372
|
+
for (let i = 0; i < Math.min(value.length, field.type.length); i++) {
|
|
373
|
+
wrapped[i] = value[i];
|
|
374
|
+
}
|
|
375
|
+
} else if (value && value._buffer && Buffer.isBuffer(value._buffer)) {
|
|
376
|
+
// Handle array proxy instances
|
|
377
|
+
value._buffer.copy(buf, field.offset, 0, field.size);
|
|
378
|
+
}
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Tipo base
|
|
383
|
+
writeValue(buf, field.type, value, field.offset);
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
toObject(bufOrObj) {
|
|
387
|
+
// Se è già un object (non buffer), ritorna così com'è
|
|
388
|
+
if (!Buffer.isBuffer(bufOrObj)) {
|
|
389
|
+
if (bufOrObj && bufOrObj._buffer && bufOrObj._buffer.length === maxSize) {
|
|
390
|
+
return bufOrObj; // Già convertito
|
|
391
|
+
}
|
|
392
|
+
// Se è un plain object, convertilo in union
|
|
393
|
+
if (typeof bufOrObj === "object") {
|
|
394
|
+
const buf = alloc(maxSize);
|
|
395
|
+
buf.fill(0);
|
|
396
|
+
for (const [name, value] of Object.entries(bufOrObj)) {
|
|
397
|
+
unionDef.set(buf, name, value);
|
|
398
|
+
}
|
|
399
|
+
return unionDef.toObject(buf);
|
|
400
|
+
}
|
|
401
|
+
throw new TypeError("Expected Buffer or union instance");
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const buf = bufOrObj;
|
|
405
|
+
const obj = {};
|
|
406
|
+
|
|
407
|
+
// Cache per oggetti nested (struct/union) per evitare di ricrearli ogni volta
|
|
408
|
+
const nestedCache = new Map();
|
|
409
|
+
|
|
410
|
+
// Aggiungi _buffer come property nascosta
|
|
411
|
+
Object.defineProperty(obj, "_buffer", {
|
|
412
|
+
value: buf,
|
|
413
|
+
writable: false,
|
|
414
|
+
enumerable: false,
|
|
415
|
+
configurable: false,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// Per union, tutte le properties leggono/scrivono all'offset 0
|
|
419
|
+
for (const field of fieldDefs) {
|
|
420
|
+
Object.defineProperty(obj, field.name, {
|
|
421
|
+
get() {
|
|
422
|
+
if (field.isBitField) {
|
|
423
|
+
return _readBitField(buf, 0, field.type, field.bitOffset, field.bitSize, sizeof);
|
|
424
|
+
} else if (field.isNested) {
|
|
425
|
+
// Cache nested objects per evitare di ricrearli ogni volta
|
|
426
|
+
if (!nestedCache.has(field.name)) {
|
|
427
|
+
const nestedBuf = buf.subarray(0, field.size);
|
|
428
|
+
const nestedObj = field.type.toObject(nestedBuf);
|
|
429
|
+
nestedCache.set(field.name, nestedObj);
|
|
430
|
+
}
|
|
431
|
+
return nestedCache.get(field.name);
|
|
432
|
+
} else if (field.isArray) {
|
|
433
|
+
const wrapped = field.type.wrap(buf.subarray(0, field.size));
|
|
434
|
+
return [...wrapped];
|
|
435
|
+
} else {
|
|
436
|
+
return readValue(buf, field.type, 0);
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
set(value) {
|
|
440
|
+
if (field.isBitField) {
|
|
441
|
+
_writeBitField(buf, 0, field.type, field.bitOffset, field.bitSize, value, sizeof);
|
|
442
|
+
} else {
|
|
443
|
+
writeValue(buf, field.type, value, 0);
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
enumerable: true,
|
|
447
|
+
configurable: false,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return obj;
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
// fromObject: write plain object values into buffer
|
|
455
|
+
fromObject: function (buf, obj) {
|
|
456
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
457
|
+
if (this.fields.some((f) => f.name === key)) {
|
|
458
|
+
this.set(buf, key, value);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
return unionDef;
|
|
465
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file SimpleCData.js
|
|
3
|
+
* @module types/SimpleCData
|
|
4
|
+
* @description SimpleCData base class for primitive C types.
|
|
5
|
+
*
|
|
6
|
+
* This module provides the `SimpleCData` base class that serves as the foundation
|
|
7
|
+
* for all primitive C types (integers, floats, chars, pointers). It handles
|
|
8
|
+
* memory allocation, value reading/writing, and provides Python ctypes-compatible
|
|
9
|
+
* methods.
|
|
10
|
+
*
|
|
11
|
+
* **Python ctypes Compatibility**:
|
|
12
|
+
* Direct equivalent to Python's `ctypes._SimpleCData` base class.
|
|
13
|
+
*
|
|
14
|
+
* @example Creating custom type
|
|
15
|
+
* ```javascript
|
|
16
|
+
* import { SimpleCData } from 'node-ctypes';
|
|
17
|
+
*
|
|
18
|
+
* class c_int32 extends SimpleCData {
|
|
19
|
+
* static _size = 4;
|
|
20
|
+
* static _type = 'int32';
|
|
21
|
+
* static _reader = (buf, off) => buf.readInt32LE(off);
|
|
22
|
+
* static _writer = (buf, off, val) => buf.writeInt32LE(val, off);
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* const x = new c_int32(42);
|
|
26
|
+
* console.log(x.value); // 42
|
|
27
|
+
* x.value = 100;
|
|
28
|
+
* console.log(x.value); // 100
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example Using from_buffer (zero-copy)
|
|
32
|
+
* ```javascript
|
|
33
|
+
* const buffer = Buffer.from([0x2A, 0x00, 0x00, 0x00]); // 42 in LE
|
|
34
|
+
* const x = c_int32.from_buffer(buffer, 0);
|
|
35
|
+
* console.log(x.value); // 42
|
|
36
|
+
* x.value = 100; // Modifies original buffer!
|
|
37
|
+
* console.log(buffer); // [0x64, 0x00, 0x00, 0x00]
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @example Using from_buffer_copy
|
|
41
|
+
* ```javascript
|
|
42
|
+
* const buffer = Buffer.from([0x2A, 0x00, 0x00, 0x00]);
|
|
43
|
+
* const x = c_int32.from_buffer_copy(buffer, 0);
|
|
44
|
+
* x.value = 100; // Does NOT modify original buffer
|
|
45
|
+
* console.log(buffer); // Still [0x2A, 0x00, 0x00, 0x00]
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates the SimpleCData base class with required dependencies injected.
|
|
51
|
+
*
|
|
52
|
+
* @param {Function} alloc - Memory allocation function
|
|
53
|
+
* @returns {Class} SimpleCData base class
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
export function createSimpleCDataClass(alloc) {
|
|
57
|
+
/**
|
|
58
|
+
* SimpleCData base class for primitive C types.
|
|
59
|
+
*
|
|
60
|
+
* Subclasses must define:
|
|
61
|
+
* - `static _size`: Size in bytes
|
|
62
|
+
* - `static _type`: Type name string
|
|
63
|
+
* - `static _reader(buffer, offset)`: Function to read value from buffer
|
|
64
|
+
* - `static _writer(buffer, offset, value)`: Function to write value to buffer
|
|
65
|
+
*
|
|
66
|
+
* **Python ctypes Compatibility**:
|
|
67
|
+
* Similar to Python's `ctypes._SimpleCData` with:
|
|
68
|
+
* - `.value` property for get/set
|
|
69
|
+
* - `.size` property
|
|
70
|
+
* - `from_buffer()` for zero-copy wrapping
|
|
71
|
+
* - `from_buffer_copy()` for copied data
|
|
72
|
+
*
|
|
73
|
+
* @class SimpleCData
|
|
74
|
+
*
|
|
75
|
+
* @example Integer type
|
|
76
|
+
* ```javascript
|
|
77
|
+
* class c_uint8 extends SimpleCData {
|
|
78
|
+
* static _size = 1;
|
|
79
|
+
* static _type = 'uint8';
|
|
80
|
+
* static _reader = (buf, off) => buf.readUInt8(off);
|
|
81
|
+
* static _writer = (buf, off, val) => buf.writeUInt8(val, off);
|
|
82
|
+
* }
|
|
83
|
+
*
|
|
84
|
+
* const byte = new c_uint8(255);
|
|
85
|
+
* console.log(byte.value); // 255
|
|
86
|
+
* console.log(byte.size); // 1
|
|
87
|
+
* console.log(byte.toString()); // "c_uint8(255)"
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @example Float type
|
|
91
|
+
* ```javascript
|
|
92
|
+
* class c_float extends SimpleCData {
|
|
93
|
+
* static _size = 4;
|
|
94
|
+
* static _type = 'float';
|
|
95
|
+
* static _reader = (buf, off) => buf.readFloatLE(off);
|
|
96
|
+
* static _writer = (buf, off, val) => buf.writeFloatLE(val, off);
|
|
97
|
+
* }
|
|
98
|
+
*
|
|
99
|
+
* const pi = new c_float(3.14159);
|
|
100
|
+
* console.log(pi.value); // 3.1415898799896240
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
class SimpleCData {
|
|
104
|
+
/**
|
|
105
|
+
* @param {number|bigint} [value] - Initial value (default: 0)
|
|
106
|
+
*/
|
|
107
|
+
constructor(value) {
|
|
108
|
+
const Ctor = this.constructor;
|
|
109
|
+
this._buffer = alloc(Ctor._size);
|
|
110
|
+
if (value !== undefined && value !== null) {
|
|
111
|
+
this.value = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get the current value
|
|
117
|
+
*/
|
|
118
|
+
get value() {
|
|
119
|
+
const Ctor = this.constructor;
|
|
120
|
+
return Ctor._reader(this._buffer, 0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Set the value
|
|
125
|
+
*/
|
|
126
|
+
set value(v) {
|
|
127
|
+
const Ctor = this.constructor;
|
|
128
|
+
Ctor._writer(this._buffer, 0, v);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Size of the type in bytes (instance property)
|
|
133
|
+
*/
|
|
134
|
+
get size() {
|
|
135
|
+
return this.constructor._size;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create instance from existing buffer (Python ctypes compatible)
|
|
140
|
+
* WARNING: Returns a VIEW into the buffer, not a copy!
|
|
141
|
+
* @param {Buffer} buffer - Source buffer
|
|
142
|
+
* @param {number} [offset=0] - Offset in buffer
|
|
143
|
+
* @returns {SimpleCData} New instance wrapping the buffer slice
|
|
144
|
+
*/
|
|
145
|
+
static from_buffer(buffer, offset = 0) {
|
|
146
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
147
|
+
throw new TypeError("from_buffer requires a Buffer");
|
|
148
|
+
}
|
|
149
|
+
const inst = Object.create(this.prototype);
|
|
150
|
+
inst._buffer = buffer.subarray(offset, offset + this._size);
|
|
151
|
+
return inst;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Create instance from a COPY of buffer data (Python ctypes compatible)
|
|
156
|
+
* @param {Buffer} buffer - Source buffer
|
|
157
|
+
* @param {number} [offset=0] - Offset in buffer
|
|
158
|
+
* @returns {SimpleCData} New instance with copied data
|
|
159
|
+
*/
|
|
160
|
+
static from_buffer_copy(buffer, offset = 0) {
|
|
161
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
162
|
+
throw new TypeError("from_buffer_copy requires a Buffer");
|
|
163
|
+
}
|
|
164
|
+
const inst = new this();
|
|
165
|
+
buffer.copy(inst._buffer, 0, offset, offset + this._size);
|
|
166
|
+
return inst;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Size of the type in bytes (static property)
|
|
171
|
+
*/
|
|
172
|
+
static get size() {
|
|
173
|
+
return this._size;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
toString() {
|
|
177
|
+
return `${this.constructor.name}(${this.value})`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
toJSON() {
|
|
181
|
+
const v = this.value;
|
|
182
|
+
return typeof v === "bigint" ? v.toString() : v;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
valueOf() {
|
|
186
|
+
return this.value;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
SimpleCData._isSimpleCData = true;
|
|
191
|
+
|
|
192
|
+
return SimpleCData;
|
|
193
|
+
}
|