ffi native dynamic library

ffi native dynamic library

ffi native dynamic library

Function

ffi allows BT scripts to directly call C ABI functions in native dynamic libraries, such as .dll for Windows, .so for Linux and .dylib for macOS.

The default BT builds for supported targets already have FFI built in, no additional components need to be installed. The static musl bt in the official Linux package does not include FFI to maintain cross-distro compatibility; bt_app built for the GNU target in the package still contains FFI. Before use, you can use BT.has('ffi') to determine whether the current binary has FFI enabled, then prepare a dynamic library that matches the current operating system and CPU architecture, and confirm the exported name and complete prototype of the function to be called from the dynamic library document or C header file.

FFI is suitable for calling existing native capabilities in long-lived VMs from CLI scripts and desktop apps. It cannot be used in web request scripts.

Quick Start

The following Windows example loads user32.dll, declares the complete signature of MessageBoxW, and closes the dynamic library after calling the function:

The format of the complete signature is:

i32(ptr, wstr, wstr, u32) in the example corresponds to the 32-bit integer return value in the C prototype, a pointer, two Windows wide strings, and a 32-bit unsigned integer.

Syntax

The recommended usage sequence is: load the dynamic library, declare the function with a complete signature, call the function, handle the return value, close the Buffer and the dynamic library.

ffi.load

Loads the dynamic library and returns a FfiLibrary object.

Parameters

ParameterTypeRequiredDefault valueValid range or optional valueDescription
pathStringYesNoneBare library name or dynamic library file pathThe bare library name is left to the operating system to find; the path with directory supports the current source code directory, @ project root and absolute path. Cannot be empty or contain NUL.
schemaObjectNoNone0..=128 functionsDeclares the functions that are allowed to be called and their signatures. When passed in, only the exact export names listed in the schema can be called; an empty object means no functions are allowed to be called.

schema fields

FieldTypeRequiredDefault valueValid range or optional valueDescription
Exact export nameStringRequired for each function to be calledNoneNot empty, no NUL, up to 255 UTF-8 bytesField names are case-sensitive. BT does not automatically add, remove or replace the A, W suffixes.
Function declarationStringYesNoneFull signature or separate return type, up to 512 UTF-8 bytesFull signature declares up to 16 arguments. It is recommended to always use the full signature.

Supported signature types

TypeAvailable positionsBT parametersBT return valueDescription
voidReturn type onlyNoneemptyNative functions have no return value.
i8, i16, i32, i64Parameters, returnsInt, BoolIntSigned integer; parameters must be within the range of the declared width, Bool is converted to 0 or 1.
u8, u16, u32, u64Parameters, returnNon-negative Int, BoolIntUnsigned integer; an error will be reported if it exceeds the declared range. The u64 return value cannot exceed the upper limit of BT Int.
isize, usizeParameters, returnsIntIntPointer width integer; currently supported platforms are checked as 64-bit.
f32, f64Parameters, ReturnsInt, FloatFloatFloat; f32 or f64 must be explicitly selected.
ptrParameters, returnnull, FfiPointer, FfiBufferFfiPointer, nullFfiBuffer can be passed in directly as a stable address; the native null pointer returns null.
cstrParameters, ReturnsString, nullString, nullUTF-8 NUL terminated string. Immediately copy to BT String upon return, illegal UTF-8 returns null.
wstrParameters, ReturnsString, nullString, nullWindows only; UTF-16 NUL terminated string. Immediately copy to BT String upon return, illegal UTF-16 returns null.

Return Value

TypeDescription
FfiLibraryA loaded dynamic-library object that calls native functions through library.export_name(...).

Declare and call function

Complete signature

The complete signature will clarify the number of parameters, the ABI type of each parameter and the return type, which is the safest and easiest to check method of calling. MessageBoxW: 'i32(ptr, wstr, wstr, u32)' in the quick start is the complete signature; the parameterless function is written as cstr(), and the function without return value is written as void(). After calling, String and empty are obtained respectively.

The number, type, and integer range of function parameters must be consistent with the schema. Detectable type errors, out-of-bounds, internal NUL, resource closed, insufficient permissions, or non-existent symbols will all cause errors before entering the native function.

Declaring only the return type

When writing only the return type, BT fixes the return type and infers the parameters according to limited rules on the first call. The following Windows example declares the return value as ptr and uses the String parameter as wstr based on the W suffix of the function name:

Omitting schema

Omitting schema allows you to call the legal exact export name, but the return type is fixed to i32. It can only be used if the true prototype of the function exactly conforms to the following rules:

BT ParametersInferred TypeConditions
Inti32Must be in the i32 range.
nullptrAs a normal null pointer, the string type is not inferred.
FfiPointer, FfiBufferptrThe resource must still be valid.
StringwstrWindows only, and the export name ends with uppercase W.
ASCII Stringcstr pointerWindows only, and the export name ends with uppercase A.

Bool, Float, empty, Bytes, Array, Object, etc. cannot be automatically inferred and must use full signatures. After the first successful call, the number of parameters and inferred types of the function are fixed, and subsequent calls must remain consistent.

If the native return value is not i32, or the function uses parameters such as Bool, Float, 64-bit integer, string, etc., do not omit schema.

FfiPointer

