@lakutata/ffi 0.0.0-stage → 3.1.5
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 +23 -0
- package/README.md +163 -2
- package/README.zh-CN.md +140 -0
- package/dist/cjs/components/NativeLibrary.d.ts +229 -0
- package/dist/cjs/components/NativeLibrary.js +346 -0
- package/dist/cjs/decorators/Library.d.ts +61 -0
- package/dist/cjs/decorators/Library.js +44 -0
- package/dist/cjs/decorators/Native.d.ts +69 -0
- package/dist/cjs/decorators/Native.js +90 -0
- package/dist/cjs/exceptions/NativeDeclarationException.d.ts +12 -0
- package/dist/cjs/exceptions/NativeDeclarationException.js +19 -0
- package/dist/cjs/exceptions/NativeLibraryClosedException.d.ts +12 -0
- package/dist/cjs/exceptions/NativeLibraryClosedException.js +19 -0
- package/dist/cjs/exceptions/NativeLibraryNotFoundException.d.ts +11 -0
- package/dist/cjs/exceptions/NativeLibraryNotFoundException.js +18 -0
- package/dist/cjs/exceptions/NativeMemoryException.d.ts +11 -0
- package/dist/cjs/exceptions/NativeMemoryException.js +18 -0
- package/dist/cjs/exports/FFI.d.ts +12 -0
- package/dist/cjs/exports/FFI.js +40 -0
- package/dist/cjs/exports/Generator.d.ts +2 -0
- package/dist/cjs/exports/Generator.js +10 -0
- package/dist/cjs/lib/Memory.d.ts +188 -0
- package/dist/cjs/lib/Memory.js +227 -0
- package/dist/cjs/lib/Runtime.d.ts +98 -0
- package/dist/cjs/lib/Runtime.js +209 -0
- package/dist/cjs/lib/backend/Functions.d.ts +65 -0
- package/dist/cjs/lib/backend/Functions.js +214 -0
- package/dist/cjs/lib/backend/KoffiTypes.d.ts +53 -0
- package/dist/cjs/lib/backend/KoffiTypes.js +426 -0
- package/dist/cjs/lib/declarations/Builtins.d.ts +33 -0
- package/dist/cjs/lib/declarations/Builtins.js +179 -0
- package/dist/cjs/lib/declarations/Layout.d.ts +41 -0
- package/dist/cjs/lib/declarations/Layout.js +180 -0
- package/dist/cjs/lib/declarations/Lexer.d.ts +40 -0
- package/dist/cjs/lib/declarations/Lexer.js +325 -0
- package/dist/cjs/lib/declarations/Parser.d.ts +90 -0
- package/dist/cjs/lib/declarations/Parser.js +1425 -0
- package/dist/cjs/lib/declarations/Platform.d.ts +50 -0
- package/dist/cjs/lib/declarations/Platform.js +27 -0
- package/dist/cjs/lib/declarations/Types.d.ts +173 -0
- package/dist/cjs/lib/declarations/Types.js +179 -0
- package/dist/cjs/lib/generator/CText.d.ts +14 -0
- package/dist/cjs/lib/generator/CText.js +133 -0
- package/dist/cjs/lib/generator/Generator.d.ts +136 -0
- package/dist/cjs/lib/generator/Generator.js +395 -0
- package/dist/cjs/lib/generator/Preprocessor.d.ts +60 -0
- package/dist/cjs/lib/generator/Preprocessor.js +94 -0
- package/dist/cjs/lib/generator/TypeScriptTypes.d.ts +35 -0
- package/dist/cjs/lib/generator/TypeScriptTypes.js +153 -0
- package/dist/cjs/lib/library/NativeCallback.d.ts +44 -0
- package/dist/cjs/lib/library/NativeCallback.js +66 -0
- package/dist/cjs/lib/library/NativeLibraryHandle.d.ts +268 -0
- package/dist/cjs/lib/library/NativeLibraryHandle.js +540 -0
- package/dist/cjs/lib/library/Resolve.d.ts +49 -0
- package/dist/cjs/lib/library/Resolve.js +130 -0
- package/dist/cjs/lib/thread/NativeThreads.d.ts +80 -0
- package/dist/cjs/lib/thread/NativeThreads.js +384 -0
- package/dist/cjs/lib/thread/NativeWorker.d.ts +6 -0
- package/dist/cjs/lib/thread/NativeWorker.js +96 -0
- package/dist/cjs/lib/thread/NativeWorkerCode.d.ts +5 -0
- package/dist/cjs/lib/thread/NativeWorkerCode.js +4 -0
- package/dist/cjs/lib/thread/PackageRequire.d.ts +6 -0
- package/dist/cjs/lib/thread/PackageRequire.js +4 -0
- package/dist/cjs/lib/thread/ThreadProtocol.d.ts +77 -0
- package/dist/cjs/lib/thread/ThreadProtocol.js +2 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/components/NativeLibrary.js +338 -0
- package/dist/esm/decorators/Library.js +40 -0
- package/dist/esm/decorators/Native.js +85 -0
- package/dist/esm/exceptions/NativeDeclarationException.js +15 -0
- package/dist/esm/exceptions/NativeLibraryClosedException.js +15 -0
- package/dist/esm/exceptions/NativeLibraryNotFoundException.js +14 -0
- package/dist/esm/exceptions/NativeMemoryException.js +14 -0
- package/dist/esm/exports/FFI.js +10 -0
- package/dist/esm/exports/Generator.js +2 -0
- package/dist/esm/lib/Memory.js +210 -0
- package/dist/esm/lib/Runtime.js +192 -0
- package/dist/esm/lib/backend/Functions.js +206 -0
- package/dist/esm/lib/backend/KoffiTypes.js +418 -0
- package/dist/esm/lib/declarations/Builtins.js +174 -0
- package/dist/esm/lib/declarations/Layout.js +175 -0
- package/dist/esm/lib/declarations/Lexer.js +321 -0
- package/dist/esm/lib/declarations/Parser.js +1421 -0
- package/dist/esm/lib/declarations/Platform.js +23 -0
- package/dist/esm/lib/declarations/Types.js +170 -0
- package/dist/esm/lib/generator/CText.js +129 -0
- package/dist/esm/lib/generator/Generator.js +388 -0
- package/dist/esm/lib/generator/Preprocessor.js +87 -0
- package/dist/esm/lib/generator/TypeScriptTypes.js +149 -0
- package/dist/esm/lib/library/NativeCallback.js +59 -0
- package/dist/esm/lib/library/NativeLibraryHandle.js +532 -0
- package/dist/esm/lib/library/Resolve.js +122 -0
- package/dist/esm/lib/thread/NativeThreads.js +377 -0
- package/dist/esm/lib/thread/NativeWorker.js +93 -0
- package/dist/esm/lib/thread/NativeWorkerCode.js +1 -0
- package/dist/esm/lib/thread/PackageRequire.js +2 -0
- package/dist/esm/lib/thread/ThreadProtocol.js +1 -0
- package/dist/types/components/NativeLibrary.d.ts +229 -0
- package/dist/types/decorators/Library.d.ts +61 -0
- package/dist/types/decorators/Native.d.ts +69 -0
- package/dist/types/exceptions/NativeDeclarationException.d.ts +12 -0
- package/dist/types/exceptions/NativeLibraryClosedException.d.ts +12 -0
- package/dist/types/exceptions/NativeLibraryNotFoundException.d.ts +11 -0
- package/dist/types/exceptions/NativeMemoryException.d.ts +11 -0
- package/dist/types/exports/FFI.d.ts +12 -0
- package/dist/types/exports/Generator.d.ts +2 -0
- package/dist/types/lib/Memory.d.ts +188 -0
- package/dist/types/lib/Runtime.d.ts +98 -0
- package/dist/types/lib/backend/Functions.d.ts +65 -0
- package/dist/types/lib/backend/KoffiTypes.d.ts +53 -0
- package/dist/types/lib/declarations/Builtins.d.ts +33 -0
- package/dist/types/lib/declarations/Layout.d.ts +41 -0
- package/dist/types/lib/declarations/Lexer.d.ts +40 -0
- package/dist/types/lib/declarations/Parser.d.ts +90 -0
- package/dist/types/lib/declarations/Platform.d.ts +50 -0
- package/dist/types/lib/declarations/Types.d.ts +173 -0
- package/dist/types/lib/generator/CText.d.ts +14 -0
- package/dist/types/lib/generator/Generator.d.ts +136 -0
- package/dist/types/lib/generator/Preprocessor.d.ts +60 -0
- package/dist/types/lib/generator/TypeScriptTypes.d.ts +35 -0
- package/dist/types/lib/library/NativeCallback.d.ts +44 -0
- package/dist/types/lib/library/NativeLibraryHandle.d.ts +268 -0
- package/dist/types/lib/library/Resolve.d.ts +49 -0
- package/dist/types/lib/thread/NativeThreads.d.ts +80 -0
- package/dist/types/lib/thread/NativeWorker.d.ts +6 -0
- package/dist/types/lib/thread/NativeWorkerCode.d.ts +5 -0
- package/dist/types/lib/thread/PackageRequire.d.ts +6 -0
- package/dist/types/lib/thread/ThreadProtocol.d.ts +77 -0
- 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
|
+
}
|