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/lib/index.js
ADDED
|
@@ -0,0 +1,3501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* node-ctypes - Python ctypes-like FFI for Node.js
|
|
3
|
+
*
|
|
4
|
+
* A simple FFI library that allows calling C functions from JavaScript,
|
|
5
|
+
* similar to Python's ctypes module.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the path to the native module, checking multiple possible locations
|
|
14
|
+
* @param {string} moduleName - The name of the npm module
|
|
15
|
+
* @param {string} nativeFile - The name of the native .node file
|
|
16
|
+
* @param {string} config - Build configuration (Release or Debug)
|
|
17
|
+
* @returns {string} The absolute path to the native module
|
|
18
|
+
*/
|
|
19
|
+
const getNativeModulePath = (moduleName, nativeFile, config = "Release") => {
|
|
20
|
+
const buildPath = (base) => path.join(base, "build", config, nativeFile);
|
|
21
|
+
|
|
22
|
+
// Check if running in Electron
|
|
23
|
+
if (process.versions?.electron) {
|
|
24
|
+
const electronProcess = process;
|
|
25
|
+
if (electronProcess.resourcesPath) {
|
|
26
|
+
const unpackedPath = path.join(
|
|
27
|
+
electronProcess.resourcesPath,
|
|
28
|
+
"app.asar.unpacked",
|
|
29
|
+
"node_modules",
|
|
30
|
+
moduleName,
|
|
31
|
+
);
|
|
32
|
+
if (existsSync(buildPath(unpackedPath))) {
|
|
33
|
+
return buildPath(unpackedPath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check in current working directory's node_modules
|
|
39
|
+
const cwdPath = buildPath(
|
|
40
|
+
path.join(process.cwd(), "node_modules", moduleName),
|
|
41
|
+
);
|
|
42
|
+
if (existsSync(cwdPath)) {
|
|
43
|
+
return cwdPath;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check local build directory in the project root (useful for local testing)
|
|
47
|
+
const localBuildPath = buildPath(process.cwd());
|
|
48
|
+
if (existsSync(localBuildPath)) {
|
|
49
|
+
return localBuildPath;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Try Debug build as fallback
|
|
53
|
+
if (config === "Release") {
|
|
54
|
+
try {
|
|
55
|
+
return getNativeModulePath(moduleName, nativeFile, "Debug");
|
|
56
|
+
} catch (e) {
|
|
57
|
+
// Continue to error below
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Native module "${nativeFile}" not found for "${moduleName}". ` +
|
|
63
|
+
`Searched in Electron resources, node_modules, and local build.`,
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Load the native module
|
|
68
|
+
let nativeModulePath;
|
|
69
|
+
try {
|
|
70
|
+
nativeModulePath = getNativeModulePath("node-ctypes", `ctypes.node`);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
const platform = process.platform;
|
|
73
|
+
const arch = process.arch;
|
|
74
|
+
nativeModulePath = getNativeModulePath(
|
|
75
|
+
"node-ctypes",
|
|
76
|
+
`ctypes-${platform}-${arch}.node`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const require = createRequire(path.join(process.cwd(), "index.js"));
|
|
80
|
+
const native = require(nativeModulePath);
|
|
81
|
+
|
|
82
|
+
// Re-esporta le classi native
|
|
83
|
+
const {
|
|
84
|
+
Version,
|
|
85
|
+
Library,
|
|
86
|
+
FFIFunction,
|
|
87
|
+
Callback,
|
|
88
|
+
ThreadSafeCallback,
|
|
89
|
+
CType,
|
|
90
|
+
StructType,
|
|
91
|
+
ArrayType,
|
|
92
|
+
} = native;
|
|
93
|
+
|
|
94
|
+
// Tipi predefiniti
|
|
95
|
+
const types = native.types;
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// Type Normalization Helper
|
|
99
|
+
// Converts SimpleCData classes to their string type names for FFI
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Converte un tipo SimpleCData nel formato richiesto dal modulo nativo FFI
|
|
104
|
+
* @param {Function} type - Classe SimpleCData
|
|
105
|
+
* @returns {string|Object} Tipo per FFI nativo
|
|
106
|
+
*/
|
|
107
|
+
function _toNativeType(type) {
|
|
108
|
+
if (typeof type === "function" && type._isSimpleCData) {
|
|
109
|
+
// String pointer types need native CType for automatic conversion
|
|
110
|
+
if (type._type === "char_p") return types.c_char_p;
|
|
111
|
+
if (type._type === "wchar_p") return types.c_wchar_p;
|
|
112
|
+
return type._type;
|
|
113
|
+
}
|
|
114
|
+
// Struct/Union classes pass through
|
|
115
|
+
if (
|
|
116
|
+
typeof type === "function" &&
|
|
117
|
+
(type.prototype instanceof Structure || type.prototype instanceof Union)
|
|
118
|
+
) {
|
|
119
|
+
return type;
|
|
120
|
+
}
|
|
121
|
+
// Native CType objects pass through
|
|
122
|
+
return type;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Normalizza un array di tipi
|
|
127
|
+
* @param {Array} types - Array di tipi
|
|
128
|
+
* @returns {Array} Array di tipi normalizzati
|
|
129
|
+
*/
|
|
130
|
+
function _toNativeTypes(types) {
|
|
131
|
+
if (!Array.isArray(types)) return types;
|
|
132
|
+
return types.map(_toNativeType);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ============================================================================
|
|
136
|
+
// Type Lookup Tables
|
|
137
|
+
// Centralizes type information to avoid duplicated switch statements
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
// TYPE_READERS removed - using SimpleCData._reader directly
|
|
141
|
+
|
|
142
|
+
// TYPE_WRITERS removed - using SimpleCData._writer directly
|
|
143
|
+
|
|
144
|
+
/** @type {Map<string, number>} */
|
|
145
|
+
// TYPE_SIZES removed - using SimpleCData._size directly
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Carica una libreria dinamica
|
|
149
|
+
* @param {string|null} libPath - Percorso della libreria o null per l'eseguibile corrente
|
|
150
|
+
* @returns {Library} Oggetto Library
|
|
151
|
+
*/
|
|
152
|
+
function load(libPath) {
|
|
153
|
+
return new Library(libPath);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Wrapper conveniente per CDLL (come in Python ctypes)
|
|
158
|
+
*/
|
|
159
|
+
class CDLL {
|
|
160
|
+
constructor(libPath) {
|
|
161
|
+
this._lib = new Library(libPath);
|
|
162
|
+
this._cache = new Map();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Ottiene una funzione dalla libreria
|
|
167
|
+
* @param {string} name - Nome della funzione
|
|
168
|
+
* @param {string|CType} returnType - Tipo di ritorno
|
|
169
|
+
* @param {Array<string|CType>} argTypes - Tipi degli argomenti
|
|
170
|
+
* @param {Object} [options] - Opzioni aggiuntive (es. { abi: 'stdcall' })
|
|
171
|
+
* @returns {Function} Funzione callable
|
|
172
|
+
*/
|
|
173
|
+
func(name, returnType, argTypes = [], options = {}) {
|
|
174
|
+
const cacheKey = `${name}:${returnType}:${argTypes.join(",")}`;
|
|
175
|
+
|
|
176
|
+
if (this._cache.has(cacheKey)) {
|
|
177
|
+
return this._cache.get(cacheKey);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const ffiFunc = this._lib.func(
|
|
181
|
+
name,
|
|
182
|
+
_toNativeType(returnType),
|
|
183
|
+
_toNativeTypes(argTypes),
|
|
184
|
+
options,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Crea wrapper minimo ottimizzato
|
|
188
|
+
// Estrae ._buffer solo da argomenti che sono oggetti NON-Buffer
|
|
189
|
+
const callMethod = function (...args) {
|
|
190
|
+
// Process args: extract _buffer from struct proxies
|
|
191
|
+
const processedArgs = [];
|
|
192
|
+
|
|
193
|
+
for (let i = 0; i < args.length; i++) {
|
|
194
|
+
const arg = args[i];
|
|
195
|
+
// Check for _buffer FIRST (handles Proxy-wrapped structs)
|
|
196
|
+
// This avoids calling Buffer.isBuffer() on Proxy which can cause issues
|
|
197
|
+
if (arg && typeof arg === "object") {
|
|
198
|
+
// Check for struct proxy FIRST - accessing _buffer on Proxy is safe
|
|
199
|
+
// but Buffer.isBuffer(proxy) can cause hangs
|
|
200
|
+
if (arg._buffer !== undefined && Buffer.isBuffer(arg._buffer)) {
|
|
201
|
+
// Struct proxy or object with _buffer
|
|
202
|
+
processedArgs.push(arg._buffer);
|
|
203
|
+
} else if (Buffer.isBuffer(arg)) {
|
|
204
|
+
// Already a buffer, use directly
|
|
205
|
+
processedArgs.push(arg);
|
|
206
|
+
} else {
|
|
207
|
+
// Other object, pass as-is
|
|
208
|
+
processedArgs.push(arg);
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
// Primitive value (number, string, bigint, null, undefined)
|
|
212
|
+
processedArgs.push(arg);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return ffiFunc.call(...processedArgs);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Aggiungi metadata come proprietà non-enumerable per non interferire
|
|
220
|
+
Object.defineProperties(callMethod, {
|
|
221
|
+
funcName: { value: name },
|
|
222
|
+
address: { value: ffiFunc.address },
|
|
223
|
+
_ffi: { value: ffiFunc },
|
|
224
|
+
// Esponi errcheck come setter/getter
|
|
225
|
+
errcheck: {
|
|
226
|
+
get() {
|
|
227
|
+
return ffiFunc._errcheck;
|
|
228
|
+
},
|
|
229
|
+
set(callback) {
|
|
230
|
+
ffiFunc._errcheck = callback;
|
|
231
|
+
ffiFunc.setErrcheck(callback);
|
|
232
|
+
},
|
|
233
|
+
enumerable: false,
|
|
234
|
+
configurable: true,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
this._cache.set(cacheKey, callMethod);
|
|
239
|
+
return callMethod;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Ottiene l'indirizzo di un simbolo
|
|
244
|
+
* @param {string} name - Nome del simbolo
|
|
245
|
+
* @returns {BigInt} Indirizzo del simbolo
|
|
246
|
+
*/
|
|
247
|
+
symbol(name) {
|
|
248
|
+
return this._lib.symbol(name);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Crea un callback JS chiamabile da C
|
|
253
|
+
* @param {Function} fn - Funzione JavaScript
|
|
254
|
+
* @param {string|CType} returnType - Tipo di ritorno
|
|
255
|
+
* @param {Array<string|CType>} argTypes - Tipi degli argomenti
|
|
256
|
+
* @returns {Object} Oggetto callback con proprietà pointer e metodi
|
|
257
|
+
*/
|
|
258
|
+
callback(fn, returnType, argTypes = []) {
|
|
259
|
+
return this._lib.callback(
|
|
260
|
+
_toNativeType(returnType),
|
|
261
|
+
_toNativeTypes(argTypes),
|
|
262
|
+
fn,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Chiude la libreria
|
|
268
|
+
*/
|
|
269
|
+
close() {
|
|
270
|
+
this._lib.close();
|
|
271
|
+
this._cache.clear();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
get path() {
|
|
275
|
+
return this._lib.path;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
get loaded() {
|
|
279
|
+
return this._lib.loaded;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* WinDLL - come CDLL ma con stdcall di default (per Windows)
|
|
285
|
+
*/
|
|
286
|
+
class WinDLL extends CDLL {
|
|
287
|
+
func(name, returnType, argTypes = [], options = {}) {
|
|
288
|
+
return super.func(name, returnType, argTypes, {
|
|
289
|
+
abi: "stdcall",
|
|
290
|
+
...options,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Crea un callback JS chiamabile da C (solo main thread)
|
|
297
|
+
*
|
|
298
|
+
* Questo callback è veloce e senza overhead, ma DEVE essere chiamato
|
|
299
|
+
* solo dal main thread di Node.js (es: qsort, EnumWindows, etc.)
|
|
300
|
+
*
|
|
301
|
+
* Per callback da thread esterni (es: CreateThread), usa threadSafeCallback()
|
|
302
|
+
*
|
|
303
|
+
* @param {Function} fn - Funzione JavaScript
|
|
304
|
+
* @param {string|CType} returnType - Tipo di ritorno
|
|
305
|
+
* @param {Array<string|CType>} argTypes - Tipi degli argomenti
|
|
306
|
+
* @returns {Object} Oggetto con .pointer (BigInt), .release()
|
|
307
|
+
*/
|
|
308
|
+
function callback(fn, returnType, argTypes = []) {
|
|
309
|
+
const cb = new Callback(
|
|
310
|
+
fn,
|
|
311
|
+
_toNativeType(returnType),
|
|
312
|
+
_toNativeTypes(argTypes),
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
get pointer() {
|
|
317
|
+
return cb.pointer;
|
|
318
|
+
},
|
|
319
|
+
release() {
|
|
320
|
+
cb.release();
|
|
321
|
+
},
|
|
322
|
+
_callback: cb,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Crea un callback JS chiamabile da qualsiasi thread
|
|
328
|
+
*
|
|
329
|
+
* Questo callback può essere invocato da thread C esterni (es: CreateThread,
|
|
330
|
+
* thread pool, etc.) in modo sicuro. Ha overhead maggiore rispetto a callback()
|
|
331
|
+
* per la sincronizzazione tra thread.
|
|
332
|
+
*
|
|
333
|
+
* Per callback dal main thread (es: qsort), preferisci callback() che è più veloce.
|
|
334
|
+
*
|
|
335
|
+
* @param {Function} fn - Funzione JavaScript
|
|
336
|
+
* @param {string|CType} returnType - Tipo di ritorno
|
|
337
|
+
* @param {Array<string|CType>} argTypes - Tipi degli argomenti
|
|
338
|
+
* @returns {Object} Oggetto con .pointer (BigInt), .release()
|
|
339
|
+
*/
|
|
340
|
+
function threadSafeCallback(fn, returnType, argTypes = []) {
|
|
341
|
+
const cb = new ThreadSafeCallback(
|
|
342
|
+
fn,
|
|
343
|
+
_toNativeType(returnType),
|
|
344
|
+
_toNativeTypes(argTypes),
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
get pointer() {
|
|
349
|
+
return cb.pointer;
|
|
350
|
+
},
|
|
351
|
+
release() {
|
|
352
|
+
cb.release();
|
|
353
|
+
},
|
|
354
|
+
_callback: cb,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Alloca memoria nativa
|
|
360
|
+
* @param {number} size - Dimensione in bytes
|
|
361
|
+
* @returns {Buffer} Buffer allocato
|
|
362
|
+
*/
|
|
363
|
+
function alloc(size) {
|
|
364
|
+
// Usa Buffer.alloc direttamente per performance (evita chiamata nativa)
|
|
365
|
+
return Buffer.alloc(size);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Crea un buffer con una stringa C null-terminata
|
|
370
|
+
* @param {string} str - Stringa JavaScript
|
|
371
|
+
* @returns {Buffer} Buffer con stringa C
|
|
372
|
+
*/
|
|
373
|
+
function cstring(str) {
|
|
374
|
+
return native.cstring(str);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Crea un buffer con una stringa wide (wchar_t*) null-terminata
|
|
379
|
+
* @param {string} str - Stringa JavaScript
|
|
380
|
+
* @returns {Buffer} Buffer con stringa wide
|
|
381
|
+
*/
|
|
382
|
+
function wstring(str) {
|
|
383
|
+
// Converti stringa JS a UTF-16LE (che è come wchar_t su Windows)
|
|
384
|
+
const buf = Buffer.from(str + "\0", "utf16le");
|
|
385
|
+
return buf;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Legge una stringa C da un puntatore
|
|
390
|
+
* @param {Buffer|BigInt|number} ptr - Puntatore alla stringa
|
|
391
|
+
* @param {number} [maxLen] - Lunghezza massima da leggere
|
|
392
|
+
* @returns {string|null} Stringa letta o null
|
|
393
|
+
*/
|
|
394
|
+
function readCString(ptr, maxLen) {
|
|
395
|
+
return native.readCString(ptr, maxLen);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Legge una stringa wide (wchar_t*) da un puntatore
|
|
400
|
+
* @param {Buffer|BigInt|number} ptr - Puntatore alla stringa wide
|
|
401
|
+
* @param {number} [size] - Numero di caratteri da leggere (non bytes)
|
|
402
|
+
* @returns {string|null} Stringa letta o null
|
|
403
|
+
*/
|
|
404
|
+
function readWString(ptr, size) {
|
|
405
|
+
if (ptr === null || ptr === undefined) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const wcharSize = native.WCHAR_SIZE; // 2 su Windows, 4 su Unix
|
|
410
|
+
|
|
411
|
+
let buf;
|
|
412
|
+
if (Buffer.isBuffer(ptr)) {
|
|
413
|
+
buf = ptr;
|
|
414
|
+
} else {
|
|
415
|
+
// Converti BigInt/number in buffer
|
|
416
|
+
const maxBytes = size ? size * wcharSize : 1024;
|
|
417
|
+
buf = native.ptrToBuffer(ptr, maxBytes);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Trova il null terminator
|
|
421
|
+
let end = size ? size * wcharSize : buf.length;
|
|
422
|
+
|
|
423
|
+
if (wcharSize === 2) {
|
|
424
|
+
// Windows: UTF-16LE
|
|
425
|
+
for (let i = 0; i < end; i += 2) {
|
|
426
|
+
if (i + 1 < buf.length && buf.readUInt16LE(i) === 0) {
|
|
427
|
+
end = i;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return buf.toString("utf16le", 0, end);
|
|
432
|
+
} else {
|
|
433
|
+
// Unix: UTF-32LE (wchar_t è 32-bit)
|
|
434
|
+
let chars = [];
|
|
435
|
+
for (let i = 0; i < end; i += 4) {
|
|
436
|
+
if (i + 3 >= buf.length) break;
|
|
437
|
+
const codePoint = buf.readUInt32LE(i);
|
|
438
|
+
if (codePoint === 0) break;
|
|
439
|
+
chars.push(String.fromCodePoint(codePoint));
|
|
440
|
+
}
|
|
441
|
+
return chars.join("");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Legge un valore dalla memoria
|
|
447
|
+
* Per tipi base comuni usa lookup table
|
|
448
|
+
* @param {Buffer|BigInt|number} ptr - Puntatore
|
|
449
|
+
* @param {string|CType} type - Tipo del valore
|
|
450
|
+
* @param {number} [offset=0] - Offset in bytes
|
|
451
|
+
* @returns {*} Valore letto
|
|
452
|
+
*/
|
|
453
|
+
function readValue(ptr, type, offset = 0) {
|
|
454
|
+
// SimpleCData class - use its _reader directly
|
|
455
|
+
if (typeof type === "function" && type._isSimpleCData) {
|
|
456
|
+
if (Buffer.isBuffer(ptr)) {
|
|
457
|
+
return type._reader(ptr, offset);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Allow passing an address (BigInt or number) and convert to buffer
|
|
461
|
+
if (typeof ptr === "bigint" || typeof ptr === "number") {
|
|
462
|
+
const size = type._size || sizeof(type);
|
|
463
|
+
const buf = ptrToBuffer(ptr, size + offset);
|
|
464
|
+
return type._reader(buf, offset);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
throw new TypeError("readValue requires a Buffer or pointer address");
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Struct type (class)
|
|
471
|
+
if (typeof type === "function" && type.prototype instanceof Structure) {
|
|
472
|
+
const def = type._structDef || type._buildStruct();
|
|
473
|
+
const tempBuf = ptr.subarray(offset, offset + def.size);
|
|
474
|
+
return def.toObject(tempBuf);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Union type (class)
|
|
478
|
+
if (typeof type === "function" && type.prototype instanceof Union) {
|
|
479
|
+
const def = type._unionDef || type._buildUnion();
|
|
480
|
+
const tempBuf = ptr.subarray(offset, offset + def.size);
|
|
481
|
+
return def.toObject(tempBuf);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
throw new TypeError(`readValue: unsupported type ${type}`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Scrive un valore in memoria
|
|
489
|
+
* Per tipi base comuni usa lookup table
|
|
490
|
+
* @param {Buffer|BigInt|number} ptr - Puntatore
|
|
491
|
+
* @param {string|CType} type - Tipo del valore
|
|
492
|
+
* @param {*} value - Valore da scrivere
|
|
493
|
+
* @param {number} [offset=0] - Offset in bytes
|
|
494
|
+
* @returns {number} Bytes scritti
|
|
495
|
+
*/
|
|
496
|
+
function writeValue(ptr, type, value, offset = 0) {
|
|
497
|
+
// SimpleCData class - use its _writer directly
|
|
498
|
+
if (typeof type === "function" && type._isSimpleCData) {
|
|
499
|
+
if (!Buffer.isBuffer(ptr)) {
|
|
500
|
+
throw new TypeError("writeValue requires a Buffer");
|
|
501
|
+
}
|
|
502
|
+
type._writer(ptr, offset, value);
|
|
503
|
+
return type._size;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Struct type (class)
|
|
507
|
+
if (typeof type === "function" && type.prototype instanceof Structure) {
|
|
508
|
+
const def = type._structDef || type._buildStruct();
|
|
509
|
+
const tempBuf = ptr.subarray(offset, offset + def.size);
|
|
510
|
+
if (Buffer.isBuffer(value)) {
|
|
511
|
+
value.copy(tempBuf, 0, 0, def.size);
|
|
512
|
+
} else if (typeof value === "object" && value !== null) {
|
|
513
|
+
for (const [k, v] of Object.entries(value)) {
|
|
514
|
+
def.set(tempBuf, k, v);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return def.size;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Union type (class)
|
|
521
|
+
if (typeof type === "function" && type.prototype instanceof Union) {
|
|
522
|
+
const def = type._unionDef || type._buildUnion();
|
|
523
|
+
const tempBuf = ptr.subarray(offset, offset + def.size);
|
|
524
|
+
if (Buffer.isBuffer(value)) {
|
|
525
|
+
value.copy(tempBuf, 0, 0, def.size);
|
|
526
|
+
} else if (typeof value === "object" && value !== null) {
|
|
527
|
+
for (const [k, v] of Object.entries(value)) {
|
|
528
|
+
def.set(tempBuf, k, v);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return def.size;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
throw new TypeError(`writeValue: unsupported type ${type}`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Restituisce la dimensione di un tipo
|
|
539
|
+
* Per tipi base comuni usa lookup table
|
|
540
|
+
* @param {string|CType|Object} type - Tipo
|
|
541
|
+
* @returns {number} Dimensione in bytes
|
|
542
|
+
*/
|
|
543
|
+
function sizeof(type) {
|
|
544
|
+
// SimpleCData class (e.g., sizeof(c_uint64))
|
|
545
|
+
if (typeof type === "function" && type._isSimpleCData) {
|
|
546
|
+
return type._size;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// SimpleCData instance (e.g., sizeof(new c_uint64()))
|
|
550
|
+
if (
|
|
551
|
+
type &&
|
|
552
|
+
type._buffer &&
|
|
553
|
+
type.constructor &&
|
|
554
|
+
type.constructor._isSimpleCData
|
|
555
|
+
) {
|
|
556
|
+
return type.constructor._size;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Struct type (class)
|
|
560
|
+
if (typeof type === "function" && type.prototype instanceof Structure) {
|
|
561
|
+
const def = type._structDef || type._buildStruct();
|
|
562
|
+
return def.size;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Union type (class)
|
|
566
|
+
if (typeof type === "function" && type.prototype instanceof Union) {
|
|
567
|
+
const def = type._unionDef || type._buildUnion();
|
|
568
|
+
return def.size;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Struct type
|
|
572
|
+
if (
|
|
573
|
+
typeof type === "object" &&
|
|
574
|
+
type !== null &&
|
|
575
|
+
typeof type.size === "number"
|
|
576
|
+
) {
|
|
577
|
+
return type.size;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Array type
|
|
581
|
+
if (
|
|
582
|
+
typeof type === "object" &&
|
|
583
|
+
type !== null &&
|
|
584
|
+
typeof type.getSize === "function"
|
|
585
|
+
) {
|
|
586
|
+
return type.getSize();
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
throw new TypeError(
|
|
590
|
+
`sizeof: unsupported type. Use SimpleCData classes like c_int32, c_uint64, etc.`,
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Converte un Buffer o indirizzo in BigInt
|
|
596
|
+
* @param {Buffer|BigInt|number} ptr - Puntatore
|
|
597
|
+
* @returns {BigInt} Indirizzo come BigInt
|
|
598
|
+
*/
|
|
599
|
+
function addressOf(ptr) {
|
|
600
|
+
if (Buffer.isBuffer(ptr)) {
|
|
601
|
+
// Ottieni l'indirizzo del buffer
|
|
602
|
+
// Questo è un po' hacky, ma funziona
|
|
603
|
+
const tempBuf = alloc(native.POINTER_SIZE);
|
|
604
|
+
// Use native.writeValue with a string 'pointer' to avoid JS-level
|
|
605
|
+
// writeValue -> c_void_p._writer recursion. native.writeValue accepts
|
|
606
|
+
// a string type name and will write the buffer address directly.
|
|
607
|
+
native.writeValue(tempBuf, "pointer", ptr, 0);
|
|
608
|
+
if (native.POINTER_SIZE === 8) return tempBuf.readBigUInt64LE(0);
|
|
609
|
+
return BigInt(tempBuf.readUInt32LE(0));
|
|
610
|
+
}
|
|
611
|
+
if (typeof ptr === "bigint") {
|
|
612
|
+
return ptr;
|
|
613
|
+
}
|
|
614
|
+
return BigInt(ptr);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Passa un oggetto per riferimento (come Python byref)
|
|
619
|
+
* Supporta sia Buffer che istanze SimpleCData
|
|
620
|
+
*
|
|
621
|
+
* @param {Buffer|SimpleCData} obj - Oggetto da passare per riferimento
|
|
622
|
+
* @returns {Buffer} Il buffer sottostante
|
|
623
|
+
*/
|
|
624
|
+
function byref(obj) {
|
|
625
|
+
// SimpleCData instance - return its buffer
|
|
626
|
+
if (obj && obj._buffer && Buffer.isBuffer(obj._buffer)) {
|
|
627
|
+
return obj._buffer;
|
|
628
|
+
}
|
|
629
|
+
// Buffer - return as-is
|
|
630
|
+
if (Buffer.isBuffer(obj)) {
|
|
631
|
+
return obj;
|
|
632
|
+
}
|
|
633
|
+
throw new TypeError("byref() argument must be a ctypes instance or Buffer");
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Cast di un puntatore a un tipo diverso
|
|
638
|
+
* Legge il valore dalla memoria interpretandolo come il nuovo tipo.
|
|
639
|
+
*
|
|
640
|
+
* @param {Buffer|BigInt} ptr - Puntatore sorgente
|
|
641
|
+
* @param {string|Object} targetType - Tipo destinazione ('int32', 'pointer', struct, etc.)
|
|
642
|
+
* @returns {*} Valore letto con il nuovo tipo
|
|
643
|
+
*/
|
|
644
|
+
function cast(ptr, targetType) {
|
|
645
|
+
// Se è un tipo struct
|
|
646
|
+
if (typeof targetType === "object" && targetType.size !== undefined) {
|
|
647
|
+
// È una struct, restituisci un oggetto che permette di leggere i campi
|
|
648
|
+
const buf = Buffer.isBuffer(ptr) ? ptr : ptrToBuffer(ptr, targetType.size);
|
|
649
|
+
return {
|
|
650
|
+
_buffer: buf,
|
|
651
|
+
_struct: targetType,
|
|
652
|
+
get contents() {
|
|
653
|
+
return targetType.toObject(buf);
|
|
654
|
+
},
|
|
655
|
+
getField(name) {
|
|
656
|
+
return targetType.get(buf, name);
|
|
657
|
+
},
|
|
658
|
+
setField(name, value) {
|
|
659
|
+
targetType.set(buf, name, value);
|
|
660
|
+
},
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Se è un tipo base, leggi il valore
|
|
665
|
+
if (Buffer.isBuffer(ptr)) {
|
|
666
|
+
return readValue(ptr, targetType, 0);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// È un BigInt/number, converti in buffer temporaneo e leggi
|
|
670
|
+
const tempBuf = ptrToBuffer(ptr, sizeof(targetType));
|
|
671
|
+
return readValue(tempBuf, targetType, 0);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Crea un buffer che punta a un indirizzo di memoria
|
|
676
|
+
* ATTENZIONE: Usare con cautela! Accesso a memoria non valida causa crash.
|
|
677
|
+
*
|
|
678
|
+
* @param {BigInt|number} address - Indirizzo di memoria
|
|
679
|
+
* @param {number} size - Dimensione del buffer
|
|
680
|
+
* @returns {Buffer} Buffer che punta all'indirizzo
|
|
681
|
+
*/
|
|
682
|
+
function ptrToBuffer(address, size) {
|
|
683
|
+
return native.ptrToBuffer(address, size);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Crea un tipo POINTER(type) - puntatore a un tipo specifico
|
|
688
|
+
*
|
|
689
|
+
* @param {string|Object} baseType - Tipo base
|
|
690
|
+
* @returns {Object} Tipo puntatore
|
|
691
|
+
*/
|
|
692
|
+
function POINTER(baseType) {
|
|
693
|
+
const baseSize =
|
|
694
|
+
typeof baseType === "object" ? baseType.size : sizeof(baseType);
|
|
695
|
+
const typeName = typeof baseType === "object" ? "struct" : baseType;
|
|
696
|
+
|
|
697
|
+
return {
|
|
698
|
+
_pointerTo: baseType,
|
|
699
|
+
_baseSize: baseSize,
|
|
700
|
+
size: native.POINTER_SIZE,
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Crea un puntatore NULL
|
|
704
|
+
*/
|
|
705
|
+
create() {
|
|
706
|
+
const buf = alloc(native.POINTER_SIZE);
|
|
707
|
+
writeValue(buf, c_void_p, 0n);
|
|
708
|
+
return buf;
|
|
709
|
+
},
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Crea un puntatore a un buffer esistente
|
|
713
|
+
*/
|
|
714
|
+
fromBuffer(targetBuf) {
|
|
715
|
+
const buf = alloc(native.POINTER_SIZE);
|
|
716
|
+
writeValue(buf, c_void_p, targetBuf);
|
|
717
|
+
return buf;
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Legge il puntatore (dereferenzia)
|
|
722
|
+
*/
|
|
723
|
+
deref(ptrBuf) {
|
|
724
|
+
const addr = readValue(ptrBuf, c_void_p);
|
|
725
|
+
if (addr === 0n || addr === null) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
// Per struct, restituisci wrapper
|
|
729
|
+
if (typeof baseType === "object" && baseType.toObject) {
|
|
730
|
+
// Non possiamo creare buffer a indirizzo arbitrario senza native
|
|
731
|
+
// Restituiamo l'indirizzo
|
|
732
|
+
return addr;
|
|
733
|
+
}
|
|
734
|
+
// Per tipi base, non possiamo leggere senza native.ptrToBuffer
|
|
735
|
+
return addr;
|
|
736
|
+
},
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Scrive un indirizzo nel puntatore
|
|
740
|
+
*/
|
|
741
|
+
set(ptrBuf, value) {
|
|
742
|
+
if (Buffer.isBuffer(value)) {
|
|
743
|
+
writeValue(ptrBuf, c_void_p, value);
|
|
744
|
+
} else {
|
|
745
|
+
writeValue(ptrBuf, c_void_p, value);
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
|
|
749
|
+
toString() {
|
|
750
|
+
return `POINTER(${typeName})`;
|
|
751
|
+
},
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Helper per definire union (tutti i campi condividono offset 0)
|
|
757
|
+
* Supporta nested structs e bitfields come struct()
|
|
758
|
+
* @param {Object} fields - Definizione dei campi { name: type, ... }
|
|
759
|
+
* @returns {Object} Definizione della union
|
|
760
|
+
*/
|
|
761
|
+
function union(fields) {
|
|
762
|
+
let maxSize = 0;
|
|
763
|
+
let maxAlignment = 1;
|
|
764
|
+
const fieldDefs = [];
|
|
765
|
+
|
|
766
|
+
for (const [name, type] of Object.entries(fields)) {
|
|
767
|
+
let size, alignment;
|
|
768
|
+
let fieldDef = { name, type, offset: 0 };
|
|
769
|
+
|
|
770
|
+
// Nested struct
|
|
771
|
+
if (_isStruct(type)) {
|
|
772
|
+
size = type.size;
|
|
773
|
+
alignment = type.alignment;
|
|
774
|
+
fieldDef.isNested = true;
|
|
775
|
+
}
|
|
776
|
+
// Array type
|
|
777
|
+
else if (_isArrayType(type)) {
|
|
778
|
+
size = type.getSize();
|
|
779
|
+
const elemSize = sizeof(type.elementType);
|
|
780
|
+
alignment = Math.min(elemSize, native.POINTER_SIZE);
|
|
781
|
+
fieldDef.isArray = true;
|
|
782
|
+
}
|
|
783
|
+
// Bit field - in union ogni bitfield occupa l'intero baseType
|
|
784
|
+
else if (_isBitField(type)) {
|
|
785
|
+
size = type.baseSize;
|
|
786
|
+
alignment = Math.min(size, native.POINTER_SIZE);
|
|
787
|
+
fieldDef.isBitField = true;
|
|
788
|
+
fieldDef.bitOffset = 0;
|
|
789
|
+
fieldDef.bitSize = type.bits;
|
|
790
|
+
fieldDef.baseSize = type.baseSize;
|
|
791
|
+
fieldDef.type = type.baseType; // Usa il tipo base per lettura/scrittura
|
|
792
|
+
}
|
|
793
|
+
// Tipo base (SimpleCData)
|
|
794
|
+
else {
|
|
795
|
+
// Validate type is a SimpleCData class
|
|
796
|
+
if (!(typeof type === "function" && type._isSimpleCData)) {
|
|
797
|
+
throw new TypeError(
|
|
798
|
+
`union field "${name}": type must be a SimpleCData class, struct, union, or array`,
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
size = type._size;
|
|
802
|
+
alignment = Math.min(size, native.POINTER_SIZE);
|
|
803
|
+
fieldDef.type = type;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
fieldDef.size = size;
|
|
807
|
+
fieldDef.alignment = alignment;
|
|
808
|
+
fieldDefs.push(fieldDef);
|
|
809
|
+
|
|
810
|
+
if (size > maxSize) {
|
|
811
|
+
maxSize = size;
|
|
812
|
+
}
|
|
813
|
+
if (alignment > maxAlignment) {
|
|
814
|
+
maxAlignment = alignment;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Padding finale per allineamento
|
|
819
|
+
if (maxSize % maxAlignment !== 0) {
|
|
820
|
+
maxSize += maxAlignment - (maxSize % maxAlignment);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Crea Map per lookup O(1)
|
|
824
|
+
const fieldMap = new Map();
|
|
825
|
+
for (const field of fieldDefs) {
|
|
826
|
+
fieldMap.set(field.name, field);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const unionDef = {
|
|
830
|
+
size: maxSize,
|
|
831
|
+
alignment: maxAlignment,
|
|
832
|
+
fields: fieldDefs,
|
|
833
|
+
isUnion: true,
|
|
834
|
+
_isStructType: true, // Per compatibilità con _isStruct()
|
|
835
|
+
|
|
836
|
+
create(values = {}) {
|
|
837
|
+
const buf = alloc(maxSize);
|
|
838
|
+
buf.fill(0);
|
|
839
|
+
|
|
840
|
+
// Prima imposta i campi diretti
|
|
841
|
+
for (const field of fieldDefs) {
|
|
842
|
+
if (values[field.name] !== undefined) {
|
|
843
|
+
unionDef.set(buf, field.name, values[field.name]);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Proxy-based instance (single Proxy instead of N defineProperty calls)
|
|
848
|
+
return new Proxy(buf, {
|
|
849
|
+
get(target, prop, receiver) {
|
|
850
|
+
// Special properties
|
|
851
|
+
if (prop === "_buffer") return target;
|
|
852
|
+
if (prop === "toObject") return () => unionDef.toObject(target);
|
|
853
|
+
if (prop === Symbol.toStringTag) return "UnionInstance";
|
|
854
|
+
if (prop === Symbol.iterator) return undefined;
|
|
855
|
+
|
|
856
|
+
// Handle Buffer/TypedArray properties FIRST before field checks
|
|
857
|
+
if (
|
|
858
|
+
prop === "length" ||
|
|
859
|
+
prop === "byteLength" ||
|
|
860
|
+
prop === "byteOffset" ||
|
|
861
|
+
prop === "buffer" ||
|
|
862
|
+
prop === "BYTES_PER_ELEMENT"
|
|
863
|
+
) {
|
|
864
|
+
return target[prop];
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Check if it's a known field
|
|
868
|
+
if (typeof prop === "string" && fieldMap.has(prop)) {
|
|
869
|
+
return unionDef.get(target, prop);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Fallback to buffer properties and methods
|
|
873
|
+
const value = target[prop];
|
|
874
|
+
if (typeof value === "function") {
|
|
875
|
+
return value.bind(target);
|
|
876
|
+
}
|
|
877
|
+
return value;
|
|
878
|
+
},
|
|
879
|
+
set(target, prop, value, receiver) {
|
|
880
|
+
// Check if it's a known field
|
|
881
|
+
if (typeof prop === "string" && fieldMap.has(prop)) {
|
|
882
|
+
unionDef.set(target, prop, value);
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// Fallback
|
|
887
|
+
return Reflect.set(target, prop, value, receiver);
|
|
888
|
+
},
|
|
889
|
+
has(target, prop) {
|
|
890
|
+
if (prop === "_buffer" || prop === "toObject") return true;
|
|
891
|
+
if (typeof prop === "string" && fieldMap.has(prop)) return true;
|
|
892
|
+
return Reflect.has(target, prop);
|
|
893
|
+
},
|
|
894
|
+
ownKeys(target) {
|
|
895
|
+
return fieldDefs.map((f) => f.name);
|
|
896
|
+
},
|
|
897
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
898
|
+
if (
|
|
899
|
+
typeof prop === "string" &&
|
|
900
|
+
(fieldMap.has(prop) || prop === "_buffer" || prop === "toObject")
|
|
901
|
+
) {
|
|
902
|
+
return {
|
|
903
|
+
enumerable: prop !== "_buffer" && prop !== "toObject",
|
|
904
|
+
configurable: true,
|
|
905
|
+
writable: true,
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
909
|
+
},
|
|
910
|
+
});
|
|
911
|
+
},
|
|
912
|
+
|
|
913
|
+
get(bufOrObj, fieldName) {
|
|
914
|
+
// NUOVO: supporta sia buffer che object wrapper
|
|
915
|
+
let buf;
|
|
916
|
+
if (Buffer.isBuffer(bufOrObj)) {
|
|
917
|
+
buf = bufOrObj;
|
|
918
|
+
} else if (bufOrObj && bufOrObj._buffer) {
|
|
919
|
+
// Object wrapper - accesso diretto tramite property
|
|
920
|
+
return bufOrObj[fieldName];
|
|
921
|
+
} else {
|
|
922
|
+
throw new TypeError("Expected Buffer or union instance");
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// O(1) lookup con Map
|
|
926
|
+
const field = fieldMap.get(fieldName);
|
|
927
|
+
if (!field) throw new Error(`Unknown field: ${fieldName}`);
|
|
928
|
+
|
|
929
|
+
// Bit field
|
|
930
|
+
if (field.isBitField) {
|
|
931
|
+
return _readBitField(
|
|
932
|
+
buf,
|
|
933
|
+
field.offset,
|
|
934
|
+
field.type,
|
|
935
|
+
field.bitOffset,
|
|
936
|
+
field.bitSize,
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Nested struct
|
|
941
|
+
if (field.isNested) {
|
|
942
|
+
const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
943
|
+
// Proxy-based nested instance
|
|
944
|
+
return new Proxy(nestedBuf, {
|
|
945
|
+
get(target, prop, receiver) {
|
|
946
|
+
if (prop === "_buffer") return target;
|
|
947
|
+
if (prop === "toObject") return () => field.type.toObject(target);
|
|
948
|
+
if (prop === Symbol.toStringTag) return "NestedUnionInstance";
|
|
949
|
+
if (prop === Symbol.iterator) return undefined;
|
|
950
|
+
// Handle Buffer/TypedArray properties
|
|
951
|
+
if (
|
|
952
|
+
prop === "length" ||
|
|
953
|
+
prop === "byteLength" ||
|
|
954
|
+
prop === "byteOffset" ||
|
|
955
|
+
prop === "buffer" ||
|
|
956
|
+
prop === "BYTES_PER_ELEMENT"
|
|
957
|
+
) {
|
|
958
|
+
return target[prop];
|
|
959
|
+
}
|
|
960
|
+
if (typeof prop === "string" && field.type.fields) {
|
|
961
|
+
if (
|
|
962
|
+
field.type.fields.some((f) => f.name === prop && !f.isAnonymous)
|
|
963
|
+
) {
|
|
964
|
+
return field.type.get(target, prop);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
const value = target[prop];
|
|
968
|
+
if (typeof value === "function") return value.bind(target);
|
|
969
|
+
return value;
|
|
970
|
+
},
|
|
971
|
+
set(target, prop, value, receiver) {
|
|
972
|
+
if (typeof prop === "string" && field.type.fields) {
|
|
973
|
+
if (
|
|
974
|
+
field.type.fields.some((f) => f.name === prop && !f.isAnonymous)
|
|
975
|
+
) {
|
|
976
|
+
field.type.set(target, prop, value);
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return Reflect.set(target, prop, value, receiver);
|
|
981
|
+
},
|
|
982
|
+
has(target, prop) {
|
|
983
|
+
if (prop === "_buffer" || prop === "toObject") return true;
|
|
984
|
+
if (typeof prop === "string" && field.type.fields) {
|
|
985
|
+
if (field.type.fields.some((f) => f.name === prop)) return true;
|
|
986
|
+
}
|
|
987
|
+
return Reflect.has(target, prop);
|
|
988
|
+
},
|
|
989
|
+
ownKeys(target) {
|
|
990
|
+
return (field.type.fields || [])
|
|
991
|
+
.filter((f) => !f.isAnonymous)
|
|
992
|
+
.map((f) => f.name);
|
|
993
|
+
},
|
|
994
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
995
|
+
if (
|
|
996
|
+
typeof prop === "string" &&
|
|
997
|
+
field.type.fields &&
|
|
998
|
+
field.type.fields.some((f) => f.name === prop)
|
|
999
|
+
) {
|
|
1000
|
+
return {
|
|
1001
|
+
enumerable: true,
|
|
1002
|
+
configurable: true,
|
|
1003
|
+
writable: true,
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
1007
|
+
},
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Array
|
|
1012
|
+
if (field.isArray) {
|
|
1013
|
+
return field.type.wrap(
|
|
1014
|
+
buf.subarray(field.offset, field.offset + field.size),
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Tipo base
|
|
1019
|
+
return readValue(buf, field.type, field.offset);
|
|
1020
|
+
},
|
|
1021
|
+
|
|
1022
|
+
set(bufOrObj, fieldName, value) {
|
|
1023
|
+
// NUOVO: supporta sia buffer che object wrapper
|
|
1024
|
+
let buf;
|
|
1025
|
+
if (Buffer.isBuffer(bufOrObj)) {
|
|
1026
|
+
buf = bufOrObj;
|
|
1027
|
+
} else if (bufOrObj && bufOrObj._buffer) {
|
|
1028
|
+
// Object wrapper - accesso diretto tramite property
|
|
1029
|
+
bufOrObj[fieldName] = value;
|
|
1030
|
+
return;
|
|
1031
|
+
} else {
|
|
1032
|
+
throw new TypeError("Expected Buffer or union instance");
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Lookup with Map
|
|
1036
|
+
const field = fieldMap.get(fieldName);
|
|
1037
|
+
if (!field) throw new Error(`Unknown field: ${fieldName}`);
|
|
1038
|
+
|
|
1039
|
+
// Bit field
|
|
1040
|
+
if (field.isBitField) {
|
|
1041
|
+
_writeBitField(
|
|
1042
|
+
buf,
|
|
1043
|
+
field.offset,
|
|
1044
|
+
field.type,
|
|
1045
|
+
field.bitOffset,
|
|
1046
|
+
field.bitSize,
|
|
1047
|
+
value,
|
|
1048
|
+
);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Nested struct
|
|
1053
|
+
if (field.isNested) {
|
|
1054
|
+
if (Buffer.isBuffer(value)) {
|
|
1055
|
+
value.copy(buf, field.offset, 0, field.size);
|
|
1056
|
+
} else if (typeof value === "object") {
|
|
1057
|
+
const nestedBuf = buf.subarray(
|
|
1058
|
+
field.offset,
|
|
1059
|
+
field.offset + field.size,
|
|
1060
|
+
);
|
|
1061
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1062
|
+
field.type.set(nestedBuf, k, v);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Array
|
|
1069
|
+
if (field.isArray) {
|
|
1070
|
+
if (Buffer.isBuffer(value)) {
|
|
1071
|
+
value.copy(buf, field.offset, 0, field.size);
|
|
1072
|
+
} else if (Array.isArray(value)) {
|
|
1073
|
+
const wrapped = field.type.wrap(
|
|
1074
|
+
buf.subarray(field.offset, field.offset + field.size),
|
|
1075
|
+
);
|
|
1076
|
+
for (let i = 0; i < Math.min(value.length, field.type.length); i++) {
|
|
1077
|
+
wrapped[i] = value[i];
|
|
1078
|
+
}
|
|
1079
|
+
} else if (value && value._buffer && Buffer.isBuffer(value._buffer)) {
|
|
1080
|
+
// Handle array proxy instances
|
|
1081
|
+
value._buffer.copy(buf, field.offset, 0, field.size);
|
|
1082
|
+
}
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// Tipo base
|
|
1087
|
+
writeValue(buf, field.type, value, field.offset);
|
|
1088
|
+
},
|
|
1089
|
+
|
|
1090
|
+
toObject(bufOrObj) {
|
|
1091
|
+
// Se è già un object (non buffer), ritorna così com'è
|
|
1092
|
+
if (!Buffer.isBuffer(bufOrObj)) {
|
|
1093
|
+
if (
|
|
1094
|
+
bufOrObj &&
|
|
1095
|
+
bufOrObj._buffer &&
|
|
1096
|
+
bufOrObj._buffer.length === maxSize
|
|
1097
|
+
) {
|
|
1098
|
+
return bufOrObj; // Già convertito
|
|
1099
|
+
}
|
|
1100
|
+
// Se è un plain object, convertilo in union
|
|
1101
|
+
if (typeof bufOrObj === "object") {
|
|
1102
|
+
const buf = alloc(maxSize);
|
|
1103
|
+
buf.fill(0);
|
|
1104
|
+
for (const [name, value] of Object.entries(bufOrObj)) {
|
|
1105
|
+
unionDef.set(buf, name, value);
|
|
1106
|
+
}
|
|
1107
|
+
return unionDef.toObject(buf);
|
|
1108
|
+
}
|
|
1109
|
+
throw new TypeError("Expected Buffer or union instance");
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
const buf = bufOrObj;
|
|
1113
|
+
const obj = {};
|
|
1114
|
+
|
|
1115
|
+
// Cache per oggetti nested (struct/union) per evitare di ricrearli ogni volta
|
|
1116
|
+
const nestedCache = new Map();
|
|
1117
|
+
|
|
1118
|
+
// Aggiungi _buffer come property nascosta
|
|
1119
|
+
Object.defineProperty(obj, "_buffer", {
|
|
1120
|
+
value: buf,
|
|
1121
|
+
writable: false,
|
|
1122
|
+
enumerable: false,
|
|
1123
|
+
configurable: false,
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
// Per union, tutte le properties leggono/scrivono all'offset 0
|
|
1127
|
+
for (const field of fieldDefs) {
|
|
1128
|
+
Object.defineProperty(obj, field.name, {
|
|
1129
|
+
get() {
|
|
1130
|
+
if (field.isBitField) {
|
|
1131
|
+
return _readBitField(
|
|
1132
|
+
buf,
|
|
1133
|
+
0,
|
|
1134
|
+
field.type,
|
|
1135
|
+
field.bitOffset,
|
|
1136
|
+
field.bitSize,
|
|
1137
|
+
);
|
|
1138
|
+
} else if (field.isNested) {
|
|
1139
|
+
// Cache nested objects per evitare di ricrearli ogni volta
|
|
1140
|
+
if (!nestedCache.has(field.name)) {
|
|
1141
|
+
const nestedBuf = buf.subarray(0, field.size);
|
|
1142
|
+
const nestedObj = field.type.toObject(nestedBuf);
|
|
1143
|
+
nestedCache.set(field.name, nestedObj);
|
|
1144
|
+
}
|
|
1145
|
+
return nestedCache.get(field.name);
|
|
1146
|
+
} else if (field.isArray) {
|
|
1147
|
+
const wrapped = field.type.wrap(buf.subarray(0, field.size));
|
|
1148
|
+
return [...wrapped];
|
|
1149
|
+
} else {
|
|
1150
|
+
return readValue(buf, field.type, 0);
|
|
1151
|
+
}
|
|
1152
|
+
},
|
|
1153
|
+
set(value) {
|
|
1154
|
+
if (field.isBitField) {
|
|
1155
|
+
_writeBitField(
|
|
1156
|
+
buf,
|
|
1157
|
+
0,
|
|
1158
|
+
field.type,
|
|
1159
|
+
field.bitOffset,
|
|
1160
|
+
field.bitSize,
|
|
1161
|
+
value,
|
|
1162
|
+
);
|
|
1163
|
+
} else {
|
|
1164
|
+
writeValue(buf, field.type, value, 0);
|
|
1165
|
+
}
|
|
1166
|
+
},
|
|
1167
|
+
enumerable: true,
|
|
1168
|
+
configurable: false,
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
return obj;
|
|
1173
|
+
},
|
|
1174
|
+
|
|
1175
|
+
// fromObject: write plain object values into buffer
|
|
1176
|
+
fromObject: function (buf, obj) {
|
|
1177
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1178
|
+
if (this.fields.some((f) => f.name === key)) {
|
|
1179
|
+
this.set(buf, key, value);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
},
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
return unionDef;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Helper to define a JS class backed by a struct definition.
|
|
1190
|
+
* Useful for TypeScript inference: const Point = defineStruct({ x: 'int32', y: 'int32' }, { packed: false });
|
|
1191
|
+
*/
|
|
1192
|
+
function defineStruct(fields, options) {
|
|
1193
|
+
const def = struct(fields, options);
|
|
1194
|
+
class AutoStruct extends Structure {}
|
|
1195
|
+
// cache structDef on the class so constructor picks it up
|
|
1196
|
+
AutoStruct._structDef = def;
|
|
1197
|
+
AutoStruct._fields_ = fields;
|
|
1198
|
+
return AutoStruct;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Helper to define a JS class backed by a union definition.
|
|
1203
|
+
*/
|
|
1204
|
+
function defineUnion(fields) {
|
|
1205
|
+
const def = union(fields);
|
|
1206
|
+
class AutoUnion extends Union {}
|
|
1207
|
+
AutoUnion._structDef = def;
|
|
1208
|
+
AutoUnion._fields_ = fields;
|
|
1209
|
+
return AutoUnion;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// --------------------------------------------------------------------------
|
|
1213
|
+
// Python-compatible class wrappers to allow `class X extends Structure` and
|
|
1214
|
+
// `class Y extends Union` patterns similar to Python ctypes.
|
|
1215
|
+
// These classes read static properties on the subclass:
|
|
1216
|
+
// - `_fields_`: array of [name, type] or object map { name: type }
|
|
1217
|
+
// - `_anonymous_`: optional array of anonymous field names (for structs)
|
|
1218
|
+
// - `_pack_`: optional packing value (true/false)
|
|
1219
|
+
// The classes expose `create()`, `toObject()`, `get()`, `set()` and a hidden
|
|
1220
|
+
// `_buffer` when an instance is created via `new`.
|
|
1221
|
+
// --------------------------------------------------------------------------
|
|
1222
|
+
|
|
1223
|
+
class Structure {
|
|
1224
|
+
constructor(...args) {
|
|
1225
|
+
const Ctor = this.constructor;
|
|
1226
|
+
const def = Ctor._structDef || Ctor._buildStruct();
|
|
1227
|
+
// Allocate buffer for instance
|
|
1228
|
+
let buf = alloc(def.size);
|
|
1229
|
+
buf.fill(0);
|
|
1230
|
+
|
|
1231
|
+
// Support positional args: new Point(10,20)
|
|
1232
|
+
if (
|
|
1233
|
+
args.length === 1 &&
|
|
1234
|
+
typeof args[0] === "object" &&
|
|
1235
|
+
!Array.isArray(args[0]) &&
|
|
1236
|
+
!Buffer.isBuffer(args[0])
|
|
1237
|
+
) {
|
|
1238
|
+
const initial = args[0];
|
|
1239
|
+
for (const [k, v] of Object.entries(initial)) {
|
|
1240
|
+
def.set(buf, k, v);
|
|
1241
|
+
}
|
|
1242
|
+
} else if (args.length === 1 && Buffer.isBuffer(args[0])) {
|
|
1243
|
+
// new Point(buffer) - wrap existing buffer
|
|
1244
|
+
buf = args[0];
|
|
1245
|
+
} else if (args.length > 0) {
|
|
1246
|
+
// Positional mapping to non-anonymous declared fields order
|
|
1247
|
+
const ordered = def.fields.filter((f) => !f.isAnonymous);
|
|
1248
|
+
for (let i = 0; i < Math.min(args.length, ordered.length); i++) {
|
|
1249
|
+
def.set(buf, ordered[i].name, args[i]);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// Store internal references on this (needed for methods like toObject)
|
|
1254
|
+
this.__buffer = buf;
|
|
1255
|
+
this.__structDef = def;
|
|
1256
|
+
|
|
1257
|
+
// Build field map for O(1) lookup
|
|
1258
|
+
const fieldMap = new Map();
|
|
1259
|
+
const anonFieldNames = new Set();
|
|
1260
|
+
for (const field of def.fields) {
|
|
1261
|
+
fieldMap.set(field.name, field);
|
|
1262
|
+
if (field.isAnonymous && field.type && Array.isArray(field.type.fields)) {
|
|
1263
|
+
for (const subField of field.type.fields) {
|
|
1264
|
+
anonFieldNames.add(subField.name);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// Return Proxy instead of using Object.defineProperty for each field
|
|
1270
|
+
return new Proxy(this, {
|
|
1271
|
+
get(target, prop, receiver) {
|
|
1272
|
+
// Special properties that exist on the instance
|
|
1273
|
+
if (prop === "_buffer") return buf;
|
|
1274
|
+
if (prop === "_structDef") return def;
|
|
1275
|
+
if (prop === "__buffer") return buf;
|
|
1276
|
+
if (prop === "__structDef") return def;
|
|
1277
|
+
if (prop === Symbol.toStringTag) return Ctor.name || "Structure";
|
|
1278
|
+
if (prop === Symbol.iterator) return undefined;
|
|
1279
|
+
|
|
1280
|
+
// Methods defined on the prototype
|
|
1281
|
+
if (typeof target[prop] === "function") {
|
|
1282
|
+
return target[prop].bind(target);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// Check if it's a known field (O(1) lookup)
|
|
1286
|
+
if (typeof prop === "string" && fieldMap.has(prop)) {
|
|
1287
|
+
return def.get(buf, prop);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// Check anonymous fields
|
|
1291
|
+
if (typeof prop === "string" && anonFieldNames.has(prop)) {
|
|
1292
|
+
return def.get(buf, prop);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// Fallback to target properties
|
|
1296
|
+
return Reflect.get(target, prop, receiver);
|
|
1297
|
+
},
|
|
1298
|
+
set(target, prop, value, receiver) {
|
|
1299
|
+
// Check if it's a known field (O(1) lookup)
|
|
1300
|
+
if (typeof prop === "string" && fieldMap.has(prop)) {
|
|
1301
|
+
def.set(buf, prop, value);
|
|
1302
|
+
return true;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// Check anonymous fields
|
|
1306
|
+
if (typeof prop === "string" && anonFieldNames.has(prop)) {
|
|
1307
|
+
def.set(buf, prop, value);
|
|
1308
|
+
return true;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// Fallback
|
|
1312
|
+
return Reflect.set(target, prop, value, receiver);
|
|
1313
|
+
},
|
|
1314
|
+
has(target, prop) {
|
|
1315
|
+
if (prop === "_buffer" || prop === "_structDef" || prop === "toObject")
|
|
1316
|
+
return true;
|
|
1317
|
+
if (typeof prop === "string" && fieldMap.has(prop)) return true;
|
|
1318
|
+
if (typeof prop === "string" && anonFieldNames.has(prop)) return true;
|
|
1319
|
+
return Reflect.has(target, prop);
|
|
1320
|
+
},
|
|
1321
|
+
ownKeys(target) {
|
|
1322
|
+
const keys = [];
|
|
1323
|
+
for (const f of def.fields) {
|
|
1324
|
+
if (f.isAnonymous && f.type && Array.isArray(f.type.fields)) {
|
|
1325
|
+
for (const sf of f.type.fields) {
|
|
1326
|
+
keys.push(sf.name);
|
|
1327
|
+
}
|
|
1328
|
+
} else {
|
|
1329
|
+
keys.push(f.name);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
return keys;
|
|
1333
|
+
},
|
|
1334
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
1335
|
+
if (
|
|
1336
|
+
typeof prop === "string" &&
|
|
1337
|
+
(fieldMap.has(prop) || anonFieldNames.has(prop))
|
|
1338
|
+
) {
|
|
1339
|
+
return {
|
|
1340
|
+
enumerable: true,
|
|
1341
|
+
configurable: true,
|
|
1342
|
+
writable: true,
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
if (prop === "_buffer" || prop === "_structDef") {
|
|
1346
|
+
return {
|
|
1347
|
+
enumerable: false,
|
|
1348
|
+
configurable: true,
|
|
1349
|
+
writable: true,
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
1353
|
+
},
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// Build the underlying struct definition and cache it on the constructor
|
|
1358
|
+
static _buildStruct() {
|
|
1359
|
+
// Accept either array _fields_ (Python style) or object map
|
|
1360
|
+
const fields = this._fields_ || {};
|
|
1361
|
+
let mapFields = {};
|
|
1362
|
+
|
|
1363
|
+
if (Array.isArray(fields)) {
|
|
1364
|
+
for (const entry of fields) {
|
|
1365
|
+
if (Array.isArray(entry) && entry.length >= 2) {
|
|
1366
|
+
mapFields[entry[0]] = entry[1];
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
} else if (typeof fields === "object" && fields !== null) {
|
|
1370
|
+
mapFields = { ...fields };
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// Handle anonymous fields list: convert to { anonymous: true, type: T }
|
|
1374
|
+
if (Array.isArray(this._anonymous_)) {
|
|
1375
|
+
for (const anonName of this._anonymous_) {
|
|
1376
|
+
if (mapFields[anonName] !== undefined) {
|
|
1377
|
+
mapFields[anonName] = {
|
|
1378
|
+
anonymous: true,
|
|
1379
|
+
type: mapFields[anonName],
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
const options = {};
|
|
1386
|
+
if (this._pack_ !== undefined) options.packed = !!this._pack_;
|
|
1387
|
+
|
|
1388
|
+
const def = struct(mapFields, options);
|
|
1389
|
+
this._structDef = def;
|
|
1390
|
+
return def;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// Create returns an instance of the JS class (not a plain object)
|
|
1394
|
+
static create(values = {}) {
|
|
1395
|
+
const def = this._structDef || this._buildStruct();
|
|
1396
|
+
// If a Buffer provided, wrap it
|
|
1397
|
+
if (Buffer.isBuffer(values)) {
|
|
1398
|
+
const inst = new this(values);
|
|
1399
|
+
return inst;
|
|
1400
|
+
}
|
|
1401
|
+
// If values is instance of this class, return it
|
|
1402
|
+
if (values && values._buffer && values._structDef === def) {
|
|
1403
|
+
return values;
|
|
1404
|
+
}
|
|
1405
|
+
return new this(values);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// Create raw plain object without synchronization (for performance)
|
|
1409
|
+
static createRaw(values = {}) {
|
|
1410
|
+
const def = this._structDef || this._buildStruct();
|
|
1411
|
+
return def.toObject(def.create(values)._buffer);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// Synchronize plain object into instance buffer
|
|
1415
|
+
syncFromObject(obj) {
|
|
1416
|
+
const plain = this.__structDef.toObject(this.__buffer);
|
|
1417
|
+
Object.assign(plain, obj);
|
|
1418
|
+
this.__structDef.fromObject(this.__buffer, plain);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// toObject: accept Buffer, instance or plain buffer
|
|
1422
|
+
static toObject(bufOrInst) {
|
|
1423
|
+
const def = this._structDef || this._buildStruct();
|
|
1424
|
+
if (bufOrInst && bufOrInst._buffer) {
|
|
1425
|
+
return def.toObject(bufOrInst._buffer);
|
|
1426
|
+
}
|
|
1427
|
+
return def.toObject(bufOrInst);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// fromObject: write plain object into buffer
|
|
1431
|
+
static fromObject(bufOrInst, obj) {
|
|
1432
|
+
const def = this._structDef || this._buildStruct();
|
|
1433
|
+
const buf = bufOrInst && bufOrInst._buffer ? bufOrInst._buffer : bufOrInst;
|
|
1434
|
+
return def.fromObject(buf, obj);
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// Instance convenience
|
|
1438
|
+
get(fieldName) {
|
|
1439
|
+
return this.__structDef.get(this.__buffer, fieldName);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
set(fieldName, value) {
|
|
1443
|
+
return this.__structDef.set(this.__buffer, fieldName, value);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
// Bulk operations for better performance
|
|
1447
|
+
setFields(fields) {
|
|
1448
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
1449
|
+
this.__structDef.set(this.__buffer, name, value);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
getFields(fieldNames) {
|
|
1454
|
+
const result = {};
|
|
1455
|
+
for (const name of fieldNames) {
|
|
1456
|
+
result[name] = this.__structDef.get(this.__buffer, name);
|
|
1457
|
+
}
|
|
1458
|
+
return result;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// bulk operations (Proxy reads directly from buffer - no cache needed)
|
|
1462
|
+
withBulkUpdate(callback) {
|
|
1463
|
+
return callback(this);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// Direct typed array access for numeric fields (maximum performance)
|
|
1467
|
+
getInt32Array(offset = 0, length) {
|
|
1468
|
+
return new Int32Array(
|
|
1469
|
+
this.__buffer.buffer,
|
|
1470
|
+
this.__buffer.byteOffset + offset,
|
|
1471
|
+
length,
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
getFloat64Array(offset = 0, length) {
|
|
1476
|
+
return new Float64Array(
|
|
1477
|
+
this.__buffer.buffer,
|
|
1478
|
+
this.__buffer.byteOffset + offset,
|
|
1479
|
+
length,
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// Get raw buffer slice for external operations
|
|
1484
|
+
getBufferSlice(offset = 0, size = this.__buffer.length - offset) {
|
|
1485
|
+
return this.__buffer.subarray(offset, offset + size);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// Vectorized operations for arrays of structs
|
|
1489
|
+
static createArray(count, initialValues = []) {
|
|
1490
|
+
const instances = [];
|
|
1491
|
+
for (let i = 0; i < count; i++) {
|
|
1492
|
+
instances.push(this.create(initialValues[i] || {}));
|
|
1493
|
+
}
|
|
1494
|
+
return instances;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// Bulk update all instances in array
|
|
1498
|
+
static updateArray(instances, updates) {
|
|
1499
|
+
for (let i = 0; i < instances.length && i < updates.length; i++) {
|
|
1500
|
+
if (updates[i] && typeof updates[i] === "object") {
|
|
1501
|
+
instances[i].setFields(updates[i]);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
toObject() {
|
|
1507
|
+
return this.__structDef.toObject(this.__buffer);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
class Union extends Structure {
|
|
1512
|
+
static _buildStruct() {
|
|
1513
|
+
const fields = this._fields_ || {};
|
|
1514
|
+
let mapFields = {};
|
|
1515
|
+
if (Array.isArray(fields)) {
|
|
1516
|
+
for (const entry of fields) {
|
|
1517
|
+
if (Array.isArray(entry) && entry.length >= 2) {
|
|
1518
|
+
mapFields[entry[0]] = entry[1];
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
} else if (typeof fields === "object" && fields !== null) {
|
|
1522
|
+
mapFields = fields;
|
|
1523
|
+
}
|
|
1524
|
+
const def = union(mapFields);
|
|
1525
|
+
this._structDef = def;
|
|
1526
|
+
return def;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/**
|
|
1531
|
+
* Helper per creare array a dimensione fissa (come c_int * 5 in Python)
|
|
1532
|
+
* Ritorna un ArrayType wrapper con supporto per indexing arr[i]
|
|
1533
|
+
*
|
|
1534
|
+
* @param {string} elementType - Tipo degli elementi ('int32', 'float', etc.)
|
|
1535
|
+
* @param {number} count - Numero di elementi
|
|
1536
|
+
* @returns {Object} ArrayType con metodi getSize(), getLength(), create(), wrap()
|
|
1537
|
+
*
|
|
1538
|
+
* @example
|
|
1539
|
+
* const IntArray5 = array('int32', 5);
|
|
1540
|
+
* const arr = IntArray5.create([1, 2, 3, 4, 5]);
|
|
1541
|
+
* console.log(arr[0]); // 1 - indexing Python-like!
|
|
1542
|
+
* arr[2] = 42;
|
|
1543
|
+
* console.log(arr[2]); // 42
|
|
1544
|
+
*/
|
|
1545
|
+
function array(elementType, count) {
|
|
1546
|
+
// Validate elementType is a SimpleCData class
|
|
1547
|
+
if (!(typeof elementType === "function" && elementType._isSimpleCData)) {
|
|
1548
|
+
throw new TypeError(
|
|
1549
|
+
"array elementType must be a SimpleCData class (e.g., c_int32, c_uint8)",
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
const elementSize = elementType._size;
|
|
1554
|
+
let nativeArray;
|
|
1555
|
+
if (typeof elementType === "object") {
|
|
1556
|
+
nativeArray = new ArrayType(elementType, count);
|
|
1557
|
+
} else {
|
|
1558
|
+
// For strings, create a mock
|
|
1559
|
+
nativeArray = {
|
|
1560
|
+
getSize: () => count * elementSize,
|
|
1561
|
+
getLength: () => count,
|
|
1562
|
+
getAlignment: () => elementSize, // Approximation
|
|
1563
|
+
create: (values) => {
|
|
1564
|
+
// Already handled in JS
|
|
1565
|
+
throw new Error("Should not be called");
|
|
1566
|
+
},
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// Funzione wrap che ritorna un Proxy
|
|
1571
|
+
const wrap = function (buffer) {
|
|
1572
|
+
return new Proxy(buffer, {
|
|
1573
|
+
get(target, prop, receiver) {
|
|
1574
|
+
// Special case for _buffer to return the underlying buffer
|
|
1575
|
+
if (prop === "_buffer") {
|
|
1576
|
+
return target;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// Special case for toString to show array contents
|
|
1580
|
+
if (prop === "toString") {
|
|
1581
|
+
return function () {
|
|
1582
|
+
try {
|
|
1583
|
+
const arr = Array.from(this);
|
|
1584
|
+
return `[${arr.join(", ")}]`;
|
|
1585
|
+
} catch (e) {
|
|
1586
|
+
return "[error]";
|
|
1587
|
+
}
|
|
1588
|
+
}.bind(receiver);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// Intercetta Symbol.iterator per iterare sugli elementi, non sui bytes
|
|
1592
|
+
if (prop === Symbol.iterator) {
|
|
1593
|
+
return function* () {
|
|
1594
|
+
for (let i = 0; i < count; i++) {
|
|
1595
|
+
yield elementType._reader(target, i * elementSize);
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
// Ignora altri Symbol
|
|
1601
|
+
if (typeof prop === "symbol") {
|
|
1602
|
+
const value = Reflect.get(target, prop, target);
|
|
1603
|
+
if (typeof value === "function") {
|
|
1604
|
+
return value.bind(target);
|
|
1605
|
+
}
|
|
1606
|
+
return value;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// Se è un numero (indice) - intercetta PRIMA di controllare target
|
|
1610
|
+
const index = Number(prop);
|
|
1611
|
+
if (Number.isInteger(index) && !isNaN(index)) {
|
|
1612
|
+
if (index >= 0 && index < count) {
|
|
1613
|
+
return readValue(target, elementType, index * elementSize);
|
|
1614
|
+
}
|
|
1615
|
+
// Indice numerico fuori bounds -> undefined (comportamento JavaScript)
|
|
1616
|
+
return undefined;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// Per tutto il resto, usa il buffer normale
|
|
1620
|
+
const value = Reflect.get(target, prop, target);
|
|
1621
|
+
// Se è una funzione, bindala al target
|
|
1622
|
+
if (typeof value === "function") {
|
|
1623
|
+
return value.bind(target);
|
|
1624
|
+
}
|
|
1625
|
+
return value;
|
|
1626
|
+
},
|
|
1627
|
+
set(target, prop, value) {
|
|
1628
|
+
// Ignora i Symbol
|
|
1629
|
+
if (typeof prop === "symbol") {
|
|
1630
|
+
return Reflect.set(target, prop, value, target);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
const index = Number(prop);
|
|
1634
|
+
if (Number.isInteger(index) && !isNaN(index)) {
|
|
1635
|
+
if (index >= 0 && index < count) {
|
|
1636
|
+
writeValue(target, elementType, value, index * elementSize);
|
|
1637
|
+
return true;
|
|
1638
|
+
}
|
|
1639
|
+
// Indice fuori bounds - non scrive nulla ma ritorna false
|
|
1640
|
+
return false;
|
|
1641
|
+
}
|
|
1642
|
+
return Reflect.set(target, prop, value, target);
|
|
1643
|
+
},
|
|
1644
|
+
});
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
// Ritorna un wrapper object che delega a nativeArray ma override create()
|
|
1648
|
+
return {
|
|
1649
|
+
// Informazioni sul tipo
|
|
1650
|
+
elementType,
|
|
1651
|
+
length: count,
|
|
1652
|
+
|
|
1653
|
+
// Metodi delegati
|
|
1654
|
+
getSize: () => nativeArray.getSize(),
|
|
1655
|
+
getLength: () => nativeArray.getLength(),
|
|
1656
|
+
getAlignment: () => nativeArray.getAlignment(),
|
|
1657
|
+
|
|
1658
|
+
// create ritorna automaticamente il proxy
|
|
1659
|
+
create: (values) => {
|
|
1660
|
+
const size = count * sizeof(elementType);
|
|
1661
|
+
const buffer = alloc(size);
|
|
1662
|
+
buffer.fill(0);
|
|
1663
|
+
if (Array.isArray(values)) {
|
|
1664
|
+
for (let i = 0; i < Math.min(values.length, count); i++) {
|
|
1665
|
+
writeValue(buffer, elementType, values[i], i * sizeof(elementType));
|
|
1666
|
+
}
|
|
1667
|
+
} else if (typeof values === "string") {
|
|
1668
|
+
for (let i = 0; i < Math.min(values.length, count); i++) {
|
|
1669
|
+
writeValue(
|
|
1670
|
+
buffer,
|
|
1671
|
+
elementType,
|
|
1672
|
+
values.charCodeAt(i),
|
|
1673
|
+
i * sizeof(elementType),
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
return wrap(buffer);
|
|
1678
|
+
},
|
|
1679
|
+
|
|
1680
|
+
// wrap esplicito
|
|
1681
|
+
wrap: wrap,
|
|
1682
|
+
|
|
1683
|
+
// Per compatibilità, esponi il native array (serve per StructType.addField)
|
|
1684
|
+
_native: nativeArray,
|
|
1685
|
+
|
|
1686
|
+
// Helper per verificare se è un ArrayType wrapper
|
|
1687
|
+
_isArrayType: true,
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
// ============================================================================
|
|
1692
|
+
// Error Handling
|
|
1693
|
+
// ============================================================================
|
|
1694
|
+
// ============================================================================
|
|
1695
|
+
|
|
1696
|
+
let _errno_funcs = null;
|
|
1697
|
+
let _win_error_funcs = null;
|
|
1698
|
+
|
|
1699
|
+
/**
|
|
1700
|
+
* Inizializza le funzioni errno (lazy loading)
|
|
1701
|
+
*/
|
|
1702
|
+
function _initErrno() {
|
|
1703
|
+
if (_errno_funcs) return _errno_funcs;
|
|
1704
|
+
|
|
1705
|
+
try {
|
|
1706
|
+
if (process.platform === "win32") {
|
|
1707
|
+
const msvcrt = new CDLL("msvcrt.dll");
|
|
1708
|
+
_errno_funcs = {
|
|
1709
|
+
_get: msvcrt.func("_get_errno", c_int32, [c_void_p]),
|
|
1710
|
+
_set: msvcrt.func("_set_errno", c_int32, [c_int32]),
|
|
1711
|
+
_lib: msvcrt,
|
|
1712
|
+
};
|
|
1713
|
+
} else if (process.platform === "darwin") {
|
|
1714
|
+
// macOS usa __error invece di __errno_location
|
|
1715
|
+
const libc = new CDLL(null);
|
|
1716
|
+
_errno_funcs = {
|
|
1717
|
+
_location: libc.func("__error", c_void_p, []),
|
|
1718
|
+
_lib: libc,
|
|
1719
|
+
};
|
|
1720
|
+
} else {
|
|
1721
|
+
// Linux e altri Unix usano __errno_location
|
|
1722
|
+
const libc = new CDLL(null);
|
|
1723
|
+
_errno_funcs = {
|
|
1724
|
+
_location: libc.func("__errno_location", c_void_p, []),
|
|
1725
|
+
_lib: libc,
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
} catch (e) {
|
|
1729
|
+
_errno_funcs = { error: e.message };
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
return _errno_funcs;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
/**
|
|
1736
|
+
* Ottiene il valore corrente di errno
|
|
1737
|
+
* @returns {number} Valore di errno
|
|
1738
|
+
*/
|
|
1739
|
+
function get_errno() {
|
|
1740
|
+
const funcs = _initErrno();
|
|
1741
|
+
if (funcs.error) {
|
|
1742
|
+
throw new Error("errno not available: " + funcs.error);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
if (process.platform === "win32") {
|
|
1746
|
+
const buf = alloc(4);
|
|
1747
|
+
funcs._get(buf);
|
|
1748
|
+
return readValue(buf, c_int32);
|
|
1749
|
+
} else {
|
|
1750
|
+
const ptr = funcs._location();
|
|
1751
|
+
return readValue(ptr, c_int32);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
/**
|
|
1756
|
+
* Imposta il valore di errno
|
|
1757
|
+
* @param {number} value - Nuovo valore
|
|
1758
|
+
*/
|
|
1759
|
+
function set_errno(value) {
|
|
1760
|
+
const funcs = _initErrno();
|
|
1761
|
+
if (funcs.error) {
|
|
1762
|
+
throw new Error("errno not available: " + funcs.error);
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
if (process.platform === "win32") {
|
|
1766
|
+
funcs._set(value);
|
|
1767
|
+
} else {
|
|
1768
|
+
const ptr = funcs._location();
|
|
1769
|
+
writeValue(ptr, c_int32, value);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
/**
|
|
1774
|
+
* Inizializza le funzioni Windows error (lazy loading)
|
|
1775
|
+
*/
|
|
1776
|
+
function _initWinError() {
|
|
1777
|
+
if (_win_error_funcs) return _win_error_funcs;
|
|
1778
|
+
|
|
1779
|
+
if (process.platform !== "win32") {
|
|
1780
|
+
_win_error_funcs = { error: "Windows only" };
|
|
1781
|
+
return _win_error_funcs;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
try {
|
|
1785
|
+
const kernel32 = new WinDLL("kernel32.dll");
|
|
1786
|
+
_win_error_funcs = {
|
|
1787
|
+
GetLastError: kernel32.func("GetLastError", c_uint32, []),
|
|
1788
|
+
SetLastError: kernel32.func("SetLastError", c_void, [c_uint32]),
|
|
1789
|
+
FormatMessageW: kernel32.func("FormatMessageW", c_uint32, [
|
|
1790
|
+
c_uint32,
|
|
1791
|
+
c_void_p,
|
|
1792
|
+
c_uint32,
|
|
1793
|
+
c_uint32,
|
|
1794
|
+
c_void_p,
|
|
1795
|
+
c_uint32,
|
|
1796
|
+
c_void_p,
|
|
1797
|
+
]),
|
|
1798
|
+
_lib: kernel32,
|
|
1799
|
+
};
|
|
1800
|
+
} catch (e) {
|
|
1801
|
+
_win_error_funcs = { error: e.message };
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
return _win_error_funcs;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
/**
|
|
1808
|
+
* Ottiene l'ultimo errore Windows (GetLastError)
|
|
1809
|
+
* @returns {number} Codice errore
|
|
1810
|
+
*/
|
|
1811
|
+
function GetLastError() {
|
|
1812
|
+
const funcs = _initWinError();
|
|
1813
|
+
if (funcs.error) {
|
|
1814
|
+
throw new Error("GetLastError not available: " + funcs.error);
|
|
1815
|
+
}
|
|
1816
|
+
return funcs.GetLastError();
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
/**
|
|
1820
|
+
* Imposta l'ultimo errore Windows (SetLastError)
|
|
1821
|
+
* @param {number} code - Codice errore
|
|
1822
|
+
*/
|
|
1823
|
+
function SetLastError(code) {
|
|
1824
|
+
const funcs = _initWinError();
|
|
1825
|
+
if (funcs.error) {
|
|
1826
|
+
throw new Error("SetLastError not available: " + funcs.error);
|
|
1827
|
+
}
|
|
1828
|
+
funcs.SetLastError(code);
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
/**
|
|
1832
|
+
* Formatta un codice errore Windows in stringa
|
|
1833
|
+
* @param {number} [code] - Codice errore (default: GetLastError())
|
|
1834
|
+
* @returns {string} Messaggio di errore
|
|
1835
|
+
*/
|
|
1836
|
+
function FormatError(code) {
|
|
1837
|
+
const funcs = _initWinError();
|
|
1838
|
+
if (funcs.error) {
|
|
1839
|
+
throw new Error("FormatError not available: " + funcs.error);
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
if (code === undefined) {
|
|
1843
|
+
code = funcs.GetLastError();
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
const FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
|
|
1847
|
+
const FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
|
|
1848
|
+
|
|
1849
|
+
const bufSize = 512;
|
|
1850
|
+
const buf = alloc(bufSize * 2); // Wide chars
|
|
1851
|
+
|
|
1852
|
+
const len = funcs.FormatMessageW(
|
|
1853
|
+
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
1854
|
+
null,
|
|
1855
|
+
code,
|
|
1856
|
+
0, // Default language
|
|
1857
|
+
buf,
|
|
1858
|
+
bufSize,
|
|
1859
|
+
null,
|
|
1860
|
+
);
|
|
1861
|
+
|
|
1862
|
+
if (len === 0) {
|
|
1863
|
+
return `Unknown error ${code}`;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
// Converti da UTF-16LE a string
|
|
1867
|
+
return buf.toString("utf16le", 0, len * 2).replace(/\r?\n$/, "");
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
/**
|
|
1871
|
+
* Crea un Error da un codice errore Windows
|
|
1872
|
+
* @param {number} [code] - Codice errore (default: GetLastError())
|
|
1873
|
+
* @returns {Error} Oggetto Error con messaggio
|
|
1874
|
+
*/
|
|
1875
|
+
function WinError(code) {
|
|
1876
|
+
if (code === undefined) {
|
|
1877
|
+
code = GetLastError();
|
|
1878
|
+
}
|
|
1879
|
+
const msg = FormatError(code);
|
|
1880
|
+
const err = new Error(`[WinError ${code}] ${msg}`);
|
|
1881
|
+
err.winerror = code;
|
|
1882
|
+
return err;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
/**
|
|
1886
|
+
* Helper per definire un bit field
|
|
1887
|
+
* @param {string} baseType - Tipo base (uint8, uint16, uint32, uint64)
|
|
1888
|
+
* @param {number} bits - Numero di bit
|
|
1889
|
+
* @returns {Object} Definizione del bit field
|
|
1890
|
+
*/
|
|
1891
|
+
function bitfield(baseType, bits) {
|
|
1892
|
+
const baseSize = sizeof(baseType);
|
|
1893
|
+
const maxBits = baseSize * 8;
|
|
1894
|
+
|
|
1895
|
+
if (bits < 1 || bits > maxBits) {
|
|
1896
|
+
throw new Error(
|
|
1897
|
+
`Bit field size must be between 1 and ${maxBits} for ${baseType}`,
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
return {
|
|
1902
|
+
_isBitField: true,
|
|
1903
|
+
baseType: baseType,
|
|
1904
|
+
bits: bits,
|
|
1905
|
+
baseSize: baseSize,
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
/**
|
|
1910
|
+
* Helper per verificare se un tipo è una struct
|
|
1911
|
+
*/
|
|
1912
|
+
function _isStruct(type) {
|
|
1913
|
+
return (
|
|
1914
|
+
typeof type === "object" &&
|
|
1915
|
+
type !== null &&
|
|
1916
|
+
typeof type.size === "number" &&
|
|
1917
|
+
Array.isArray(type.fields) &&
|
|
1918
|
+
typeof type.create === "function"
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
/**
|
|
1923
|
+
* Helper per verificare se un tipo è un array type
|
|
1924
|
+
*/
|
|
1925
|
+
function _isArrayType(type) {
|
|
1926
|
+
return (
|
|
1927
|
+
typeof type === "object" && type !== null && type._isArrayType === true
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
/**
|
|
1932
|
+
* Helper per verificare se un tipo è un bit field
|
|
1933
|
+
*/
|
|
1934
|
+
function _isBitField(type) {
|
|
1935
|
+
return typeof type === "object" && type !== null && type._isBitField === true;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
/**
|
|
1939
|
+
* Helper per leggere un valore intero unsigned da un buffer
|
|
1940
|
+
* @private
|
|
1941
|
+
*/
|
|
1942
|
+
function _readUintFromBuffer(buf, offset, byteSize) {
|
|
1943
|
+
switch (byteSize) {
|
|
1944
|
+
case 1:
|
|
1945
|
+
return buf.readUInt8(offset);
|
|
1946
|
+
case 2:
|
|
1947
|
+
return buf.readUInt16LE(offset);
|
|
1948
|
+
case 4:
|
|
1949
|
+
return buf.readUInt32LE(offset);
|
|
1950
|
+
case 8:
|
|
1951
|
+
return buf.readBigUInt64LE(offset);
|
|
1952
|
+
default:
|
|
1953
|
+
throw new Error(`Unsupported byte size: ${byteSize}`);
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
/**
|
|
1958
|
+
* Helper per scrivere un valore intero unsigned in un buffer
|
|
1959
|
+
* @private
|
|
1960
|
+
*/
|
|
1961
|
+
function _writeUintToBuffer(buf, offset, byteSize, value) {
|
|
1962
|
+
switch (byteSize) {
|
|
1963
|
+
case 1:
|
|
1964
|
+
buf.writeUInt8(value, offset);
|
|
1965
|
+
break;
|
|
1966
|
+
case 2:
|
|
1967
|
+
buf.writeUInt16LE(value, offset);
|
|
1968
|
+
break;
|
|
1969
|
+
case 4:
|
|
1970
|
+
buf.writeUInt32LE(value, offset);
|
|
1971
|
+
break;
|
|
1972
|
+
case 8:
|
|
1973
|
+
buf.writeBigUInt64LE(value, offset);
|
|
1974
|
+
break;
|
|
1975
|
+
default:
|
|
1976
|
+
throw new Error(`Unsupported byte size: ${byteSize}`);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
/**
|
|
1981
|
+
* Legge un valore bit field da un buffer
|
|
1982
|
+
* @private
|
|
1983
|
+
*/
|
|
1984
|
+
function _readBitField(buf, offset, baseType, bitOffset, bitSize) {
|
|
1985
|
+
const baseSize = sizeof(baseType);
|
|
1986
|
+
const value = _readUintFromBuffer(buf, offset, baseSize);
|
|
1987
|
+
|
|
1988
|
+
// Estrai i bit
|
|
1989
|
+
if (baseSize === 8) {
|
|
1990
|
+
const mask = (1n << BigInt(bitSize)) - 1n;
|
|
1991
|
+
return Number((value >> BigInt(bitOffset)) & mask);
|
|
1992
|
+
} else {
|
|
1993
|
+
const mask = (1 << bitSize) - 1;
|
|
1994
|
+
return (value >> bitOffset) & mask;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
/**
|
|
1999
|
+
* Scrive un valore bit field in un buffer
|
|
2000
|
+
* @private
|
|
2001
|
+
*/
|
|
2002
|
+
function _writeBitField(buf, offset, baseType, bitOffset, bitSize, newValue) {
|
|
2003
|
+
const baseSize = sizeof(baseType);
|
|
2004
|
+
let value = _readUintFromBuffer(buf, offset, baseSize);
|
|
2005
|
+
|
|
2006
|
+
// Modifica i bit
|
|
2007
|
+
if (baseSize === 8) {
|
|
2008
|
+
const mask = (1n << BigInt(bitSize)) - 1n;
|
|
2009
|
+
const clearMask = ~(mask << BigInt(bitOffset));
|
|
2010
|
+
value =
|
|
2011
|
+
(value & clearMask) | ((BigInt(newValue) & mask) << BigInt(bitOffset));
|
|
2012
|
+
} else {
|
|
2013
|
+
const mask = (1 << bitSize) - 1;
|
|
2014
|
+
const clearMask = ~(mask << bitOffset);
|
|
2015
|
+
value = (value & clearMask) | ((newValue & mask) << bitOffset);
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
_writeUintToBuffer(buf, offset, baseSize, value);
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
/**
|
|
2022
|
+
* Helper per definire struct con supporto per:
|
|
2023
|
+
* - Alignment corretto
|
|
2024
|
+
* - Nested structs
|
|
2025
|
+
* - Bit fields
|
|
2026
|
+
*
|
|
2027
|
+
* @param {Object} fields - Definizione dei campi { name: type, ... }
|
|
2028
|
+
* - type può essere: 'int32', 'uint8', etc. (tipi base)
|
|
2029
|
+
* - type può essere: un'altra struct (nested)
|
|
2030
|
+
* - type può essere: bitfield('uint32', 4) (bit field)
|
|
2031
|
+
* @param {Object} [options] - Opzioni { packed: false }
|
|
2032
|
+
* @returns {Object} Definizione della struttura
|
|
2033
|
+
*/
|
|
2034
|
+
function struct(fields, options = {}) {
|
|
2035
|
+
const packed = options.packed || false;
|
|
2036
|
+
let totalSize = 0;
|
|
2037
|
+
const fieldDefs = [];
|
|
2038
|
+
let maxAlignment = 1;
|
|
2039
|
+
|
|
2040
|
+
// Stato per bit fields consecutivi
|
|
2041
|
+
let currentBitFieldBase = null;
|
|
2042
|
+
let currentBitFieldOffset = 0;
|
|
2043
|
+
let currentBitOffset = 0;
|
|
2044
|
+
|
|
2045
|
+
for (const [name, type] of Object.entries(fields)) {
|
|
2046
|
+
let fieldDef;
|
|
2047
|
+
|
|
2048
|
+
// Caso 0: Anonymous field - { type: SomeStruct, anonymous: true }
|
|
2049
|
+
if (
|
|
2050
|
+
typeof type === "object" &&
|
|
2051
|
+
type !== null &&
|
|
2052
|
+
type.anonymous === true &&
|
|
2053
|
+
type.type
|
|
2054
|
+
) {
|
|
2055
|
+
// Reset bit field state
|
|
2056
|
+
currentBitFieldBase = null;
|
|
2057
|
+
currentBitOffset = 0;
|
|
2058
|
+
|
|
2059
|
+
const actualType = type.type;
|
|
2060
|
+
const size = actualType.size;
|
|
2061
|
+
const alignment = packed ? 1 : actualType.alignment;
|
|
2062
|
+
|
|
2063
|
+
// Applica padding per allineamento
|
|
2064
|
+
if (!packed && totalSize % alignment !== 0) {
|
|
2065
|
+
totalSize += alignment - (totalSize % alignment);
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
fieldDef = {
|
|
2069
|
+
name,
|
|
2070
|
+
type: actualType,
|
|
2071
|
+
offset: totalSize,
|
|
2072
|
+
size,
|
|
2073
|
+
alignment,
|
|
2074
|
+
isNested: true,
|
|
2075
|
+
isAnonymous: true,
|
|
2076
|
+
};
|
|
2077
|
+
|
|
2078
|
+
totalSize += size;
|
|
2079
|
+
|
|
2080
|
+
if (alignment > maxAlignment) {
|
|
2081
|
+
maxAlignment = alignment;
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
// Caso 1: Bit field
|
|
2085
|
+
else if (_isBitField(type)) {
|
|
2086
|
+
const { baseType, bits, baseSize } = type;
|
|
2087
|
+
const alignment = packed ? 1 : Math.min(baseSize, native.POINTER_SIZE);
|
|
2088
|
+
|
|
2089
|
+
// Verifica se possiamo continuare nel bit field corrente
|
|
2090
|
+
const canContinue =
|
|
2091
|
+
currentBitFieldBase === baseType &&
|
|
2092
|
+
currentBitOffset + bits <= baseSize * 8;
|
|
2093
|
+
|
|
2094
|
+
if (!canContinue) {
|
|
2095
|
+
// Inizia un nuovo bit field
|
|
2096
|
+
// Applica padding per allineamento
|
|
2097
|
+
if (!packed && totalSize % alignment !== 0) {
|
|
2098
|
+
totalSize += alignment - (totalSize % alignment);
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
currentBitFieldBase = baseType;
|
|
2102
|
+
currentBitFieldOffset = totalSize;
|
|
2103
|
+
currentBitOffset = 0;
|
|
2104
|
+
totalSize += baseSize;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
fieldDef = {
|
|
2108
|
+
name,
|
|
2109
|
+
type: baseType,
|
|
2110
|
+
offset: currentBitFieldOffset,
|
|
2111
|
+
size: 0, // Bit fields non aggiungono size extra
|
|
2112
|
+
alignment,
|
|
2113
|
+
isBitField: true,
|
|
2114
|
+
bitOffset: currentBitOffset,
|
|
2115
|
+
bitSize: bits,
|
|
2116
|
+
baseSize,
|
|
2117
|
+
};
|
|
2118
|
+
|
|
2119
|
+
currentBitOffset += bits;
|
|
2120
|
+
|
|
2121
|
+
if (alignment > maxAlignment) {
|
|
2122
|
+
maxAlignment = alignment;
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
// Caso 2: Nested struct
|
|
2126
|
+
else if (_isStruct(type)) {
|
|
2127
|
+
// Reset bit field state
|
|
2128
|
+
currentBitFieldBase = null;
|
|
2129
|
+
currentBitOffset = 0;
|
|
2130
|
+
|
|
2131
|
+
const nestedStruct = type;
|
|
2132
|
+
const size = nestedStruct.size;
|
|
2133
|
+
const alignment = packed ? 1 : nestedStruct.alignment;
|
|
2134
|
+
|
|
2135
|
+
// Applica padding per allineamento
|
|
2136
|
+
if (!packed && totalSize % alignment !== 0) {
|
|
2137
|
+
totalSize += alignment - (totalSize % alignment);
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
fieldDef = {
|
|
2141
|
+
name,
|
|
2142
|
+
type: nestedStruct,
|
|
2143
|
+
offset: totalSize,
|
|
2144
|
+
size,
|
|
2145
|
+
alignment,
|
|
2146
|
+
isNested: true,
|
|
2147
|
+
};
|
|
2148
|
+
|
|
2149
|
+
totalSize += size;
|
|
2150
|
+
|
|
2151
|
+
if (alignment > maxAlignment) {
|
|
2152
|
+
maxAlignment = alignment;
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
// Caso 3: Array type
|
|
2156
|
+
else if (_isArrayType(type)) {
|
|
2157
|
+
// Reset bit field state
|
|
2158
|
+
currentBitFieldBase = null;
|
|
2159
|
+
currentBitOffset = 0;
|
|
2160
|
+
|
|
2161
|
+
const size = type.getSize();
|
|
2162
|
+
const elemSize = sizeof(type.elementType);
|
|
2163
|
+
const alignment = packed ? 1 : Math.min(elemSize, native.POINTER_SIZE);
|
|
2164
|
+
|
|
2165
|
+
// Applica padding per allineamento
|
|
2166
|
+
if (!packed && totalSize % alignment !== 0) {
|
|
2167
|
+
totalSize += alignment - (totalSize % alignment);
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
fieldDef = {
|
|
2171
|
+
name,
|
|
2172
|
+
type,
|
|
2173
|
+
offset: totalSize,
|
|
2174
|
+
size,
|
|
2175
|
+
alignment,
|
|
2176
|
+
isArray: true,
|
|
2177
|
+
};
|
|
2178
|
+
|
|
2179
|
+
totalSize += size;
|
|
2180
|
+
|
|
2181
|
+
if (alignment > maxAlignment) {
|
|
2182
|
+
maxAlignment = alignment;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
// Caso 4: Tipo base (SimpleCData)
|
|
2186
|
+
else {
|
|
2187
|
+
// Reset bit field state
|
|
2188
|
+
currentBitFieldBase = null;
|
|
2189
|
+
currentBitOffset = 0;
|
|
2190
|
+
|
|
2191
|
+
// Validate type is a SimpleCData class
|
|
2192
|
+
if (!(typeof type === "function" && type._isSimpleCData)) {
|
|
2193
|
+
throw new TypeError(
|
|
2194
|
+
`struct field "${name}": type must be a SimpleCData class, struct, union, or array`,
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
const size = type._size;
|
|
2199
|
+
const naturalAlign = Math.min(size, native.POINTER_SIZE);
|
|
2200
|
+
const alignment = packed ? 1 : naturalAlign;
|
|
2201
|
+
|
|
2202
|
+
// Applica padding per allineamento
|
|
2203
|
+
if (!packed && totalSize % alignment !== 0) {
|
|
2204
|
+
totalSize += alignment - (totalSize % alignment);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
fieldDef = {
|
|
2208
|
+
name,
|
|
2209
|
+
type,
|
|
2210
|
+
offset: totalSize,
|
|
2211
|
+
size,
|
|
2212
|
+
alignment,
|
|
2213
|
+
};
|
|
2214
|
+
|
|
2215
|
+
totalSize += size;
|
|
2216
|
+
|
|
2217
|
+
if (alignment > maxAlignment) {
|
|
2218
|
+
maxAlignment = alignment;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
fieldDefs.push(fieldDef);
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
// Padding finale per allineamento della struct
|
|
2226
|
+
if (!packed && totalSize % maxAlignment !== 0) {
|
|
2227
|
+
totalSize += maxAlignment - (totalSize % maxAlignment);
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
// Crea Map per lookup O(1) invece di find() O(n)
|
|
2231
|
+
const fieldMap = new Map();
|
|
2232
|
+
for (const field of fieldDefs) {
|
|
2233
|
+
fieldMap.set(field.name, field);
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
// Pre-compile reader/writer functions for each field
|
|
2237
|
+
const fieldReaders = new Map();
|
|
2238
|
+
const fieldWriters = new Map();
|
|
2239
|
+
|
|
2240
|
+
for (const field of fieldDefs) {
|
|
2241
|
+
const offset = field.offset;
|
|
2242
|
+
const name = field.name;
|
|
2243
|
+
|
|
2244
|
+
// Skip complex types - they need special handling
|
|
2245
|
+
if (field.isBitField || field.isNested || field.isArray) {
|
|
2246
|
+
continue;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
// Pre-compile based on SimpleCData type
|
|
2250
|
+
// Use the type's _reader and _writer directly for optimization
|
|
2251
|
+
const fieldType = field.type;
|
|
2252
|
+
if (fieldType && fieldType._isSimpleCData) {
|
|
2253
|
+
const fieldOffset = offset; // Capture offset for closure
|
|
2254
|
+
fieldReaders.set(name, (buf) => fieldType._reader(buf, fieldOffset));
|
|
2255
|
+
fieldWriters.set(name, (buf, val) =>
|
|
2256
|
+
fieldType._writer(buf, fieldOffset, val),
|
|
2257
|
+
);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
const structDef = {
|
|
2262
|
+
size: totalSize,
|
|
2263
|
+
alignment: maxAlignment,
|
|
2264
|
+
fields: fieldDefs,
|
|
2265
|
+
packed: packed,
|
|
2266
|
+
_isStructType: true,
|
|
2267
|
+
|
|
2268
|
+
/**
|
|
2269
|
+
* Alloca e inizializza una nuova istanza
|
|
2270
|
+
* @param {Object} [values] - Valori iniziali
|
|
2271
|
+
* @returns {Proxy} Proxy-based struct instance
|
|
2272
|
+
*/
|
|
2273
|
+
create(values = {}) {
|
|
2274
|
+
const buf = alloc(totalSize);
|
|
2275
|
+
buf.fill(0); // Inizializza a zero
|
|
2276
|
+
|
|
2277
|
+
// Prima imposta i campi diretti
|
|
2278
|
+
for (const field of fieldDefs) {
|
|
2279
|
+
if (values[field.name] !== undefined) {
|
|
2280
|
+
structDef.set(buf, field.name, values[field.name]);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
// Poi gestisci i campi anonymous - i loro campi possono essere passati direttamente nell'oggetto values
|
|
2285
|
+
for (const field of fieldDefs) {
|
|
2286
|
+
if (field.isAnonymous && field.type && field.type.fields) {
|
|
2287
|
+
for (const subField of field.type.fields) {
|
|
2288
|
+
if (values[subField.name] !== undefined) {
|
|
2289
|
+
structDef.set(buf, subField.name, values[subField.name]);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
// Proxy-based instance with pre-compiled readers/writers
|
|
2296
|
+
return new Proxy(buf, {
|
|
2297
|
+
get(target, prop, receiver) {
|
|
2298
|
+
// Special properties
|
|
2299
|
+
if (prop === "_buffer") return target;
|
|
2300
|
+
if (prop === "toObject") return () => structDef.toObject(target);
|
|
2301
|
+
if (prop === Symbol.toStringTag) return "StructInstance";
|
|
2302
|
+
if (prop === Symbol.iterator) return undefined;
|
|
2303
|
+
|
|
2304
|
+
// Handle Buffer/TypedArray properties FIRST before field checks
|
|
2305
|
+
// TypedArray methods need direct access to avoid Proxy interference
|
|
2306
|
+
if (
|
|
2307
|
+
prop === "length" ||
|
|
2308
|
+
prop === "byteLength" ||
|
|
2309
|
+
prop === "byteOffset" ||
|
|
2310
|
+
prop === "buffer" ||
|
|
2311
|
+
prop === "BYTES_PER_ELEMENT"
|
|
2312
|
+
) {
|
|
2313
|
+
return target[prop];
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
// Use pre-compiled reader if available
|
|
2317
|
+
if (typeof prop === "string") {
|
|
2318
|
+
const reader = fieldReaders.get(prop);
|
|
2319
|
+
if (reader) return reader(target);
|
|
2320
|
+
|
|
2321
|
+
// Fallback to general get for complex types
|
|
2322
|
+
if (fieldMap.has(prop)) {
|
|
2323
|
+
return structDef.get(target, prop);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
// Check anonymous fields
|
|
2328
|
+
for (const f of fieldDefs) {
|
|
2329
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
2330
|
+
if (f.type.fields.some((sf) => sf.name === prop)) {
|
|
2331
|
+
return structDef.get(target, prop);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// Fallback to buffer properties and methods
|
|
2337
|
+
const value = target[prop];
|
|
2338
|
+
if (typeof value === "function") {
|
|
2339
|
+
return value.bind(target);
|
|
2340
|
+
}
|
|
2341
|
+
return value;
|
|
2342
|
+
},
|
|
2343
|
+
set(target, prop, value, receiver) {
|
|
2344
|
+
// FAST PATH: Use pre-compiled writer if available
|
|
2345
|
+
if (typeof prop === "string") {
|
|
2346
|
+
const writer = fieldWriters.get(prop);
|
|
2347
|
+
if (writer) {
|
|
2348
|
+
writer(target, value);
|
|
2349
|
+
return true;
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
// Fallback to general set for complex types
|
|
2353
|
+
if (fieldMap.has(prop)) {
|
|
2354
|
+
structDef.set(target, prop, value);
|
|
2355
|
+
return true;
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
// Check anonymous fields
|
|
2360
|
+
for (const f of fieldDefs) {
|
|
2361
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
2362
|
+
if (f.type.fields.some((sf) => sf.name === prop)) {
|
|
2363
|
+
structDef.set(target, prop, value);
|
|
2364
|
+
return true;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// Fallback
|
|
2370
|
+
return Reflect.set(target, prop, value, target);
|
|
2371
|
+
},
|
|
2372
|
+
has(target, prop) {
|
|
2373
|
+
if (prop === "_buffer" || prop === "toObject") return true;
|
|
2374
|
+
if (typeof prop === "string" && fieldMap.has(prop)) return true;
|
|
2375
|
+
// Check anonymous fields
|
|
2376
|
+
for (const f of fieldDefs) {
|
|
2377
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
2378
|
+
if (f.type.fields.some((sf) => sf.name === prop)) return true;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
return Reflect.has(target, prop);
|
|
2382
|
+
},
|
|
2383
|
+
ownKeys(target) {
|
|
2384
|
+
const keys = [];
|
|
2385
|
+
for (const f of fieldDefs) {
|
|
2386
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
2387
|
+
for (const sf of f.type.fields) {
|
|
2388
|
+
keys.push(sf.name);
|
|
2389
|
+
}
|
|
2390
|
+
} else {
|
|
2391
|
+
keys.push(f.name);
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
return keys;
|
|
2395
|
+
},
|
|
2396
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
2397
|
+
if (
|
|
2398
|
+
typeof prop === "string" &&
|
|
2399
|
+
(fieldMap.has(prop) || prop === "_buffer" || prop === "toObject")
|
|
2400
|
+
) {
|
|
2401
|
+
return {
|
|
2402
|
+
enumerable: prop !== "_buffer" && prop !== "toObject",
|
|
2403
|
+
configurable: true,
|
|
2404
|
+
writable: true,
|
|
2405
|
+
};
|
|
2406
|
+
}
|
|
2407
|
+
// Check anonymous fields
|
|
2408
|
+
for (const f of fieldDefs) {
|
|
2409
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
2410
|
+
if (f.type.fields.some((sf) => sf.name === prop)) {
|
|
2411
|
+
return {
|
|
2412
|
+
enumerable: true,
|
|
2413
|
+
configurable: true,
|
|
2414
|
+
writable: true,
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
2420
|
+
},
|
|
2421
|
+
});
|
|
2422
|
+
},
|
|
2423
|
+
|
|
2424
|
+
/**
|
|
2425
|
+
* Legge un campo dalla struttura
|
|
2426
|
+
* @param {Buffer|Object} bufOrObj - Buffer della struttura O object wrapper
|
|
2427
|
+
* @param {string} fieldName - Nome del campo (supporta 'outer.inner' per nested)
|
|
2428
|
+
* @returns {*} Valore del campo
|
|
2429
|
+
*/
|
|
2430
|
+
get(bufOrObj, fieldName) {
|
|
2431
|
+
// supporta sia buffer che object wrapper o plain object
|
|
2432
|
+
let buf;
|
|
2433
|
+
if (Buffer.isBuffer(bufOrObj)) {
|
|
2434
|
+
buf = bufOrObj;
|
|
2435
|
+
} else if (bufOrObj && bufOrObj._buffer) {
|
|
2436
|
+
// Object wrapper - gestisci dot notation
|
|
2437
|
+
if (fieldName.includes(".")) {
|
|
2438
|
+
const parts = fieldName.split(".");
|
|
2439
|
+
let current = bufOrObj;
|
|
2440
|
+
for (const part of parts) {
|
|
2441
|
+
current = current[part];
|
|
2442
|
+
if (current === undefined) return undefined;
|
|
2443
|
+
}
|
|
2444
|
+
return current;
|
|
2445
|
+
}
|
|
2446
|
+
// Accesso diretto tramite property
|
|
2447
|
+
return bufOrObj[fieldName];
|
|
2448
|
+
} else if (typeof bufOrObj === "object" && bufOrObj !== null) {
|
|
2449
|
+
// Plain object (da toObject/create) - accesso diretto
|
|
2450
|
+
if (fieldName.includes(".")) {
|
|
2451
|
+
const parts = fieldName.split(".");
|
|
2452
|
+
let current = bufOrObj;
|
|
2453
|
+
for (const part of parts) {
|
|
2454
|
+
current = current[part];
|
|
2455
|
+
if (current === undefined) return undefined;
|
|
2456
|
+
}
|
|
2457
|
+
return current;
|
|
2458
|
+
}
|
|
2459
|
+
// Accesso diretto tramite property
|
|
2460
|
+
return bufOrObj[fieldName];
|
|
2461
|
+
} else {
|
|
2462
|
+
throw new TypeError("Expected Buffer or struct instance");
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
// Fast path: nome semplice senza dot notation
|
|
2466
|
+
// Lookup with Map
|
|
2467
|
+
let field = fieldMap.get(fieldName);
|
|
2468
|
+
|
|
2469
|
+
if (field) {
|
|
2470
|
+
// Bit field
|
|
2471
|
+
if (field.isBitField) {
|
|
2472
|
+
return _readBitField(
|
|
2473
|
+
buf,
|
|
2474
|
+
field.offset,
|
|
2475
|
+
field.type,
|
|
2476
|
+
field.bitOffset,
|
|
2477
|
+
field.bitSize,
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
// Nested struct (senza dot = ritorna intero oggetto)
|
|
2482
|
+
if (field.isNested) {
|
|
2483
|
+
const nestedBuf = buf.subarray(
|
|
2484
|
+
field.offset,
|
|
2485
|
+
field.offset + field.size,
|
|
2486
|
+
);
|
|
2487
|
+
// Proxy-based nested instance
|
|
2488
|
+
return field.type.create
|
|
2489
|
+
? // Se ha create(), usa quello per creare il proxy
|
|
2490
|
+
new Proxy(nestedBuf, {
|
|
2491
|
+
get(target, prop, receiver) {
|
|
2492
|
+
if (prop === "_buffer") return target;
|
|
2493
|
+
if (prop === "toObject")
|
|
2494
|
+
return () => field.type.toObject(target);
|
|
2495
|
+
if (prop === Symbol.toStringTag)
|
|
2496
|
+
return "NestedStructInstance";
|
|
2497
|
+
if (prop === Symbol.iterator) return undefined;
|
|
2498
|
+
// Handle Buffer/TypedArray properties
|
|
2499
|
+
if (
|
|
2500
|
+
prop === "length" ||
|
|
2501
|
+
prop === "byteLength" ||
|
|
2502
|
+
prop === "byteOffset" ||
|
|
2503
|
+
prop === "buffer" ||
|
|
2504
|
+
prop === "BYTES_PER_ELEMENT"
|
|
2505
|
+
) {
|
|
2506
|
+
return target[prop];
|
|
2507
|
+
}
|
|
2508
|
+
if (typeof prop === "string") {
|
|
2509
|
+
// Check direct fields
|
|
2510
|
+
const nestedFieldMap = field.type.fields
|
|
2511
|
+
? new Map(field.type.fields.map((f) => [f.name, f]))
|
|
2512
|
+
: new Map();
|
|
2513
|
+
if (nestedFieldMap.has(prop)) {
|
|
2514
|
+
return field.type.get(target, prop);
|
|
2515
|
+
}
|
|
2516
|
+
// Check anonymous fields
|
|
2517
|
+
for (const sf of field.type.fields || []) {
|
|
2518
|
+
if (sf.isAnonymous && sf.type && sf.type.fields) {
|
|
2519
|
+
if (sf.type.fields.some((ssf) => ssf.name === prop)) {
|
|
2520
|
+
return field.type.get(target, prop);
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
const value = target[prop];
|
|
2526
|
+
if (typeof value === "function") return value.bind(target);
|
|
2527
|
+
return value;
|
|
2528
|
+
},
|
|
2529
|
+
set(target, prop, value, receiver) {
|
|
2530
|
+
if (typeof prop === "string") {
|
|
2531
|
+
const nestedFieldMap = field.type.fields
|
|
2532
|
+
? new Map(field.type.fields.map((f) => [f.name, f]))
|
|
2533
|
+
: new Map();
|
|
2534
|
+
if (nestedFieldMap.has(prop)) {
|
|
2535
|
+
field.type.set(target, prop, value);
|
|
2536
|
+
return true;
|
|
2537
|
+
}
|
|
2538
|
+
// Check anonymous fields
|
|
2539
|
+
for (const sf of field.type.fields || []) {
|
|
2540
|
+
if (sf.isAnonymous && sf.type && sf.type.fields) {
|
|
2541
|
+
if (sf.type.fields.some((ssf) => ssf.name === prop)) {
|
|
2542
|
+
field.type.set(target, prop, value);
|
|
2543
|
+
return true;
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
return Reflect.set(target, prop, value, receiver);
|
|
2549
|
+
},
|
|
2550
|
+
has(target, prop) {
|
|
2551
|
+
if (prop === "_buffer" || prop === "toObject") return true;
|
|
2552
|
+
if (typeof prop === "string" && field.type.fields) {
|
|
2553
|
+
if (field.type.fields.some((f) => f.name === prop))
|
|
2554
|
+
return true;
|
|
2555
|
+
}
|
|
2556
|
+
return Reflect.has(target, prop);
|
|
2557
|
+
},
|
|
2558
|
+
ownKeys(target) {
|
|
2559
|
+
return (field.type.fields || []).map((f) => f.name);
|
|
2560
|
+
},
|
|
2561
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
2562
|
+
if (
|
|
2563
|
+
typeof prop === "string" &&
|
|
2564
|
+
field.type.fields &&
|
|
2565
|
+
field.type.fields.some((f) => f.name === prop)
|
|
2566
|
+
) {
|
|
2567
|
+
return {
|
|
2568
|
+
enumerable: true,
|
|
2569
|
+
configurable: true,
|
|
2570
|
+
writable: true,
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
2574
|
+
},
|
|
2575
|
+
})
|
|
2576
|
+
: field.type.toObject(nestedBuf);
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
// Array field
|
|
2580
|
+
if (field.isArray) {
|
|
2581
|
+
const arrayBuf = buf.subarray(
|
|
2582
|
+
field.offset,
|
|
2583
|
+
field.offset + field.size,
|
|
2584
|
+
);
|
|
2585
|
+
return field.type.wrap(arrayBuf);
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
// Tipo base - fast path!
|
|
2589
|
+
return readValue(buf, field.type, field.offset);
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
// supporto per accesso nested 'outer.inner' o anonymous fields
|
|
2593
|
+
const parts = fieldName.split(".");
|
|
2594
|
+
const firstPart = parts[0];
|
|
2595
|
+
|
|
2596
|
+
field = fieldMap.get(firstPart);
|
|
2597
|
+
|
|
2598
|
+
// Se non trovato, cerca negli anonymous fields
|
|
2599
|
+
if (!field) {
|
|
2600
|
+
for (const f of fieldDefs) {
|
|
2601
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
2602
|
+
const hasField = f.type.fields.some(
|
|
2603
|
+
(subField) => subField.name === firstPart,
|
|
2604
|
+
);
|
|
2605
|
+
if (hasField) {
|
|
2606
|
+
const nestedBuf = buf.subarray(f.offset, f.offset + f.size);
|
|
2607
|
+
return f.type.get(nestedBuf, fieldName);
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
throw new Error(`Unknown field: ${firstPart}`);
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
// Nested struct con dot notation
|
|
2615
|
+
if (field.isNested && parts.length > 1) {
|
|
2616
|
+
const nestedBuf = buf.subarray(field.offset, field.offset + field.size);
|
|
2617
|
+
return field.type.get(nestedBuf, parts.slice(1).join("."));
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
throw new Error(`Unknown field: ${fieldName}`);
|
|
2621
|
+
},
|
|
2622
|
+
|
|
2623
|
+
/**
|
|
2624
|
+
* Scrive un campo nella struttura
|
|
2625
|
+
* @param {Buffer|Object} bufOrObj - Buffer della struttura O object wrapper
|
|
2626
|
+
* @param {string} fieldName - Nome del campo (supporta 'outer.inner' per nested)
|
|
2627
|
+
* @param {*} value - Valore da scrivere
|
|
2628
|
+
*/
|
|
2629
|
+
set(bufOrObj, fieldName, value) {
|
|
2630
|
+
// supporta sia buffer che object wrapper o plain object
|
|
2631
|
+
let buf;
|
|
2632
|
+
if (Buffer.isBuffer(bufOrObj)) {
|
|
2633
|
+
buf = bufOrObj;
|
|
2634
|
+
} else if (bufOrObj && bufOrObj._buffer) {
|
|
2635
|
+
// Object wrapper - accesso diretto tramite property
|
|
2636
|
+
bufOrObj[fieldName] = value;
|
|
2637
|
+
return;
|
|
2638
|
+
} else if (typeof bufOrObj === "object" && bufOrObj !== null) {
|
|
2639
|
+
// Plain object (da toObject/create) - accesso diretto
|
|
2640
|
+
bufOrObj[fieldName] = value;
|
|
2641
|
+
return;
|
|
2642
|
+
} else {
|
|
2643
|
+
throw new TypeError("Expected Buffer or struct instance");
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
// Fast path: nome semplice senza dot notation
|
|
2647
|
+
// Lookup with Map
|
|
2648
|
+
let field = fieldMap.get(fieldName);
|
|
2649
|
+
|
|
2650
|
+
if (field) {
|
|
2651
|
+
// Bit field
|
|
2652
|
+
if (field.isBitField) {
|
|
2653
|
+
_writeBitField(
|
|
2654
|
+
buf,
|
|
2655
|
+
field.offset,
|
|
2656
|
+
field.type,
|
|
2657
|
+
field.bitOffset,
|
|
2658
|
+
field.bitSize,
|
|
2659
|
+
value,
|
|
2660
|
+
);
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
// Nested struct (senza dot = imposta da oggetto)
|
|
2665
|
+
if (field.isNested) {
|
|
2666
|
+
if (Buffer.isBuffer(value)) {
|
|
2667
|
+
value.copy(buf, field.offset, 0, field.size);
|
|
2668
|
+
} else if (typeof value === "object") {
|
|
2669
|
+
const nestedBuf = buf.subarray(
|
|
2670
|
+
field.offset,
|
|
2671
|
+
field.offset + field.size,
|
|
2672
|
+
);
|
|
2673
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2674
|
+
field.type.set(nestedBuf, k, v);
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
// Array field
|
|
2681
|
+
if (field.isArray) {
|
|
2682
|
+
if (Buffer.isBuffer(value)) {
|
|
2683
|
+
value.copy(buf, field.offset, 0, field.size);
|
|
2684
|
+
} else if (Array.isArray(value)) {
|
|
2685
|
+
const arrayBuf = buf.subarray(
|
|
2686
|
+
field.offset,
|
|
2687
|
+
field.offset + field.size,
|
|
2688
|
+
);
|
|
2689
|
+
const wrapped = field.type.wrap(arrayBuf);
|
|
2690
|
+
for (
|
|
2691
|
+
let i = 0;
|
|
2692
|
+
i < Math.min(value.length, field.type.length);
|
|
2693
|
+
i++
|
|
2694
|
+
) {
|
|
2695
|
+
wrapped[i] = value[i];
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
// Tipo base - fast path!
|
|
2702
|
+
writeValue(buf, field.type, value, field.offset);
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
// Slow path: supporto per accesso nested 'outer.inner' o anonymous fields
|
|
2707
|
+
const parts = fieldName.split(".");
|
|
2708
|
+
const firstPart = parts[0];
|
|
2709
|
+
|
|
2710
|
+
field = fieldMap.get(firstPart);
|
|
2711
|
+
|
|
2712
|
+
// Se non trovato, cerca negli anonymous fields
|
|
2713
|
+
if (!field) {
|
|
2714
|
+
for (const f of fieldDefs) {
|
|
2715
|
+
if (f.isAnonymous && f.type && f.type.fields) {
|
|
2716
|
+
// Verifica se il campo esiste nel tipo anonimo
|
|
2717
|
+
const hasField = f.type.fields.some(
|
|
2718
|
+
(subField) => subField.name === firstPart,
|
|
2719
|
+
);
|
|
2720
|
+
if (hasField) {
|
|
2721
|
+
// Scrive nel campo dell'anonymous field
|
|
2722
|
+
const nestedBuf = buf.subarray(f.offset, f.offset + f.size);
|
|
2723
|
+
f.type.set(nestedBuf, fieldName, value);
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
throw new Error(`Unknown field: ${firstPart}`);
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
// Bit field
|
|
2732
|
+
if (field.isBitField) {
|
|
2733
|
+
_writeBitField(
|
|
2734
|
+
buf,
|
|
2735
|
+
field.offset,
|
|
2736
|
+
field.type,
|
|
2737
|
+
field.bitOffset,
|
|
2738
|
+
field.bitSize,
|
|
2739
|
+
value,
|
|
2740
|
+
);
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
// Nested struct
|
|
2745
|
+
if (field.isNested) {
|
|
2746
|
+
if (parts.length > 1) {
|
|
2747
|
+
// Accesso a campo annidato singolo
|
|
2748
|
+
const nestedBuf = buf.subarray(
|
|
2749
|
+
field.offset,
|
|
2750
|
+
field.offset + field.size,
|
|
2751
|
+
);
|
|
2752
|
+
field.type.set(nestedBuf, parts.slice(1).join("."), value);
|
|
2753
|
+
} else if (Buffer.isBuffer(value)) {
|
|
2754
|
+
// Copia buffer
|
|
2755
|
+
value.copy(buf, field.offset, 0, field.size);
|
|
2756
|
+
} else if (typeof value === "object") {
|
|
2757
|
+
// Imposta campi da oggetto
|
|
2758
|
+
const nestedBuf = buf.subarray(
|
|
2759
|
+
field.offset,
|
|
2760
|
+
field.offset + field.size,
|
|
2761
|
+
);
|
|
2762
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2763
|
+
field.type.set(nestedBuf, k, v);
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
// Array field
|
|
2770
|
+
if (field.isArray) {
|
|
2771
|
+
if (Buffer.isBuffer(value)) {
|
|
2772
|
+
// Copia buffer array
|
|
2773
|
+
value.copy(buf, field.offset, 0, field.size);
|
|
2774
|
+
} else if (Array.isArray(value)) {
|
|
2775
|
+
// Inizializza da array JavaScript
|
|
2776
|
+
const arrayBuf = buf.subarray(
|
|
2777
|
+
field.offset,
|
|
2778
|
+
field.offset + field.size,
|
|
2779
|
+
);
|
|
2780
|
+
const wrapped = field.type.wrap(arrayBuf);
|
|
2781
|
+
for (let i = 0; i < Math.min(value.length, field.type.length); i++) {
|
|
2782
|
+
wrapped[i] = value[i];
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
// Tipo base
|
|
2789
|
+
writeValue(buf, field.type, value, field.offset);
|
|
2790
|
+
},
|
|
2791
|
+
|
|
2792
|
+
/**
|
|
2793
|
+
* Legge tutti i campi come oggetto (ricorsivo per nested)
|
|
2794
|
+
* @param {Buffer} buf - Buffer della struttura
|
|
2795
|
+
* @returns {Object} Oggetto con tutti i campi
|
|
2796
|
+
*/
|
|
2797
|
+
/**
|
|
2798
|
+
* Converte buffer in plain object (KOFFI EAGER APPROACH)
|
|
2799
|
+
* Legge TUTTI i valori UNA VOLTA e crea PLAIN properties (no getters)
|
|
2800
|
+
* Molto più veloce! Sincronizzazione lazy quando serve _buffer
|
|
2801
|
+
*/
|
|
2802
|
+
toObject(bufOrObj) {
|
|
2803
|
+
// Se è già un object (non buffer), controlla se ha _buffer
|
|
2804
|
+
if (!Buffer.isBuffer(bufOrObj)) {
|
|
2805
|
+
// Se ha _buffer, estrailo e ricarica i valori (utile dopo modifiche FFI)
|
|
2806
|
+
if (bufOrObj && bufOrObj._buffer && Buffer.isBuffer(bufOrObj._buffer)) {
|
|
2807
|
+
bufOrObj = bufOrObj._buffer; // Estrai buffer e procedi con il reload
|
|
2808
|
+
} else if (typeof bufOrObj === "object" && bufOrObj !== null) {
|
|
2809
|
+
// È già un plain object, restituiscilo così com'è
|
|
2810
|
+
return bufOrObj;
|
|
2811
|
+
} else {
|
|
2812
|
+
throw new TypeError("Expected Buffer or struct instance");
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
const buf = bufOrObj;
|
|
2817
|
+
const result = {};
|
|
2818
|
+
|
|
2819
|
+
// Leggi tutti i campi in una volta (eager approach)
|
|
2820
|
+
for (const field of fieldDefs) {
|
|
2821
|
+
if (field.isBitField) {
|
|
2822
|
+
result[field.name] = _readBitField(
|
|
2823
|
+
buf,
|
|
2824
|
+
field.offset,
|
|
2825
|
+
field.type,
|
|
2826
|
+
field.bitOffset,
|
|
2827
|
+
field.bitSize,
|
|
2828
|
+
);
|
|
2829
|
+
} else if (field.isNested) {
|
|
2830
|
+
const nestedBuf = buf.subarray(
|
|
2831
|
+
field.offset,
|
|
2832
|
+
field.offset + field.size,
|
|
2833
|
+
);
|
|
2834
|
+
const nestedObj = field.type.toObject(nestedBuf);
|
|
2835
|
+
if (field.isAnonymous) {
|
|
2836
|
+
// Anonymous fields: promote their fields to parent level
|
|
2837
|
+
Object.assign(result, nestedObj);
|
|
2838
|
+
} else {
|
|
2839
|
+
result[field.name] = nestedObj;
|
|
2840
|
+
}
|
|
2841
|
+
} else if (field.isArray) {
|
|
2842
|
+
const arrayBuf = buf.subarray(
|
|
2843
|
+
field.offset,
|
|
2844
|
+
field.offset + field.size,
|
|
2845
|
+
);
|
|
2846
|
+
result[field.name] = field.type.wrap(arrayBuf);
|
|
2847
|
+
} else {
|
|
2848
|
+
// Tipo base - lettura diretta
|
|
2849
|
+
result[field.name] = readValue(buf, field.type, field.offset);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
return result;
|
|
2854
|
+
},
|
|
2855
|
+
|
|
2856
|
+
/**
|
|
2857
|
+
* Ottiene il buffer di una nested struct
|
|
2858
|
+
* @param {Buffer} buf - Buffer della struttura parent
|
|
2859
|
+
* @param {string} fieldName - Nome del campo nested
|
|
2860
|
+
* @returns {Buffer} Slice del buffer per la nested struct
|
|
2861
|
+
*/
|
|
2862
|
+
getNestedBuffer(buf, fieldName) {
|
|
2863
|
+
// O(1) lookup con Map
|
|
2864
|
+
const field = fieldMap.get(fieldName);
|
|
2865
|
+
if (!field) throw new Error(`Unknown field: ${fieldName}`);
|
|
2866
|
+
if (!field.isNested)
|
|
2867
|
+
throw new Error(`Field ${fieldName} is not a nested struct`);
|
|
2868
|
+
return buf.subarray(field.offset, field.offset + field.size);
|
|
2869
|
+
},
|
|
2870
|
+
};
|
|
2871
|
+
|
|
2872
|
+
// Crea un'istanza C++ StructType per le operazioni native
|
|
2873
|
+
try {
|
|
2874
|
+
const cppStructType = new StructType();
|
|
2875
|
+
// Per ora non aggiungiamo campi - testiamo se ToObject funziona senza
|
|
2876
|
+
structDef._cppStructType = cppStructType;
|
|
2877
|
+
} catch (e) {
|
|
2878
|
+
// Se fallisce, continua senza C++ (fallback a JS)
|
|
2879
|
+
console.warn(
|
|
2880
|
+
"Failed to create C++ StructType, using JavaScript fallback:",
|
|
2881
|
+
e.message,
|
|
2882
|
+
);
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
// fromObject: write plain object values into buffer
|
|
2886
|
+
structDef.fromObject = function (buf, obj) {
|
|
2887
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2888
|
+
if (this.fields.some((f) => f.name === key)) {
|
|
2889
|
+
this.set(buf, key, value);
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
};
|
|
2893
|
+
|
|
2894
|
+
// createRaw: return plain object without synchronization
|
|
2895
|
+
structDef.createRaw = function (values = {}) {
|
|
2896
|
+
return this.toObject(this.create(values)._buffer);
|
|
2897
|
+
};
|
|
2898
|
+
|
|
2899
|
+
return structDef;
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
// ============================================================================
|
|
2903
|
+
// Python-compatible Simple C Data Types (instantiable)
|
|
2904
|
+
// Usage: const v = new c_uint64(42); v.value; byref(v);
|
|
2905
|
+
// ============================================================================
|
|
2906
|
+
|
|
2907
|
+
/**
|
|
2908
|
+
* Base class for simple C data types (Python ctypes compatible)
|
|
2909
|
+
* Subclasses must define static _size, _type, _reader, _writer
|
|
2910
|
+
*/
|
|
2911
|
+
class SimpleCData {
|
|
2912
|
+
/**
|
|
2913
|
+
* @param {number|bigint} [value] - Initial value (default: 0)
|
|
2914
|
+
*/
|
|
2915
|
+
constructor(value) {
|
|
2916
|
+
const Ctor = this.constructor;
|
|
2917
|
+
this._buffer = alloc(Ctor._size);
|
|
2918
|
+
if (value !== undefined && value !== null) {
|
|
2919
|
+
this.value = value;
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
/**
|
|
2924
|
+
* Get the current value
|
|
2925
|
+
*/
|
|
2926
|
+
get value() {
|
|
2927
|
+
const Ctor = this.constructor;
|
|
2928
|
+
return Ctor._reader(this._buffer, 0);
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
/**
|
|
2932
|
+
* Set the value
|
|
2933
|
+
*/
|
|
2934
|
+
set value(v) {
|
|
2935
|
+
const Ctor = this.constructor;
|
|
2936
|
+
Ctor._writer(this._buffer, 0, v);
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
/**
|
|
2940
|
+
* Size of the type in bytes (instance property)
|
|
2941
|
+
*/
|
|
2942
|
+
get size() {
|
|
2943
|
+
return this.constructor._size;
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
/**
|
|
2947
|
+
* Create instance from existing buffer (Python ctypes compatible)
|
|
2948
|
+
* WARNING: Returns a VIEW into the buffer, not a copy!
|
|
2949
|
+
* @param {Buffer} buffer - Source buffer
|
|
2950
|
+
* @param {number} [offset=0] - Offset in buffer
|
|
2951
|
+
* @returns {SimpleCData} New instance wrapping the buffer slice
|
|
2952
|
+
*/
|
|
2953
|
+
static from_buffer(buffer, offset = 0) {
|
|
2954
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
2955
|
+
throw new TypeError("from_buffer requires a Buffer");
|
|
2956
|
+
}
|
|
2957
|
+
const inst = Object.create(this.prototype);
|
|
2958
|
+
inst._buffer = buffer.subarray(offset, offset + this._size);
|
|
2959
|
+
return inst;
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
/**
|
|
2963
|
+
* Create instance from a COPY of buffer data (Python ctypes compatible)
|
|
2964
|
+
* @param {Buffer} buffer - Source buffer
|
|
2965
|
+
* @param {number} [offset=0] - Offset in buffer
|
|
2966
|
+
* @returns {SimpleCData} New instance with copied data
|
|
2967
|
+
*/
|
|
2968
|
+
static from_buffer_copy(buffer, offset = 0) {
|
|
2969
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
2970
|
+
throw new TypeError("from_buffer_copy requires a Buffer");
|
|
2971
|
+
}
|
|
2972
|
+
const inst = new this();
|
|
2973
|
+
buffer.copy(inst._buffer, 0, offset, offset + this._size);
|
|
2974
|
+
return inst;
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
/**
|
|
2978
|
+
* Size of the type in bytes (static property)
|
|
2979
|
+
*/
|
|
2980
|
+
static get size() {
|
|
2981
|
+
return this._size;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
toString() {
|
|
2985
|
+
return `${this.constructor.name}(${this.value})`;
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
toJSON() {
|
|
2989
|
+
const v = this.value;
|
|
2990
|
+
return typeof v === "bigint" ? v.toString() : v;
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
valueOf() {
|
|
2994
|
+
return this.value;
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
SimpleCData._isSimpleCData = true;
|
|
2999
|
+
|
|
3000
|
+
class c_void extends SimpleCData {
|
|
3001
|
+
static _size = 0;
|
|
3002
|
+
static _type = "void";
|
|
3003
|
+
static _reader = (buf, off) => undefined;
|
|
3004
|
+
static _writer = (buf, off, val) => {};
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
// ============================================================================
|
|
3008
|
+
// Integer types - signed
|
|
3009
|
+
// ============================================================================
|
|
3010
|
+
|
|
3011
|
+
class c_int8 extends SimpleCData {
|
|
3012
|
+
static _size = 1;
|
|
3013
|
+
static _type = "int8";
|
|
3014
|
+
static _reader = (buf, off) => buf.readInt8(off);
|
|
3015
|
+
static _writer = (buf, off, val) => buf.writeInt8(val, off);
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
class c_int16 extends SimpleCData {
|
|
3019
|
+
static _size = 2;
|
|
3020
|
+
static _type = "int16";
|
|
3021
|
+
static _reader = (buf, off) => buf.readInt16LE(off);
|
|
3022
|
+
static _writer = (buf, off, val) => buf.writeInt16LE(val, off);
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
class c_int32 extends SimpleCData {
|
|
3026
|
+
static _size = 4;
|
|
3027
|
+
static _type = "int32";
|
|
3028
|
+
static _reader = (buf, off) => buf.readInt32LE(off);
|
|
3029
|
+
static _writer = (buf, off, val) => buf.writeInt32LE(val, off);
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
class c_int64 extends SimpleCData {
|
|
3033
|
+
static _size = 8;
|
|
3034
|
+
static _type = "int64";
|
|
3035
|
+
static _reader = (buf, off) => buf.readBigInt64LE(off);
|
|
3036
|
+
static _writer = (buf, off, val) => buf.writeBigInt64LE(BigInt(val), off);
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
// ============================================================================
|
|
3040
|
+
// Integer types - unsigned
|
|
3041
|
+
// ============================================================================
|
|
3042
|
+
|
|
3043
|
+
class c_uint8 extends SimpleCData {
|
|
3044
|
+
static _size = 1;
|
|
3045
|
+
static _type = "uint8";
|
|
3046
|
+
static _reader = (buf, off) => buf.readUInt8(off);
|
|
3047
|
+
static _writer = (buf, off, val) => buf.writeUInt8(val, off);
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
class c_uint16 extends SimpleCData {
|
|
3051
|
+
static _size = 2;
|
|
3052
|
+
static _type = "uint16";
|
|
3053
|
+
static _reader = (buf, off) => buf.readUInt16LE(off);
|
|
3054
|
+
static _writer = (buf, off, val) => buf.writeUInt16LE(val, off);
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
class c_uint32 extends SimpleCData {
|
|
3058
|
+
static _size = 4;
|
|
3059
|
+
static _type = "uint32";
|
|
3060
|
+
static _reader = (buf, off) => buf.readUInt32LE(off);
|
|
3061
|
+
static _writer = (buf, off, val) => buf.writeUInt32LE(val, off);
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
class c_uint64 extends SimpleCData {
|
|
3065
|
+
static _size = 8;
|
|
3066
|
+
static _type = "uint64";
|
|
3067
|
+
static _reader = (buf, off) => buf.readBigUInt64LE(off);
|
|
3068
|
+
static _writer = (buf, off, val) => buf.writeBigUInt64LE(BigInt(val), off);
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
// ============================================================================
|
|
3072
|
+
// Floating point types
|
|
3073
|
+
// ============================================================================
|
|
3074
|
+
|
|
3075
|
+
class c_float extends SimpleCData {
|
|
3076
|
+
static _size = 4;
|
|
3077
|
+
static _type = "float";
|
|
3078
|
+
static _reader = (buf, off) => buf.readFloatLE(off);
|
|
3079
|
+
static _writer = (buf, off, val) => buf.writeFloatLE(val, off);
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
class c_double extends SimpleCData {
|
|
3083
|
+
static _size = 8;
|
|
3084
|
+
static _type = "double";
|
|
3085
|
+
static _reader = (buf, off) => buf.readDoubleLE(off);
|
|
3086
|
+
static _writer = (buf, off, val) => buf.writeDoubleLE(val, off);
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
// ============================================================================
|
|
3090
|
+
// Boolean type
|
|
3091
|
+
// ============================================================================
|
|
3092
|
+
|
|
3093
|
+
class c_bool extends SimpleCData {
|
|
3094
|
+
static _size = 1;
|
|
3095
|
+
static _type = "bool";
|
|
3096
|
+
static _reader = (buf, off) => buf.readUInt8(off) !== 0;
|
|
3097
|
+
static _writer = (buf, off, val) => buf.writeUInt8(val ? 1 : 0, off);
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
// ============================================================================
|
|
3101
|
+
// Character types
|
|
3102
|
+
// ============================================================================
|
|
3103
|
+
|
|
3104
|
+
class c_char extends SimpleCData {
|
|
3105
|
+
static _size = 1;
|
|
3106
|
+
static _type = "char";
|
|
3107
|
+
static _reader = (buf, off) => buf.readInt8(off);
|
|
3108
|
+
static _writer = (buf, off, val) => {
|
|
3109
|
+
if (typeof val === "string") {
|
|
3110
|
+
buf.writeUInt8(val.charCodeAt(0), off);
|
|
3111
|
+
} else {
|
|
3112
|
+
buf.writeInt8(val, off);
|
|
3113
|
+
}
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
class c_wchar extends SimpleCData {
|
|
3118
|
+
static _size = native.WCHAR_SIZE;
|
|
3119
|
+
static _type = "wchar";
|
|
3120
|
+
static _reader = (buf, off) => {
|
|
3121
|
+
if (native.WCHAR_SIZE === 2) {
|
|
3122
|
+
return String.fromCharCode(buf.readUInt16LE(off));
|
|
3123
|
+
}
|
|
3124
|
+
return String.fromCodePoint(buf.readUInt32LE(off));
|
|
3125
|
+
};
|
|
3126
|
+
static _writer = (buf, off, val) => {
|
|
3127
|
+
const code = typeof val === "string" ? val.codePointAt(0) : val;
|
|
3128
|
+
if (native.WCHAR_SIZE === 2) {
|
|
3129
|
+
buf.writeUInt16LE(code, off);
|
|
3130
|
+
} else {
|
|
3131
|
+
buf.writeUInt32LE(code, off);
|
|
3132
|
+
}
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
3135
|
+
|
|
3136
|
+
// ============================================================================
|
|
3137
|
+
// Pointer types
|
|
3138
|
+
// ============================================================================
|
|
3139
|
+
|
|
3140
|
+
class c_void_p extends SimpleCData {
|
|
3141
|
+
static _size = native.POINTER_SIZE;
|
|
3142
|
+
static _type = "pointer";
|
|
3143
|
+
static _reader = (buf, off) =>
|
|
3144
|
+
native.POINTER_SIZE === 8
|
|
3145
|
+
? buf.readBigUInt64LE(off)
|
|
3146
|
+
: BigInt(buf.readUInt32LE(off));
|
|
3147
|
+
static _writer = (buf, off, val) => {
|
|
3148
|
+
// Accept Buffers (pointer to memory) and struct proxies with _buffer
|
|
3149
|
+
if (Buffer.isBuffer(val)) {
|
|
3150
|
+
const addr = addressOf(val);
|
|
3151
|
+
const v = typeof addr === "bigint" ? addr : BigInt(addr || 0);
|
|
3152
|
+
if (native.POINTER_SIZE === 8) buf.writeBigUInt64LE(v, off);
|
|
3153
|
+
else buf.writeUInt32LE(Number(v), off);
|
|
3154
|
+
return;
|
|
3155
|
+
}
|
|
3156
|
+
if (
|
|
3157
|
+
val &&
|
|
3158
|
+
typeof val === "object" &&
|
|
3159
|
+
val._buffer &&
|
|
3160
|
+
Buffer.isBuffer(val._buffer)
|
|
3161
|
+
) {
|
|
3162
|
+
const addr = addressOf(val._buffer);
|
|
3163
|
+
const v = typeof addr === "bigint" ? addr : BigInt(addr || 0);
|
|
3164
|
+
if (native.POINTER_SIZE === 8) buf.writeBigUInt64LE(v, off);
|
|
3165
|
+
else buf.writeUInt32LE(Number(v), off);
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
// Fallback: accept BigInt or number
|
|
3170
|
+
const v = typeof val === "bigint" ? val : BigInt(val || 0);
|
|
3171
|
+
if (native.POINTER_SIZE === 8) {
|
|
3172
|
+
buf.writeBigUInt64LE(v, off);
|
|
3173
|
+
} else {
|
|
3174
|
+
buf.writeUInt32LE(Number(v), off);
|
|
3175
|
+
}
|
|
3176
|
+
};
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
class c_size_t extends SimpleCData {
|
|
3180
|
+
static _size = native.POINTER_SIZE;
|
|
3181
|
+
static _type = "size_t";
|
|
3182
|
+
static _reader = (buf, off) =>
|
|
3183
|
+
native.POINTER_SIZE === 8
|
|
3184
|
+
? buf.readBigUInt64LE(off)
|
|
3185
|
+
: BigInt(buf.readUInt32LE(off));
|
|
3186
|
+
static _writer = (buf, off, val) => {
|
|
3187
|
+
const v = typeof val === "bigint" ? val : BigInt(val || 0);
|
|
3188
|
+
if (native.POINTER_SIZE === 8) {
|
|
3189
|
+
buf.writeBigUInt64LE(v, off);
|
|
3190
|
+
} else {
|
|
3191
|
+
buf.writeUInt32LE(Number(v), off);
|
|
3192
|
+
}
|
|
3193
|
+
};
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
// ============================================================================
|
|
3197
|
+
// Platform-dependent types
|
|
3198
|
+
// ============================================================================
|
|
3199
|
+
|
|
3200
|
+
const _longSize = native.sizeof ? native.sizeof("long") : 4;
|
|
3201
|
+
|
|
3202
|
+
class c_long extends SimpleCData {
|
|
3203
|
+
static _size = _longSize;
|
|
3204
|
+
static _type = "long";
|
|
3205
|
+
static _reader =
|
|
3206
|
+
_longSize === 8
|
|
3207
|
+
? (buf, off) => buf.readBigInt64LE(off)
|
|
3208
|
+
: (buf, off) => buf.readInt32LE(off);
|
|
3209
|
+
static _writer =
|
|
3210
|
+
_longSize === 8
|
|
3211
|
+
? (buf, off, val) => buf.writeBigInt64LE(BigInt(val), off)
|
|
3212
|
+
: (buf, off, val) => buf.writeInt32LE(val, off);
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
class c_ulong extends SimpleCData {
|
|
3216
|
+
static _size = _longSize;
|
|
3217
|
+
static _type = "ulong";
|
|
3218
|
+
static _reader =
|
|
3219
|
+
_longSize === 8
|
|
3220
|
+
? (buf, off) => buf.readBigUInt64LE(off)
|
|
3221
|
+
: (buf, off) => buf.readUInt32LE(off);
|
|
3222
|
+
static _writer =
|
|
3223
|
+
_longSize === 8
|
|
3224
|
+
? (buf, off, val) => buf.writeBigUInt64LE(BigInt(val), off)
|
|
3225
|
+
: (buf, off, val) => buf.writeUInt32LE(val, off);
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
// ============================================================================
|
|
3229
|
+
// String pointer types
|
|
3230
|
+
// ============================================================================
|
|
3231
|
+
|
|
3232
|
+
class c_char_p extends SimpleCData {
|
|
3233
|
+
static _size = native.POINTER_SIZE;
|
|
3234
|
+
static _type = "char_p";
|
|
3235
|
+
static _reader = (buf, off) =>
|
|
3236
|
+
native.POINTER_SIZE === 8
|
|
3237
|
+
? buf.readBigUInt64LE(off)
|
|
3238
|
+
: BigInt(buf.readUInt32LE(off));
|
|
3239
|
+
static _writer = (buf, off, val) => {
|
|
3240
|
+
if (Buffer.isBuffer(val)) {
|
|
3241
|
+
const addr = addressOf(val);
|
|
3242
|
+
const v = typeof addr === "bigint" ? addr : BigInt(addr || 0);
|
|
3243
|
+
if (native.POINTER_SIZE === 8) {
|
|
3244
|
+
buf.writeBigUInt64LE(v, off);
|
|
3245
|
+
} else {
|
|
3246
|
+
buf.writeUInt32LE(Number(v), off);
|
|
3247
|
+
}
|
|
3248
|
+
} else {
|
|
3249
|
+
const v = typeof val === "bigint" ? val : BigInt(val || 0);
|
|
3250
|
+
if (native.POINTER_SIZE === 8) {
|
|
3251
|
+
buf.writeBigUInt64LE(v, off);
|
|
3252
|
+
} else {
|
|
3253
|
+
buf.writeUInt32LE(Number(v), off);
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
};
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
class c_wchar_p extends SimpleCData {
|
|
3260
|
+
static _size = native.POINTER_SIZE;
|
|
3261
|
+
static _type = "wchar_p";
|
|
3262
|
+
static _reader = (buf, off) =>
|
|
3263
|
+
native.POINTER_SIZE === 8
|
|
3264
|
+
? buf.readBigUInt64LE(off)
|
|
3265
|
+
: BigInt(buf.readUInt32LE(off));
|
|
3266
|
+
static _writer = (buf, off, val) => {
|
|
3267
|
+
if (Buffer.isBuffer(val)) {
|
|
3268
|
+
const addr = addressOf(val);
|
|
3269
|
+
const v = typeof addr === "bigint" ? addr : BigInt(addr || 0);
|
|
3270
|
+
if (native.POINTER_SIZE === 8) {
|
|
3271
|
+
buf.writeBigUInt64LE(v, off);
|
|
3272
|
+
} else {
|
|
3273
|
+
buf.writeUInt32LE(Number(v), off);
|
|
3274
|
+
}
|
|
3275
|
+
} else {
|
|
3276
|
+
const v = typeof val === "bigint" ? val : BigInt(val || 0);
|
|
3277
|
+
if (native.POINTER_SIZE === 8) {
|
|
3278
|
+
buf.writeBigUInt64LE(v, off);
|
|
3279
|
+
} else {
|
|
3280
|
+
buf.writeUInt32LE(Number(v), off);
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
// ============================================================================
|
|
3287
|
+
// Python-compatible aliases
|
|
3288
|
+
// ============================================================================
|
|
3289
|
+
|
|
3290
|
+
const c_byte = c_int8;
|
|
3291
|
+
const c_ubyte = c_uint8;
|
|
3292
|
+
const c_short = c_int16;
|
|
3293
|
+
const c_ushort = c_uint16;
|
|
3294
|
+
const c_int = c_int32;
|
|
3295
|
+
const c_uint = c_uint32;
|
|
3296
|
+
const c_longlong = c_int64;
|
|
3297
|
+
const c_ulonglong = c_uint64;
|
|
3298
|
+
|
|
3299
|
+
// ============================================================================
|
|
3300
|
+
// Alias Python-compatibili per Memory Management
|
|
3301
|
+
// ============================================================================
|
|
3302
|
+
|
|
3303
|
+
/**
|
|
3304
|
+
* Crea un buffer di stringa (compatibile con Python ctypes)
|
|
3305
|
+
* @param {number|string|Buffer} init - Dimensione in bytes, stringa, o bytes
|
|
3306
|
+
* @returns {Buffer} Buffer allocato
|
|
3307
|
+
*/
|
|
3308
|
+
function create_string_buffer(init) {
|
|
3309
|
+
if (typeof init === "number") {
|
|
3310
|
+
// create_string_buffer(size) - alloca buffer vuoto
|
|
3311
|
+
return alloc(init);
|
|
3312
|
+
} else if (typeof init === "string") {
|
|
3313
|
+
// create_string_buffer("string") - crea buffer con stringa
|
|
3314
|
+
return cstring(init);
|
|
3315
|
+
} else if (Buffer.isBuffer(init)) {
|
|
3316
|
+
// create_string_buffer(bytes) - copia bytes
|
|
3317
|
+
const buf = alloc(init.length + 1);
|
|
3318
|
+
init.copy(buf);
|
|
3319
|
+
buf[init.length] = 0; // null terminator
|
|
3320
|
+
return buf;
|
|
3321
|
+
}
|
|
3322
|
+
throw new TypeError(
|
|
3323
|
+
"create_string_buffer requires number, string, or Buffer",
|
|
3324
|
+
);
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
/**
|
|
3328
|
+
* Crea un buffer di stringa Unicode (compatibile con Python ctypes)
|
|
3329
|
+
* @param {number|string} init - Dimensione in caratteri o stringa
|
|
3330
|
+
* @returns {Buffer} Buffer allocato con stringa wide
|
|
3331
|
+
*/
|
|
3332
|
+
function create_unicode_buffer(init) {
|
|
3333
|
+
const wcharSize = native.WCHAR_SIZE; // 2 su Windows, 4 su Unix
|
|
3334
|
+
|
|
3335
|
+
if (typeof init === "number") {
|
|
3336
|
+
// create_unicode_buffer(size) - alloca buffer vuoto per N caratteri
|
|
3337
|
+
return alloc(init * wcharSize);
|
|
3338
|
+
} else if (typeof init === "string") {
|
|
3339
|
+
// create_unicode_buffer("string") - crea buffer con stringa wide
|
|
3340
|
+
return wstring(init);
|
|
3341
|
+
}
|
|
3342
|
+
throw new TypeError("create_unicode_buffer requires number or string");
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
/**
|
|
3346
|
+
* Legge una stringa da un indirizzo di memoria (compatibile con Python ctypes)
|
|
3347
|
+
* @param {Buffer|BigInt|number} address - Indirizzo di memoria
|
|
3348
|
+
* @param {number} [size] - Numero di bytes da leggere (default: fino a null)
|
|
3349
|
+
* @returns {string|null} Stringa letta
|
|
3350
|
+
*/
|
|
3351
|
+
function string_at(address, size) {
|
|
3352
|
+
return readCString(address, size);
|
|
3353
|
+
}
|
|
3354
|
+
|
|
3355
|
+
/**
|
|
3356
|
+
* Legge una stringa wide da un indirizzo di memoria (compatibile con Python ctypes)
|
|
3357
|
+
* @param {Buffer|BigInt|number} address - Indirizzo di memoria
|
|
3358
|
+
* @param {number} [size] - Numero di caratteri wide da leggere
|
|
3359
|
+
* @returns {string|null} Stringa letta
|
|
3360
|
+
*/
|
|
3361
|
+
function wstring_at(address, size) {
|
|
3362
|
+
return readWString(address, size);
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
// Alias lowercase per compatibilità Python (addressof invece di addressOf)
|
|
3366
|
+
const addressof = addressOf;
|
|
3367
|
+
|
|
3368
|
+
/**
|
|
3369
|
+
* Copia memoria da sorgente a destinazione (come memmove in C)
|
|
3370
|
+
* @param {Buffer} dst - Buffer destinazione
|
|
3371
|
+
* @param {Buffer|BigInt} src - Buffer o indirizzo sorgente
|
|
3372
|
+
* @param {number} count - Numero di bytes da copiare
|
|
3373
|
+
*/
|
|
3374
|
+
function memmove(dst, src, count) {
|
|
3375
|
+
if (!Buffer.isBuffer(dst)) {
|
|
3376
|
+
throw new TypeError("dst must be a Buffer");
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
let srcBuf;
|
|
3380
|
+
if (Buffer.isBuffer(src)) {
|
|
3381
|
+
srcBuf = src;
|
|
3382
|
+
} else {
|
|
3383
|
+
srcBuf = ptrToBuffer(src, count);
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3386
|
+
srcBuf.copy(dst, 0, 0, count);
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
/**
|
|
3390
|
+
* Riempie memoria con un valore (come memset in C)
|
|
3391
|
+
* @param {Buffer} dst - Buffer destinazione
|
|
3392
|
+
* @param {number} value - Valore byte (0-255)
|
|
3393
|
+
* @param {number} count - Numero di bytes da riempire
|
|
3394
|
+
*/
|
|
3395
|
+
function memset(dst, value, count) {
|
|
3396
|
+
if (!Buffer.isBuffer(dst)) {
|
|
3397
|
+
throw new TypeError("dst must be a Buffer");
|
|
3398
|
+
}
|
|
3399
|
+
dst.fill(value, 0, count);
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
// Export come ES module
|
|
3403
|
+
export {
|
|
3404
|
+
// Classi
|
|
3405
|
+
Version,
|
|
3406
|
+
Library,
|
|
3407
|
+
CDLL,
|
|
3408
|
+
WinDLL,
|
|
3409
|
+
FFIFunction,
|
|
3410
|
+
Callback,
|
|
3411
|
+
ThreadSafeCallback,
|
|
3412
|
+
CType,
|
|
3413
|
+
|
|
3414
|
+
// Funzioni base
|
|
3415
|
+
load,
|
|
3416
|
+
callback,
|
|
3417
|
+
threadSafeCallback,
|
|
3418
|
+
|
|
3419
|
+
// Memory Management - Python-compatibili
|
|
3420
|
+
create_string_buffer,
|
|
3421
|
+
create_unicode_buffer,
|
|
3422
|
+
string_at,
|
|
3423
|
+
wstring_at,
|
|
3424
|
+
addressof,
|
|
3425
|
+
memmove,
|
|
3426
|
+
memset,
|
|
3427
|
+
|
|
3428
|
+
// Memory - utility senza equivalente Python diretto
|
|
3429
|
+
readValue,
|
|
3430
|
+
writeValue,
|
|
3431
|
+
sizeof,
|
|
3432
|
+
ptrToBuffer,
|
|
3433
|
+
|
|
3434
|
+
// Strutture
|
|
3435
|
+
struct,
|
|
3436
|
+
union,
|
|
3437
|
+
defineStruct,
|
|
3438
|
+
defineUnion,
|
|
3439
|
+
array,
|
|
3440
|
+
bitfield,
|
|
3441
|
+
StructType,
|
|
3442
|
+
ArrayType,
|
|
3443
|
+
Structure,
|
|
3444
|
+
Union,
|
|
3445
|
+
|
|
3446
|
+
// Classes for Python-like subclassing: class X extends Structure { static _fields_ = [...] }
|
|
3447
|
+
// See Structure/Union implementation below
|
|
3448
|
+
|
|
3449
|
+
// Puntatori (Python-compatible)
|
|
3450
|
+
byref,
|
|
3451
|
+
cast,
|
|
3452
|
+
POINTER,
|
|
3453
|
+
|
|
3454
|
+
// Error handling
|
|
3455
|
+
get_errno,
|
|
3456
|
+
set_errno,
|
|
3457
|
+
GetLastError,
|
|
3458
|
+
SetLastError,
|
|
3459
|
+
FormatError,
|
|
3460
|
+
WinError,
|
|
3461
|
+
|
|
3462
|
+
// SimpleCData base class
|
|
3463
|
+
SimpleCData,
|
|
3464
|
+
|
|
3465
|
+
// Tipi (classes, now instantiable)
|
|
3466
|
+
types,
|
|
3467
|
+
c_void,
|
|
3468
|
+
c_int,
|
|
3469
|
+
c_uint,
|
|
3470
|
+
c_int8,
|
|
3471
|
+
c_uint8,
|
|
3472
|
+
c_int16,
|
|
3473
|
+
c_uint16,
|
|
3474
|
+
c_int32,
|
|
3475
|
+
c_uint32,
|
|
3476
|
+
c_int64,
|
|
3477
|
+
c_uint64,
|
|
3478
|
+
c_float,
|
|
3479
|
+
c_double,
|
|
3480
|
+
c_char,
|
|
3481
|
+
c_char_p,
|
|
3482
|
+
c_wchar,
|
|
3483
|
+
c_wchar_p,
|
|
3484
|
+
c_void_p,
|
|
3485
|
+
c_bool,
|
|
3486
|
+
c_size_t,
|
|
3487
|
+
c_long,
|
|
3488
|
+
c_ulong,
|
|
3489
|
+
// Python-compatible aliases
|
|
3490
|
+
c_byte,
|
|
3491
|
+
c_ubyte,
|
|
3492
|
+
c_short,
|
|
3493
|
+
c_ushort,
|
|
3494
|
+
c_longlong,
|
|
3495
|
+
c_ulonglong,
|
|
3496
|
+
};
|
|
3497
|
+
|
|
3498
|
+
// Costanti esportate
|
|
3499
|
+
export const POINTER_SIZE = native.POINTER_SIZE;
|
|
3500
|
+
export const WCHAR_SIZE = native.WCHAR_SIZE;
|
|
3501
|
+
export const NULL = null;
|