Skip to content

Commit b3edaa2

Browse files
authored
node:ffi, new helpers and options (#1)
1 parent a9b3d6a commit b3edaa2

26 files changed

Lines changed: 979 additions & 296 deletions

.npmignore

Lines changed: 0 additions & 4 deletions
This file was deleted.

README.md

Lines changed: 118 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
About
22
=====
33

4-
Foreign Function Interface helper. Provides a friendly abstraction/API for:
4+
Foreign Function Interface (FFI) helper. Provides a friendly abstraction/API for:
55

66
- [ffi-napi](https://www.npmjs.com/package/ffi-napi) (MIT)
77
- [koffi](https://www.npmjs.com/package/koffi) (MIT)
@@ -101,6 +101,11 @@ npm install @xan105/ffi
101101
Please note that `ffi-napi` and `koffi` are optional peer dependencies.<br />
102102
Install the one you wish to use yourself (or both 🙃).
103103

104+
### ⚛️ Electron
105+
106+
⚠️ NB: As of this writing `ffi-napi` does not work with Electron >= 21.x.<br />
107+
Due to [Electron and the V8 Memory Cage](https://www.electronjs.org/blog/v8-memory-cage).
108+
104109
API
105110
===
106111

@@ -120,24 +125,29 @@ import ... from "@xan105/ffi/koffi";
120125

121126
Load the given library path and return an handle function to call library's symbol(s).
122127

123-
**Option**
128+
⚙️ **Option**
124129

125130
- `ignoreLoadingFail?: boolean` (false)
126131

127-
Silent fail if the given library couldn't be loaded.<br />
128-
💡 Handle will return `undefined` in that case.
132+
When set to `true` the handle function will silently fail if the given library couldn't be loaded and return `undefined` in such case.
129133

130134
- `ignoreMissingSymbol?: boolean` (false)
131135

132-
Silent fail if the given library doesn't have the called symbol.<br />
133-
💡 Handle will return `undefined` in that case.
136+
When set to `true` the handle function will silently fail if the given library doesn't have the called symbol and return `undefined` in such case.
137+
138+
- `lazy` (false)
134139

135-
- `abi?: string` ("func" for koffi and "default_abi" for ffi-napi)
140+
When set to `true` use `RTLD_LAZY` (lazy-binding) on POSIX platforms otherwise use `RTLD_NOW`.
136141

137-
ABI convention to use. Use this when you need to ex: winapi x86 requires "stdcall".
142+
- `abi?: string` (koffi: "func" | ffi-napi: "default_abi")
143+
144+
ABI convention to use. Use this when you need to.<br />
145+
_ex: winapi x86 requires "stdcall"._
138146

139147
**Return**
140148

149+
An handle function to call library's symbol(s).
150+
141151
```ts
142152
function(symbol: string | number, result: unknown, parameters: unknown[]): unknown
143153
```
@@ -153,21 +163,20 @@ See the corresponding FFI library for more information on what to pass for `resu
153163
```js
154164
import { load } from "@xan105/ffi/[ napi | koffi ]";
155165
const lib = load("libm");
156-
const ceil = lib("ceil", "double", ["double"])
166+
const ceil = lib("ceil", "double", ["double"]);
157167
ceil(1.5); //2
158168
```
159169

160170
#### `dlopen(path: string, symbols: object, option?: object): object`
161171

162-
Open library and define exported symbols. This is a friendly wrapper to `load()` inspired by Deno FFI `dlopen` syntax.
163-
172+
Open library and define exported symbols. This is a friendly wrapper to `load()` inspired by Deno FFI `dlopen` syntax.<br />
164173
If you ever use ffi-napi `ffi.Library()` this will be familiar.
165174

166175
**Param**
167176

168177
- `path: string`
169178

170-
Library path to load
179+
Library path to load.
171180

172181
- `symbols: object`
173182

@@ -176,28 +185,50 @@ If you ever use ffi-napi `ffi.Library()` this will be familiar.
176185
```ts
177186
{
178187
name: {
188+
symbol?: string | number,
179189
result?: unknown,
180190
parameters?: unknown[],
181191
nonblocking?: boolean,
182-
symbol?: string | number
192+
stub?: boolean
183193
},
184194
...
185195
}
186196
```
197+
198+
By default the property `name` is used for `symbol`. Use `symbol` if you are using a symbol name different than the given property name or if you want to call by ordinal (Koffi).
187199

188-
By default the property name is used for `symbol` when omitted. Use `symbol` if you are using a different name than the symbol name or if you want to call by ordinal (Koffi).
200+
`result` and `parameters` are the same as for the returned handle from `load()`.<br />
201+
If omitted, `result` is set to "void" and `parameters` to an empty array.<br />
202+
See the corresponding FFI library for more information on what to pass for `result` and `parameters` as they have string type parser, structure/array/pointer interface, ... and other features.
189203

190-
When `nonblocking` is `true` (default false) this will return the promisified `async()` method of the corresponding symbol (see corresponding ffi library asynchronous calling). The rest is the same as for `load()`.
204+
When `nonblocking` is `true` the corresponding symbol will return the promisified `async()` method (asynchronous calling). 💡 If set, this superseed the _"global"_ `nonblocking` option (see below).
191205

192-
- option?: object
206+
When `stub` is `true` the corresponding symbol will return a no-op if its missing.<br />
207+
💡 If set, this superseed the _"global"_ `stub` option (see below).
208+
209+
- ⚙️ `option?: object`
193210

194-
Pass option(s) to `load()`. See above.
211+
Same as `load()` (see above) in addition to the following:
212+
213+
+ `errorAtRuntime?: boolean` (false)
214+
215+
When set to `true`, initialisation error will be thrown on symbol invocation.
216+
217+
+ `nonblocking?: boolean` (false)
218+
219+
When set to `true`, every symbols will return the corresponding promisified `async()` method (asynchronous calling).<br />
220+
💡 This can be overriden per symbol (see symbol definition above).
221+
222+
+ `stub?: boolean` (false)
223+
224+
When set to `true`, every missing symbols will return a no-op.<br />
225+
💡 This can be overriden per symbol (see symbol definition above).
195226

196227
**Return**
197228

198229
An object with the given symbol(s) as properties.
199230

200-
Throws on error
231+
Throws on error.
201232

202233
**Example**
203234

@@ -348,9 +379,54 @@ library.doSomething();
348379
callback.close();
349380
```
350381

351-
#### `pointer(value: unknown, direction?: string): any`
382+
#### `pointer(value: unknown, direction?: string): unknown`
352383

353-
Just a shorthand to `ref.refType(x)` (ffi-napi) and `koffi.out/inout(koffi.pointer(x))` (koffi) to define a pointer.
384+
Just a shorthand to define a pointer.
385+
386+
```js
387+
import { dlopen, types, pointer } from "@xan105/ffi/[ napi | koffi ]";
388+
389+
const dylib = dlopen("shell32.dll", {
390+
SHQueryUserNotificationState: {
391+
result: types.win32.HRESULT,
392+
parameters: [
393+
pointer(types.win32.ENUM, "out")
394+
]
395+
}
396+
}, { abi: "stdcall" });
397+
```
398+
399+
#### `struct(schema: unknown): unknown`
400+
401+
Just a shorthand to define a structure.
402+
403+
```js
404+
import { dlopen, types, struct, pointer } from "@xan105/ffi/[ napi | koffi ]";
405+
406+
const POINT = struct({ //define struct
407+
x: types.win32.LONG,
408+
y: types.win32.LONG
409+
});
410+
411+
const dylib = dlopen("user32.dll", { //lib loading
412+
GetCursorPos: {
413+
result: types.win32.BOOL,
414+
parameters: [ pointer(POINT, "out") ] //struct pointer
415+
}
416+
}, { abi: "stdcall" });
417+
418+
//⚠️ NB: Struct are use differently afterwards:
419+
420+
//Koffi
421+
const cursorPos = {};
422+
GetCursorPos(cursorPos);
423+
console.log(cursorPos) //{ x: 0, y: 0 }
424+
425+
//ffi-napi
426+
const cursorPos = new POINT();
427+
getCursorPos(cursorPos.ref());
428+
console.log({ x: cursorPos.x, y: cursorPos.y });
429+
```
354430

355431
#### `alloc(type: unknown): { pointer: Buffer, get: ()=> unknown }`
356432

@@ -363,4 +439,26 @@ const dylib = dlopen(...); //lib loading
363439
const number = alloc("int"); //allocate Buffer for the output data
364440
dylib.manipulate_number(number.pointer);
365441
const result = number.get();
442+
```
443+
444+
#### `lastError(option?: object): string[] | number`
445+
446+
Shorthand to errno (POSIX) and GetLastError (win32).
447+
448+
⚙️ **Option**
449+
450+
- `translate?: boolean` (true)
451+
452+
When an error code is known it will be 'translated' to its corresponding message and code values as<br /> `[message: string, code?: string]`. If you only want the raw numerical code set it to `false`.
453+
454+
eg:
455+
```js
456+
if(result !== 0){ //something went wrong
457+
458+
console.log(lastError())
459+
//['No such file or directory', 'ENOENT']
460+
461+
console.log(lastError({ translate: false }));
462+
// 2
463+
}
366464
```

lib/ffi-napi/helper.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@ found in the LICENSE file in the root directory of this source tree.
77
import process from "node:process";
88
import ffi from "ffi-napi";
99
import ref from "ref-napi";
10+
import ref_struct from "ref-struct-di";
1011
import { shouldObj } from "@xan105/is/assert";
11-
import { asArray } from "@xan105/is/opt";
12+
import { asArray, asBoolean } from "@xan105/is/opt";
13+
import { isWindows } from "@xan105/is";
14+
import { errorLookup } from "@xan105/error";
15+
import { GetLastError } from "./util/win32.js";
16+
17+
const StructType = ref_struct(ref);
1218

1319
class Callback{
1420

@@ -63,14 +69,29 @@ function pointer(value){
6369
return ref.refType(value);
6470
}
6571

72+
function struct(schema){
73+
return StructType(schema);
74+
}
75+
6676
function alloc(type){
67-
const buff = Object.assign(Object.create(null), {
77+
return Object.assign(Object.create(null), {
6878
pointer: ref.alloc(type),
6979
get: function(){
7080
return this.pointer.deref();
7181
}
7282
});
73-
return buff;
7483
}
7584

76-
export { Callback, pointer, alloc };
85+
function lastError(option = {}){
86+
const errno = isWindows() ? GetLastError() : ffi.errno();
87+
const options = { translate: asBoolean(option?.translate) ?? true };
88+
return options.translate ? errorLookup(errno) : errno;
89+
}
90+
91+
export {
92+
Callback,
93+
pointer,
94+
struct,
95+
alloc,
96+
lastError
97+
};

0 commit comments

Comments
 (0)