Claude-skill-registry Bun FFI
This skill should be used when the user asks about "bun:ffi", "foreign function interface", "calling C from Bun", "native libraries", "dlopen", "shared libraries", "calling native code", or integrating C/C++ libraries with Bun.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/bun-ffi" ~/.claude/skills/majiayu000-claude-skill-registry-bun-ffi && rm -rf "$T"
manifest:
skills/data/bun-ffi/SKILL.mdsource content
Bun FFI
Bun's FFI allows calling native C/C++ libraries from JavaScript.
Quick Start
import { dlopen, suffix, FFIType } from "bun:ffi"; // Load library const lib = dlopen(`libc.${suffix}`, { printf: { args: [FFIType.cstring], returns: FFIType.int, }, }); // Call function lib.symbols.printf("Hello from C!\n");
Loading Libraries
Platform-Specific Paths
import { dlopen, suffix } from "bun:ffi"; // suffix is: "dylib" (macOS), "so" (Linux), "dll" (Windows) // System library const libc = dlopen(`libc.${suffix}`, { ... }); // Custom library const myLib = dlopen(`./libmylib.${suffix}`, { ... }); // Absolute path const sqlite = dlopen("/usr/lib/libsqlite3.so", { ... });
Cross-Platform Loading
function getLibPath(name: string): string { const platform = process.platform; const paths = { darwin: `/usr/local/lib/lib${name}.dylib`, linux: `/usr/lib/lib${name}.so`, win32: `C:\\Windows\\System32\\${name}.dll`, }; return paths[platform] || paths.linux; } const lib = dlopen(getLibPath("mylib"), { ... });
FFI Types
import { FFIType } from "bun:ffi"; const types = { // Integers i8: FFIType.i8, // int8_t i16: FFIType.i16, // int16_t i32: FFIType.i32, // int32_t / int i64: FFIType.i64, // int64_t / long long // Unsigned integers u8: FFIType.u8, // uint8_t u16: FFIType.u16, // uint16_t u32: FFIType.u32, // uint32_t u64: FFIType.u64, // uint64_t // Floats f32: FFIType.f32, // float f64: FFIType.f64, // double // Pointers ptr: FFIType.ptr, // void* cstring: FFIType.cstring, // const char* // Other bool: FFIType.bool, // bool void: FFIType.void, // void };
Function Definitions
import { dlopen, FFIType, ptr, CString } from "bun:ffi"; const lib = dlopen("./libmath.so", { // Simple function add: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32, }, // String function greet: { args: [FFIType.cstring], returns: FFIType.cstring, }, // Pointer function allocate: { args: [FFIType.u64], returns: FFIType.ptr, }, // Void function log_message: { args: [FFIType.cstring], returns: FFIType.void, }, // No args get_version: { args: [], returns: FFIType.cstring, }, }); // Call functions const sum = lib.symbols.add(1, 2); // 3 const message = lib.symbols.greet(ptr(Buffer.from("World\0")));
Working with Strings
import { dlopen, FFIType, ptr, CString } from "bun:ffi"; // Passing strings to C const str = Buffer.from("Hello\0"); // Must be null-terminated lib.symbols.print_string(ptr(str)); // Receiving strings from C const result = lib.symbols.get_string(); const jsString = new CString(result); // Convert to JS string console.log(jsString.toString());
Working with Pointers
import { dlopen, FFIType, ptr, toArrayBuffer } from "bun:ffi"; const lib = dlopen("./libdata.so", { create_buffer: { args: [FFIType.u64], returns: FFIType.ptr, }, fill_buffer: { args: [FFIType.ptr, FFIType.u8, FFIType.u64], returns: FFIType.void, }, free_buffer: { args: [FFIType.ptr], returns: FFIType.void, }, }); // Allocate buffer const size = 1024; const bufPtr = lib.symbols.create_buffer(size); // Fill buffer lib.symbols.fill_buffer(bufPtr, 0xff, size); // Read buffer as ArrayBuffer const arrayBuffer = toArrayBuffer(bufPtr, 0, size); const view = new Uint8Array(arrayBuffer); console.log(view); // [255, 255, 255, ...] // Free buffer lib.symbols.free_buffer(bufPtr);
Structs
import { dlopen, FFIType, ptr } from "bun:ffi"; // C struct: // struct Point { int32_t x; int32_t y; }; const lib = dlopen("./libgeom.so", { create_point: { args: [FFIType.i32, FFIType.i32], returns: FFIType.ptr, // Returns Point* }, get_distance: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.f64, }, }); // Create struct manually const point = new ArrayBuffer(8); // 2 x int32 const view = new DataView(point); view.setInt32(0, 10, true); // x = 10 view.setInt32(4, 20, true); // y = 20 // Pass to C lib.symbols.get_distance(ptr(point), ptr(point));
Callbacks
import { dlopen, FFIType, callback } from "bun:ffi"; const lib = dlopen("./libsort.so", { sort_array: { args: [FFIType.ptr, FFIType.u64, FFIType.ptr], // callback returns: FFIType.void, }, }); // Create callback const compareCallback = callback( { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32, }, (a, b) => { const aVal = new DataView(toArrayBuffer(a, 0, 4)).getInt32(0, true); const bVal = new DataView(toArrayBuffer(b, 0, 4)).getInt32(0, true); return aVal - bVal; } ); // Use callback lib.symbols.sort_array(arrayPtr, length, compareCallback.ptr); // Close callback when done compareCallback.close();
Example: SQLite
import { dlopen, FFIType, ptr, CString } from "bun:ffi"; const sqlite = dlopen("libsqlite3.dylib", { sqlite3_open: { args: [FFIType.cstring, FFIType.ptr], returns: FFIType.i32, }, sqlite3_exec: { args: [FFIType.ptr, FFIType.cstring, FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.i32, }, sqlite3_close: { args: [FFIType.ptr], returns: FFIType.i32, }, }); // Open database const dbPtrArray = new BigInt64Array(1); const dbPath = Buffer.from("test.db\0"); sqlite.symbols.sqlite3_open(ptr(dbPath), ptr(dbPtrArray)); const db = dbPtrArray[0]; // Execute query const sql = Buffer.from("CREATE TABLE test (id INTEGER);\0"); sqlite.symbols.sqlite3_exec(db, ptr(sql), null, null, null); // Close sqlite.symbols.sqlite3_close(db);
Memory Management
// Manual allocation const buffer = new ArrayBuffer(1024); const pointer = ptr(buffer); // Buffer stays valid as long as ArrayBuffer exists // JavaScript GC will clean up ArrayBuffer // For C-allocated memory, call C's free function lib.symbols.free(cPointer);
Thread Safety
// FFI calls are synchronous and block the main thread // For long-running operations, use Web Workers: // worker.ts import { dlopen } from "bun:ffi"; const lib = dlopen(...); self.onmessage = (e) => { const result = lib.symbols.expensive_operation(e.data); self.postMessage(result); };
Common Errors
| Error | Cause | Fix |
|---|---|---|
| Wrong path | Check library path |
| Wrong function name | Check function export |
| Wrong FFI types | Match C types exactly |
| Memory error | Check pointer validity |
When to Load References
Load
references/type-mappings.md when:
- Complex type conversions
- Struct layouts
- Union types
Load
references/performance.md when:
- Optimizing FFI calls
- Batching operations
- Memory pooling