Foreign function interface (FFI)
Quadrate can call C functions directly through its Foreign Function Interface. This allows you to:
- Use existing C libraries
- Write performance-critical code in C
- Interface with system APIs
- Create custom native modules
Basic example
Let's create a simple C function that Quadrate can call.
Step 1: write the C code
Create greet.c:
#include <qdrt/ffi.h>
#include <stdio.h>
qd_exec_result hello(qd_context* ctx) {
// Pop a string from the stack
qd_stack_element_t elem;
qd_stack_pop(ctx->st, &elem);
// Print the greeting
printf("Hello, %s!\n", qd_string_data(elem.value.s));
// Release the string (required for memory management)
qd_string_release(elem.value.s);
return (qd_exec_result){0}; // 0 = success
}
Step 2: compile to a static library
cc -c greet.c -o greet.o -I/usr/include
ar rcs libgreet.a greet.o
Step 3: import in Quadrate
Create main.qd:
import "libgreet.a" as "greet" {
pub fn hello(name:str -- )
}
fn main() {
"Seb" greet::hello
}
Step 4: run
quad run main.qd
Output:
Hello, Seb!
Function signature
All FFI functions must have this signature:
qd_exec_result function_name(qd_context* ctx)
The function name in C must match the name declared in the Quadrate import block.
Required header
#include <qdrt/ffi.h> // All FFI types and functions
Stack operations
Popping values
qd_stack_element_t elem;
qd_stack_error err = qd_stack_pop(ctx->st, &elem);
if (err != QD_STACK_OK) {
// Handle stack underflow
return (qd_exec_result){1};
}
// Check the type
switch (elem.type) {
case QD_STACK_TYPE_INT:
int64_t i = elem.value.i;
break;
case QD_STACK_TYPE_FLOAT:
double f = elem.value.f;
break;
case QD_STACK_TYPE_STR:
const char* s = qd_string_data(elem.value.s);
// Don't forget to release!
qd_string_release(elem.value.s);
break;
case QD_STACK_TYPE_PTR:
void* p = elem.value.p;
break;
}
Pushing values
// Push an integer
qd_stack_push_int(ctx->st, 42);
// Push a float
qd_stack_push_float(ctx->st, 3.14);
// Push a string (copies the string)
qd_stack_push_str(ctx->st, "hello");
// Push a pointer
qd_stack_push_ptr(ctx->st, some_pointer);
Type mapping
| Quadrate | C Type | Stack Field | Type Constant |
|---|---|---|---|
i64 |
int64_t |
elem.value.i |
QD_STACK_TYPE_INT |
f64 |
double |
elem.value.f |
QD_STACK_TYPE_FLOAT |
str |
qd_string* |
elem.value.s |
QD_STACK_TYPE_STR |
ptr |
void* |
elem.value.p |
QD_STACK_TYPE_PTR |
Complete example: math operations
Here's a more complete example with multiple functions and return values.
math_ext.c
#include <qdrt/ffi.h>
#include <math.h>
// Calculate hypotenuse: ( a:f64 b:f64 -- c:f64 )
qd_exec_result hypot(qd_context* ctx) {
qd_stack_element_t b, a;
qd_stack_pop(ctx->st, &b); // Pop b (top)
qd_stack_pop(ctx->st, &a); // Pop a (below b)
double result = sqrt(a.value.f * a.value.f + b.value.f * b.value.f);
qd_stack_push_float(ctx->st, result);
return (qd_exec_result){0};
}
// Factorial: ( n:i64 -- result:i64 )
qd_exec_result factorial(qd_context* ctx) {
qd_stack_element_t elem;
qd_stack_pop(ctx->st, &elem);
int64_t n = elem.value.i;
int64_t result = 1;
for (int64_t i = 2; i <= n; i++) {
result *= i;
}
qd_stack_push_int(ctx->st, result);
return (qd_exec_result){0};
}
main.qd
import "libmath_ext.a" as "mathx" {
pub fn hypot(a:f64 b:f64 -- c:f64)
pub fn factorial(n:i64 -- result:i64)
}
use fmt
fn main() {
// Calculate hypotenuse of 3-4-5 triangle
3.0 4.0 mathx::hypot "%f\n" fmt::printf // 5.0
// Calculate 10!
10 mathx::factorial "%d\n" fmt::printf // 3628800
}
Error handling
Return a non-zero error code to indicate failure:
qd_exec_result my_function(qd_context* ctx) {
qd_stack_element_t elem;
qd_stack_error err = qd_stack_pop(ctx->st, &elem);
if (err != QD_STACK_OK) {
fprintf(stderr, "my_function: stack underflow\n");
return (qd_exec_result){1}; // Error code 1
}
if (elem.type != QD_STACK_TYPE_INT) {
fprintf(stderr, "my_function: expected integer\n");
return (qd_exec_result){2}; // Error code 2
}
// ... do work ...
return (qd_exec_result){0}; // Success
}
In Quadrate, use the ! suffix for failable functions:
import "libmylib.a" as "mylib" {
pub fn my_function(x:i64 -- result:i64)!
}
fn main() {
42 mylib::my_function! -> result
}
Memory management
Important: Always release strings after use:
qd_stack_element_t elem;
qd_stack_pop(ctx->st, &elem);
if (elem.type == QD_STACK_TYPE_STR) {
const char* str = qd_string_data(elem.value.s);
// ... use the string ...
qd_string_release(elem.value.s); // Required!
}
Failure to release strings will cause memory leaks.
Build integration
For larger projects, add FFI libraries to your build system:
Makefile
CFLAGS = -I$(QUADRATE_PREFIX)/include
LDFLAGS = -L$(QUADRATE_PREFIX)/lib
libmylib.a: mylib.o
ar rcs $@ $^
mylib.o: mylib.c
$(CC) $(CFLAGS) -c $< -o $@
Meson
mylib = static_library('mylib', 'mylib.c',
include_directories: include_directories('/usr/include'))
Tips
- Check stack size before popping to avoid crashes
- Validate types - don't assume the stack contains what you expect
- Release strings - memory leaks are easy to introduce
- Use descriptive error messages - they help debugging
- Keep functions small - easier to test and maintain
What's next?
Continue to File Processing Examples to see complete programs using these concepts.