ffi Foreign Function Interface
ffi is Alia's standard library foreign function interface (FFI) module, allowing Alia to interoperate with other programming languages and to call external function libraries and system APIs.
Importing the Module
import 'ffi' as ffi ~ import as a variable
import 'ffi' ~ import into scope
Properties
available
Checks whether FFI is available (koffi is installed), returning a Boolean.
if ffi.available {
print 'FFI available'
}
Methods
importAlias
Binds a function from an external language as an Alia syntax keyword. Two forms are supported:
Import from a specified file:
ffi.importAlias from './add.c' named 'C' with signature 'add <int a> <int b> -> int'
Import from a system library:
ffi.importAlias named 'C' with signature 'printf <String format> ... -> int'
Once imported, the function can be called directly as a syntax keyword:
print add 1 1 ~ prints 2
Variadic parameters: a signature ending with ... declares a variadic function; when calling, each variadic argument after the fixed parameters is given as a "type + value" pair:
ffi.importAlias named 'C' with signature 'printf <String format> ... -> int'
let n = printf 'Value: %d %s' 'int' 42 'str' 'hello'
Typed pointers: the <type>* suffix, as well as the <*const T> and <*mut T> prefixes, all map to pointers of the corresponding element type (int*/*const int → pointer to int), while char*/String* still map to strings:
ffi.importAlias from './mylib.c' named 'C' with signature 'sum_array <int* arr> <int n> -> int'
ffi.importAlias from './mylib.c' named 'C' with signature 'sum_array <*const int arr> <int n> -> int'
extern
Registers external declarations (functions or statics) in batch. The signature takes the form of a function declaration (optionally with a static prefix):
~ Function declaration
ffi.extern in 'C' from './mylib.c' with signature 'make_point <int x> <int y> -> Point'
~ Static declaration
ffi.extern in 'C' from './mylib.c' with signature 'static GLOBAL_COUNTER <int>'
static NAME <type> locates the symbol via lib.symbol(name) and decodes it as the declared type when called; the call returns that value.
struct
Defines a struct type (repr(C)) and returns the type name. Fields map as field name → type:
ffi.struct named 'Point' with *[x: 'int', y: 'int']
ffi.extern in 'C' from './mylib.c' with signature 'point_area <*const Point p> -> int'
Field types support fixed-length arrays [T; N] (mapped to koffi arrays):
ffi.struct named 'Vec4' with *[v: '[double; 4]']
union
Defines a union type (repr(C)), used the same way as struct:
ffi.union named 'Value' with *[i: 'int', f: 'float', d: 'double']
enum
Defines an enum type, with members mapping as member name → numeric value:
ffi.enum named 'Color' with *[RED: 0, GREEN: 1, BLUE: 2]
ffi.extern in 'C' from './mylib.c' with signature 'color_name <Color c> -> String'
proto
Defines a named callback prototype type; the signature omits the function name:
ffi.proto named 'IntBin' with signature 'int <int a> <int b> -> int'
exportAlias
Exports an Alia Alias object as a callback handle callable from external languages (in-process export). A function signature must be provided;
the returned handle can be passed as a pointer argument to registered external functions (importAlias/extern):
alias addNumbers <int a> <int b> -> int { a + b }
let cb = ffi.exportAlias in 'C' named addNumbers with signature 'int <int a> <int b> -> int'
open
Loads a shared library and returns a library handle. When the path is omitted or null, the system symbol table is opened.
const lib = ffi.open './mylib.so'
alloc
Allocates memory by type (ffi.alloc T by N allocates N elements, N defaults to 1), returning a pointer (which can serve as a pointer argument to C functions, or be read/written by ffi.load/ffi.store).
let p = ffi.alloc 'int' by 3 ~ 3 ints
let p1 = ffi.alloc 'int' ~ 1 int
free
Frees memory allocated by ffi.alloc.
ffi.free p
load
Reads the value pointed to by a pointer (ffi.load P as T at I; when at I is omitted, element 0 is read).
let v = ffi.load p as 'int'
let v3 = ffi.load p as 'int' at 2
store
Writes a value to the location pointed to by a pointer (ffi.store P as T to V at I; when at I is omitted, element 0 is written).
ffi.store p as 'int' to 10
ffi.store p as 'int' to 30 at 2
address
Gets the memory address (native address value) corresponding to a pointer value, usable for transferring pointers and with ffi.load.
let addr = ffi.address p
call
Calls a function pointer (ffi.call P with signature S using A?: P is a function pointer, such as an ffi.callback/ffi.exportAlias handle or the result of an external function returning void*; S is a signature without the function name; A is an array of arguments in order, optional).
ffi.extern in 'C' from './mylib.c' with signature 'get_adder <> -> void*'
let fp = call get_adder
let r = ffi.call fp with signature 'int <int a> <int b> -> int' using [3, 5]
invoke
Calls a registered external function.
let result = ffi.invoke 'add_int' with [3, 5]
has
Checks whether a function has been registered, returning a Boolean.
let r = ffi.has 'add_int'
build
Compiles a C source file into a shared library, returning the path of the compiled file.
const path = ffi.build './mylib.c'
defineType
Registers a custom C type. definition can be 'opaque' (opaque handle), 'ptr'/'pointer'
(a generic pointer), or any koffi type string.
ffi.defineType named 'GtkWidget' as 'opaque'
callback
Constructs a C callback from an Alia alias, returning a callback handle (which can be passed as a pointer argument to registered external functions
(importAlias/extern)). The callback signature omits the function name and only declares parameters and the return type.
alias onSum <int a> <int b> -> int { a + b }
const cb = ffi.callback with signature 'int <int a> <int b> -> int' named onSum
ffi.importAlias from './cb.c' named 'C' with signature 'invoke_cb <ptr cb> <int a> <int b> -> int'
let r = call invoke_cb [cb, 3, 5] ~ 8
Callbacks can also be constructed from a named prototype defined with ffi.proto (type specifies the prototype name):
ffi.proto named 'IntBin' with signature 'int <int a> <int b> -> int'
const cb = ffi.callback as 'IntBin' named onSum
Callback slots are held by reference by the FFI runtime (protecting them from GC) and need no manual release.
Pointer-argument callbacks: *mut void/*const void parameters in a callback signature are passed into the callback as pointer values.
Comparator-style callbacks receive the pointer through a <Value> parameter and then read its value by element type with ffi.load;
because return values inside branches require an explicit return when compiled to the JS/NodeJS target, comparator branches must use explicit returns:
alias cmpInt <Value a> <Value b> -> int {
let x = ffi.load a as 'int'
let y = ffi.load b as 'int'
if x < y { return 0 - 1 } else { if x > y { return 1 } else { return 0 } }
}
ffi.extern in 'C' from './mylib.c' with signature 'qsort <*mut void base> <size_t n> <size_t size> <*mut void cmp> -> void'
let p = ffi.alloc 'int' by 4
ffi.store p as 'int' to 3 at 0
ffi.store p as 'int' to 1 at 1
ffi.store p as 'int' to 4 at 2
ffi.store p as 'int' to 2 at 3
let cb = ffi.callback with signature 'int <*mut void a> <*mut void b> -> int' named cmpInt
call qsort [p, 4, 4, cb]
let first = ffi.load p as 'int' at 0 ~ 1
let last = ffi.load p as 'int' at 3 ~ 4
ffi.free p
pointer
Allocates a single element of the specified type, returning a Buffer that can be passed to C functions as a pointer argument.
let p = ffi.pointer 'int'
deref
Reads the value pointed to by a pointer.
let v = ffi.deref p as 'int'
Reference Resolution
- Absolute path: used as-is.
- Relative path: resolved relative to the current module directory; in single-file or code mode, relative to the execution directory.
.csource file: compiled first into a shared library, with output written to theffibuild/directory next to the source file; the compiled artifact is then loaded.- Bare library name: names without a path separator are resolved by the system dynamic linker.
- Omitted or
null: opens the system global symbol table. - Lookup fallback: when no library is explicitly specified, a library file with the same name as the function is looked up in the execution directory and its
ffibuild/subdirectory.
Type Mapping
| Alia Type | C Type |
|---|---|
int | int |
int8 ~ int64 | corresponding C integer type |
uint8 ~ uint64 | corresponding C unsigned integer type |
float | float |
double | double |
Number | double |
str / String | str |
bool / Boolean | bool |
gboolean | int (0/1, a Boolean on the Alia side) |
gint / guint / gsize / gssize etc. | corresponding integer types |
void | void |
ptr / pointer / gpointer / Buffer / Value / unrecognized types | void * |
<type>* (e.g. int*, double*) | pointer to that element type |
<*const T> / <*mut T> (e.g. *const Point) | pointer to that element type (read-only/writable) |
char* / str* / String* | str (C string) |
Custom types (defineType) | the registered koffi type |
Types defined by struct / union / enum / proto | the corresponding koffi composite type |
[T; N] (struct/union fields) | koffi.array(T, N) fixed-length array |
... (end of signature) | variadic |
Example Code
import 'ffi' as ffi
~ Import a function from a C file
ffi.importAlias from './math.c' named 'C' with signature 'add <int a> <int b> -> int'
let result = add 3 5
print result ~ prints 8
~ Import a function from a system library
ffi.importAlias named 'C' with signature 'abs <int x> -> int'
print abs (-42) ~ prints 42
~ Variadic function
ffi.importAlias named 'C' with signature 'printf <String format> ... -> int'
print printf 'Hi %s' 'str' 'Alia' ~ prints 7
~ Export an Alia function as a callback handle
alias multiply <int a> <int b> -> int { a * b }
let cb = ffi.exportAlias in 'C' named multiply with signature 'int <int a> <int b> -> int'
~ Check whether a function has been registered
let hasAdd = ffi.has 'add'
print hasAdd ~ prints true
~ Memory allocation and out parameters (divmod writes out the quotient and remainder)
ffi.extern in 'C' from './math.c' with signature 'divmod <int a> <int b> <*mut int q> <*mut int r> -> void'
let q = ffi.alloc 'int'
let r = ffi.alloc 'int'
divmod 17 5 q r
let qv = ffi.load q as 'int' ~ 3
let rv = ffi.load r as 'int' ~ 2
ffi.free q
ffi.free r