{"id":19478877,"url":"https://github.com/xan105/node-ffi","last_synced_at":"2025-04-25T15:30:58.033Z","repository":{"id":65821889,"uuid":"532180013","full_name":"xan105/node-ffi","owner":"xan105","description":"Friendly abstraction/API for FFI with a Deno like syntax","archived":false,"fork":false,"pushed_at":"2024-10-26T01:32:39.000Z","size":271,"stargazers_count":5,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-14T06:50:01.143Z","etag":null,"topics":["dlopen","ffi","ffi-napi","foreign","function","interface","koffi","node-ffi","nodejs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xan105.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"xan105","custom":"https://www.paypal.me/xan105","patreon":"xan105"}},"created_at":"2022-09-03T06:44:51.000Z","updated_at":"2024-12-23T13:23:29.000Z","dependencies_parsed_at":"2023-02-21T02:15:38.273Z","dependency_job_id":"3360e771-5673-45c3-a4ad-42955ef80931","html_url":"https://github.com/xan105/node-ffi","commit_stats":{"total_commits":21,"total_committers":2,"mean_commits":10.5,"dds":0.04761904761904767,"last_synced_commit":"302dd55fd9a5471347751410427d52ad3b7423c2"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xan105%2Fnode-ffi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xan105%2Fnode-ffi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xan105%2Fnode-ffi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xan105%2Fnode-ffi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xan105","download_url":"https://codeload.github.com/xan105/node-ffi/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250844231,"owners_count":21496526,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dlopen","ffi","ffi-napi","foreign","function","interface","koffi","node-ffi","nodejs"],"created_at":"2024-11-10T19:51:54.281Z","updated_at":"2025-04-25T15:30:57.505Z","avatar_url":"https://github.com/xan105.png","language":"JavaScript","funding_links":["https://github.com/sponsors/xan105","https://www.paypal.me/xan105","https://patreon.com/xan105"],"categories":[],"sub_categories":[],"readme":"About\n=====\n\nForeign Function Interface (FFI) helper. Provides a friendly abstraction/API for:\n\n- [ffi-napi](https://www.npmjs.com/package/ffi-napi) (MIT)\n- [koffi](https://www.npmjs.com/package/koffi) (MIT)\n\nSyntax is inspired by Deno FFI. The goal was to be able to easily switch from `ffi-napi` to `koffi` or vice versa.\n\n📦 Scoped `@xan105` packages are for my own personal use but feel free to use them.\n\nExample\n=======\n\nLoading a library with Deno like syntax\n\n```js\nimport { dlopen } from \"@xan105/ffi/[ napi | koffi ]\";\n\nconst lib = dlopen(\"libm\", {\n  ceil: { \n    result: \"double\", \n    parameters: [ \"double\" ] \n  }\n});\nlib.ceil(1.5); // 2\n```\n\nAsynchronous calling\n\n```js\nimport { dlopen } from \"@xan105/ffi/[ napi | koffi ]\";\n\nconst lib = dlopen(\"libm\", {\n  ceil: { \n    result: \"double\", \n    parameters: [ \"double\" ],\n    nonblocking: true \n  }\n});\nawait lib.ceil(1.5); // 2\n```\n\nCalling directly from a library\n\n```js\nimport { load, types } from \"@xan105/ffi/[ napi | koffi ]\";\n\nconst lib = load(\"user32.dll\", { abi: \"stdcall\" });\nconst MessageBoxA = lib(\"MessageBoxA\", \"int\", [\n  \"void *\", \n  types.win32.LPCSTR, \n  types.win32.LPCSTR, \n  \"uint\"\n]);\n\nconst MB_ICONINFORMATION = 0x40;\nMessageBoxA(null, \"Hello World!\", \"Message\", MB_ICONINFORMATION);\n```\n\nCallback with Deno like syntax\n\n```js\nimport { dlopen, Callback} from \"@xan105/ffi/koffi\";\n\nconst lib = dlopen(\n  \"./callback.so\",\n  {\n    set_status_callback: {\n      parameters: [\"function\"],\n      result: \"void\"\n    },\n    start_long_operation: {\n      parameters: [],\n      result: \"void\"\n    }\n  }\n);\n\nconst callback = new Callback(\n  {\n    parameters: [\"u8\"],\n    result: \"void\",\n  },\n  (success) =\u003e {}\n);\n\nlib.set_status_callback(callback.pointer);\nlib.start_long_operation();\ncallback.close();\n```\n\nInstall\n=======\n\n```\nnpm install @xan105/ffi\n```\n\nPlease note that `ffi-napi` and `koffi` are optional peer dependencies.\u003cbr /\u003e\nInstall the one you wish to use yourself (or both 🙃).\n\n### ⚛️ Electron\n\n⚠️ NB: As of this writing `ffi-napi` does not work with Electron \u003e= 21.x.\u003cbr /\u003e\nDue to [Electron and the V8 Memory Cage](https://www.electronjs.org/blog/v8-memory-cage).\n\nAPI\n===\n\n⚠️ This module is only available as an ECMAScript module (ESM).\n\n💡 This lib doesn't have a default entry point. Choose the export corresponding to your liking.\n\n```js\nimport ... from \"@xan105/ffi/napi\";\n//OR\nimport ... from \"@xan105/ffi/koffi\";\n```\n\n### Named export\n\n#### `load(path: string, option?: object): function`\n\nLoad the given library path and return an handle function to call library's symbol(s).\n\n⚙️ **Option**\n\n- `ignoreLoadingFail?: boolean` (false)\n\nWhen set to `true` the handle function will silently fail if the given library couldn't be loaded and return `undefined` in such case.\n\n- `ignoreMissingSymbol?: boolean` (false)\n\nWhen 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.\n\n- `lazy?: boolean` (false)\n\nWhen set to `true` use `RTLD_LAZY` (lazy-binding) on POSIX platforms otherwise use `RTLD_NOW`.\n\n- `global?: boolean` (false)\n\nWhen set to `true` use `RTLD_GLOBAL` on POSIX platforms otherwise use `RTLD_LOCAL`.\n\n- `abi?: string` (koffi: \"func\" | ffi-napi: \"default_abi\")\n\nABI convention to use. Use this when you need to.\u003cbr /\u003e\n_ex: Win32 API (x86) requires \"stdcall\"._\n\n```js\n[\n  \"cdecl\", \"ms_cdecl\", //koffi \u0026 ffi-napi\n  \"stdcall\", //koffi \u0026 ffi-napi\n  \"fastcall\", //koffi \u0026 ffi-napi\n  \"thiscall\", //koffi \u0026 ffi-napi\n  \"win64\", //ffi-napi\n  \"unix64\", //ffi-napi\n  \"sysv\", //ffi-napi\n  \"vfp\" //ffi-napi\n]\n```\n\n- `integrity?: string` (none)\n\nSubresource Integrity.\n\n**Return**\n\nAn handle function to call library's symbol(s).\n\n```ts\nfunction(symbol: string | number, result: unknown, parameters: unknown[]): unknown\n```\n\n💡 `Koffi` can call by ordinal (symbol:number)\n\nSee 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.\n\n❌ Throws on error\n\n**Example**:\n\n```js\nimport { load } from \"@xan105/ffi/[ napi | koffi ]\";\nconst lib = load(\"libm\");\nconst ceil = lib(\"ceil\", \"double\", [\"double\"]);\nceil(1.5); //2\n```\n\n#### `dlopen(path: string, symbols: object, option?: object): object`\n\nOpen library and define exported symbols. This is a friendly wrapper to `load()` inspired by Deno FFI `dlopen` syntax.\u003cbr /\u003e\nIf you ever use ffi-napi `ffi.Library()` this will be familiar.\n\n**Param**\n\n- `path: string`\n\n  Library path to load.\n  \n- `symbols: object`\n\n  Symbol(s) definition:\n\n```ts\n  {\n    name: {\n      symbol?: string | number,\n      result?: unknown,\n      parameters?: unknown[],\n      nonblocking?: boolean,\n      stub?: boolean\n    },\n    ...\n  }\n```\n\n  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).\n  \n  `result` and `parameters` are the same as for the returned handle from `load()`.\u003cbr /\u003e\n  If omitted, `result` is set to \"void\" and `parameters` to an empty array.\u003cbr /\u003e\n  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.\n  \n  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).\n  \n  When `stub` is `true` the corresponding symbol will return a no-op if its missing.\u003cbr /\u003e\n  💡 If set, this superseed the _\"global\"_ `stub` option (see below).\n  \n- ⚙️ `option?: object`\n\n  Same as `load()` (see above) in addition to the following:\n  \n    + `errorAtRuntime?: boolean` (false)\n    \n      When set to `true`, initialisation error will be thrown on symbol invocation. \n    \n    + `nonblocking?: boolean` (false)\n    \n      When set to `true`, every symbols will return the corresponding promisified `async()` method (asynchronous calling).\u003cbr /\u003e\n     💡 This can be overriden per symbol (see symbol definition above).\n    \n    + `stub?: boolean` (false)\n    \n      When set to `true`, every missing symbols will return a no-op.\u003cbr /\u003e\n      💡 This can be overriden per symbol (see symbol definition above).\n  \n**Return** \n\n  An object with the given symbol(s) as properties.\n  \n  ❌ Throws on error. \n  \n**Example**\n\n```js\nimport { dlopen, types } from \"@xan105/ffi/[ napi | koffi ]\";\nconst { BOOL } = types.win32;\n\nconst lib = dlopen(\"xinput1_4\", {\n  \"XInputEnable\": {\n    parameters: [BOOL],\n    nonblocking: true\n  }\n}, { abi: \"stdcall\" });\n\nawait lib.XInputEnable(1);\n```\n\n#### `const types: object`\n\nThe FFI Library's primitive types as well as corresponding alias are exposed for convenience.\nSuch as Deno types (rust) and Windows specific types (DWORD,...).\n\n💡 Windows specific types are grouped together under `win32`.\n\n```js\nimport { types } from \"@xan105/ffi/[ napi | koffi ]\";\nconst { DWORD, LPCSTR } = types.win32;\n```\n\n💡 When using `koffi` alias are also set with `koffi.alias()` so you can use them as string.\n\n```js\nimport { load } from \"@xan105/ffi/koffi\";\nconst lib = load(\"user32.dll\", { abi: \"stdcall\" });\nconst MessageBoxA = lib(\"MessageBoxA\", \"int\", [\"void *\", \"LPCSTR\", \"LPCSTR\", \"uint\"]);\n```\n\n⚠️ Types are not exposed under their own namespace because some words are illegal or already in use in JavaScript.\nYou can still use destructuring if needed as long as the name is \"allowed\".\n\n❌ No\n\n```\nimport { i32 } from \"@xan105/ffi/koffi/types\"\n```\n\n✔️ Yes\n```\nimport { types } from \"@xan105/ffi/koffi\"\nconst { i32 } = types;\n```\n\n🚫 Forbidden\n\n```\nimport { types } from \"@xan105/ffi/napi\"\nconst { function } = types;\n```\n\n#### `class Callback`\n\nCreate a callback to be called at a later time (registered callback).\n\nThis is a class wrapper to the FFI library's callback function(s) inspired by Deno FFI `UnsafeCallback class` syntax.\n\n##### Constructor\n  \n  `(definition: { result: unknown, parameters: unknown[], abi?: string }, callback?: Function | null)`\n  \n##### Properties\n  \n  - `pointer: unknown` _(read only)_\n  \n  The pointer to the callback.\n  \n  - `address: number | BigInt | null` _(read only)_\n  \n  The memory address of the pointer.\n  \n  - `type: unknown` _(read only)_\n  \n  The type of the callback.\n  \n##### Methods\n  \n  - `close(): void`\n  \n  Dispose of the callback. Remove function pointer associated with this instance.\n\n  - `register(callback?: Function): void`\n  \n  Register the callback. If a callback was already registered with this instance it will be disposed of.\n\n##### Example\n  \n```js\nimport { dlopen, types, Callback } from \"@xan105/ffi/[ napi | koffi ]\";\n\nconst library = dlopen(\"./callback.so\", {\n    setCallback: {\n      parameters: [types.function],\n      result: \"void\",\n    },\n    doSomething(): {\n      parameters: [],\n      result: \"void\",\n    },\n});\n\nconst callback = new Callback(\n  { parameters: [], result: \"void\" },\n  () =\u003e {},\n);\n\nlibrary.setCallback(callback.pointer);\nlibrary.doSomething();\n\n// After callback is no longer needed\ncallback.close();\n```\n\nYou can also register the callback at a later time:\n\n```js\nimport { dlopen, Callback } from \"@xan105/ffi/[ napi | koffi ]\";\n\nconst callback = new Callback(\n  { parameters: [], result: \"void\" }\n);\n\nconst library = dlopen(\"./callback.so\", {\n    setCallback: {\n      parameters: [callback.type],\n      result: \"void\",\n    },\n    doSomething(): {\n      parameters: [],\n      result: \"void\",\n    },\n});\n\ncallback.register(()=\u003e{});\n\nlibrary.setCallback(callback.pointer);\nlibrary.doSomething();\n\n// After callback is no longer needed\ncallback.close();\n```\n\n#### `pointer(value: unknown, direction?: string): unknown`\n\nJust a shorthand to define a pointer.\n\n```js\nimport { dlopen, types, pointer } from \"@xan105/ffi/[ napi | koffi ]\";\n\nconst dylib = dlopen(\"shell32.dll\", {\n  SHQueryUserNotificationState: {\n    result: types.win32.HRESULT,\n    parameters: [\n      pointer(types.win32.ENUM, \"out\")\n    ]\n  }\n}, { abi: \"stdcall\" });\n```\n\n#### `struct(schema: unknown): unknown`\n\nJust a shorthand to define a structure.\n\n```js\nimport { dlopen, types, struct, pointer } from \"@xan105/ffi/[ napi | koffi ]\";\n\nconst POINT = struct({ //define struct\n  x: types.win32.LONG,\n  y: types.win32.LONG\n});\n\nconst dylib = dlopen(\"user32.dll\", { //lib loading\n    GetCursorPos: {\n      result: types.win32.BOOL,\n      parameters: [ pointer(POINT, \"out\") ] //struct pointer\n    }\n  }, { abi: \"stdcall\" });\n```\n\n⚠️ NB: Struct are use differently afterwards:\n\n- Koffi\n\n```js\nconst cursorPos = {};\nGetCursorPos(cursorPos);\nconsole.log(cursorPos) \n//{ x: 0, y: 0 }\n```\n\n- ffi-napi\n\n```js\nconst cursorPos = new POINT();\nGetCursorPos(cursorPos.ref());\n\n//access the properties directly\nconsole.log({ x: cursorPos.x, y: cursorPos.y }); //{ x: 0, y: 0 }\n\n//or call .toObject()/.toJSON() (alias) to get a JS Object\nconsole.log(cursorPos.toObject()); //{ x: 0, y: 0 }\n```\n\n#### `structEx(schema: object): object`\n\n💡 It is worth noting that while the goal of this lib is to write the same code with different FFI libraries; \nwhen using Koffi you can just use Koffi's `struct()` function as Koffi converts JS objects to C structs, and vice-versa.\n\nDefine a structure. The returned object has 2 properties:\n\n- `type: unknown` \n\nThe type of the struct.\n\n- `create: ()=\u003e Class instance`\n\nReturn an instance of a class wrapper to the FFI library's struct functions.\n\n##### Class properties\n  \n  - `pointer: unknown` _(read only)_\n  \n  The pointer to the struct.\n  \n  - `values: object`\n  \n  Get or set the values of the struct.\n  \n##### Example\n  \n```js\nimport { dlopen, types, struct, pointer } from \"@xan105/ffi/[ napi | koffi ]\";\n\nconst POINT = struct({ //define struct\n  x: types.win32.LONG,\n  y: types.win32.LONG\n});\n\nconst dylib = dlopen(\"user32.dll\", { //lib loading\n    GetCursorPos: {\n      result: types.win32.BOOL,\n      parameters: [ pointer(POINT.type, \"out\") ] //struct pointer\n    }\n  }, { abi: \"stdcall\" });\n\nconst cursorPos = POINT.create();\nGetCursorPos(cursorPos.pointer);\nconsole.log(cursorPos.values) //{ x: 0, y: 0 }\n```\n\n#### `alloc(type: unknown): { pointer: Buffer, get: ()=\u003e unknown }`\n\nAllocate a buffer and get the corresponding data when passing a pointer to allow the called function to manipulate memory.\n\n```js\nimport { dlopen, alloc } from \"@xan105/ffi/[ napi | koffi ]\";\nconst dylib = dlopen(...); //lib loading\n\nconst number = alloc(\"int\"); //allocate Buffer for the output data\ndylib.manipulate_number(number.pointer);\nconst result = number.get();\n```\n\n#### `lastError(option?: object): string[] | number`\n\nShorthand to errno (POSIX) and GetLastError (win32).\n\n⚙️ **Option**\n\n - `translate?: boolean` (true)\n \nWhen an error code is known it will be 'translated' to its corresponding message and code values as\u003cbr /\u003e `[message: string, code?: string]`. If you only want the raw numerical code set it to `false`.\n\nex:\n```js\nif(result !== 0){ //something went wrong\n\n  console.log(lastError())\n  //['No such file or directory', 'ENOENT']\n\n  console.log(lastError({ translate: false }));\n  // 2\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxan105%2Fnode-ffi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxan105%2Fnode-ffi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxan105%2Fnode-ffi/lists"}