@lakutata/ffi 0.0.0-stage → 3.1.2

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.
Files changed (129) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +163 -2
  3. package/README.zh-CN.md +140 -0
  4. package/dist/cjs/components/NativeLibrary.d.ts +229 -0
  5. package/dist/cjs/components/NativeLibrary.js +346 -0
  6. package/dist/cjs/decorators/Library.d.ts +61 -0
  7. package/dist/cjs/decorators/Library.js +44 -0
  8. package/dist/cjs/decorators/Native.d.ts +69 -0
  9. package/dist/cjs/decorators/Native.js +90 -0
  10. package/dist/cjs/exceptions/NativeDeclarationException.d.ts +12 -0
  11. package/dist/cjs/exceptions/NativeDeclarationException.js +19 -0
  12. package/dist/cjs/exceptions/NativeLibraryClosedException.d.ts +12 -0
  13. package/dist/cjs/exceptions/NativeLibraryClosedException.js +19 -0
  14. package/dist/cjs/exceptions/NativeLibraryNotFoundException.d.ts +11 -0
  15. package/dist/cjs/exceptions/NativeLibraryNotFoundException.js +18 -0
  16. package/dist/cjs/exceptions/NativeMemoryException.d.ts +11 -0
  17. package/dist/cjs/exceptions/NativeMemoryException.js +18 -0
  18. package/dist/cjs/exports/FFI.d.ts +12 -0
  19. package/dist/cjs/exports/FFI.js +40 -0
  20. package/dist/cjs/exports/Generator.d.ts +2 -0
  21. package/dist/cjs/exports/Generator.js +10 -0
  22. package/dist/cjs/lib/Memory.d.ts +188 -0
  23. package/dist/cjs/lib/Memory.js +227 -0
  24. package/dist/cjs/lib/Runtime.d.ts +98 -0
  25. package/dist/cjs/lib/Runtime.js +209 -0
  26. package/dist/cjs/lib/backend/Functions.d.ts +65 -0
  27. package/dist/cjs/lib/backend/Functions.js +214 -0
  28. package/dist/cjs/lib/backend/KoffiTypes.d.ts +53 -0
  29. package/dist/cjs/lib/backend/KoffiTypes.js +426 -0
  30. package/dist/cjs/lib/declarations/Builtins.d.ts +33 -0
  31. package/dist/cjs/lib/declarations/Builtins.js +179 -0
  32. package/dist/cjs/lib/declarations/Layout.d.ts +41 -0
  33. package/dist/cjs/lib/declarations/Layout.js +180 -0
  34. package/dist/cjs/lib/declarations/Lexer.d.ts +40 -0
  35. package/dist/cjs/lib/declarations/Lexer.js +325 -0
  36. package/dist/cjs/lib/declarations/Parser.d.ts +90 -0
  37. package/dist/cjs/lib/declarations/Parser.js +1425 -0
  38. package/dist/cjs/lib/declarations/Platform.d.ts +50 -0
  39. package/dist/cjs/lib/declarations/Platform.js +27 -0
  40. package/dist/cjs/lib/declarations/Types.d.ts +173 -0
  41. package/dist/cjs/lib/declarations/Types.js +179 -0
  42. package/dist/cjs/lib/generator/CText.d.ts +14 -0
  43. package/dist/cjs/lib/generator/CText.js +133 -0
  44. package/dist/cjs/lib/generator/Generator.d.ts +136 -0
  45. package/dist/cjs/lib/generator/Generator.js +395 -0
  46. package/dist/cjs/lib/generator/Preprocessor.d.ts +60 -0
  47. package/dist/cjs/lib/generator/Preprocessor.js +94 -0
  48. package/dist/cjs/lib/generator/TypeScriptTypes.d.ts +35 -0
  49. package/dist/cjs/lib/generator/TypeScriptTypes.js +153 -0
  50. package/dist/cjs/lib/library/NativeCallback.d.ts +44 -0
  51. package/dist/cjs/lib/library/NativeCallback.js +66 -0
  52. package/dist/cjs/lib/library/NativeLibraryHandle.d.ts +268 -0
  53. package/dist/cjs/lib/library/NativeLibraryHandle.js +540 -0
  54. package/dist/cjs/lib/library/Resolve.d.ts +49 -0
  55. package/dist/cjs/lib/library/Resolve.js +130 -0
  56. package/dist/cjs/lib/thread/NativeThreads.d.ts +80 -0
  57. package/dist/cjs/lib/thread/NativeThreads.js +384 -0
  58. package/dist/cjs/lib/thread/NativeWorker.d.ts +6 -0
  59. package/dist/cjs/lib/thread/NativeWorker.js +96 -0
  60. package/dist/cjs/lib/thread/NativeWorkerCode.d.ts +5 -0
  61. package/dist/cjs/lib/thread/NativeWorkerCode.js +4 -0
  62. package/dist/cjs/lib/thread/PackageRequire.d.ts +6 -0
  63. package/dist/cjs/lib/thread/PackageRequire.js +4 -0
  64. package/dist/cjs/lib/thread/ThreadProtocol.d.ts +77 -0
  65. package/dist/cjs/lib/thread/ThreadProtocol.js +2 -0
  66. package/dist/cjs/package.json +1 -0
  67. package/dist/esm/components/NativeLibrary.js +338 -0
  68. package/dist/esm/decorators/Library.js +40 -0
  69. package/dist/esm/decorators/Native.js +85 -0
  70. package/dist/esm/exceptions/NativeDeclarationException.js +15 -0
  71. package/dist/esm/exceptions/NativeLibraryClosedException.js +15 -0
  72. package/dist/esm/exceptions/NativeLibraryNotFoundException.js +14 -0
  73. package/dist/esm/exceptions/NativeMemoryException.js +14 -0
  74. package/dist/esm/exports/FFI.js +10 -0
  75. package/dist/esm/exports/Generator.js +2 -0
  76. package/dist/esm/lib/Memory.js +210 -0
  77. package/dist/esm/lib/Runtime.js +192 -0
  78. package/dist/esm/lib/backend/Functions.js +206 -0
  79. package/dist/esm/lib/backend/KoffiTypes.js +418 -0
  80. package/dist/esm/lib/declarations/Builtins.js +174 -0
  81. package/dist/esm/lib/declarations/Layout.js +175 -0
  82. package/dist/esm/lib/declarations/Lexer.js +321 -0
  83. package/dist/esm/lib/declarations/Parser.js +1421 -0
  84. package/dist/esm/lib/declarations/Platform.js +23 -0
  85. package/dist/esm/lib/declarations/Types.js +170 -0
  86. package/dist/esm/lib/generator/CText.js +129 -0
  87. package/dist/esm/lib/generator/Generator.js +388 -0
  88. package/dist/esm/lib/generator/Preprocessor.js +87 -0
  89. package/dist/esm/lib/generator/TypeScriptTypes.js +149 -0
  90. package/dist/esm/lib/library/NativeCallback.js +59 -0
  91. package/dist/esm/lib/library/NativeLibraryHandle.js +532 -0
  92. package/dist/esm/lib/library/Resolve.js +122 -0
  93. package/dist/esm/lib/thread/NativeThreads.js +377 -0
  94. package/dist/esm/lib/thread/NativeWorker.js +93 -0
  95. package/dist/esm/lib/thread/NativeWorkerCode.js +1 -0
  96. package/dist/esm/lib/thread/PackageRequire.js +2 -0
  97. package/dist/esm/lib/thread/ThreadProtocol.js +1 -0
  98. package/dist/types/components/NativeLibrary.d.ts +229 -0
  99. package/dist/types/decorators/Library.d.ts +61 -0
  100. package/dist/types/decorators/Native.d.ts +69 -0
  101. package/dist/types/exceptions/NativeDeclarationException.d.ts +12 -0
  102. package/dist/types/exceptions/NativeLibraryClosedException.d.ts +12 -0
  103. package/dist/types/exceptions/NativeLibraryNotFoundException.d.ts +11 -0
  104. package/dist/types/exceptions/NativeMemoryException.d.ts +11 -0
  105. package/dist/types/exports/FFI.d.ts +12 -0
  106. package/dist/types/exports/Generator.d.ts +2 -0
  107. package/dist/types/lib/Memory.d.ts +188 -0
  108. package/dist/types/lib/Runtime.d.ts +98 -0
  109. package/dist/types/lib/backend/Functions.d.ts +65 -0
  110. package/dist/types/lib/backend/KoffiTypes.d.ts +53 -0
  111. package/dist/types/lib/declarations/Builtins.d.ts +33 -0
  112. package/dist/types/lib/declarations/Layout.d.ts +41 -0
  113. package/dist/types/lib/declarations/Lexer.d.ts +40 -0
  114. package/dist/types/lib/declarations/Parser.d.ts +90 -0
  115. package/dist/types/lib/declarations/Platform.d.ts +50 -0
  116. package/dist/types/lib/declarations/Types.d.ts +173 -0
  117. package/dist/types/lib/generator/CText.d.ts +14 -0
  118. package/dist/types/lib/generator/Generator.d.ts +136 -0
  119. package/dist/types/lib/generator/Preprocessor.d.ts +60 -0
  120. package/dist/types/lib/generator/TypeScriptTypes.d.ts +35 -0
  121. package/dist/types/lib/library/NativeCallback.d.ts +44 -0
  122. package/dist/types/lib/library/NativeLibraryHandle.d.ts +268 -0
  123. package/dist/types/lib/library/Resolve.d.ts +49 -0
  124. package/dist/types/lib/thread/NativeThreads.d.ts +80 -0
  125. package/dist/types/lib/thread/NativeWorker.d.ts +6 -0
  126. package/dist/types/lib/thread/NativeWorkerCode.d.ts +5 -0
  127. package/dist/types/lib/thread/PackageRequire.d.ts +6 -0
  128. package/dist/types/lib/thread/ThreadProtocol.d.ts +77 -0
  129. package/package.json +85 -4
