Native interoperability
Using one FFI declaration with Deno and C++
Commit 3fd7d226 added VexaScript's foreign-function interface on July 22,
2026. The same source declaration can generate Deno FFI code or native C++
calls.
Declaring a native library
@FFILibrary lists possible library names for each operating system. The
runtime tries them in order.
@FFILibrary(
"SDL2.dll",
"libSDL2.so",
"libSDL2-2.0.so.0",
"/Library/Frameworks/SDL2.framework/SDL2",
)
class SDL2 {
@FFIName("SDL_Init")
static Init(flags: int): int
static SDL_Quit(): void
}
@FFIName maps a VexaScript method to a different C symbol. Methods without
the annotation use their own name.
| Operation | Deno | Native C++ |
|---|---|---|
| Open library | Deno.dlopen |
Platform dynamic loader |
| Find symbol | Generated Deno descriptor | Cached typed lookup |
| Call function | Deno FFI function | C++ function pointer |
| Nonblocking call | Deno nonblocking symbol | Worker task and event-loop callback |
Application code calls the same typed class on both targets.
Supported values
A C API needs exact value sizes and ownership rules. It cannot rely on the
broad TypeScript number type.
| VexaScript value | Native value |
|---|---|
int, long, or sized integer |
Integer with a defined width and sign |
number |
Floating-point value |
boolean |
ABI boolean value |
string |
Temporary UTF-8 pointer |
ArrayBuffer |
Pointer to its bytes |
FFIPointer |
Opaque native address |
@FFIStruct object |
Pointer to fixed-layout storage |
void |
No return value |
Numeric, boolean, pointer, and void results are supported. String results and arbitrary structs returned by value still need more ABI rules.
Struct layout
@FFIStruct, @FFISize, and @FFIOffset define the exact layout of a C
structure.
@FFIStruct
@FFISize(16)
class SDLRect(
@FFIOffset(0) x: int = 0,
@FFIOffset(4) y: int = 0,
@FFIOffset(8) width: int = 0,
@FFIOffset(12) height: int = 0,
)
Each instance uses an ArrayBuffer. Deno can pass the bytes directly, and C++
can use the same layout. Offsets may overlap for C unions such as SDL_Event.
The native DataView uses std::memcpy for fixed-width reads and writes. This
works with unaligned buffers and avoids C++ aliasing problems. Optimizing C++
compilers turn these fixed-size copies into direct loads and stores.
Asynchronous native calls
A foreign method that returns Promise<T> is treated as nonblocking. Deno marks
the symbol as nonblocking. Native C++ runs the function on a worker and resumes
the Promise on the main event loop.
Arguments sent to the worker must remain valid for the whole call. The native runtime copies plain data and does not leave untracked pointers to managed objects on the worker thread.
SDL2 sample and C++ helpers
The SDL2 sample opens a window, reads an overlaid event struct, passes buffers, moves a rectangle from keyboard input, waits asynchronously, and releases its resources. It ran for 600 frames under Deno and native C++ with SDL's dummy video driver.
Target-specific bindings can also use @CppHeader, @CppFlags, and
@CppBody. These annotations are separate from the portable FFI API so a C++
helper is not presented as if it also worked in Deno.
The current design keeps library names, symbol names, value layouts, and ownership rules in source code. It does not hide differences that matter for memory safety or platform compatibility.