Native library integration with C function binding, pointer manipulation, type-safe FFI declarations, and seamless interoperability between JavaScript and native code.
import { dlopen, FFIType, ptr, CString, toArrayBuffer, toBuffer } from "bun:ffi";Load native libraries and bind C functions with automatic type marshalling and memory management.
/**
* Load a native library and bind its functions
* @param name - Path to shared library file (.so, .dll, .dylib) or library name
* @param symbols - Object mapping function names to their FFI signatures
* @returns Library object with bound functions
*/
function dlopen<Fns extends Record<string, FFIFunction>>(
name: string | import("bun").BunFile | URL,
symbols: Fns
): Library<Fns>;
/**
* Compile C source code and create FFI bindings
* @param options - Compilation options
* @returns Library with compiled symbols
*/
function cc<Fns extends Record<string, FFIFunction>>(options: {
source: string | import("bun").BunFile | URL;
library?: string[] | string;
include?: string[] | string;
symbols: Fns;
define?: Record<string, string>;
flags?: string | string[];
}): Library<Fns>;
/**
* Create a single C function from a pointer
* @param fn - FFI function declaration with pointer
* @returns Callable function with close method
*/
function CFunction(fn: FFIFunction & { ptr: Pointer }): CallableFunction & {
close(): void;
};
/**
* Link symbols that are already loaded in memory
* @param symbols - Map of symbols with function pointers
* @returns Library with linked symbols
*/
function linkSymbols<Fns extends Record<string, FFIFunction>>(symbols: Fns): Library<Fns>;
interface FFIFunction {
/** Parameter types (in order) */
readonly args?: readonly FFITypeOrString[];
/** Return type */
readonly returns?: FFITypeOrString;
/** Function pointer (for pre-loaded functions) */
readonly ptr?: Pointer | bigint;
/** Whether function can be called from other threads */
readonly threadsafe?: boolean;
}
type Symbols = Readonly<Record<string, FFIFunction>>;
interface Library<Fns extends Symbols> {
/** Bound symbols as callable functions */
symbols: ConvertFns<Fns>;
/** Close the library and free resources */
close(): void;
}
type FFITypeOrString = FFIType | keyof FFITypeStringToType;Usage Examples:
import { dlopen, FFIType } from "bun:ffi";
// Load math library
const mathLib = dlopen("libm.so.6", {
sin: {
args: ["double"],
returns: "double"
},
cos: {
args: ["double"],
returns: "double"
},
pow: {
args: ["double", "double"],
returns: "double"
}
});
// Use bound functions
const result = mathLib.symbols.sin(Math.PI / 2); // 1.0
const power = mathLib.symbols.pow(2, 8); // 256.0
// Load custom library
const myLib = dlopen("./libmycode.so", {
add_numbers: {
args: ["int32_t", "int32_t"],
returns: "int32_t"
},
process_string: {
args: ["cstring"],
returns: "cstring"
},
allocate_buffer: {
args: ["uint32_t"],
returns: "ptr"
}
});
// Call custom functions
const sum = myLib.symbols.add_numbers(42, 58); // 100
const processed = myLib.symbols.process_string("hello");
// Clean up
mathLib.close();
myLib.close();Comprehensive type mapping between JavaScript and C types with automatic marshalling.
/**
* FFI type enumeration for C type mapping
*/
enum FFIType {
/** 8-bit character */
char = 0,
/** 8-bit signed integer */
int8_t = 1,
/** 8-bit signed integer (alias) */
i8 = 1,
/** 8-bit unsigned integer */
uint8_t = 2,
/** 8-bit unsigned integer (alias) */
u8 = 2,
/** 16-bit signed integer */
int16_t = 3,
/** 16-bit signed integer (alias) */
i16 = 3,
/** 16-bit unsigned integer */
uint16_t = 4,
/** 16-bit unsigned integer (alias) */
u16 = 4,
/** 32-bit signed integer */
int32_t = 5,
/** 32-bit signed integer (alias) */
i32 = 5,
/** 32-bit signed integer (C int) */
int = 5,
/** 32-bit unsigned integer */
uint32_t = 6,
/** 32-bit unsigned integer (alias) */
u32 = 6,
/** 64-bit signed integer */
int64_t = 7,
/** 64-bit signed integer (alias) */
i64 = 7,
/** 64-bit unsigned integer */
uint64_t = 8,
/** 64-bit unsigned integer (alias) */
u64 = 8,
/** 64-bit floating point */
double = 9,
/** 64-bit floating point (alias) */
f64 = 9,
/** 32-bit floating point */
float = 10,
/** 32-bit floating point (alias) */
f32 = 10,
/** Boolean value */
bool = 11,
/** Pointer value */
ptr = 12,
/** Pointer value (alias) */
pointer = 12,
/** No return value */
void = 13,
/** C string (null-terminated) */
cstring = 14,
/** Fast 64-bit signed integer (may be number or bigint) */
i64_fast = 15,
/** Fast 64-bit unsigned integer (may be number or bigint) */
u64_fast = 16,
/** Function pointer */
function = 17,
/** Node-API environment */
napi_env = 18,
/** Node-API value */
napi_value = 19,
/** Buffer type */
buffer = 20
}
type Pointer = number & { __pointer__: null };
/**
* FFI type to JavaScript argument type mapping
*/
interface FFITypeToArgsType {
[FFIType.char]: number;
[FFIType.int8_t]: number;
[FFIType.i8]: number;
[FFIType.uint8_t]: number;
[FFIType.u8]: number;
[FFIType.int16_t]: number;
[FFIType.i16]: number;
[FFIType.uint16_t]: number;
[FFIType.u16]: number;
[FFIType.int32_t]: number;
[FFIType.i32]: number;
[FFIType.int]: number;
[FFIType.uint32_t]: number;
[FFIType.u32]: number;
[FFIType.int64_t]: number | bigint;
[FFIType.i64]: number | bigint;
[FFIType.uint64_t]: number | bigint;
[FFIType.u64]: number | bigint;
[FFIType.double]: number;
[FFIType.f64]: number;
[FFIType.float]: number;
[FFIType.f32]: number;
[FFIType.bool]: boolean;
[FFIType.ptr]: NodeJS.TypedArray | Pointer | CString | null;
[FFIType.pointer]: NodeJS.TypedArray | Pointer | CString | null;
[FFIType.void]: undefined;
[FFIType.cstring]: NodeJS.TypedArray | Pointer | CString | null;
[FFIType.i64_fast]: number | bigint;
[FFIType.u64_fast]: number | bigint;
[FFIType.function]: Pointer | JSCallback;
[FFIType.napi_env]: unknown;
[FFIType.napi_value]: unknown;
[FFIType.buffer]: NodeJS.TypedArray | DataView;
}
/**
* FFI type to JavaScript return type mapping
*/
interface FFITypeToReturnsType {
[FFIType.char]: number;
[FFIType.int8_t]: number;
[FFIType.i8]: number;
[FFIType.uint8_t]: number;
[FFIType.u8]: number;
[FFIType.int16_t]: number;
[FFIType.i16]: number;
[FFIType.uint16_t]: number;
[FFIType.u16]: number;
[FFIType.int32_t]: number;
[FFIType.i32]: number;
[FFIType.int]: number;
[FFIType.uint32_t]: number;
[FFIType.u32]: number;
[FFIType.int64_t]: bigint;
[FFIType.i64]: bigint;
[FFIType.uint64_t]: bigint;
[FFIType.u64]: bigint;
[FFIType.double]: number;
[FFIType.f64]: number;
[FFIType.float]: number;
[FFIType.f32]: number;
[FFIType.bool]: boolean;
[FFIType.ptr]: Pointer | null;
[FFIType.pointer]: Pointer | null;
[FFIType.void]: undefined;
[FFIType.cstring]: CString;
[FFIType.i64_fast]: number | bigint;
[FFIType.u64_fast]: number | bigint;
[FFIType.function]: Pointer | null;
[FFIType.napi_env]: unknown;
[FFIType.napi_value]: unknown;
[FFIType.buffer]: NodeJS.TypedArray | DataView;
}
/**
* String names to FFI type mapping
*/
interface FFITypeStringToType {
["char"]: FFIType.char;
["int8_t"]: FFIType.int8_t;
["i8"]: FFIType.i8;
["uint8_t"]: FFIType.uint8_t;
["u8"]: FFIType.u8;
["int16_t"]: FFIType.int16_t;
["i16"]: FFIType.i16;
["uint16_t"]: FFIType.uint16_t;
["u16"]: FFIType.u16;
["int32_t"]: FFIType.int32_t;
["i32"]: FFIType.i32;
["int"]: FFIType.int;
["uint32_t"]: FFIType.uint32_t;
["u32"]: FFIType.u32;
["int64_t"]: FFIType.int64_t;
["i64"]: FFIType.i64;
["uint64_t"]: FFIType.uint64_t;
["u64"]: FFIType.u64;
["double"]: FFIType.double;
["f64"]: FFIType.f64;
["float"]: FFIType.float;
["f32"]: FFIType.f32;
["bool"]: FFIType.bool;
["ptr"]: FFIType.ptr;
["pointer"]: FFIType.pointer;
["void"]: FFIType.void;
["cstring"]: FFIType.cstring;
["function"]: FFIType.pointer;
["usize"]: FFIType.uint64_t;
["callback"]: FFIType.pointer;
["napi_env"]: FFIType.napi_env;
["napi_value"]: FFIType.napi_value;
["buffer"]: FFIType.buffer;
}
type ToFFIType<T extends FFITypeOrString> = T extends FFIType ? T : T extends string ? FFITypeStringToType[T] : never;
type ConvertFns<Fns extends Symbols> = {
[K in keyof Fns]: {
(
...args: Fns[K]["args"] extends infer A extends readonly FFITypeOrString[]
? { [L in keyof A]: FFITypeToArgsType[ToFFIType<A[L]>] }
: [unknown] extends [Fns[K]["args"]]
? []
: never
): [unknown] extends [Fns[K]["returns"]]
? undefined
: FFITypeToReturnsType[ToFFIType<NonNullable<Fns[K]["returns"]>>];
};
};Low-level pointer manipulation and memory management utilities.
/**
* Get pointer address of a typed array or buffer
* @param view - Typed array, ArrayBuffer, or DataView
* @param byteOffset - Optional byte offset
* @returns Pointer address
*/
function ptr(view: NodeJS.TypedArray | ArrayBufferLike | DataView, byteOffset?: number): Pointer;
/**
* Read a pointer as a Buffer
* @param ptr - Memory address to read
* @param byteOffset - Bytes to skip before reading
* @param byteLength - Bytes to read (0-terminated if not provided)
* @returns Buffer containing data
*/
function toBuffer(ptr: Pointer, byteOffset?: number, byteLength?: number): Buffer;
/**
* Read a pointer as an ArrayBuffer
* @param ptr - Memory address to read
* @param byteOffset - Bytes to skip before reading
* @param byteLength - Bytes to read (0-terminated if not provided)
* @returns ArrayBuffer containing data
*/
function toArrayBuffer(ptr: Pointer, byteOffset?: number, byteLength?: number): ArrayBuffer;
/**
* Low-level memory reading functions
*/
namespace read {
function u8(ptr: Pointer, byteOffset?: number): number;
function i8(ptr: Pointer, byteOffset?: number): number;
function u16(ptr: Pointer, byteOffset?: number): number;
function i16(ptr: Pointer, byteOffset?: number): number;
function u32(ptr: Pointer, byteOffset?: number): number;
function i32(ptr: Pointer, byteOffset?: number): number;
function f32(ptr: Pointer, byteOffset?: number): number;
function u64(ptr: Pointer, byteOffset?: number): bigint;
function i64(ptr: Pointer, byteOffset?: number): bigint;
function f64(ptr: Pointer, byteOffset?: number): number;
function ptr(ptr: Pointer, byteOffset?: number): number;
function intptr(ptr: Pointer, byteOffset?: number): number;
}Usage Examples:
import { ptr, CString, read } from "bun:ffi";
// Get pointer from typed array
const buffer = new ArrayBuffer(16);
const bufferPtr = ptr(buffer);
// Working with C strings from pointers
const lib = dlopen("./mylib.so", {
get_version: {
returns: "cstring"
}
});
const versionPtr = lib.symbols.get_version();
const version = new CString(versionPtr);
console.log("Version:", version.toString());
// Working with typed arrays
const uint8Array = new Uint8Array([1, 2, 3, 4, 5]);
const arrayPtr = ptr(uint8Array);
// Pass pointer to native function
const lib = dlopen("./mylib.so", {
process_bytes: {
args: ["ptr", "uint32_t"],
returns: "int32_t"
}
});
const uint8Array = new Uint8Array([1, 2, 3, 4, 5]);
const arrayPtr = ptr(uint8Array);
const result = lib.symbols.process_bytes(arrayPtr, uint8Array.length);Create JavaScript callbacks callable from native code with proper type marshalling.
/**
* C string class for working with null-terminated strings
*/
class CString extends String {
/**
* Create a C string from a pointer
* @param ptr - Pointer to C string data
* @param byteOffset - Byte offset to start reading
* @param byteLength - Number of bytes to read
*/
constructor(ptr: Pointer, byteOffset?: number, byteLength?: number);
/** Pointer to the C string */
ptr: Pointer;
/** Byte offset */
byteOffset?: number;
/** Byte length */
byteLength?: number;
/** Get the pointer as an ArrayBuffer */
get arrayBuffer(): ArrayBuffer;
}
/**
* JavaScript callback that can be called from native code
*/
class JSCallback {
/**
* Create a JavaScript callback for FFI
* @param callback - JavaScript function to call
* @param definition - C function signature
*/
constructor(callback: (...args: any[]) => any, definition: FFIFunction);
/** Pointer to the callback function (null if closed) */
readonly ptr: Pointer | null;
/** Whether callback is thread-safe */
readonly threadsafe: boolean;
/** Free the callback resources */
close(): void;
}Usage Examples:
import { dlopen, JSCallback } from "bun:ffi";
// Create a JavaScript callback
const myCallback = new JSCallback((a: number, b: number) => {
console.log(`Native code called with: ${a}, ${b}`);
return a + b;
}, {
args: ["int32_t", "int32_t"],
returns: "int32_t"
});
// Load library that accepts callbacks
const lib = dlopen("./callback_lib.so", {
register_callback: {
args: ["ptr"],
returns: "void"
},
call_callback: {
args: ["int32_t", "int32_t"],
returns: "int32_t"
}
});
// Register the callback
lib.symbols.register_callback(myCallback.ptr);
// Native code can now call our JavaScript function
const result = lib.symbols.call_callback(10, 20);
console.log("Result:", result); // 30
// Clean up callback
myCallback.close();Work with C structures using JavaScript objects and automatic layout management.
/**
* View the generated C code for FFI bindings
* @param symbols - Symbol definitions to view
* @param is_callback - Whether viewing callback code
* @returns Generated C source code
*/
function viewSource(symbols: Symbols, is_callback?: false): string[];
function viewSource(callback: FFIFunction, is_callback: true): string;
/**
* Platform-specific dynamic library file extension
*/
const suffix: string;Usage Examples:
import { dlopen, ptr, read, toBuffer } from "bun:ffi";
// Working with structures manually using pointers and read functions
const lib = dlopen("./struct_lib.so", {
get_point: {
returns: "ptr"
},
process_point: {
args: ["ptr"],
returns: "double"
}
});
// Get a pointer to a structure from native code
const pointPtr = lib.symbols.get_point();
// Read structure fields manually
const x = read.f64(pointPtr, 0); // x at offset 0
const y = read.f64(pointPtr, 8); // y at offset 8
const z = read.f64(pointPtr, 16); // z at offset 16
console.log(`Point: (${x}, ${y}, ${z})`);
// Process the point
const distance = lib.symbols.process_point(pointPtr);
console.log("Distance:", distance);Advanced memory management utilities for working with native code.
Usage Examples:
import { ptr, read, toBuffer, toArrayBuffer } from "bun:ffi";
// Working with typed arrays (automatic memory management)
const array = new Int32Array(10);
// Initialize array with values
for (let i = 0; i < 10; i++) {
array[i] = i * i;
}
// Get pointer for passing to native functions
const arrayPtr = ptr(array);
// Read values using FFI read functions
for (let i = 0; i < 10; i++) {
const value = read.i32(arrayPtr, i * 4);
console.log(`array[${i}] = ${value}`);
}
// Convert pointer back to different views
const buffer = toBuffer(arrayPtr, 0, array.byteLength);
const arrayBuffer = toArrayBuffer(arrayPtr, 0, array.byteLength);
console.log("Buffer length:", buffer.length);
console.log("ArrayBuffer length:", arrayBuffer.byteLength);/** FFI function callable symbol marker */
const FFIFunctionCallableSymbol: unique symbol;
/** Native module handle (platform-specific) */
interface NativeLibrary {
handle: number;
symbols: Record<string, any>;
}
/** FFI compilation and linking options */
interface CompilerOptions {
source: string | import("bun").BunFile | URL;
library?: string[] | string;
include?: string[] | string;
define?: Record<string, string>;
flags?: string | string[];
}