@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.
- 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,540 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.NativeLibraryHandle = void 0;
|
|
7
|
+
exports.loadNativeLibrary = loadNativeLibrary;
|
|
8
|
+
const koffi_1 = __importDefault(require("koffi"));
|
|
9
|
+
const NativeDeclarationException_js_1 = require("../../exceptions/NativeDeclarationException.js");
|
|
10
|
+
const NativeLibraryClosedException_js_1 = require("../../exceptions/NativeLibraryClosedException.js");
|
|
11
|
+
const Functions_js_1 = require("../backend/Functions.js");
|
|
12
|
+
const Layout_js_1 = require("../declarations/Layout.js");
|
|
13
|
+
const Parser_js_1 = require("../declarations/Parser.js");
|
|
14
|
+
const Platform_js_1 = require("../declarations/Platform.js");
|
|
15
|
+
const Types_js_1 = require("../declarations/Types.js");
|
|
16
|
+
const Runtime_js_1 = require("../Runtime.js");
|
|
17
|
+
const NativeThreads_js_1 = require("../thread/NativeThreads.js");
|
|
18
|
+
const NativeCallback_js_1 = require("./NativeCallback.js");
|
|
19
|
+
const Resolve_js_1 = require("./Resolve.js");
|
|
20
|
+
/**
|
|
21
|
+
* The declarations of a library, shared by the libraries loaded with the same declarations: read once, their C types and
|
|
22
|
+
* their prototypes made once (koffi never frees a type)
|
|
23
|
+
*/
|
|
24
|
+
class LibraryModel {
|
|
25
|
+
#prototypes;
|
|
26
|
+
/**
|
|
27
|
+
* Read the declarations.
|
|
28
|
+
* @param declarations The C declarations.
|
|
29
|
+
*/
|
|
30
|
+
constructor(declarations) {
|
|
31
|
+
this.parser = new Parser_js_1.DeclarationParser(Platform_js_1.CURRENT_PLATFORM);
|
|
32
|
+
this.names = new Runtime_js_1.TypeNames(this.parser);
|
|
33
|
+
this.#prototypes = new Map();
|
|
34
|
+
if (declarations)
|
|
35
|
+
this.parser.parse(declarations);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A prototype, read once.
|
|
39
|
+
* @param text The prototype.
|
|
40
|
+
*/
|
|
41
|
+
prototype(text) {
|
|
42
|
+
let declaration = this.#prototypes.get(text);
|
|
43
|
+
if (!declaration) {
|
|
44
|
+
declaration = this.parser.prototype(text);
|
|
45
|
+
this.#prototypes.set(text, declaration);
|
|
46
|
+
}
|
|
47
|
+
return declaration;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const models = new Map();
|
|
51
|
+
/**
|
|
52
|
+
* The model of declarations, the same for the same text
|
|
53
|
+
* @param declarations
|
|
54
|
+
*/
|
|
55
|
+
function modelOf(declarations) {
|
|
56
|
+
let model = models.get(declarations);
|
|
57
|
+
if (!model) {
|
|
58
|
+
model = new LibraryModel(declarations);
|
|
59
|
+
models.set(declarations, model);
|
|
60
|
+
}
|
|
61
|
+
return model;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The value a callback returns to the library when it throws: zero, false, a null pointer
|
|
65
|
+
* @param type
|
|
66
|
+
*/
|
|
67
|
+
function zeroOf(type) {
|
|
68
|
+
switch (type.kind) {
|
|
69
|
+
case 'primitive':
|
|
70
|
+
if (type.name === 'void')
|
|
71
|
+
return undefined;
|
|
72
|
+
if (type.name === 'bool')
|
|
73
|
+
return false;
|
|
74
|
+
return 0;
|
|
75
|
+
case 'enum':
|
|
76
|
+
return 0;
|
|
77
|
+
case 'pointer':
|
|
78
|
+
return null;
|
|
79
|
+
default:
|
|
80
|
+
return {};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A native library loaded with its declarations, without the container (scripts, tests): its functions, variables and
|
|
85
|
+
* callbacks, and the native memory read and written with its types. Made by {@link loadNativeLibrary}; a
|
|
86
|
+
* `NativeLibrary` component holds one.
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* import {loadNativeLibrary, type NativeLibraryHandle} from '@lakutata/ffi'
|
|
90
|
+
*
|
|
91
|
+
* const libc: NativeLibraryHandle = loadNativeLibrary('c', 'typedef struct { int quot; int rem; } div_t;')
|
|
92
|
+
* const div = libc.func<(numerator: number, denominator: number) => {quot: number, rem: number}>('div_t div(int numerator, int denominator)')
|
|
93
|
+
* console.log(div(17, 5))
|
|
94
|
+
* await libc.close()
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
class NativeLibraryHandle {
|
|
98
|
+
#library;
|
|
99
|
+
#file;
|
|
100
|
+
#model;
|
|
101
|
+
#parser;
|
|
102
|
+
#names;
|
|
103
|
+
#options;
|
|
104
|
+
#functions = new Map();
|
|
105
|
+
#callbacks = new Set();
|
|
106
|
+
#allocations = new Set();
|
|
107
|
+
#frees = new Map();
|
|
108
|
+
#calls;
|
|
109
|
+
#threads;
|
|
110
|
+
//The errno and the last error of Windows of the thread after the last asynchronous call completed in the threads
|
|
111
|
+
#threadErrno;
|
|
112
|
+
#threadWindowsError;
|
|
113
|
+
#pending = 0;
|
|
114
|
+
#drained = [];
|
|
115
|
+
#closed = false;
|
|
116
|
+
/**
|
|
117
|
+
* Load a library (see {@link loadNativeLibrary}).
|
|
118
|
+
* @param names Its file names: one, or one by platform; `c` for the C library.
|
|
119
|
+
* @param declarations Its C declarations: the types, functions and variables its functions use.
|
|
120
|
+
* @param options Where it is found, how it is closed.
|
|
121
|
+
*/
|
|
122
|
+
constructor(names, declarations = '', options = {}) {
|
|
123
|
+
this.#options = options;
|
|
124
|
+
this.#model = modelOf(declarations);
|
|
125
|
+
this.#parser = this.#model.parser;
|
|
126
|
+
this.#names = this.#model.names;
|
|
127
|
+
const { library, file } = (0, Resolve_js_1.loadLibrary)(names, options);
|
|
128
|
+
this.#library = library;
|
|
129
|
+
this.#file = file;
|
|
130
|
+
if (options.threads !== undefined && (!Number.isInteger(options.threads) || options.threads < 0))
|
|
131
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(`The threads of a library are a number of threads (${options.threads})`);
|
|
132
|
+
if (options.threads)
|
|
133
|
+
this.#threads = new NativeThreads_js_1.NativeThreads(file, declarations, options.threads, options.crt);
|
|
134
|
+
this.#calls = {
|
|
135
|
+
start: () => {
|
|
136
|
+
if (this.#closed)
|
|
137
|
+
throw new NativeLibraryClosedException_js_1.NativeLibraryClosedException(`The library ${this.#file} is closed`);
|
|
138
|
+
this.#pending++;
|
|
139
|
+
},
|
|
140
|
+
end: () => {
|
|
141
|
+
if (--this.#pending > 0)
|
|
142
|
+
return;
|
|
143
|
+
const drained = this.#drained;
|
|
144
|
+
this.#drained = [];
|
|
145
|
+
for (const resolve of drained)
|
|
146
|
+
resolve();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The file the library was loaded from.
|
|
152
|
+
*/
|
|
153
|
+
get file() {
|
|
154
|
+
return this.#file;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Whether it is closed.
|
|
158
|
+
*/
|
|
159
|
+
get closed() {
|
|
160
|
+
return this.#closed;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The asynchronous calls running.
|
|
164
|
+
*/
|
|
165
|
+
get pending() {
|
|
166
|
+
return this.#pending;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The worker threads of the library (0: the asynchronous calls run in the thread pool of the runtime).
|
|
170
|
+
*/
|
|
171
|
+
get threads() {
|
|
172
|
+
return this.#threads?.count ?? 0;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Settled once the threads of the library have loaded it (at once without threads): rejected with the error of a
|
|
176
|
+
* thread that could not. The asynchronous calls wait for them anyway.
|
|
177
|
+
*/
|
|
178
|
+
get ready() {
|
|
179
|
+
return this.#threads?.ready ?? Promise.resolve();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The constants of its declarations: the values of the enumerations, of the static constants and of the macros whose
|
|
183
|
+
* value is an integer.
|
|
184
|
+
*/
|
|
185
|
+
get constants() {
|
|
186
|
+
return Object.fromEntries(this.#parser.declarations.constants);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Add C declarations to the ones of the library (and of the libraries loaded with the same declarations, which share
|
|
190
|
+
* them).
|
|
191
|
+
* @param text The declarations.
|
|
192
|
+
*/
|
|
193
|
+
declare(text) {
|
|
194
|
+
this.#parser.parse(text);
|
|
195
|
+
this.#threads?.declare(text);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* A function of the library, from its prototype: the function of koffi itself when nothing has to be converted.
|
|
199
|
+
* @param prototype Its C prototype, with the types of the declarations: `int sqlite3_open(const char *filename,
|
|
200
|
+
* _Out_ sqlite3 **db)`.
|
|
201
|
+
* @param options Whether it is asynchronous, how its strings are freed, how its 64-bit integers are given.
|
|
202
|
+
* @returns The function.
|
|
203
|
+
*/
|
|
204
|
+
func(prototype, options = {}) {
|
|
205
|
+
this.#open();
|
|
206
|
+
const key = `${prototype}\u0000${JSON.stringify(options)}`;
|
|
207
|
+
let bound = this.#functions.get(key);
|
|
208
|
+
if (!bound) {
|
|
209
|
+
bound = this.#bind(this.#model.prototype(prototype), options, prototype, false);
|
|
210
|
+
this.#functions.set(key, bound);
|
|
211
|
+
}
|
|
212
|
+
return bound;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* A function of the declarations, by its name.
|
|
216
|
+
* @param name Its name.
|
|
217
|
+
* @param options Whether it is asynchronous, how its strings are freed, how its 64-bit integers are given.
|
|
218
|
+
* @returns The function.
|
|
219
|
+
*/
|
|
220
|
+
function(name, options = {}) {
|
|
221
|
+
this.#open();
|
|
222
|
+
const declaration = this.#parser.declarations.functions.get(name);
|
|
223
|
+
if (!declaration)
|
|
224
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(`The function ${name} is not declared`);
|
|
225
|
+
const key = `\u0001${name}\u0000${JSON.stringify(options)}`;
|
|
226
|
+
let bound = this.#functions.get(key);
|
|
227
|
+
if (!bound) {
|
|
228
|
+
bound = this.#bind(declaration, options, name, true);
|
|
229
|
+
this.#functions.set(key, bound);
|
|
230
|
+
}
|
|
231
|
+
return bound;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* A variable the library exports: declared in the declarations (`extern const char sqlite3_version[];`), or by the
|
|
235
|
+
* declaration given.
|
|
236
|
+
* @param declaration Its name, or its declaration.
|
|
237
|
+
* @returns Its address, and the functions reading and writing its value.
|
|
238
|
+
*/
|
|
239
|
+
variable(declaration) {
|
|
240
|
+
this.#open();
|
|
241
|
+
let name = declaration.trim();
|
|
242
|
+
if (!/^[A-Za-z_]\w*$/.test(name)) {
|
|
243
|
+
const before = new Set(this.#parser.declarations.variables.keys());
|
|
244
|
+
this.#parser.parse(/;\s*$/.test(name) ? name : `${name};`);
|
|
245
|
+
name = [...this.#parser.declarations.variables.keys()].find((variable) => !before.has(variable)) ?? name.match(/(\w+)\s*(\[[^\]]*\])?\s*;?\s*$/)?.[1] ?? name;
|
|
246
|
+
}
|
|
247
|
+
const variable = this.#parser.declarations.variables.get(name);
|
|
248
|
+
if (!variable)
|
|
249
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(`The variable ${name} is not declared`);
|
|
250
|
+
let address;
|
|
251
|
+
try {
|
|
252
|
+
address = this.#library.symbol(variable.symbol);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(`The variable ${name} is not in the library (${variable.symbol}): ${error.message}`);
|
|
256
|
+
}
|
|
257
|
+
const type = variable.type;
|
|
258
|
+
const unknownLength = type.kind === 'array' && type.length === 0;
|
|
259
|
+
const converter = Runtime_js_1.TYPES.converter(type);
|
|
260
|
+
return {
|
|
261
|
+
address: address,
|
|
262
|
+
get: () => {
|
|
263
|
+
this.#open();
|
|
264
|
+
if (unknownLength) {
|
|
265
|
+
const element = type.of;
|
|
266
|
+
return (element.kind === 'primitive' && element.name === 'char' ? koffi_1.default.decode.string(address) : address);
|
|
267
|
+
}
|
|
268
|
+
const value = koffi_1.default.decode(address, Runtime_js_1.TYPES.type(type));
|
|
269
|
+
return (converter?.fromNative ? converter.fromNative(value) : value);
|
|
270
|
+
},
|
|
271
|
+
set: (value) => {
|
|
272
|
+
this.#open();
|
|
273
|
+
if (variable.constant)
|
|
274
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(`The variable ${name} is const`);
|
|
275
|
+
if (unknownLength)
|
|
276
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(`The array ${name} has no length: write its elements through its address`);
|
|
277
|
+
koffi_1.default.encode(address, Runtime_js_1.TYPES.type(type), converter?.toNative ? converter.toNative(value) : value);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* A persistent callback: a JS function the library can call at any time, released by `release()` or when the
|
|
283
|
+
* library is closed.
|
|
284
|
+
* @param type Its type: the name of a pointer-to-function type of the declarations (`vendor_log`), or its prototype
|
|
285
|
+
* (`void vendor_log(int level, const char *message)`).
|
|
286
|
+
* @param callback The JS function.
|
|
287
|
+
* @returns The callback, whose `pointer` is given to the library.
|
|
288
|
+
*/
|
|
289
|
+
callback(type, callback) {
|
|
290
|
+
this.#open();
|
|
291
|
+
let prototype;
|
|
292
|
+
if (type.includes('(')) {
|
|
293
|
+
prototype = this.#parser.prototype(type).type;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
const named = this.#names.type(type);
|
|
297
|
+
const target = named.kind === 'pointer' ? named.to : named;
|
|
298
|
+
if (target.kind !== 'function')
|
|
299
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(`The type ${type} is not a function type (${(0, Types_js_1.typeText)(named)})`);
|
|
300
|
+
prototype = target;
|
|
301
|
+
}
|
|
302
|
+
const onError = this.#options.onCallbackError ?? ((error) => console.error('A callback of a native library threw:', error));
|
|
303
|
+
const zero = zeroOf(prototype.result);
|
|
304
|
+
const safe = (...args) => {
|
|
305
|
+
try {
|
|
306
|
+
return callback(...args);
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
onError(error);
|
|
310
|
+
return zero;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
const pointer = koffi_1.default.register((0, Functions_js_1.convertedCallback)(prototype, Runtime_js_1.TYPES, safe), koffi_1.default.pointer(Runtime_js_1.TYPES.prototype(prototype)));
|
|
314
|
+
const registered = new NativeCallback_js_1.NativeCallback(pointer, (released) => this.#callbacks.delete(released));
|
|
315
|
+
this.#callbacks.add(registered);
|
|
316
|
+
return registered;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Read native memory, as a type of the declarations: a value, or an array of values.
|
|
320
|
+
* @param pointer The address.
|
|
321
|
+
* @param type The type: `int`, `struct timeval`, `char *` (a string).
|
|
322
|
+
* @param count The number of values, for an array.
|
|
323
|
+
* @returns The value, or the array.
|
|
324
|
+
*/
|
|
325
|
+
read(pointer, type, count) {
|
|
326
|
+
this.#open();
|
|
327
|
+
return (0, Runtime_js_1.readNative)(this.#names, pointer, type, count);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Write a value to native memory, as a type of the declarations.
|
|
331
|
+
* @param pointer The address.
|
|
332
|
+
* @param type The type.
|
|
333
|
+
* @param value The value.
|
|
334
|
+
*/
|
|
335
|
+
write(pointer, type, value) {
|
|
336
|
+
this.#open();
|
|
337
|
+
(0, Runtime_js_1.writeNative)(this.#names, pointer, type, value);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* The size of a type of the declarations, in bytes.
|
|
341
|
+
* @param type The type.
|
|
342
|
+
*/
|
|
343
|
+
sizeOf(type) {
|
|
344
|
+
return (0, Runtime_js_1.typeSize)(this.#names, type);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* The offset of a member of a struct of the declarations, in bytes.
|
|
348
|
+
* @param type The struct.
|
|
349
|
+
* @param member The member.
|
|
350
|
+
*/
|
|
351
|
+
offsetOf(type, member) {
|
|
352
|
+
const record = this.#names.type(type);
|
|
353
|
+
if (record.kind !== 'struct' && record.kind !== 'union')
|
|
354
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(`${type} is not a struct or a union`);
|
|
355
|
+
const placed = (0, Layout_js_1.layoutOf)(record, Platform_js_1.CURRENT_PLATFORM).members.find((candidate) => candidate.member.name === member);
|
|
356
|
+
if (!placed)
|
|
357
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(`${type} has no member ${member}`);
|
|
358
|
+
return placed.offset;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Allocate native memory for values of a type of the declarations: memory that does not move, which the library can
|
|
362
|
+
* keep; freed by `free()`, or when the library is closed.
|
|
363
|
+
* @param type The type.
|
|
364
|
+
* @param count The number of values.
|
|
365
|
+
* @returns Its address.
|
|
366
|
+
*/
|
|
367
|
+
alloc(type, count = 1) {
|
|
368
|
+
this.#open();
|
|
369
|
+
const pointer = (0, Runtime_js_1.allocateNative)(this.#names, type, count);
|
|
370
|
+
this.#allocations.add(pointer);
|
|
371
|
+
return pointer;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Free memory allocated by `alloc()`.
|
|
375
|
+
* @param pointer Its address.
|
|
376
|
+
*/
|
|
377
|
+
free(pointer) {
|
|
378
|
+
const address = (0, Runtime_js_1.checkedPointer)(pointer, 'free');
|
|
379
|
+
this.#allocations.delete(address);
|
|
380
|
+
(0, Runtime_js_1.freeNative)(address);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* The errno of the last call of this thread (on Windows, the one of the C library given by the `crt` option); with
|
|
384
|
+
* threads, the one of the thread of the last asynchronous call completed (reliable with one thread, or when the
|
|
385
|
+
* asynchronous calls are awaited one at a time).
|
|
386
|
+
*/
|
|
387
|
+
errno() {
|
|
388
|
+
return this.#threadErrno ?? (0, Runtime_js_1.nativeErrno)(this.#options.crt);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* The last error of the Windows API (GetLastError) after the last call of this thread; with threads, the one of the
|
|
392
|
+
* thread of the last asynchronous call completed; undefined on the other systems.
|
|
393
|
+
*/
|
|
394
|
+
windowsError() {
|
|
395
|
+
return this.#threadWindowsError ?? (0, Runtime_js_1.nativeWindowsError)();
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* The asynchronous calls running, once they have all returned.
|
|
399
|
+
* @param timeout How long to wait at most, in milliseconds.
|
|
400
|
+
* @returns Whether they have all returned.
|
|
401
|
+
*/
|
|
402
|
+
drain(timeout = Infinity) {
|
|
403
|
+
if (!this.#pending)
|
|
404
|
+
return Promise.resolve(true);
|
|
405
|
+
return new Promise((resolve) => {
|
|
406
|
+
const timer = Number.isFinite(timeout) ? setTimeout(() => resolve(false), timeout) : undefined;
|
|
407
|
+
this.#drained.push(() => {
|
|
408
|
+
if (timer)
|
|
409
|
+
clearTimeout(timer);
|
|
410
|
+
resolve(true);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Close the library: new asynchronous calls are refused, the running ones awaited (a worker thread stopped during
|
|
416
|
+
* one crashes Bun), the callbacks released, the memory of `alloc()` freed, the library unloaded (unless kept
|
|
417
|
+
* loaded; musl never unloads). Its functions must not be called any more: a function of an unloaded library ends
|
|
418
|
+
* the process. When calls still run after the timeout, the ones in the thread pool go on, the ones in the threads
|
|
419
|
+
* of the library are rejected (their threads end once they return), and the library, its callbacks and its memory
|
|
420
|
+
* are left as they are: a call returning into an unloaded library, or calling a released callback, would end the
|
|
421
|
+
* process.
|
|
422
|
+
* @param timeout How long to wait for the asynchronous calls, in milliseconds.
|
|
423
|
+
*/
|
|
424
|
+
async close(timeout = 10000) {
|
|
425
|
+
if (this.#closed)
|
|
426
|
+
return;
|
|
427
|
+
this.#closed = true;
|
|
428
|
+
//Decided before the threads close: they reject the calls still running, which then no longer count
|
|
429
|
+
const drained = await this.drain(timeout);
|
|
430
|
+
await this.#threads?.close();
|
|
431
|
+
this.#functions.clear();
|
|
432
|
+
if (!drained)
|
|
433
|
+
return;
|
|
434
|
+
for (const callback of [...this.#callbacks])
|
|
435
|
+
callback.release();
|
|
436
|
+
for (const pointer of this.#allocations)
|
|
437
|
+
(0, Runtime_js_1.freeNative)(pointer);
|
|
438
|
+
this.#allocations.clear();
|
|
439
|
+
if (!this.#options.keepLoaded)
|
|
440
|
+
this.#library.unload();
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Throws once the library is closed
|
|
444
|
+
*/
|
|
445
|
+
#open() {
|
|
446
|
+
if (this.#closed)
|
|
447
|
+
throw new NativeLibraryClosedException_js_1.NativeLibraryClosedException(`The library ${this.#file} is closed`);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* A function bound, with its options; an asynchronous function of a library with threads, called in them
|
|
451
|
+
* @param declaration
|
|
452
|
+
* @param options
|
|
453
|
+
* @param text its prototype, or the name of the declared function
|
|
454
|
+
* @param named
|
|
455
|
+
*/
|
|
456
|
+
#bind(declaration, options, text, named) {
|
|
457
|
+
if (options.async && this.#threads)
|
|
458
|
+
return this.#threaded(this.#threads, { declaration: text, named: named, options: { free: options.free, int64: options.int64 }, type: declaration });
|
|
459
|
+
return (0, Functions_js_1.bindFunction)(this.#library, declaration, Runtime_js_1.TYPES, (text) => this.#names.koffi(text), {
|
|
460
|
+
async: options.async,
|
|
461
|
+
int64: options.int64,
|
|
462
|
+
calls: this.#calls,
|
|
463
|
+
...options.free ? { free: this.#freeFunction(options.free) } : {}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* An asynchronous function called in the threads of the library, counted as its other asynchronous calls
|
|
468
|
+
* @param threads
|
|
469
|
+
* @param fn
|
|
470
|
+
*/
|
|
471
|
+
#threaded(threads, fn) {
|
|
472
|
+
threads.bind(fn);
|
|
473
|
+
return (...args) => {
|
|
474
|
+
try {
|
|
475
|
+
this.#calls.start();
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
return Promise.reject(error);
|
|
479
|
+
}
|
|
480
|
+
return threads.call(fn, args).then((result) => {
|
|
481
|
+
this.#threadErrno = result.errno;
|
|
482
|
+
this.#threadWindowsError = result.windowsError;
|
|
483
|
+
this.#calls.end();
|
|
484
|
+
return result.value;
|
|
485
|
+
}, (error) => {
|
|
486
|
+
this.#calls.end();
|
|
487
|
+
throw error;
|
|
488
|
+
});
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* The function freeing the strings of a function: one of the library, or free() of the C library
|
|
493
|
+
* @param free
|
|
494
|
+
*/
|
|
495
|
+
#freeFunction(free) {
|
|
496
|
+
if (free === true)
|
|
497
|
+
return (pointer) => koffi_1.default.free(pointer);
|
|
498
|
+
let found = this.#frees.get(free);
|
|
499
|
+
if (!found) {
|
|
500
|
+
try {
|
|
501
|
+
found = this.#library.func(free, 'void', ['void *']);
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
throw new NativeDeclarationException_js_1.NativeDeclarationException(`The function ${free} freeing the strings is not in the library: ${error.message}`);
|
|
505
|
+
}
|
|
506
|
+
this.#frees.set(free, found);
|
|
507
|
+
}
|
|
508
|
+
return found;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
exports.NativeLibraryHandle = NativeLibraryHandle;
|
|
512
|
+
/**
|
|
513
|
+
* Load a native library with its C declarations, without the container (scripts, tests); in an application, declare a
|
|
514
|
+
* `NativeLibrary` component instead. The library is looked for in the directories of `search`, then where the system
|
|
515
|
+
* looks for libraries.
|
|
516
|
+
* @param names Its file names: one (`sqlite3`, completed as the platform names libraries), one by platform
|
|
517
|
+
* (`{darwin: 'libsqlite3.dylib', linux: 'libsqlite3.so.0', win32: 'winsqlite3.dll'}`), or `c` for the C library.
|
|
518
|
+
* @param declarations Its C declarations: the types its functions use, its functions, its variables, its constants.
|
|
519
|
+
* @param options Where it is found, how it is closed.
|
|
520
|
+
* @returns The library.
|
|
521
|
+
* @throws {NativeLibraryNotFoundException} When it cannot be loaded (the message lists what was tried).
|
|
522
|
+
* @throws {NativeDeclarationException} When the declarations cannot be read.
|
|
523
|
+
* @example
|
|
524
|
+
* ```typescript
|
|
525
|
+
* import {loadNativeLibrary, type Out, type Pointer} from '@lakutata/ffi'
|
|
526
|
+
*
|
|
527
|
+
* const sqlite = loadNativeLibrary({darwin: 'libsqlite3.dylib', linux: 'libsqlite3.so.0', win32: 'winsqlite3.dll'}, `
|
|
528
|
+
* typedef struct sqlite3 sqlite3;
|
|
529
|
+
* #define SQLITE_OK 0
|
|
530
|
+
* `)
|
|
531
|
+
* const open = sqlite.func<(filename: string, db: Out<Pointer>) => number>('int sqlite3_open(const char *filename, _Out_ sqlite3 **db)')
|
|
532
|
+
* const close = sqlite.func<(db: Pointer) => number>('int sqlite3_close(sqlite3 *db)')
|
|
533
|
+
* const db: Out<Pointer> = [null]
|
|
534
|
+
* if (open(':memory:', db) === Number(sqlite.constants.SQLITE_OK)) close(db[0]!)
|
|
535
|
+
* await sqlite.close()
|
|
536
|
+
* ```
|
|
537
|
+
*/
|
|
538
|
+
function loadNativeLibrary(names, declarations = '', options = {}) {
|
|
539
|
+
return new NativeLibraryHandle(names, declarations, options);
|
|
540
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type LibraryHandle } from 'koffi';
|
|
2
|
+
/**
|
|
3
|
+
* The file of a library: one name for every platform, or one by platform (the names of process.platform, `default`
|
|
4
|
+
* for the others). A name without a directory and without an extension is completed as the platform names its
|
|
5
|
+
* libraries (`sqlite3`: `libsqlite3.dylib`, `libsqlite3.so`, `sqlite3.dll`); `c` and `m` are the C library and its
|
|
6
|
+
* mathematical functions, found where each system has them
|
|
7
|
+
*/
|
|
8
|
+
export type LibraryNames = string | Partial<Record<NodeJS.Platform | 'default', string | readonly string[]>>;
|
|
9
|
+
/**
|
|
10
|
+
* Where a library is looked for
|
|
11
|
+
*/
|
|
12
|
+
export interface ResolveOptions {
|
|
13
|
+
/**
|
|
14
|
+
* The file itself: the only one tried when given (an alias of the application can start it)
|
|
15
|
+
*/
|
|
16
|
+
readonly path?: string;
|
|
17
|
+
/**
|
|
18
|
+
* The directories tried first, in their order
|
|
19
|
+
*/
|
|
20
|
+
readonly search?: readonly string[];
|
|
21
|
+
/**
|
|
22
|
+
* The directory of the libraries of a packaged application, tried after the search directories
|
|
23
|
+
*/
|
|
24
|
+
readonly packaged?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The file names a name gives on a system: the name itself when it has a directory or an extension, else the names the
|
|
28
|
+
* system gives libraries
|
|
29
|
+
* @param name
|
|
30
|
+
* @param os
|
|
31
|
+
*/
|
|
32
|
+
export declare function fileNames(name: string, os?: NodeJS.Platform): string[];
|
|
33
|
+
/**
|
|
34
|
+
* The names of a library on a system
|
|
35
|
+
* @param names
|
|
36
|
+
* @param os
|
|
37
|
+
*/
|
|
38
|
+
export declare function libraryNames(names: LibraryNames, os?: NodeJS.Platform): string[];
|
|
39
|
+
/**
|
|
40
|
+
* Load a library: the file given; else each name in the search directories, in the directory of a packaged
|
|
41
|
+
* application, then where the system looks for libraries (dlopen, LoadLibrary)
|
|
42
|
+
* @param names
|
|
43
|
+
* @param options
|
|
44
|
+
* @returns The library and the file it was loaded from.
|
|
45
|
+
*/
|
|
46
|
+
export declare function loadLibrary(names: LibraryNames, options?: ResolveOptions): {
|
|
47
|
+
library: LibraryHandle;
|
|
48
|
+
file: string;
|
|
49
|
+
};
|