When the native function is declared as ptr, the empty address returns null, and the non-empty address returns FfiPointer.

FfiPointer can only be used for null checking, comparison, or passed back to FFI as ptr parameter. BT does not allow reading, writing, conversion to integers, or pointer arithmetic. It relies on the source dynamic library or FfiBuffer; after the corresponding resource is closed, the Pointer will become invalid immediately.

If the native function returns a pointer that needs to be released, the return type should be declared as ptr, and then the release function provided by the dynamic library should be called. Do not declare the address that needs to be released as cstr or wstr.

ffi.buffer

Creates fixed-length, zero-padded, address-stable, writable memory that is at least 16-byte aligned and returns FfiBuffer. It is suitable for passing to ptr parameters that need to write data by native functions.

Parameters

ParameterTypeRequiredDefault valueValid range or optional valueDescription
sizeIntYesNone1..=BT_BYTES_LIMITThe visible byte length of the Buffer; it cannot be expanded after creation.

Return Value

TypeDescription
FfiBufferBT managed writable native memory, which can be passed directly to the parameter declared as ptr.

Method

MethodParametersRequiredDefault valueValid rangeReturn valueDescription
len()NoneNoneNoneNoneIntReturns the visible byte length of the Buffer.
ptr(offset)offset: IntNo00..=len()FfiPointerReturns the address of the specified offset; the len() position can only be passed as the end pointer.
write(data, offset)data: Bytes; offset: Intdata is yes; offset is nooffset is 0The writing range cannot exceed the boundsIntWrite Bytes into Buffer and return the number of written bytes.
to_bytes(offset, length)offset: Int; length: IntNooffset is 0; length is the remaining lengthThe read range cannot go out of boundsBytesCopy the specified range into ordinary Bytes.
to_string(offset)offset: IntNo00..len()String, nullRead NUL-terminated UTF-8; illegal UTF-8 returns null, and an error will be reported if NUL is not found.
to_wstring(offset)offset: IntNo0Windows only; offset must be 2-byte alignedString, nullRead NUL-terminated UTF-16; illegal UTF-16 returns null.

The following example has a native function write UTF-8 text:

ffi.close

Turn off FfiLibrary or FfiBuffer. Resources should be actively shut down when they are no longer in use, and resident processes especially need to be released in time.

Parameters

ParametersTypeRequiredDefault valueValid range or optional valueDescription
valueFfiLibrary, FfiBufferYesNoneThe resource returned by ffi.load() or ffi.buffer()FfiPointer cannot be closed individually.

Return Value

TypeDescription
BoolReturn true when the resource is closed successfully for the first time; return false when the resource has been closed.

After closing the dynamic library, the functions in it can no longer be called; after closing the Buffer, its methods can no longer be accessed. FfiPointers generated by these resources will also expire at the same time.

Capability and resource status

FieldTypeDescription
enabledBoolWhether the current interpreter contains FFI.
open_librariesIntThe number of currently open dynamic libraries.
buffersIntThe number of currently alive FfiBuffers.
buffer_bytesIntThe total number of bytes actually occupied by the current FfiBuffer.

The same process can open up to 32 dynamic libraries at the same time, keep 256 FfiBuffers alive, and the total buffer size can be up to 64 MiB. A single schema can declare up to 128 functions, and a single complete signature can contain up to 16 parameters; when the schema is omitted, a single dynamic library can use up to 256 different functions.

Applicable platforms

  • Windows x64
- Linux x64 GNU (x86_64-unknown-linux-gnu, without musl)
  • macOS Intel
  • macOS Apple Silicon

All platforms use the target system's default C ABI. wstr and the ability to infer strings based on the A, W suffixes are only available on Windows.

Notes

  • It is preferred to use the complete signature and check the official dynamic library documentation or C header file item by item. Wrong parameter types, return types, or calling conventions can directly lead to process crashes.
  • FFI is not a sandbox. Crash, illegal memory access and other native errors in dynamic libraries cannot be safely caught by BT; dynamic libraries that are untrusted or require timeout isolation should be called in independent sub-processes.
  • Native calls are synchronous blocking operations. Do not call time-consuming functions in execution paths that require asynchronous non-blocking execution.
- Structures, unions, variadic arguments, callbacks, and pointer arithmetic are not supported, and are not suitable for APIs requiring the UI main thread, COM apartments, native thread callbacks, or asynchronous saving of pointers.
  • String passed to cstr or wstr is only valid during this synchronous call; native functions must not save this address. FfiBuffer cannot be saved by native functions and used asynchronously after the call is completed.
- cstr and wstr return NUL-terminated strings that are only guaranteed to be readable by native documents. BT will copy the text immediately, but will not release the address for the native library.

- BOOL for Windows x64 is typically declared as i32, handles as ptr, and strings for the W API as wstr.

  • ffi is controlled by the ffi permissions in BT_PERMISSION_ALLOW and BT_PERMISSION_DENY; ffi.close() is always allowed to execute, ensuring that existing resources can still be released after the permissions are tightened.
  • When distributing BT executable files containing FFI by yourself, you should fulfill the corresponding obligations according to the licenses you use; the BT build process will not generate additional license bypass files.
- The complete Windows example is at examples/ffi-user32/.