Skip to content
FutureBlitz

    Install

    Guide

    Using a C library

    Declaring a C library's functions, linking it from project.toml, its types and callbacks, and fb bindgen.

    A C library’s functions are declared in an Extern block and called like your own Functions. There’s no wrapper to compile: fb links the library, and fb bindgen writes the block from its header.

    1. Declare its functions

    Extern "C" Lib "name" names one of the package’s [native] libraries; without Lib, the symbols come from the C runtime and the system libraries every program links, as these do:

    Import std.core
    
    Extern "C"
        Function abs(n: Int32): Int32
        Function strlen(text: CString): UInt64
        Function frexp(x: Float64, ByRef exponent: Int32): Float64
        Function CAtoi(text: CString): Int32 = "atoi"
    End Extern
    
    Print(abs(-42), strlen("FutureBlitz"))
    
    Local exponent: Int32 = 0
    Local mantissa = frexp(48.0, ByRef exponent)
    Print("48 is {mantissa} * 2^{exponent}")
    Print(CAtoi("1234") + 1)
    Output
    42 11
    48 is 0.75 * 2^6
    1235
    • = "symbol" calls a C function by another name.
    • A String passed for a CString parameter is copied with a NUL after it for the call alone.
    • A ByRef parameter passes the variable’s address, so out-parameters need no Unsafe code.
    • An Extern Function is called from safe code; one whose misuse could break memory whatever it’s given is declared Unsafe Function, and only Unsafe blocks call it.

    Any other library is named in project.toml’s [native], and its Extern block says Lib and that name. Here it’s C’s math library, which every system has:

    project.toml
    [project]
    name = "roots"
    version = "0.1.0"
    
    [native]
    m = { system = "m" }
    main.fb
    Import std.core
    
    Extern "C" Lib "m"
        Function cbrt(x: Float64): Float64
        Function hypot(x: Float64, y: Float64): Float64
    End Extern
    
    Print(cbrt(27.0))
    Print(hypot(3.0, 4.0))
    Output
    3
    5

    A library comes in one of three forms:

    • static, as sqlite3 = { static = "libs/sqlite3" }, links an archive into the executable, so the program is still one file: libs/libsqlite3.a, libs/sqlite3.a, or libs/sqlite3.lib.
    • system, as m = { system = "m" }, is a library the system has, found by its name.
    • dynamic, as codec = { dynamic = "native/{target}/codec" }, is a shared library that fb copies beside the executable, one for each target.

    project.toml has every form.

    3. Match C’s types

    FutureBlitzC
    Int8 to Int64, UInt8 to UInt64int8_t to uint64_t
    CLong, CULong, CWCharlong, unsigned long, wchar_t, the target’s sizes
    Float32, Float64float, double
    Boolbool
    Ptr<T>, Ptr<Void>T*, void*
    CStringconst char*
    Callback(A, B): RR (*)(A, B)
    @CLayout Structa struct, by value or through a Ptr

    A @CLayout Struct has its Fields where C puts them, so it passes by value as the target’s C ABI does, as clang would.

    Import std.core
    
    @CLayout Struct DivResult
        Field quot: Int32
        Field rem: Int32
    End Struct
    
    Extern "C"
        Function div(numerator: Int32, denominator: Int32): DivResult
    End Extern
    
    Local r = div(17, 5)
    Print("17 / 5 is {r.quot} remainder {r.rem}")
    Output
    17 / 5 is 3 remainder 2

    4. Pass callbacks

    A Callback parameter takes a top-level Function whose types match, which C can call back.

    Import std.core
    
    Extern "C"
        Function qsort(items: Ptr<Void>, count: UInt64, size: UInt64,
                       compare: Callback(Ptr<Void>, Ptr<Void>): Int32)
    End Extern
    
    Function Ascending(a: Ptr<Void>, b: Ptr<Void>): Int32
        Unsafe
            Local x = (a As Ptr<Int32>).Value
            Local y = (b As Ptr<Int32>).Value
            Return x < y ? -1 : (x > y ? 1 : 0)
        End Unsafe
    End Function
    
    Local numbers: Int32[] = [42, 7, 19, 3]
    Unsafe
        qsort(AddressOf(numbers[0]), 4, 4, Ascending)
    End Unsafe
    Print(numbers)
    Output
    [3, 7, 19, 42]

    Pointers are read and written only in Unsafe code, and the rest of the program stays checked.

    5. Let fb bindgen write it

    For a library of many functions, fb bindgen reads its header and writes the module: a @CLayout Struct for each struct, an Enum or Consts for each enum, a Const for each macro that’s a number, and one Public Extern "C" Lib block of its functions. What can’t map, like a union or varargs, is a // skipped: comment that says why.

    fb bindgen libs/sqlite3.h --lib sqlite3 -o sqlite3.fb

    Import "./sqlite3" then uses it. Read through what it wrote: a function whose misuse could break memory whatever it’s given, like one that writes through a pointer, is better declared Unsafe Function, so only Unsafe code calls it.

    The language reference’s C interop chapter has every rule, with std.ffi’s CData and Buffer for memory C owns.

    Report an issue with this page or with FutureBlitz on GitHub.