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";