diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index 4bf051c..0000000
--- a/.npmignore
+++ /dev/null
@@ -1,4 +0,0 @@
-.github/
-test/
-.eslintrc.json
-tsconfig.json
\ No newline at end of file
diff --git a/README.md b/README.md
index e4cf7e3..94c9711 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
About
=====
-Foreign Function Interface helper. Provides a friendly abstraction/API for:
+Foreign Function Interface (FFI) helper. Provides a friendly abstraction/API for:
- [ffi-napi](https://www.npmjs.com/package/ffi-napi) (MIT)
- [koffi](https://www.npmjs.com/package/koffi) (MIT)
@@ -101,6 +101,11 @@ npm install @xan105/ffi
Please note that `ffi-napi` and `koffi` are optional peer dependencies.
Install the one you wish to use yourself (or both 🙃).
+### ⚛️ Electron
+
+⚠️ NB: As of this writing `ffi-napi` does not work with Electron >= 21.x.
+Due to [Electron and the V8 Memory Cage](https://www.electronjs.org/blog/v8-memory-cage).
+
API
===
@@ -120,24 +125,29 @@ import ... from "@xan105/ffi/koffi";
Load the given library path and return an handle function to call library's symbol(s).
-**Option**
+⚙️ **Option**
- `ignoreLoadingFail?: boolean` (false)
-Silent fail if the given library couldn't be loaded.
-💡 Handle will return `undefined` in that case.
+When set to `true` the handle function will silently fail if the given library couldn't be loaded and return `undefined` in such case.
- `ignoreMissingSymbol?: boolean` (false)
-Silent fail if the given library doesn't have the called symbol.
-💡 Handle will return `undefined` in that case.
+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.
+
+- `lazy` (false)
-- `abi?: string` ("func" for koffi and "default_abi" for ffi-napi)
+When set to `true` use `RTLD_LAZY` (lazy-binding) on POSIX platforms otherwise use `RTLD_NOW`.
-ABI convention to use. Use this when you need to ex: winapi x86 requires "stdcall".
+- `abi?: string` (koffi: "func" | ffi-napi: "default_abi")
+
+ABI convention to use. Use this when you need to.
+_ex: winapi x86 requires "stdcall"._
**Return**
+An handle function to call library's symbol(s).
+
```ts
function(symbol: string | number, result: unknown, parameters: unknown[]): unknown
```
@@ -153,21 +163,20 @@ See the corresponding FFI library for more information on what to pass for `resu
```js
import { load } from "@xan105/ffi/[ napi | koffi ]";
const lib = load("libm");
-const ceil = lib("ceil", "double", ["double"])
+const ceil = lib("ceil", "double", ["double"]);
ceil(1.5); //2
```
#### `dlopen(path: string, symbols: object, option?: object): object`
-Open library and define exported symbols. This is a friendly wrapper to `load()` inspired by Deno FFI `dlopen` syntax.
-
+Open library and define exported symbols. This is a friendly wrapper to `load()` inspired by Deno FFI `dlopen` syntax.
If you ever use ffi-napi `ffi.Library()` this will be familiar.
**Param**
- `path: string`
- Library path to load
+ Library path to load.
- `symbols: object`
@@ -176,28 +185,50 @@ If you ever use ffi-napi `ffi.Library()` this will be familiar.
```ts
{
name: {
+ symbol?: string | number,
result?: unknown,
parameters?: unknown[],
nonblocking?: boolean,
- symbol?: string | number
+ stub?: boolean
},
...
}
```
+
+ 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).
- 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).
+ `result` and `parameters` are the same as for the returned handle from `load()`.
+ If omitted, `result` is set to "void" and `parameters` to an empty array.
+ 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.
- 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()`.
+ 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).
-- option?: object
+ When `stub` is `true` the corresponding symbol will return a no-op if its missing.
+ 💡 If set, this superseed the _"global"_ `stub` option (see below).
+
+- ⚙️ `option?: object`
- Pass option(s) to `load()`. See above.
+ Same as `load()` (see above) in addition to the following:
+
+ + `errorAtRuntime?: boolean` (false)
+
+ When set to `true`, initialisation error will be thrown on symbol invocation.
+
+ + `nonblocking?: boolean` (false)
+
+ When set to `true`, every symbols will return the corresponding promisified `async()` method (asynchronous calling).
+ 💡 This can be overriden per symbol (see symbol definition above).
+
+ + `stub?: boolean` (false)
+
+ When set to `true`, every missing symbols will return a no-op.
+ 💡 This can be overriden per symbol (see symbol definition above).
**Return**
An object with the given symbol(s) as properties.
- ❌ Throws on error
+ ❌ Throws on error.
**Example**
@@ -348,9 +379,54 @@ library.doSomething();
callback.close();
```
-#### `pointer(value: unknown, direction?: string): any`
+#### `pointer(value: unknown, direction?: string): unknown`
-Just a shorthand to `ref.refType(x)` (ffi-napi) and `koffi.out/inout(koffi.pointer(x))` (koffi) to define a pointer.
+Just a shorthand to define a pointer.
+
+```js
+import { dlopen, types, pointer } from "@xan105/ffi/[ napi | koffi ]";
+
+const dylib = dlopen("shell32.dll", {
+ SHQueryUserNotificationState: {
+ result: types.win32.HRESULT,
+ parameters: [
+ pointer(types.win32.ENUM, "out")
+ ]
+ }
+}, { abi: "stdcall" });
+```
+
+#### `struct(schema: unknown): unknown`
+
+Just a shorthand to define a structure.
+
+```js
+import { dlopen, types, struct, pointer } from "@xan105/ffi/[ napi | koffi ]";
+
+const POINT = struct({ //define struct
+ x: types.win32.LONG,
+ y: types.win32.LONG
+});
+
+const dylib = dlopen("user32.dll", { //lib loading
+ GetCursorPos: {
+ result: types.win32.BOOL,
+ parameters: [ pointer(POINT, "out") ] //struct pointer
+ }
+ }, { abi: "stdcall" });
+
+//⚠️ NB: Struct are use differently afterwards:
+
+//Koffi
+const cursorPos = {};
+GetCursorPos(cursorPos);
+console.log(cursorPos) //{ x: 0, y: 0 }
+
+//ffi-napi
+const cursorPos = new POINT();
+getCursorPos(cursorPos.ref());
+console.log({ x: cursorPos.x, y: cursorPos.y });
+```
#### `alloc(type: unknown): { pointer: Buffer, get: ()=> unknown }`
@@ -363,4 +439,26 @@ const dylib = dlopen(...); //lib loading
const number = alloc("int"); //allocate Buffer for the output data
dylib.manipulate_number(number.pointer);
const result = number.get();
+```
+
+#### `lastError(option?: object): string[] | number`
+
+Shorthand to errno (POSIX) and GetLastError (win32).
+
+⚙️ **Option**
+
+ - `translate?: boolean` (true)
+
+When an error code is known it will be 'translated' to its corresponding message and code values as
`[message: string, code?: string]`. If you only want the raw numerical code set it to `false`.
+
+eg:
+```js
+if(result !== 0){ //something went wrong
+
+ console.log(lastError())
+ //['No such file or directory', 'ENOENT']
+
+ console.log(lastError({ translate: false }));
+ // 2
+}
```
\ No newline at end of file
diff --git a/lib/ffi-napi/helper.js b/lib/ffi-napi/helper.js
index ce21b98..710f265 100644
--- a/lib/ffi-napi/helper.js
+++ b/lib/ffi-napi/helper.js
@@ -7,8 +7,14 @@ found in the LICENSE file in the root directory of this source tree.
import process from "node:process";
import ffi from "ffi-napi";
import ref from "ref-napi";
+import ref_struct from "ref-struct-di";
import { shouldObj } from "@xan105/is/assert";
-import { asArray } from "@xan105/is/opt";
+import { asArray, asBoolean } from "@xan105/is/opt";
+import { isWindows } from "@xan105/is";
+import { errorLookup } from "@xan105/error";
+import { GetLastError } from "./util/win32.js";
+
+const StructType = ref_struct(ref);
class Callback{
@@ -63,14 +69,29 @@ function pointer(value){
return ref.refType(value);
}
+function struct(schema){
+ return StructType(schema);
+}
+
function alloc(type){
- const buff = Object.assign(Object.create(null), {
+ return Object.assign(Object.create(null), {
pointer: ref.alloc(type),
get: function(){
return this.pointer.deref();
}
});
- return buff;
}
-export { Callback, pointer, alloc };
\ No newline at end of file
+function lastError(option = {}){
+ const errno = isWindows() ? GetLastError() : ffi.errno();
+ const options = { translate: asBoolean(option?.translate) ?? true };
+ return options.translate ? errorLookup(errno) : errno;
+}
+
+export {
+ Callback,
+ pointer,
+ struct,
+ alloc,
+ lastError
+};
\ No newline at end of file
diff --git a/lib/ffi-napi/open.js b/lib/ffi-napi/open.js
index 13aa842..44f30ef 100644
--- a/lib/ffi-napi/open.js
+++ b/lib/ffi-napi/open.js
@@ -7,7 +7,7 @@ found in the LICENSE file in the root directory of this source tree.
import { platform } from "node:process";
import { promisify } from "node:util";
import ffi from "ffi-napi";
-import { Failure, attempt, errorLookup } from "@xan105/error";
+import { Failure, attemptify, errorLookup } from "@xan105/error";
import { asBoolean, asArray } from "@xan105/is/opt";
import {
shouldObj,
@@ -44,58 +44,54 @@ function load(path, option = {}){
}[platform] ?? ".so";
if (path.indexOf(ext) === -1) path += ext;
- const [ dylib, err ] = attempt(ffi.DynamicLibrary, [
- path,
+ const [ dylib, err ] = attemptify(ffi.DynamicLibrary)(
+ path,
options.lazy ? ffi.DynamicLibrary.FLAGS.RTLD_LAZY : ffi.DynamicLibrary.FLAGS.RTLD_NOW,
ffi["FFI_" + options.abi.toUpperCase()]
- ]);
-
- if(err && !options.ignoreLoadingFail){
- const errCode = RegExp(/\d+$/).exec(err)?.[0];
- if(errCode == null){ //If couldn't extract error code
- err.code = "ERR_FFI";
- throw err; //Throw the default ffi error
- } else {
- const [ message ] = errorLookup(+errCode); //lookup error code
- throw new Failure(message, {
- code: "ERR_FFI",
- cause: err,
- info: { lib: path }
- });
- }
- }
+ );
const handle = function(symbol, result, parameters){
try{
- if (!dylib) return undefined;
+ if (err) throw err;
const fnPtr = dylib.get(symbol);
return ffi.ForeignFunction(fnPtr, result, parameters);
}catch(err){
+ //lookup error code
const errCode = RegExp(/\d+$/).exec(err)?.[0];
- if(errCode == null){ //If couldn't extract error code
- err.code = "ERR_FFI";
- throw err; //Throw the default ffi error
- } else {
- const [ message, code ] = errorLookup(+errCode); //lookup error code
- if(
- options.ignoreMissingSymbol &&
- (code === "ERROR_PROC_NOT_FOUND" || err.message.includes("undefined symbol"))
- ) return undefined;
- throw new Failure(message, {
- code: "ERR_FFI",
- cause: err,
- info: { symbol }
- });
- }
+ const [ message, code ] = errCode ? errorLookup(+errCode) : [ err.message ];
+
+ if(
+ options.ignoreMissingSymbol &&
+ (code === "ERROR_PROC_NOT_FOUND" || message.includes("undefined symbol"))
+ ) return undefined;
+
+ if(!dylib && options.ignoreLoadingFail) return undefined;
+
+ throw new Failure(message, {
+ code: "ERR_FFI",
+ cause: err,
+ info: { lib: path, symbol }
+ });
}
};
return handle;
}
-function dlopen(path, symbols, option){
+function dlopen(path, symbols, option = {}){
shouldObjWithinObj(symbols);
+ shouldObj(option);
+
+ const options = {
+ errorAtRuntime: asBoolean(option.errorAtRuntime) ?? false,
+ nonblocking: asBoolean(option.nonblocking) ?? false,
+ stub: asBoolean(option.stub) ?? false
+ };
+
+ delete option.errorAtRuntime;
+ delete option.nonblocking;
+ delete option.stub;
const lib = Object.create(null);
const handle = load(path, option);
@@ -104,14 +100,23 @@ function dlopen(path, symbols, option){
if (name === "__proto__") continue; //not allowed
- const parameters = asArray(definition.parameters) ?? [];
- const result = definition.result || "void";
- const nonblocking = asBoolean(definition.nonblocking) ?? false;
const symbol = definition.symbol || name;
+ const result = definition.result || "void";
+ const parameters = asArray(definition.parameters) ?? [];
- const fn = handle(symbol, result, parameters);
+ //override
+ const nonblocking = asBoolean(definition.nonblocking) ?? options.nonblocking;
+ const stub = asBoolean(definition.stub) ?? options.stub;
+
+ const [ fn, err ] = attemptify(handle)(symbol, result, parameters);
+ if(err && options.errorAtRuntime === false ) throw err;
+
if(typeof fn === "function")
lib[name] = nonblocking ? promisify(fn.async) : fn;
+ else if(err)
+ lib[name] = nonblocking ? ()=>{ return Promise.reject(err) } : ()=>{ throw err };
+ else if(stub)
+ lib[name] = nonblocking ? ()=>{ return Promise.resolve() } : ()=>{};
}
return lib;
}
diff --git a/lib/ffi-napi/util/win32.js b/lib/ffi-napi/util/win32.js
new file mode 100644
index 0000000..ab264fa
--- /dev/null
+++ b/lib/ffi-napi/util/win32.js
@@ -0,0 +1,12 @@
+import { dlopen } from "../open.js";
+import { DWORD } from "../types/windows.js";
+
+export const { GetLastError } = dlopen("kernel32.dll", {
+ "GetLastError": {
+ result: DWORD,
+ nonblocking: false //!important
+ }
+}, {
+ abi: "stdcall",
+ ignoreLoadingFail: true
+});
\ No newline at end of file
diff --git a/lib/koffi/helper.js b/lib/koffi/helper.js
index d2a44b7..1e15563 100644
--- a/lib/koffi/helper.js
+++ b/lib/koffi/helper.js
@@ -8,7 +8,10 @@ import { Buffer } from "node:buffer";
import { randomUUID } from "node:crypto";
import koffi from "koffi";
import { shouldStringNotEmpty, shouldObj } from "@xan105/is/assert";
-import { asArray } from "@xan105/is/opt";
+import { asArray, asBoolean } from "@xan105/is/opt";
+import { isWindows } from "@xan105/is";
+import { errorLookup } from "@xan105/error";
+import { GetLastError } from "./util/win32.js";
class Callback{
@@ -71,14 +74,29 @@ function pointer(value, direction = "in"){
}
}
+function struct(schema){
+ return koffi.struct(randomUUID(), schema);
+}
+
function alloc(type){
- const buff = Object.assign(Object.create(null), {
+ return Object.assign(Object.create(null), {
pointer: Buffer.alloc(koffi.sizeof(type)),
get: function(){
return koffi.decode(this.pointer, type);
}
});
- return buff;
}
-export { Callback, pointer, alloc };
\ No newline at end of file
+function lastError(option = {}){
+ const errno = isWindows() ? GetLastError() : koffi.errno();
+ const options = { translate: asBoolean(option?.translate) ?? true };
+ return options.translate ? errorLookup(errno) : errno;
+}
+
+export {
+ Callback,
+ pointer,
+ struct,
+ alloc,
+ lastError
+};
\ No newline at end of file
diff --git a/lib/koffi/open.js b/lib/koffi/open.js
index 05332b1..20ef5cc 100644
--- a/lib/koffi/open.js
+++ b/lib/koffi/open.js
@@ -42,18 +42,10 @@ function load(path, option = {}){
if (path.indexOf(ext) === -1) path += ext;
const [dylib, err] = attemptify(koffi.load)(path, { lazy: options.lazy });
-
- if(err && !options.ignoreLoadingFail){
- throw new Failure(err.message, {
- code: "ERR_FFI",
- cause: err,
- info: { lib: path }
- });
- }
-
+
const handle = function(symbol, result, parameters){
try{
- if (!dylib) return undefined;
+ if (err) throw err;
return dylib[options.abi](symbol, result, parameters);
}catch(err){
if (
@@ -61,10 +53,13 @@ function load(path, option = {}){
err.message.startsWith("Cannot find function") &&
err.message.endsWith("in shared library")
) return undefined;
+
+ if(!dylib && options.ignoreLoadingFail) return undefined;
+
throw new Failure(err.message, {
code: "ERR_FFI",
cause: err,
- info: { symbol }
+ info: { lib: path, symbol }
});
}
};
@@ -72,10 +67,21 @@ function load(path, option = {}){
return handle;
}
-function dlopen(path, symbols, option){
+function dlopen(path, symbols, option = {}){
shouldObjWithinObj(symbols);
-
+ shouldObj(option);
+
+ const options = {
+ errorAtRuntime: asBoolean(option.errorAtRuntime) ?? false,
+ nonblocking: asBoolean(option.nonblocking) ?? false,
+ stub: asBoolean(option.stub) ?? false
+ };
+
+ delete option.errorAtRuntime;
+ delete option.nonblocking;
+ delete option.stub;
+
const lib = Object.create(null);
const handle = load(path, option);
@@ -83,16 +89,25 @@ function dlopen(path, symbols, option){
if (name === "__proto__") continue; //not allowed
- const parameters = asArray(definition.parameters) ?? [];
- const result = definition.result || "void";
- const nonblocking = asBoolean(definition.nonblocking) ?? false;
const symbol = definition.symbol || name;
+ const result = definition.result || "void";
+ const parameters = asArray(definition.parameters) ?? [];
+
+ //override
+ const nonblocking = asBoolean(definition.nonblocking) ?? options.nonblocking;
+ const stub = asBoolean(definition.stub) ?? options.stub;
+
+ const [ fn, err ] = attemptify(handle)(symbol, result, parameters);
+ if(err && options.errorAtRuntime === false ) throw err;
- const fn = handle(symbol, result, parameters);
if(typeof fn === "function")
lib[name] = nonblocking ? promisify(fn.async) : fn;
+ else if(err)
+ lib[name] = nonblocking ? ()=>{ return Promise.reject(err) } : ()=>{ throw err };
+ else if(stub)
+ lib[name] = nonblocking ? ()=>{ return Promise.resolve() } : ()=>{};
}
-
+
return lib;
}
diff --git a/lib/koffi/util/win32.js b/lib/koffi/util/win32.js
new file mode 100644
index 0000000..898101f
--- /dev/null
+++ b/lib/koffi/util/win32.js
@@ -0,0 +1,12 @@
+import { dlopen } from "../open.js";
+import { DWORD } from "../types/windows.js";
+
+export const { GetLastError } = dlopen("kernel32.dll", {
+ "GetLastError": {
+ result: DWORD,
+ nonblocking: false
+ }
+}, {
+ abi: "stdcall",
+ ignoreLoadingFail: true
+});
\ No newline at end of file
diff --git a/lib/node/helper.js b/lib/node/helper.js
new file mode 100644
index 0000000..cae88b6
--- /dev/null
+++ b/lib/node/helper.js
@@ -0,0 +1,67 @@
+/*
+Copyright (c) Anthony Beaumont
+This source code is licensed under the MIT License
+found in the LICENSE file in the root directory of this source tree.
+*/
+
+/*
+⚠️ Experimental and not yet tested
+Based on https://github.com/nodejs/node/pull/46905
+*/
+
+import { Buffer } from "node:buffer";
+import { endianness } from "node:os";
+import { sizeof, getBufferPointer } from "node:ffi";
+
+function alloc(type){
+ const buffer = Buffer.alloc(sizeof(type));
+ return Object.assign(Object.create(null), {
+ pointer: getBufferPointer(buffer),
+ get: function(){
+ switch(type){
+ case "char":
+ case "signed char":
+ return buffer.readInt8();
+ case "uchar":
+ case "unsigned char":
+ return buffer.readUInt8();
+ case "short":
+ case "short int":
+ case "signed short":
+ case "signed short int":
+ return buffer["readInt16" + endianness]();
+ case "ushort":
+ case "unsigned short":
+ case "unsigned short int":
+ return buffer["readUInt16" + endianness]();
+ case "int":
+ case "signed":
+ case "signed int":
+ return buffer["readInt32" + endianness]();
+ case "uint":
+ case "unsigned":
+ case "unsigned int":
+ return buffer["readUInt32" + endianness]();
+ case "long":
+ case "long int":
+ case "signed long":
+ case "signed long int":
+ return buffer["readBigInt64" + endianness]();
+ case "ulong":
+ case "unsigned long":
+ case "unsigned long int":
+ return buffer["readBigUInt64" + endianness]();
+ case "float":
+ return buffer["readFloat" + endianness]();
+ case "double":
+ return buffer["readDouble" + endianness]();
+ default:
+ return buffer;
+ }
+ }
+ });
+}
+
+export {
+ alloc
+};
\ No newline at end of file
diff --git a/lib/node/index.js b/lib/node/index.js
new file mode 100644
index 0000000..812fed0
--- /dev/null
+++ b/lib/node/index.js
@@ -0,0 +1,43 @@
+/*
+Copyright (c) Anthony Beaumont
+This source code is licensed under the MIT License
+found in the LICENSE file in the root directory of this source tree.
+*/
+
+/*
+⚠️ Experimental and not yet tested
+Based on https://github.com/nodejs/node/pull/46905
+*/
+
+import * as deno from "./types/deno.js";
+
+export * from "./open.js";
+export * from "./helper.js";
+export const types = Object.assign(Object.create(null), {
+ //node/lib/ffi.js | These are not exported
+ "void": "void",
+ "char": "char",
+ "signed char": "char",
+ "unsigned char": "uchar",
+ "short": "short",
+ "short int": "short",
+ "signed short": "short",
+ "signed short int": "short",
+ "unsigned short": "ushort",
+ "unsigned short int": "ushort",
+ "int": "int",
+ "signed": "int",
+ "signed int": "int",
+ "unsigned": "uint",
+ "unsigned int": "uint",
+ "long": "long",
+ "long int": "long",
+ "signed long": "long",
+ "signed long int": "long",
+ "unsigned long": "ulong",
+ "unsigned long int": "ulong",
+ "float": "float",
+ "double": "double",
+ "pointer": "pointer",
+ ...deno.types,
+});
\ No newline at end of file
diff --git a/lib/node/open.js b/lib/node/open.js
new file mode 100644
index 0000000..86d1204
--- /dev/null
+++ b/lib/node/open.js
@@ -0,0 +1,101 @@
+/*
+Copyright (c) Anthony Beaumont
+This source code is licensed under the MIT License
+found in the LICENSE file in the root directory of this source tree.
+*/
+
+/*
+⚠️ Experimental and not yet tested
+Based on https://github.com/nodejs/node/pull/46905
+*/
+
+import { platform } from "node:process";
+import { getNativeFunction } from "node:ffi";
+import { Failure, attemptify } from "@xan105/error";
+import { asBoolean, asArray } from "@xan105/is/opt";
+import {
+ shouldObj,
+ shouldObjWithinObj,
+ shouldStringNotEmpty
+} from "@xan105/is/assert";
+
+function load(path, option = {}){
+
+ shouldStringNotEmpty(path);
+ shouldObj(option);
+
+ const options = {
+ ignoreLoadingFail: asBoolean(option.ignoreLoadingFail) ?? false,
+ ignoreMissingSymbol: asBoolean(option.ignoreMissingSymbol) ?? false
+ };
+
+ const ext = {
+ "win32": ".dll",
+ "darwin": ".dylib",
+ }[platform] ?? ".so";
+
+ if (path.indexOf(ext) === -1) path += ext;
+
+ const handle = function(symbol, result, parameters){
+ try{
+ return getNativeFunction(path, symbol, result, parameters);
+ }catch(err){
+ if (err.code === "ERR_FFI_SYMBOL_NOT_FOUND" && options.ignoreMissingSymbol)
+ return undefined;
+
+ if(err.code === "ERR_FFI_LIBRARY_LOAD_FAILED" && options.ignoreLoadingFail)
+ return undefined;
+
+ throw new Failure(err.message, {
+ code: "ERR_FFI",
+ cause: err,
+ info: { lib: path, symbol }
+ });
+ }
+ };
+
+ return handle;
+}
+
+function dlopen(path, symbols, option = {}){
+
+ shouldObjWithinObj(symbols);
+ shouldObj(option);
+
+ const options = {
+ errorAtRuntime: asBoolean(option.errorAtRuntime) ?? false,
+ stub: asBoolean(option.stub) ?? false
+ };
+
+ delete option.errorAtRuntime;
+ delete option.stub;
+
+ const lib = Object.create(null);
+ const handle = load(path, option);
+
+ for (const [name, definition] of Object.entries(symbols)){
+
+ if (name === "__proto__") continue; //not allowed
+
+ const symbol = definition.symbol || name;
+ const result = definition.result || "void";
+ const parameters = asArray(definition.parameters) ?? [];
+
+ //override
+ const stub = asBoolean(definition.stub) ?? options.stub;
+
+ const [ fn, err ] = attemptify(handle)(symbol, result, parameters);
+ if(err && options.errorAtRuntime === false ) throw err;
+
+ if(typeof fn === "function")
+ lib[name] = fn;
+ else if(err)
+ lib[name] = ()=>{ throw err };
+ else if(stub)
+ lib[name] = ()=>{};
+ }
+
+ return lib;
+}
+
+export { load, dlopen }
\ No newline at end of file
diff --git a/lib/node/types/deno.js b/lib/node/types/deno.js
new file mode 100644
index 0000000..f78c6ed
--- /dev/null
+++ b/lib/node/types/deno.js
@@ -0,0 +1,25 @@
+/*
+Copyright (c) Anthony Beaumont
+This source code is licensed under the MIT License
+found in the LICENSE file in the root directory of this source tree.
+*/
+
+/*
+⚠️ Experimental and not yet tested
+Based on https://github.com/nodejs/node/pull/46905
+*/
+
+export const types = Object.assign(Object.create(null), {
+ i8 : "char",
+ u8 : "uchar",
+ i16 : "short",
+ u16 : "ushort",
+ i32 : "int",
+ u32 : "uint",
+ i64 : "long",
+ u64 : "ulong",
+ //usize : koffi.types.size_t,
+ f32 : "float",
+ f64 : "double",
+ "function" : "pointer"
+});
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index c31116b..8d418c3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,23 +23,23 @@
],
"license": "MIT",
"dependencies": {
- "@xan105/error": "^1.6.1",
- "@xan105/is": "^2.8.2"
+ "@xan105/error": "^1.6.2",
+ "@xan105/is": "^2.9.2"
},
"devDependencies": {
"@types/ffi-napi": "^4.0.7",
"@types/node": "^20.5.6",
- "@typescript-eslint/eslint-plugin": "^6.4.1",
- "@typescript-eslint/parser": "^6.4.1",
- "eslint": "^8.48.0",
+ "@typescript-eslint/eslint-plugin": "^6.13.1",
+ "@typescript-eslint/parser": "^6.13.1",
+ "eslint": "^8.54.0",
"typescript": "^5.2.2"
},
"engines": {
- "node": ">=16.0.0"
+ "node": ">=18.0.0"
},
"peerDependencies": {
"ffi-napi": "^4.0.3",
- "koffi": "^2.6.1"
+ "koffi": "^2.6.10"
},
"peerDependenciesMeta": {
"ffi-napi": {
@@ -84,9 +84,9 @@
}
},
"node_modules/@eslint/eslintrc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
- "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
+ "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
@@ -107,21 +107,21 @@
}
},
"node_modules/@eslint/js": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz",
- "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==",
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
+ "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
- "version": "0.11.10",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
- "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
+ "version": "0.11.13",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
+ "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
"dev": true,
"dependencies": {
- "@humanwhocodes/object-schema": "^1.2.1",
+ "@humanwhocodes/object-schema": "^2.0.1",
"debug": "^4.1.1",
"minimatch": "^3.0.5"
},
@@ -143,9 +143,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
- "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
+ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"dev": true
},
"node_modules/@nodelib/fs.scandir": {
@@ -195,9 +195,9 @@
}
},
"node_modules/@types/json-schema": {
- "version": "7.0.12",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
- "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==",
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/node": {
@@ -225,22 +225,22 @@
}
},
"node_modules/@types/semver": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
- "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==",
+ "version": "7.5.6",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
+ "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.1.tgz",
- "integrity": "sha512-3F5PtBzUW0dYlq77Lcqo13fv+58KDwUib3BddilE8ajPJT+faGgxmI9Sw+I8ZS22BYwoir9ZhNXcLi+S+I2bkw==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz",
+ "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
- "@typescript-eslint/scope-manager": "6.4.1",
- "@typescript-eslint/type-utils": "6.4.1",
- "@typescript-eslint/utils": "6.4.1",
- "@typescript-eslint/visitor-keys": "6.4.1",
+ "@typescript-eslint/scope-manager": "6.13.1",
+ "@typescript-eslint/type-utils": "6.13.1",
+ "@typescript-eslint/utils": "6.13.1",
+ "@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@@ -266,15 +266,15 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.1.tgz",
- "integrity": "sha512-610G6KHymg9V7EqOaNBMtD1GgpAmGROsmfHJPXNLCU9bfIuLrkdOygltK784F6Crboyd5tBFayPB7Sf0McrQwg==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz",
+ "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "6.4.1",
- "@typescript-eslint/types": "6.4.1",
- "@typescript-eslint/typescript-estree": "6.4.1",
- "@typescript-eslint/visitor-keys": "6.4.1",
+ "@typescript-eslint/scope-manager": "6.13.1",
+ "@typescript-eslint/types": "6.13.1",
+ "@typescript-eslint/typescript-estree": "6.13.1",
+ "@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4"
},
"engines": {
@@ -294,13 +294,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.4.1.tgz",
- "integrity": "sha512-p/OavqOQfm4/Hdrr7kvacOSFjwQ2rrDVJRPxt/o0TOWdFnjJptnjnZ+sYDR7fi4OimvIuKp+2LCkc+rt9fIW+A==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz",
+ "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.4.1",
- "@typescript-eslint/visitor-keys": "6.4.1"
+ "@typescript-eslint/types": "6.13.1",
+ "@typescript-eslint/visitor-keys": "6.13.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -311,13 +311,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.4.1.tgz",
- "integrity": "sha512-7ON8M8NXh73SGZ5XvIqWHjgX2f+vvaOarNliGhjrJnv1vdjG0LVIz+ToYfPirOoBi56jxAKLfsLm40+RvxVVXA==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz",
+ "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/typescript-estree": "6.4.1",
- "@typescript-eslint/utils": "6.4.1",
+ "@typescript-eslint/typescript-estree": "6.13.1",
+ "@typescript-eslint/utils": "6.13.1",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
@@ -338,9 +338,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.4.1.tgz",
- "integrity": "sha512-zAAopbNuYu++ijY1GV2ylCsQsi3B8QvfPHVqhGdDcbx/NK5lkqMnCGU53amAjccSpk+LfeONxwzUhDzArSfZJg==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz",
+ "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -351,13 +351,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.4.1.tgz",
- "integrity": "sha512-xF6Y7SatVE/OyV93h1xGgfOkHr2iXuo8ip0gbfzaKeGGuKiAnzS+HtVhSPx8Www243bwlW8IF7X0/B62SzFftg==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz",
+ "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.4.1",
- "@typescript-eslint/visitor-keys": "6.4.1",
+ "@typescript-eslint/types": "6.13.1",
+ "@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -378,17 +378,17 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.4.1.tgz",
- "integrity": "sha512-F/6r2RieNeorU0zhqZNv89s9bDZSovv3bZQpUNOmmQK1L80/cV4KEu95YUJWi75u5PhboFoKUJBnZ4FQcoqhDw==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz",
+ "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
- "@typescript-eslint/scope-manager": "6.4.1",
- "@typescript-eslint/types": "6.4.1",
- "@typescript-eslint/typescript-estree": "6.4.1",
+ "@typescript-eslint/scope-manager": "6.13.1",
+ "@typescript-eslint/types": "6.13.1",
+ "@typescript-eslint/typescript-estree": "6.13.1",
"semver": "^7.5.4"
},
"engines": {
@@ -403,12 +403,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.1.tgz",
- "integrity": "sha512-y/TyRJsbZPkJIZQXrHfdnxVnxyKegnpEvnRGNam7s3TRR2ykGefEWOhaef00/UUN3IZxizS7BTO3svd3lCOJRQ==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz",
+ "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.4.1",
+ "@typescript-eslint/types": "6.13.1",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -419,10 +419,16 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+ "dev": true
+ },
"node_modules/@xan105/error": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/@xan105/error/-/error-1.6.1.tgz",
- "integrity": "sha512-85PUTzZ08YfpMmBxx9UfkLrlEQBjyVw2kPqFeFYKlTEUFNzMH7MTAw4VZCEnuhBkKk7b2K6EWNBvwajC9f8tVw==",
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/@xan105/error/-/error-1.6.2.tgz",
+ "integrity": "sha512-QruXXcDHKx6ApVAVa95SQuZZXPugXfdF04tiUxffEVku31Md9cxYQN3QJ88v8CUvggEIyqf6rT1tbeOaGc1clw==",
"funding": [
{
"type": "github",
@@ -438,13 +444,13 @@
}
],
"engines": {
- "node": ">=16.0.0"
+ "node": ">=18.0.0"
}
},
"node_modules/@xan105/is": {
- "version": "2.8.2",
- "resolved": "https://registry.npmjs.org/@xan105/is/-/is-2.8.2.tgz",
- "integrity": "sha512-WGpCd6omIy2oS+rZZVoer2Yu+CURFlLreiGnaYlb4LMlMFz8TF8SJhHCUslSe8hAJUGzmXr5/5YD2sFIw1mhNQ==",
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/@xan105/is/-/is-2.9.2.tgz",
+ "integrity": "sha512-Eb1c9yMWosKb/JOo8BRk6r6GbZWxsJXH1TA7yjU/bV+EY4pDBQJYR5oxcrHdVli+ROAhFuuFHXwM9dy023Ow6g==",
"funding": [
{
"type": "github",
@@ -460,16 +466,16 @@
}
],
"dependencies": {
- "@xan105/error": "^1.6.1"
+ "@xan105/error": "^1.6.2"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/acorn": {
- "version": "8.10.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
- "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
+ "version": "8.11.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
+ "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@@ -693,18 +699,19 @@
}
},
"node_modules/eslint": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz",
- "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==",
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
+ "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
- "@eslint/eslintrc": "^2.1.2",
- "@eslint/js": "8.48.0",
- "@humanwhocodes/config-array": "^0.11.10",
+ "@eslint/eslintrc": "^2.1.3",
+ "@eslint/js": "8.54.0",
+ "@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
@@ -840,9 +847,9 @@
"dev": true
},
"node_modules/fast-glob": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
- "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -1023,9 +1030,9 @@
}
},
"node_modules/globals": {
- "version": "13.21.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz",
- "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==",
+ "version": "13.23.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
+ "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@@ -1207,9 +1214,9 @@
}
},
"node_modules/koffi": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.6.1.tgz",
- "integrity": "sha512-f+g1yGJHHAUq6ogPzvsiLn+ZO1GYTNHH3JKyt49T4JhpaFAIovlDCpu5RscywGC3VsntvfzrqArQQJVb4YS4UQ==",
+ "version": "2.6.10",
+ "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.6.10.tgz",
+ "integrity": "sha512-wHT3swfhHsbg7lT0MNzswWTqq/3D1ww9MP1qt71AAtT0ML5l1jJhJa9+IxvJGzZ2+q2TvZf5+u3bBzLV4GIZbA==",
"hasInstallScript": true,
"optional": true,
"peer": true
@@ -1451,9 +1458,9 @@
}
},
"node_modules/punycode": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
- "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"engines": {
"node": ">=6"
diff --git a/package.json b/package.json
index ebf5d3f..1c4819c 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@xan105/ffi",
"version": "1.0.0",
- "description": "Foreign Function Interface Helper.",
+ "description": "Friendly abstraction/API for FFI with a Deno like syntax.",
"type": "module",
"exports": {
"./napi": {
@@ -13,20 +13,28 @@
"default": "./lib/koffi/index.js"
}
},
+ "files": [
+ "/lib/ffi-napi",
+ "/lib/koffi",
+ "/types"
+ ],
"engines": {
- "node": ">=16.0.0"
+ "node": ">=18.0.0"
},
"scripts": {
"lint": "eslint \"./lib/**/*.{js,mjs}\" \"./types/**/*.d.ts\"",
+ "test": "node --test test/",
"check": "tsc --noemit --checkjs",
"declare": "tsc --declaration --emitDeclarationOnly --outDir \"./types\""
},
"keywords": [
"ffi",
+ "dlopen",
"ffi-napi",
"koffi",
- "dlopen",
- "deno"
+ "foreign",
+ "function",
+ "interface"
],
"author": {
"name": "Anthony Beaumont",
@@ -36,7 +44,7 @@
"license": "MIT",
"repository": {
"type": "git",
- "url": "https://github.com/xan105/node-ffi.git"
+ "url": "git+https://github.com/xan105/node-ffi.git"
},
"bugs": {
"url": "https://github.com/xan105/node-ffi/issues"
@@ -59,18 +67,18 @@
"devDependencies": {
"@types/ffi-napi": "^4.0.7",
"@types/node": "^20.5.6",
- "@typescript-eslint/eslint-plugin": "^6.4.1",
- "@typescript-eslint/parser": "^6.4.1",
- "eslint": "^8.48.0",
+ "@typescript-eslint/eslint-plugin": "^6.13.1",
+ "@typescript-eslint/parser": "^6.13.1",
+ "eslint": "^8.54.0",
"typescript": "^5.2.2"
},
"dependencies": {
- "@xan105/error": "^1.6.1",
- "@xan105/is": "^2.8.2"
+ "@xan105/error": "^1.6.2",
+ "@xan105/is": "^2.9.2"
},
"peerDependencies": {
"ffi-napi": "^4.0.3",
- "koffi": "^2.6.1"
+ "koffi": "^2.6.10"
},
"peerDependenciesMeta": {
"ffi-napi": {
diff --git a/test/dlopen.js b/test/dlopen.js
new file mode 100644
index 0000000..32ff266
--- /dev/null
+++ b/test/dlopen.js
@@ -0,0 +1,56 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import { isWindows } from "@xan105/is";
+
+const APIs = {
+ koffi: await import("../lib/koffi/index.js"),
+ "ffi-napi": await import("../lib/ffi-napi/index.js")
+};
+
+for (const [name, ffi] of Object.entries(APIs))
+{
+
+ test(`[${name}] Basic dlopen`, {
+ skip: isWindows ? false : "This test runs on Windows"
+ }, () => {
+
+ const POINT = ffi.struct({
+ x: ffi.types.win32.LONG,
+ y: ffi.types.win32.LONG
+ });
+
+ const {
+ setCursorPos,
+ getCursorPos
+ } = ffi.dlopen("user32.dll", {
+ setCursorPos: {
+ symbol: "SetCursorPos",
+ result: ffi.types.win32.BOOL,
+ parameters: [ ffi.types.i32, ffi.types.i32 ]
+ },
+ getCursorPos: {
+ symbol: "GetCursorPos",
+ result: ffi.types.win32.BOOL,
+ parameters: [ ffi.pointer(POINT, "out") ]
+ }
+ }, { abi: "stdcall" });
+
+ const expected = { x: 1, y: 1 };
+ setCursorPos(...Object.values(expected));
+
+ if(name === "ffi-napi")
+ {
+ const cursorPos = new POINT();
+ getCursorPos(cursorPos.ref());
+ const actual = { x: cursorPos.x, y: cursorPos.y };
+ assert.deepEqual(actual, expected);
+ }
+ else
+ {
+ const actual = {};
+ getCursorPos(actual);
+ assert.deepEqual(actual, expected);
+ }
+ });
+
+};
\ No newline at end of file
diff --git a/test/errorAtRuntime.js b/test/errorAtRuntime.js
new file mode 100644
index 0000000..7f31772
--- /dev/null
+++ b/test/errorAtRuntime.js
@@ -0,0 +1,99 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import { isWindows } from "@xan105/is";
+
+const APIs = {
+ koffi: await import("../lib/koffi/index.js"),
+ "ffi-napi": await import("../lib/ffi-napi/index.js")
+};
+
+for (const [name, ffi] of Object.entries(APIs))
+{
+
+ test(`[${name}] errorAtRuntime: fail load lib`, {
+ skip: isWindows ? false : "This test runs on Windows"
+ }, () => {
+
+ const lib = ffi.dlopen("xoutput1_4", { //fake
+ "XInputEnable": {
+ parameters: [ ffi.types.win32.BOOL ]
+ }
+ }, {
+ abi: "stdcall",
+ errorAtRuntime: true
+ });
+
+ try{
+ lib.XInputEnable(1);
+ }catch(err){
+ assert.equal(err.code, "ERR_FFI");
+
+ if(name === "ffi-napi")
+ assert.equal(err.cause.message, "Dynamic Linking Error: Win32 error 126");
+ else
+ assert.equal(err.cause.message, "Failed to load shared library: The specified module could not be found.");
+ }
+ });
+
+ test(`[${name}] errorAtRuntime: fail load lib (ignore)`, {
+ skip: isWindows ? false : "This test runs on Windows"
+ }, () => {
+
+ const lib = ffi.dlopen("xoutput1_4", { //fake
+ "XInputEnable": {
+ parameters: [ ffi.types.win32.BOOL ]
+ }
+ }, {
+ abi: "stdcall",
+ errorAtRuntime: true,
+ ignoreLoadingFail: true
+ });
+
+ assert.equal(lib.XInputEnable, undefined);
+ });
+
+ test(`[${name}] errorAtRuntime: missing symbol`, {
+ skip: isWindows ? false : "This test runs on Windows"
+ }, () => {
+
+ const lib = ffi.dlopen("xinput1_4", {
+ "XInputEnable": {
+ symbol: "XOutputEnable", //fake
+ parameters: [ ffi.types.win32.BOOL ]
+ }
+ }, {
+ abi: "stdcall",
+ errorAtRuntime: true
+ });
+
+ try{
+ lib.XInputEnable(1);
+ }catch(err){
+ assert.equal(err.code, "ERR_FFI");
+
+ if(name === "ffi-napi")
+ assert.equal(err.cause.message, "Dynamic Symbol Retrieval Error: Win32 error 127");
+ else
+ assert.equal(err.cause.message, "Cannot find function 'XOutputEnable' in shared library");
+ }
+ });
+
+ test(`[${name}] errorAtRuntime: missing symbol (ignore)`, {
+ skip: isWindows ? false : "This test runs on Windows"
+ }, () => {
+
+ const lib = ffi.dlopen("xinput1_4", {
+ "XInputEnable": {
+ symbol: "XOutputEnable", //fake
+ parameters: [ ffi.types.win32.BOOL ]
+ }
+ }, {
+ abi: "stdcall",
+ errorAtRuntime: true,
+ ignoreMissingSymbol: true
+ });
+
+ assert.equal(lib.XInputEnable, undefined);
+ });
+
+};
\ No newline at end of file
diff --git a/test/lastError.js b/test/lastError.js
new file mode 100644
index 0000000..a44ba6d
--- /dev/null
+++ b/test/lastError.js
@@ -0,0 +1,30 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import { isWindows } from "@xan105/is";
+
+const APIs = {
+ koffi: await import("../lib/koffi/index.js"),
+ "ffi-napi": await import("../lib/ffi-napi/index.js")
+};
+
+for (const [name, ffi] of Object.entries(APIs))
+{
+
+ test(`[${name}] lastError()`, {
+ skip: isWindows ? false : "This test runs on Windows"
+ }, () => {
+
+ const lib = ffi.dlopen("kernel32", {
+ "SetLastError": {
+ parameters: [ ffi.types.win32.DWORD ]
+ }
+ }, { abi: "stdcall" });
+
+ lib.SetLastError(1);
+ const errno = ffi.lastError();
+
+ const expected = ["Incorrect function", "ERROR_INVALID_FUNCTION"];
+ assert.deepEqual(errno, expected);
+ });
+
+};
\ No newline at end of file
diff --git a/test/load.js b/test/load.js
new file mode 100644
index 0000000..79092f3
--- /dev/null
+++ b/test/load.js
@@ -0,0 +1,50 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import { isWindows } from "@xan105/is";
+
+const APIs = {
+ koffi: await import("../lib/koffi/index.js"),
+ "ffi-napi": await import("../lib/ffi-napi/index.js")
+};
+
+for (const [name, ffi] of Object.entries(APIs))
+{
+
+ test(`[${name}] Basic dylib calling`, {
+ skip: isWindows ? false : "This test runs on Windows"
+ }, () => {
+
+ const lib = ffi.load("user32.dll", { abi: "stdcall" });
+ const setCursorPos = lib("SetCursorPos", ffi.types.win32.BOOL, [
+ ffi.types.i32,
+ ffi.types.i32
+ ]);
+
+ const POINT = ffi.struct({
+ x: ffi.types.win32.LONG,
+ y: ffi.types.win32.LONG
+ });
+
+ const getCursorPos = lib("GetCursorPos", ffi.types.win32.BOOL, [
+ ffi.pointer(POINT, "out")
+ ]);
+
+ const expected = { x: 0, y: 0 };
+ setCursorPos(...Object.values(expected));
+
+ if(name === "ffi-napi")
+ {
+ const cursorPos = new POINT();
+ getCursorPos(cursorPos.ref());
+ const actual = { x: cursorPos.x, y: cursorPos.y };
+ assert.deepEqual(actual, expected);
+ }
+ else
+ {
+ const actual = {};
+ getCursorPos(actual);
+ assert.deepEqual(actual, expected);
+ }
+ });
+
+};
\ No newline at end of file
diff --git a/test/nonblocking.js b/test/nonblocking.js
new file mode 100644
index 0000000..7fa2c0f
--- /dev/null
+++ b/test/nonblocking.js
@@ -0,0 +1,35 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import { isWindows, isPromise } from "@xan105/is";
+
+const APIs = {
+ koffi: await import("../lib/koffi/index.js"),
+ "ffi-napi": await import("../lib/ffi-napi/index.js")
+};
+
+for (const [name, ffi] of Object.entries(APIs))
+{
+
+ test(`[${name}] nonblocking`, {
+ skip: isWindows ? false : "This test runs on Windows"
+ }, () => {
+
+ const { setCursorPos } = ffi.dlopen("user32.dll", {
+ setCursorPos: {
+ symbol: "SetCursorPos",
+ result: ffi.types.win32.BOOL,
+ parameters: [ ffi.types.i32, ffi.types.i32 ],
+ nonblocking: true
+ }
+ }, { abi: "stdcall" });
+
+ assert.ok(typeof setCursorPos === "function");
+
+ const handle = setCursorPos(2,2);
+ handle.then((value)=>{
+ assert.ok(typeof value === "number");
+ });
+ assert.ok(isPromise(handle));
+ });
+
+};
\ No newline at end of file
diff --git a/test/test.js b/test/test.js
deleted file mode 100644
index 5817378..0000000
--- a/test/test.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { dlopen, types, Callback } from "../lib/ffi-napi/index.js";
-//import { dlopen, types, Callback } from "../lib/koffi/index.js";
-
-const lib = dlopen("xinput1_4", {
- "XInputEnable": {
- parameters: [types.win32.BOOL],
- nonblocking: true
- }
-}, {
- abi: "stdcall",
- ignoreLoadingFail: false,
- ignoreMissingSymbol: false
-});
-
-console.log(lib);
-
-await lib.XInputEnable(1);
-
-const cb = new Callback({}, ()=>{});
-console.log(cb.type);
-console.log(cb.pointer);
-console.log(cb.address);
-cb.close();
-console.log(cb.pointer);
-
-console.log(types);
\ No newline at end of file
diff --git a/test/test2.js b/test/test2.js
deleted file mode 100644
index 9354d07..0000000
--- a/test/test2.js
+++ /dev/null
@@ -1,9 +0,0 @@
-//import { load, types } from "../lib/koffi/index.js";
-import { load, types } from "../lib/ffi-napi/index.js";
-
-const call = load("user32.dll", { abi: "stdcall" });
-
-const MessageBoxA = call("MessageBoxA", "int", ["void *", types.win32.LPCSTR , types.win32.LPCSTR, "uint"]);
-
-const MB_ICONINFORMATION = 0x40;
-MessageBoxA(null, "Hello World!", "Message", MB_ICONINFORMATION);
\ No newline at end of file
diff --git a/types/ffi-napi/helper.d.ts b/types/ffi-napi/helper.d.ts
index d28a30a..e8c55b6 100644
--- a/types/ffi-napi/helper.d.ts
+++ b/types/ffi-napi/helper.d.ts
@@ -2,14 +2,16 @@ export class Callback {
constructor(definition: object, callback?: (unknown) => unknown | null);
register(callback?: (unknown) => unknown): void;
get type(): ref.Type>;
- get pointer(): ref.Pointer<() => any>;
+ get pointer(): ref.Pointer<() => unknown>;
get address(): number | null;
close(): void;
#private;
}
-export function pointer(value: unknown): ref.Type>;
+export function pointer(value: unknown): ref.Type>;
+export function struct(schema: unknown): unknown;
export function alloc(type: unknown): {
pointer: Buffer;
get: () => unknown;
};
-import ref from "ref-napi";
+export function lastError(option?: { translate?: boolean }): string[] | number;
+import ref from "ref-napi";
\ No newline at end of file
diff --git a/types/ffi-napi/open.d.ts b/types/ffi-napi/open.d.ts
index 3483855..0689997 100644
--- a/types/ffi-napi/open.d.ts
+++ b/types/ffi-napi/open.d.ts
@@ -1,3 +1,3 @@
-export function load(path: string, option?: object): (symbol: string, result: unknown, parameters: unknown[]) => ffi.ForeignFunction | undefined;
+export function load(path: string, option?: object): (symbol: string, result: unknown, parameters: unknown[]) => ffi.ForeignFunction | undefined;
export function dlopen(path: string, symbols: object, option?: object): object;
import ffi from "ffi-napi";
diff --git a/types/koffi/helper.d.ts b/types/koffi/helper.d.ts
index 16350da..f2f7f62 100644
--- a/types/koffi/helper.d.ts
+++ b/types/koffi/helper.d.ts
@@ -9,9 +9,11 @@ export class Callback {
#private;
}
export function pointer(value: unknown, direction?: string): koffi.IKoffiCType;
+export function struct(schema: unknown): unknown;
export function alloc(type: unknown): {
pointer: Buffer;
get: () => unknown;
};
+export function lastError(option?: { translate?: boolean }): string[] | number;
import koffi from "koffi";
import { Buffer } from "buffer";
diff --git a/types/koffi/open.d.ts b/types/koffi/open.d.ts
index e44e6b7..3b55c72 100644
--- a/types/koffi/open.d.ts
+++ b/types/koffi/open.d.ts
@@ -1,2 +1,18 @@
-export function load(path: string, option?: object): (symbol: string | number, result: unknown, parameters: unknown[]) => unknown;
-export function dlopen(path: string, symbols: object, option?: object): object;
+declare interface loadOption {
+ ignoreLoadingFail?: boolean,
+ ignoreMissingSymbol?: boolean,
+ lazy?: boolean,
+ abi?: string
+}
+export function load(path: string, option?: loadOption): (symbol: string | number, result: unknown, parameters: unknown[]) => unknown;
+
+declare interface dlopenOption {
+ ignoreLoadingFail?: boolean,
+ ignoreMissingSymbol?: boolean,
+ lazy?: boolean,
+ abi?: string,
+ errorAtRuntime?: boolean,
+ nonblocking?: boolean,
+ stub?: boolean
+}
+export function dlopen(path: string, symbols: object, option?: dlopenOption): object;
diff --git a/types/koffi/types/windows.d.ts b/types/koffi/types/windows.d.ts
index 4d55c72..24ac273 100644
--- a/types/koffi/types/windows.d.ts
+++ b/types/koffi/types/windows.d.ts
@@ -1,50 +1,50 @@
-export const VOID: any;
-export const ENUM: any;
-export const DWORD: any;
-export const WORD: any;
-export const SHORT: any;
-export const BYTE: any;
-export const WCHAR: any;
-export const ACCESS_MASK: any;
-export const ATOM: any;
+export const VOID: unknown;
+export const ENUM: unknown;
+export const DWORD: unknown;
+export const WORD: unknown;
+export const SHORT: unknown;
+export const BYTE: unknown;
+export const WCHAR: unknown;
+export const ACCESS_MASK: unknown;
+export const ATOM: unknown;
export const PVOID: koffi.IKoffiCType;
export const LONG_PTR: koffi.IKoffiCType;
export const ULONG_PTR: koffi.IKoffiCType;
-export const BOOL: any;
-export const BOOLEAN: any;
+export const BOOL: unknown;
+export const BOOLEAN: unknown;
export const CALLBACK: koffi.IKoffiCType;
-export const CCHAR: any;
-export const CHAR: any;
-export const COLORREF: any;
-export const DWORDLONG: any;
+export const CCHAR: unknown;
+export const CHAR: unknown;
+export const COLORREF: unknown;
+export const DWORDLONG: unknown;
export const DWORD_PTR: koffi.IKoffiCType;
-export const DWORD32: any;
-export const DWORD64: any;
-export const FLOAT: any;
-export const HALF_PTR: any;
-export const HRESULT: any;
-export const NTSTATUS: any;
-export const INT: any;
-export const INT_PTR: any;
-export const INT8: any;
-export const INT16: any;
-export const INT32: any;
-export const INT64: any;
-export const LANGID: any;
-export const LCID: any;
-export const LCTYPE: any;
-export const LGRPID: any;
-export const LONG: any;
-export const LONGLONG: any;
-export const LONG32: any;
-export const LONG64: any;
+export const DWORD32: unknown;
+export const DWORD64: unknown;
+export const FLOAT: unknown;
+export const HALF_PTR: unknown;
+export const HRESULT: unknown;
+export const NTSTATUS: unknown;
+export const INT: unknown;
+export const INT_PTR: unknown;
+export const INT8: unknown;
+export const INT16: unknown;
+export const INT32: unknown;
+export const INT64: unknown;
+export const LANGID: unknown;
+export const LCID: unknown;
+export const LCTYPE: unknown;
+export const LGRPID: unknown;
+export const LONG: unknown;
+export const LONGLONG: unknown;
+export const LONG32: unknown;
+export const LONG64: unknown;
export const LPARAM: koffi.IKoffiCType;
-export const LPBOOL: any;
+export const LPBOOL: unknown;
export const LPBYTE: koffi.IKoffiCType;
-export const LPCOLORREF: any;
-export const LPCSTR: any;
-export const LPCTSTR: any;
-export const LPCWSTR: any;
+export const LPCOLORREF: unknown;
+export const LPCSTR: unknown;
+export const LPCTSTR: unknown;
+export const LPCWSTR: unknown;
export const LPVOID: koffi.IKoffiCType;
export const LPCVOID: koffi.IKoffiCType;
export const LPDWORD: koffi.IKoffiCType;
@@ -108,21 +108,21 @@ export const PWSTR: koffi.IKoffiCType;
export const QWORD: koffi.IKoffiCType;
export const SIZE: koffi.IKoffiCType;
export const SSIZE: koffi.IKoffiCType;
-export const TBYTE: any;
-export const TCHAR: any;
-export const UCHAR: any;
-export const UHALF_PTR: any;
-export const UINT: any;
-export const UINT_PTR: any;
-export const UINT8: any;
-export const UINT16: any;
-export const UINT32: any;
-export const UINT64: any;
-export const ULONG: any;
-export const ULONGLONG: any;
-export const ULONG32: any;
-export const ULONG64: any;
-export const USHORT: any;
-export const USN: any;
-export const WPARAM: any;
+export const TBYTE: unknown;
+export const TCHAR: unknown;
+export const UCHAR: unknown;
+export const UHALF_PTR: unknown;
+export const UINT: unknown;
+export const UINT_PTR: unknown;
+export const UINT8: unknown;
+export const UINT16: unknown;
+export const UINT32: unknown;
+export const UINT64: unknown;
+export const ULONG: unknown;
+export const ULONGLONG: unknown;
+export const ULONG32: unknown;
+export const ULONG64: unknown;
+export const USHORT: unknown;
+export const USN: unknown;
+export const WPARAM: unknown;
import koffi from "koffi";