About
Foreign Function Interface helper. Provides a friendly abstraction/API for:
Syntax is inspired by Deno FFI. The goal was to be able to easily switch from ffi-napi
to koffi
or vice versa.
@xan105
packages are for my own personal use but feel free to use them.
- Forked form
@xan105/ffi
(MIT)
Example
Loading a library with Deno like syntax
import { dlopen } from "@jellybrick/ffi-cjs/[ napi | koffi ]";
const lib = dlopen("libm", {
ceil: {
result: "double",
parameters: [ "double" ]
}
});
lib.ceil(1.5); // 2
Async
import { dlopen } from "@jellybrick/ffi-cjs/[ napi | koffi ]";
const lib = dlopen("libm", {
ceil: {
result: "double",
parameters: [ "double" ],
nonblocking: true
}
});
await lib.ceil(1.5); // 2
Calling directly from a library
import { load, types } from "@jellybrick/ffi-cjs/[ napi | koffi ]";
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);
Callback with Deno like syntax
import { dlopen, Callback} from "@jellybrick/ffi-cjs/koffi";
const library = dlopen(
"./callback.so",
{
set_status_callback: {
parameters: ["function"],
result: "void"
},
start_long_operation: {
parameters: [],
result: "void"
}
}
);
const callback = new Callback(
{
parameters: ["u8"],
result: "void",
},
(success) => {}
);
library.set_status_callback(callback.pointer);
library.start_long_operation();
Install
npm install @jellybrick/ffi-cjs
Please note that ffi-napi
and koffi
are optional peer dependencies.
Install the one you wish to use yourself (or both
API
TypeScript
import ... from "@jellybrick/ffi-cjs/napi";
//OR
import ... from "@jellybrick/ffi-cjs/koffi";
JavaScript
const ... = require("@jellybrick/ffi-cjs/napi");
//OR
const ... = require("@jellybrick/ffi-cjs/koffi");
Named export
load(path: string, option?: object): function
Load the given library path and return an handle function to call library's symbol(s).
Option
-
ignoreLoadingFail?: boolean
(false)
Silent fail if the given library couldn't be loaded.
undefined
in that case.
-
ignoreMissingSymbol?: boolean
(false)
Silent fail if the given library doesn't have the called symbol.
undefined
in that case.
-
abi?: string
("func" for koffi and "default_abi" for ffi-napi)
ABI convention to use. Use this when you need to ex: winapi x86 requires "stdcall".
Return
function(symbol: string | number, result: unknown, parameters: unknown[]): any;
Koffi
can call by ordinal (symbol:number)
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.
Example:
import { load } from "@jellybrick/ffi-cjs/[ napi | koffi ]";
const call = load("libm");
const ceil = call("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.
If you ever use ffi-napi ffi.Library()
this will be familiar.
Param
-
path: string
Library path to load
-
symbols: object
Symbol(s) definition:
{
name: {
result?: unknown,
parameters?: unknown[],
nonblocking?: boolean,
symbol?: string | number
},
...
}
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).
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()
.
-
option?: object
Pass option(s) to
load()
. See above.
Return
An object with the given symbol(s) as properties.
Example
import { dlopen, types } from "@jellybrick/ffi-cjs/[ napi | koffi ]";
const { BOOL } = types.win32;
const lib = dlopen("xinput1_4", {
"XInputEnable": {
parameters: [BOOL],
nonblocking: true
}
}, { abi: "stdcall" });
await lib.XInputEnable(1);
const types: object
The FFI Library's primitive types as well as corresponding alias are exposed for convenience. Such as Deno types (rust) and Windows specific types (DWORD,...).
win32
.
import { types } from "@jellybrick/ffi-cjs/[ napi | koffi ]";
const { DWORD, LPCSTR } = types.win32;
koffi
alias are also set with koffi.alias()
so you can use them as string.
import { load } from "@jellybrick/ffi-cjs/koffi";
const call = load("user32.dll", { abi: "stdcall" });
const MessageBoxA = call("MessageBoxA", "int", ["void *", "LPCSTR", "LPCSTR", "uint"]);
import { i32 } from "@jellybrick/ffi-cjs/koffi/types"
import { types } from "@jellybrick/ffi-cjs/koffi"
const { i32 } = types;
import { types } from "@jellybrick/ffi-cjs/napi"
const { function } = types;
class Callback
Create a callback to be called at a later time (registered callback).
This is a class wrapper to the FFI library's callback function(s) inspired by Deno FFI UnsafeCallback class
syntax.
Constructor
(definition: { result: unknown, parameters: unknown[] }, callback?: Function | null)
Properties
pointer: unknown
The pointer to the callback.
address: number | BigInt | null
The memory address of the pointer.
type: unknown
The type of the callback.
Methods
close(): void
Dispose of the callback. Remove function pointer associated with this instance.
register(callback?: Function): void
Register the callback. If a callback was already registered with this instance it will be disposed of.
Example
import { dlopen, types, Callback } from "@jellybrick/ffi-cjs/[ napi | koffi ]";
const library = dlopen("./callback.so", {
setCallback: {
parameters: [types.function],
result: "void",
},
doSomething(): {
parameters: [],
result: "void",
},
});
const callback = new Callback(
{ parameters: [], result: "void" },
() => {},
);
library.setCallback(callback.pointer);
library.doSomething();
// After callback is no longer needed
callback.close();
You can also register the callback at a later time:
import { dlopen, Callback } from "@jellybrick/ffi-cjs/[ napi | koffi ]";
const callback = new Callback(
{ parameters: [], result: "void" }
);
const library = dlopen("./callback.so", {
setCallback: {
parameters: [callback.type],
result: "void",
},
doSomething(): {
parameters: [],
result: "void",
},
});
callback.register(()=>{});
library.setCallback(callback.pointer);
library.doSomething();
// After callback is no longer needed
callback.close();
pointer(value: unknown, direction?: string): any
Just a shorthand to ref.refType(x)
(ffi-napi) and koffi.out/inout(koffi.pointer(x))
(koffi) to define a pointer.
alloc(type: unknown): { pointer: Buffer, get: ()=> unknown }
Allocate a buffer and get the corresponding data when passing a pointer to allow the called function to manipulate memory.
import { dlopen, alloc } from "@jellybrick/ffi-cjs/[ napi | koffi ]";
const dylib = dlopen(...); //lib loading
const number = alloc("int"); //allocate Buffer for the output data
dylib.manipulate_number(number.pointer);
const result = number.get();