@@ -0,0 +1,210 @@
1
+ import koffi from 'koffi';
2
+ import { BUILTIN_TYPES, checkedPointer, copyBuffer, allocateNative, freeNative, nativeErrno, nativeView, nativeWindowsError, readNative, typeSize, writeNative } from './Runtime.js';
3
+ /**
4
+ * Read a C string (UTF-8, ended by a NUL byte) from native memory: a `char *` given as a pointer (a `void *` result, an
5
+ * argument of a callback, a member of a struct).
6
+ * @param pointer Its address.
7
+ * @param length Its length in bytes, when it is not ended by a NUL byte.
8
+ * @returns The string.
9
+ * @example
10
+ * ```typescript
11
+ * import {readCString, type Pointer} from '@lakutata/ffi'
12
+ *
13
+ * export function messageOf(pointer: Pointer): string {
14
+ * return readCString(pointer)
15
+ * }
16
+ * ```
17
+ */
18
+ export function readCString(pointer, length) {
19
+ const address = checkedPointer(pointer, 'readCString');
20
+ return length === undefined ? koffi.decode.string(address) : koffi.decode.string(address, length);
21
+ }
22
+ /**
23
+ * Read a wide string from native memory: a `wchar_t *` (UTF-16 on Windows, UTF-32 elsewhere), ended by a NUL character.
24
+ * @param pointer Its address.
25
+ * @param length Its length in characters, when it is not ended by a NUL character.
26
+ * @returns The string.
27
+ * @example
28
+ * ```typescript
29
+ * import {readWString, type Pointer} from '@lakutata/ffi'
30
+ *
31
+ * export function titleOf(pointer: Pointer): string {
32
+ * return readWString(pointer)
33
+ * }
34
+ * ```
35
+ */
36
+ export function readWString(pointer, length) {
37
+ const address = checkedPointer(pointer, 'readWString');
38
+ return length === undefined ? koffi.decode.wstring(address) : koffi.decode.wstring(address, length);
39
+ }
40
+ /**
41
+ * Copy native memory into a buffer of its own, which stays valid once the native memory is freed (about 10 GB/s).
42
+ * @param pointer The address of the memory.
43
+ * @param length Its length in bytes.
44
+ * @returns The copy.
45
+ * @example
46
+ * ```typescript
47
+ * import {readBuffer, type Pointer} from '@lakutata/ffi'
48
+ *
49
+ * export function frameOf(pixels: Pointer, width: number, height: number): Buffer {
50
+ * return readBuffer(pixels, width * height * 4)
51
+ * }
52
+ * ```
53
+ */
54
+ export function readBuffer(pointer, length) {
55
+ return copyBuffer(pointer, length);
56
+ }
57
+ /**
58
+ * A view of native memory, without copy: worth it for large blocks read often (8 MiB: about 0.13 ms rather than 1 ms
59
+ * for a copy). The view must not be used once the native memory is freed: the process would crash. Electron forbids
60
+ * these views: it throws there (use readBuffer).
61
+ * @param pointer The address of the memory.
62
+ * @param length Its length in bytes.
63
+ * @returns The memory, as an ArrayBuffer.
64
+ * @throws {NativeMemoryException} In Electron, or for a null pointer.
65
+ * @example
66
+ * ```typescript
67
+ * import {viewBuffer, type Pointer} from '@lakutata/ffi'
68
+ *
69
+ * export function firstPixel(pixels: Pointer): number {
70
+ * return new Uint32Array(viewBuffer(pixels, 4))[0]
71
+ * }
72
+ * ```
73
+ */
74
+ export function viewBuffer(pointer, length) {
75
+ return nativeView(pointer, length);
76
+ }
77
+ /**
78
+ * Read a value of a built-in type from native memory (`int`, `double`, `uint64_t`, `char *`…): the 64-bit integers as
79
+ * bigints. The types of a library are read by `read()` of the library.
80
+ * @param pointer Its address.
81
+ * @param type Its type.
82
+ * @returns The value.
83
+ * @example
84
+ * ```typescript
85
+ * import {readValue, type Pointer} from '@lakutata/ffi'
86
+ *
87
+ * export function countAt(pointer: Pointer): number {
88
+ * return readValue(pointer, 'int32_t') as number
89
+ * }
90
+ * ```
91
+ */
92
+ export function readValue(pointer, type) {
93
+ return readNative(BUILTIN_TYPES, pointer, type);
94
+ }
95
+ /**
96
+ * Read an array of values of a built-in type from native memory.
97
+ * @param pointer The address of its first element.
98
+ * @param type The type of its elements (`char *` reads strings).
99
+ * @param count Its length.
100
+ * @returns The values.
101
+ * @example
102
+ * ```typescript
103
+ * import {readArray, type Pointer} from '@lakutata/ffi'
104
+ *
105
+ * //The row of a callback of sqlite3_exec: char **values
106
+ * export function rowOf(values: Pointer, count: number): string[] {
107
+ * return readArray(values, 'char *', count) as string[]
108
+ * }
109
+ * ```
110
+ */
111
+ export function readArray(pointer, type, count) {
112
+ return readNative(BUILTIN_TYPES, pointer, type, count);
113
+ }
114
+ /**
115
+ * Write a value of a built-in type to native memory.
116
+ * @param pointer Its address.
117
+ * @param type Its type.
118
+ * @param value The value.
119
+ * @example
120
+ * ```typescript
121
+ * import {allocMemory, freeMemory, readValue, writeValue} from '@lakutata/ffi'
122
+ *
123
+ * const counter: bigint = allocMemory('int64_t')
124
+ * writeValue(counter, 'int64_t', 42n)
125
+ * console.log(readValue(counter, 'int64_t'))
126
+ * freeMemory(counter)
127
+ * ```
128
+ */
129
+ export function writeValue(pointer, type, value) {
130
+ writeNative(BUILTIN_TYPES, pointer, type, value);
131
+ }
132
+ /**
133
+ * Allocate native memory for values of a built-in type: memory that does not move, which a library can keep (a buffer
134
+ * given to a function is valid during the call only). Freed by freeMemory.
135
+ * @param type The type of the values.
136
+ * @param count Their number.
137
+ * @returns Its address.
138
+ * @example
139
+ * ```typescript
140
+ * import {allocMemory, freeMemory} from '@lakutata/ffi'
141
+ *
142
+ * const buffer: bigint = allocMemory('uint8_t', 4096)
143
+ * freeMemory(buffer)
144
+ * ```
145
+ */
146
+ export function allocMemory(type, count = 1) {
147
+ return allocateNative(BUILTIN_TYPES, type, count);
148
+ }
149
+ /**
150
+ * Free native memory allocated by allocMemory.
151
+ * @param pointer Its address.
152
+ * @example
153
+ * ```typescript
154
+ * import {allocMemory, freeMemory} from '@lakutata/ffi'
155
+ *
156
+ * freeMemory(allocMemory('double', 16))
157
+ * ```
158
+ */
159
+ export function freeMemory(pointer) {
160
+ freeNative(pointer);
161
+ }
162
+ /**
163
+ * The size of a built-in type on the platform, in bytes (`long` is 8 on 64-bit Linux and macOS, 4 on Windows).
164
+ * @param type The type.
165
+ * @returns Its size.
166
+ * @example
167
+ * ```typescript
168
+ * import {sizeOf} from '@lakutata/ffi'
169
+ *
170
+ * console.log(sizeOf('long'), sizeOf('wchar_t'), sizeOf('void *'))
171
+ * ```
172
+ */
173
+ export function sizeOf(type) {
174
+ return typeSize(BUILTIN_TYPES, type);
175
+ }
176
+ /**
177
+ * The errno of the last native call of the thread. On Windows, each C library has its own errno: the one of the C
178
+ * library of Microsoft (ucrtbase.dll) by default.
179
+ * @param crt On Windows, the C library the function called uses (`msvcrt.dll` for the old libraries).
180
+ * @returns The errno.
181
+ * @example
182
+ * ```typescript
183
+ * import {lastErrno, loadNativeLibrary} from '@lakutata/ffi'
184
+ *
185
+ * const libc = loadNativeLibrary('c')
186
+ * const open = libc.func<(path: string, flags: number) => number>('int open(const char *path, int flags, ...)')
187
+ * if (open('/surely/missing', 0) < 0) console.log('errno', lastErrno())
188
+ * ```
189
+ */
190
+ export function lastErrno(crt) {
191
+ return nativeErrno(crt);
192
+ }
193
+ /**
194
+ * The last error of the Windows API of the thread (GetLastError), kept after each native call; undefined on the other
195
+ * systems.
196
+ * @returns The error.
197
+ * @example
198
+ * ```typescript
199
+ * import {lastWindowsError, loadNativeLibrary} from '@lakutata/ffi'
200
+ *
201
+ * if (process.platform === 'win32') {
202
+ * const kernel32 = loadNativeLibrary('kernel32.dll')
203
+ * const attributes = kernel32.func<(path: string) => number>('DWORD WINAPI GetFileAttributesW(LPCWSTR path)')
204
+ * if (attributes('C:\\missing') === 0xFFFFFFFF) console.log('error', lastWindowsError())
205
+ * }
206
+ * ```
207
+ */
208
+ export function lastWindowsError() {
209
+ return nativeWindowsError();
210
+ }
@@ -0,0 +1,192 @@
1
+ import koffi from 'koffi';
2
+ import { NativeDeclarationException } from '../exceptions/NativeDeclarationException.js';
3
+ import { NativeMemoryException } from '../exceptions/NativeMemoryException.js';
4
+ import { KoffiTypes } from './backend/KoffiTypes.js';
5
+ import { DeclarationParser } from './declarations/Parser.js';
6
+ import { CURRENT_PLATFORM } from './declarations/Platform.js';
7
+ import { loadLibrary } from './library/Resolve.js';
8
+ /**
9
+ * The koffi types of the process: one per C type, whatever library declares it
10
+ */
11
+ export const TYPES = new KoffiTypes(CURRENT_PLATFORM);
12
+ /**
13
+ * The C types read from their names, with the declarations of a library or the built-in types only
14
+ */
15
+ export class TypeNames {
16
+ #parser;
17
+ #types = new Map();
18
+ /**
19
+ * The type names of a library.
20
+ * @param parser Its declarations.
21
+ */
22
+ constructor(parser) {
23
+ this.#parser = parser;
24
+ }
25
+ /**
26
+ * A type, from its name.
27
+ * @param text Its name: `int`, `struct timeval`, `const char *`.
28
+ */
29
+ type(text) {
30
+ let type = this.#types.get(text);
31
+ if (!type) {
32
+ type = this.#parser.typeName(text);
33
+ this.#types.set(text, type);
34
+ }
35
+ return type;
36
+ }
37
+ /**
38
+ * The koffi type of a type name.
39
+ * @param text Its name.
40
+ */
41
+ koffi(text) {
42
+ return TYPES.type(this.type(text));
43
+ }
44
+ }
45
+ /**
46
+ * The type names of the built-in types (of no library)
47
+ */
48
+ export const BUILTIN_TYPES = new TypeNames(new DeclarationParser(CURRENT_PLATFORM));
49
+ let copier;
50
+ /**
51
+ * memcpy of the C library, copying native memory into buffers
52
+ */
53
+ function memcpy() {
54
+ if (!copier) {
55
+ const library = process.platform === 'win32' ? koffi.load('msvcrt.dll') : loadLibrary('c').library;
56
+ copier = library.func('memcpy', 'void *', ['void *', 'void *', 'size_t']);
57
+ }
58
+ return copier;
59
+ }
60
+ /**
61
+ * A pointer checked: not null, a bigint or a number
62
+ * @param pointer
63
+ * @param what
64
+ */
65
+ export function checkedPointer(pointer, what) {
66
+ if (pointer === null || pointer === undefined || pointer === 0n || pointer === 0)
67
+ throw new NativeMemoryException(`${what}: the pointer is null`);
68
+ if (typeof pointer === 'number')
69
+ return BigInt(pointer);
70
+ if (typeof pointer !== 'bigint')
71
+ throw new NativeMemoryException(`${what}: a pointer (a bigint) is expected, not ${typeof pointer}`);
72
+ return pointer;
73
+ }
74
+ /**
75
+ * A length checked: a non-negative integer
76
+ * @param length
77
+ * @param what
78
+ */
79
+ function checkedLength(length, what) {
80
+ if (!Number.isInteger(length) || length < 0)
81
+ throw new NativeMemoryException(`${what}: the length must be a non-negative integer (${length})`);
82
+ return length;
83
+ }
84
+ /**
85
+ * A copy of native memory, in a buffer of its own.
86
+ * @param pointer The address of the memory.
87
+ * @param length Its length in bytes.
88
+ */
89
+ export function copyBuffer(pointer, length) {
90
+ const buffer = Buffer.allocUnsafe(checkedLength(length, 'readBuffer'));
91
+ if (length)
92
+ memcpy()(buffer, checkedPointer(pointer, 'readBuffer'), length);
93
+ return buffer;
94
+ }
95
+ /**
96
+ * A view of native memory, without copy: refused in Electron, which forbids them (koffi would end the process).
97
+ * @param pointer The address of the memory.
98
+ * @param length Its length in bytes.
99
+ */
100
+ export function nativeView(pointer, length) {
101
+ if (process.versions.electron)
102
+ throw new NativeMemoryException('Electron does not allow views of native memory: use readBuffer, which copies it');
103
+ return koffi.view(checkedPointer(pointer, 'viewBuffer'), checkedLength(length, 'viewBuffer'));
104
+ }
105
+ /**
106
+ * A value read from native memory, converted as the package gives values (64-bit integers as bigints).
107
+ * @param names The type names to read the type with.
108
+ * @param pointer The address.
109
+ * @param type The type name.
110
+ * @param count The number of values, for an array.
111
+ */
112
+ export function readNative(names, pointer, type, count) {
113
+ const cType = names.type(type);
114
+ const address = checkedPointer(pointer, 'read');
115
+ const converter = TYPES.converter(cType);
116
+ if (count === undefined) {
117
+ const value = koffi.decode(address, TYPES.type(cType));
118
+ return converter?.fromNative ? converter.fromNative(value) : value;
119
+ }
120
+ const values = Array.from(koffi.decode(address, TYPES.type(cType), checkedLength(count, 'readArray')));
121
+ return converter?.fromNative ? values.map(converter.fromNative) : values;
122
+ }
123
+ /**
124
+ * A value written to native memory.
125
+ * @param names The type names to read the type with.
126
+ * @param pointer The address.
127
+ * @param type The type name.
128
+ * @param value The value.
129
+ */
130
+ export function writeNative(names, pointer, type, value) {
131
+ const cType = names.type(type);
132
+ const converter = TYPES.converter(cType);
133
+ koffi.encode(checkedPointer(pointer, 'write'), TYPES.type(cType), converter?.toNative ? converter.toNative(value) : value);
134
+ }
135
+ /**
136
+ * Native memory allocated for values of a type (C's malloc: freed by freeNative).
137
+ * @param names The type names to read the type with.
138
+ * @param type The type name.
139
+ * @param count The number of values.
140
+ */
141
+ export function allocateNative(names, type, count = 1) {
142
+ if (!Number.isInteger(count) || count < 1)
143
+ throw new NativeMemoryException(`allocMemory: the count must be a positive integer (${count})`);
144
+ const pointer = koffi.alloc(names.koffi(type), count);
145
+ return typeof pointer === 'bigint' ? pointer : koffi.address(pointer);
146
+ }
147
+ /**
148
+ * Free native memory allocated by allocateNative (or by the malloc of the C library the package is built with).
149
+ * @param pointer The address.
150
+ */
151
+ export function freeNative(pointer) {
152
+ koffi.free(checkedPointer(pointer, 'freeMemory'));
153
+ }
154
+ //The _errno() of each C library of Windows
155
+ const windowsErrno = new Map();
156
+ let windowsLastError;
157
+ /**
158
+ * The errno of the last call made by the thread: on Windows, the one of the C library of Microsoft (ucrtbase.dll), each
159
+ * C library having its own; elsewhere the one koffi keeps after each call.
160
+ * @param crt On Windows, the C library the function called uses.
161
+ */
162
+ export function nativeErrno(crt = 'ucrtbase.dll') {
163
+ if (process.platform !== 'win32')
164
+ return koffi.errno();
165
+ const name = crt.toLowerCase();
166
+ let errno = windowsErrno.get(name);
167
+ if (!errno) {
168
+ errno = koffi.load(crt).func('_errno', 'int *', []);
169
+ windowsErrno.set(name, errno);
170
+ }
171
+ return koffi.decode(errno(), 'int');
172
+ }
173
+ /**
174
+ * The last error of the Windows API of the thread (GetLastError), kept by koffi after each call; undefined elsewhere.
175
+ */
176
+ export function nativeWindowsError() {
177
+ if (process.platform !== 'win32')
178
+ return undefined;
179
+ windowsLastError ??= koffi.load('kernel32.dll').func('__stdcall', 'GetLastError', 'uint32_t', []);
180
+ return windowsLastError();
181
+ }
182
+ /**
183
+ * The size of a type on the platform, by its name.
184
+ * @param names The type names.
185
+ * @param type The type name.
186
+ */
187
+ export function typeSize(names, type) {
188
+ const cType = names.type(type);
189
+ if (cType.kind === 'function')
190
+ throw new NativeDeclarationException(`A function has no size: ${type}`);
191
+ return koffi.sizeof(TYPES.type(cType));
192
+ }
@@ -0,0 +1,206 @@
1
+ import koffi from 'koffi';
2
+ import { NativeDeclarationException } from '../../exceptions/NativeDeclarationException.js';
3
+ import { primitiveBig, typeText } from '../declarations/Types.js';
4
+ import { toBigInt } from './KoffiTypes.js';
5
+ /**
6
+ * A 64-bit integer as a number, when it is a safe one
7
+ * @param value
8
+ */
9
+ export function toSafeNumber(value) {
10
+ if (typeof value !== 'bigint')
11
+ return value;
12
+ if (value > BigInt(Number.MAX_SAFE_INTEGER) || value < BigInt(Number.MIN_SAFE_INTEGER))
13
+ throw new RangeError(`${value} is not a safe integer: declare the function without int64: 'number' to get a bigint`);
14
+ return Number(value);
15
+ }
16
+ /**
17
+ * Whether a type is a pointer to a string type (char *, char16_t *, wchar_t *)
18
+ * @param type
19
+ */
20
+ function isString(type) {
21
+ return type.kind === 'pointer' && type.to.kind === 'primitive' && ['char', 'char16', 'wchar'].includes(type.to.name);
22
+ }
23
+ /**
24
+ * The koffi type of a string type, freed by a function once read
25
+ * @param type
26
+ * @param free
27
+ */
28
+ function disposableString(type, free) {
29
+ const name = type.to.name;
30
+ return koffi.disposable(name === 'char' ? 'char *' : name === 'char16' ? 'char16_t *' : 'wchar_t *', free);
31
+ }
32
+ /**
33
+ * A JS function given as a callback, its arguments and its result converted when its prototype has 64-bit integers or
34
+ * structs with bitfields; the same function otherwise
35
+ * @param type
36
+ * @param types
37
+ * @param callback
38
+ */
39
+ export function convertedCallback(type, types, callback) {
40
+ const steps = type.parameters.map((parameter) => types.converter(parameter.type));
41
+ const result = types.converter(type.result);
42
+ if (!steps.some((step) => !!step?.fromNative) && !result?.toNative)
43
+ return callback;
44
+ return (...args) => {
45
+ for (let i = 0; i < steps.length; i++)
46
+ if (steps[i]?.fromNative)
47
+ args[i] = steps[i].fromNative(args[i]);
48
+ const value = callback(...args);
49
+ return result?.toNative ? result.toNative(value) : value;
50
+ };
51
+ }
52
+ /**
53
+ * The steps of the arguments of a function: the callbacks converted, the structs with bitfields written, the output
54
+ * integers and structs read back as the package gives them
55
+ * @param type
56
+ * @param types
57
+ * @param options
58
+ */
59
+ function argumentSteps(type, types, options) {
60
+ const steps = [];
61
+ type.parameters.forEach((parameter, index) => {
62
+ const target = parameter.type.kind === 'pointer' ? parameter.type.to : undefined;
63
+ if (parameter.direction === 'in') {
64
+ if (target?.kind === 'function') {
65
+ const prototype = target;
66
+ const probe = () => undefined;
67
+ if (convertedCallback(prototype, types, probe) === probe)
68
+ return;
69
+ steps.push({ index: index, before: (value) => typeof value === 'function' ? convertedCallback(prototype, types, value) : value });
70
+ return;
71
+ }
72
+ const converter = types.converter(target && (target.kind === 'struct' || target.kind === 'union') ? target : parameter.type);
73
+ if (converter?.toNative && (parameter.type.kind !== 'pointer' || target?.kind === 'struct' || target?.kind === 'union')) {
74
+ steps.push({ index: index, before: (value) => isPlainObject(value) ? converter.toNative(value) : value });
75
+ }
76
+ return;
77
+ }
78
+ if (!target)
79
+ return;
80
+ //An output integer: the array given gets a bigint (or a number, with int64: 'number')
81
+ if ((target.kind === 'primitive' && primitiveBig(target.name)) || (target.kind === 'enum' && primitiveBig(target.base))) {
82
+ const convert = options.int64 === 'number' ? toSafeNumber : toBigInt;
83
+ steps.push({ index: index, after: (given) => {
84
+ if (Array.isArray(given))
85
+ given[0] = convert(given[0]);
86
+ } });
87
+ return;
88
+ }
89
+ //An output struct: filled through an object koffi knows, then given back as the package gives it
90
+ const converter = types.converter(target);
91
+ if (converter && (target.kind === 'struct' || target.kind === 'union')) {
92
+ steps.push({
93
+ index: index,
94
+ before: (value) => isPlainObject(value) ? (parameter.direction === 'inout' && converter.toNative ? converter.toNative(value) : { ...value }) : value,
95
+ after: (given, passed) => {
96
+ if (isPlainObject(given) && passed !== given)
97
+ Object.assign(given, converter.fromNative ? converter.fromNative(passed) : passed);
98
+ }
99
+ });
100
+ }
101
+ });
102
+ return steps;
103
+ }
104
+ /**
105
+ * Whether a value is a plain object (a struct given as an object), not a pointer, a buffer or a koffi union
106
+ * @param value
107
+ */
108
+ function isPlainObject(value) {
109
+ return !!value && typeof value === 'object' && !ArrayBuffer.isView(value) && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
110
+ }
111
+ /**
112
+ * A function of a library, bound by its declaration: the function of koffi itself when nothing has to be converted,
113
+ * else a function converting its arguments and its result; a function returning a promise when asynchronous
114
+ * @param library
115
+ * @param declaration
116
+ * @param types
117
+ * @param variadicTypes
118
+ * @param options
119
+ */
120
+ export function bindFunction(library, declaration, types, variadicTypes, options = {}) {
121
+ const type = declaration.type;
122
+ if (options.async && type.variadic)
123
+ throw new NativeDeclarationException(`The variadic function ${declaration.name} cannot be called asynchronously`);
124
+ const result = options.free && isString(type.result) ? disposableString(type.result, options.free) : types.type(type.result);
125
+ const parameters = type.parameters.map((parameter) => {
126
+ const target = parameter.type.kind === 'pointer' ? parameter.type.to : undefined;
127
+ if (parameter.direction === 'in')
128
+ return types.type(parameter.type);
129
+ if (!target)
130
+ throw new NativeDeclarationException(`The parameter ${parameter.name ?? ''} of ${declaration.name} is ${parameter.direction === 'out' ? '_Out_' : '_Inout_'} but not a pointer (${typeText(parameter.type)})`.replace(' ', ' '));
131
+ const pointed = options.free && isString(target) ? koffi.pointer(disposableString(target, options.free)) : types.type(parameter.type);
132
+ return parameter.direction === 'out' ? koffi.out(pointed) : koffi.inout(pointed);
133
+ });
134
+ let bound;
135
+ try {
136
+ bound = library.func(`__${type.convention}`, declaration.symbol, result, type.variadic ? [...parameters, '...'] : parameters);
137
+ }
138
+ catch (error) {
139
+ throw new NativeDeclarationException(`The function ${declaration.name} cannot be bound (${declaration.symbol}): ${error.message}`);
140
+ }
141
+ const steps = argumentSteps(type, types, options);
142
+ const resultConverter = options.int64 === 'number' && (type.result.kind === 'primitive' && primitiveBig(type.result.name)) ? toSafeNumber : types.converter(type.result)?.fromNative;
143
+ const fixed = type.parameters.length;
144
+ const prepare = (args) => {
145
+ const passed = args.slice();
146
+ for (const step of steps)
147
+ if (step.before)
148
+ passed[step.index] = step.before(args[step.index]);
149
+ //The extra arguments of a variadic function: the koffi type of each C type name given
150
+ if (type.variadic)
151
+ for (let i = fixed; i < passed.length; i += 2)
152
+ if (typeof passed[i] === 'string')
153
+ passed[i] = variadicTypes(passed[i]);
154
+ return passed;
155
+ };
156
+ const finish = (args, passed, value) => {
157
+ for (const step of steps)
158
+ if (step.after)
159
+ step.after(args[step.index], passed[step.index]);
160
+ return resultConverter ? resultConverter(value) : value;
161
+ };
162
+ if (!options.async) {
163
+ if (!steps.length && !resultConverter && !type.variadic)
164
+ return bound;
165
+ return (...args) => {
166
+ const passed = prepare(args);
167
+ return finish(args, passed, bound(...passed));
168
+ };
169
+ }
170
+ return (...args) => {
171
+ //Refused once the library is closed: a rejected promise, as every failure of the call
172
+ try {
173
+ options.calls?.start();
174
+ }
175
+ catch (error) {
176
+ return Promise.reject(error);
177
+ }
178
+ let passed;
179
+ try {
180
+ passed = prepare(args);
181
+ }
182
+ catch (error) {
183
+ options.calls?.end();
184
+ return Promise.reject(error);
185
+ }
186
+ return new Promise((resolve, reject) => {
187
+ try {
188
+ bound.async(...passed, (error, value) => {
189
+ options.calls?.end();
190
+ if (error)
191
+ return reject(error);
192
+ try {
193
+ resolve(finish(args, passed, value));
194
+ }
195
+ catch (conversion) {
196
+ reject(conversion);
197
+ }
198
+ });
199
+ }
200
+ catch (error) {
201
+ options.calls?.end();
202
+ reject(error);
203
+ }
204
+ });
205
+ };
206
+ }