Skip to main content

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.
  • .c source file: compiled first into a shared library, with output written to the ffibuild/ 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 TypeC Type
intint
int8 ~ int64corresponding C integer type
uint8 ~ uint64corresponding C unsigned integer type
floatfloat
doubledouble
Numberdouble
str / Stringstr
bool / Booleanbool
gbooleanint (0/1, a Boolean on the Alia side)
gint / guint / gsize / gssize etc.corresponding integer types
voidvoid
ptr / pointer / gpointer / Buffer / Value / unrecognized typesvoid *
<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 / protothe 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

Next Steps​