{"id":16376455,"url":"https://github.com/bengl/sbffi","last_synced_at":"2025-09-16T14:30:45.414Z","repository":{"id":54563769,"uuid":"265432070","full_name":"bengl/sbffi","owner":"bengl","description":"FFI for node.js, written using NAPI, dyncall, and shared buffers.","archived":false,"fork":false,"pushed_at":"2023-08-04T18:56:18.000Z","size":1489,"stargazers_count":104,"open_issues_count":5,"forks_count":7,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-12-27T12:46:35.097Z","etag":null,"topics":["dyncall","ffi","napi","nodejs"],"latest_commit_sha":null,"homepage":null,"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/bengl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","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}},"created_at":"2020-05-20T02:52:24.000Z","updated_at":"2024-07-07T12:20:17.000Z","dependencies_parsed_at":"2024-10-18T15:29:34.130Z","dependency_job_id":"8a48f510-e3f3-4902-b945-9a764dec4bc0","html_url":"https://github.com/bengl/sbffi","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bengl%2Fsbffi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bengl%2Fsbffi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bengl%2Fsbffi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bengl%2Fsbffi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bengl","download_url":"https://codeload.github.com/bengl/sbffi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":232792147,"owners_count":18577261,"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":["dyncall","ffi","napi","nodejs"],"created_at":"2024-10-11T03:24:44.179Z","updated_at":"2025-09-16T14:30:39.916Z","avatar_url":"https://github.com/bengl.png","language":"JavaScript","readme":"# sbffi\n\nA super-quick [FFI](https://en.wikipedia.org/wiki/Foreign_function_interface)\nfor Node.js.\n\n[`dyncall`](https://dyncall.org/) is used to make dynamic calls to native\nfunctions. In order to avoid some cost of translating JavaScript values into raw\nC types, a shared buffer is used for both arguments and return values. Writing\nvalues to a buffer turns out to be quite a bit faster than unpacking them in\nnative code.\n\n## Usage\n\n**`sbffi.getNativeFunction(pathToSharedLibrary, functionName, returnType, [argType1, argType2, ...])`**\n\nAll the arguments are strings. The types must be standard C types. See the\n**Types** section below for details. When functions take 64-bit types, the\nparameters must be passed as BigInts. 64-bit return values will also be\nBigInts.\n\n```c\n// adder.c: some C library compiled to libadder.so\n\nuint32_t add(uint32_t a, uint32_t b) {\n  return a + b;\n}\n```\n\n```js\n// index.js\n\nconst { getNativeFunction } = require('sbffi');\n\nconst libPath = '/path/to/libadder.so';\nconst add = getNativeFunction(libPath, 'add', 'uint32_t', ['uint32_t', 'uint32_t']);\n\nconst result = add(23, 34);\n// 57\n```\n\n### Types\n\nThe following types are supported:\n\n* `(u)int[8|16|32|64]_t`\n* `bool`\n* `(unsigned) char`\n* `(unsigned) short`\n* `(unsigned) int`\n* `(unsigned) long`\n* `(unsigned) long long`\n* `float`\n* `double`\n* `size_t`\n\n128-bit types are not yet supported, and while this list may grow over time, for\nnow other types can be used if they're aliases of the above types.\n\nSee the section below about pointers.\n\n### Pointers\n\nPointers are currently assumed to be 64-bit, and can be passed to native\nfunctions by specifying the type as `pointer` or referring to any other type\nwith an asterisk in the string, for example: `uint8_t *`.\n\nYou can put raw data into a Buffer, and then get a pointer to the start of that\nbuffer with:\n\n**`const bufferPointer = sbffi.getBufferPointer(buffer);`**\n\nArrays and strings must be passed as pointers.\n\n### Callbacks\n\nYou can use two different styles of callbacks with `sbffi`.\n\n* **Simple callbacks** are simply passed into the function as normal. They must\n  be called _exactly once_ by the underlying native function.\n\n* **Advanced callbacks** must be wrapped with `createCallback`, and `.destroy()`\n  must be called on them when the underlying native function will no longer call\n  it. There's a slight performance advantage in using advanced callbacks and\n  re-using them, since simple callbacks create a `Napi::ThreadsafeFunction` per\n  invocation. In addition, APIs that may call the same callback multiple times\n  may be used with advanced callbacks, but not with simple callbacks.\n\nIn either case, to specify a simple callback, identify it in the arguments array\npassed to `getNatveFunction()` as `[cbReturnType, [cbArgTyp1, cbArgType2,\n...]]`.\n\nFor advanced callbacks, after specifying them in the native function signature,\nyou can initialize them as follows:\n\n```js\nfunction myCb (result) { /* ... */ }\nconst advancedCallback = createCallback(myCb, [cbReturnType, [cbArgTyp1, cbArgType2, ...]]);\n```\n\nYou can then pass `advancedCallback` to a native function that takes in a\ncallbacks with that signature, just as you would any other callback. When the\ncallback is no longer needed, you can call `advancedCallback.destroy()`.\n_Failure to call `.destroy()` will keep the Node.js process alive._\n\n### Structs\n\nFor now, `sbfffi` doesn't have any built-in support for structs. That being\nsaid, there are some helpful libraries like\n[`shared-structs`](https://www.npmjs.com/package/shared-structs) and\n[`ref-napi`](https://www.npmjs.com/package/ref-napi) (and its family of\nmodules). As long as you can build up a C struct into a Buffer, you can pass\npointers to them into C functions. Non-pointer struct arguments or return values\nare not supported.\n\n## Development\n\nUsing a non-release version of `sbffi` requires that\n[`cmake`](https://cmake.org/) is installed in order to compile the native\naddon.\n\n## Benchmarks\n\nA simple benchmark can be run with `npm run bench`. This will test calling a\nsimple adding function from the test library using the following techniques:\n\n* **`ffi-napi`**: A successor to `node-ffi` compatible with modern versions of\n  Node.js.\n* **`sbffi`**: This library.\n* **`napi-addon`**: A very simple/normal Node.js addon using NAPI in C.\n* **`napi-addon-sb`**: A NAPI addon using the same shared-buffer technique as\n  `sbffi`, but with a hard-coded function call, rather than a dynamic/FFI call.\n* **`wasm`**: The adding function compiled to WebAssembly.\n* **`js`**: Re-implementing the function in plain JavaScript.\n\nEach function will be called 100000 times, in 5 repetitions, timed with\n`console.time()`. Here are the results on my machine (AMD Ryzen 9 5900X) with\nUbuntu 22.04.1 and Node.js 19.6.0:\n\n```\nffi-napi ... done!\nsbffi ... done!\nnapi-addon ... done!\nnapi-addon-sb ... done!\nwasm ... done!\njs ... done!\n---\nffi-napi ... done!\nsbffi ... done!\nnapi-addon ... done!\nnapi-addon-sb ... done!\nwasm ... done!\njs ... done!\n---\nffi-napi ... done!\nsbffi ... done!\nnapi-addon ... done!\nnapi-addon-sb ... done!\nwasm ... done!\njs ... done!\n---\nffi-napi ... done!\nsbffi ... done!\nnapi-addon ... done!\nnapi-addon-sb ... done!\nwasm ... done!\njs ... done!\n---\nffi-napi ... done!\nsbffi ... done!\nnapi-addon ... done!\nnapi-addon-sb ... done!\nwasm ... done!\njs ... done!\n---\n┌───────────────┬──────┬───────────┬────────────┬────────────────────┐\n│    (index)    │ min  │    max    │    mean    │       stddev       │\n├───────────────┼──────┼───────────┼────────────┼────────────────────┤\n│   ffi-napi    │ 3216 │ 122159103 │ 8271.61785 │ 291769.95704110194 │\n│     sbffi     │ 120  │  3657727  │ 177.070514 │ 9480.134200345783  │\n│  napi-addon   │ 120  │  2877439  │ 181.689724 │ 9197.256781039703  │\n│ napi-addon-sb │  80  │  3256319  │ 143.028966 │ 10193.011672112762 │\n│     wasm      │  60  │  3317759  │ 170.443644 │ 16172.981925863021 │\n│      js       │  70  │  3395583  │ 141.67375  │ 12967.590912612988 │\n└───────────────┴──────┴───────────┴────────────┴────────────────────┘\n```\n\nFor this benchmark, I generally see roughly similar performance between `sbffi`\nand a typical NAPI addon. Of course, YMMV.\n\n## Contributing\n\nPlease see [CONTRIBUTING.md](./CONTRIBUTING.md),\n[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and [TODO.md](./TODO.md).\n\n## License\n\nPlease see [LICENSE.txt](./LICENSE.txt).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbengl%2Fsbffi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbengl%2Fsbffi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbengl%2Fsbffi/lists"}