--- title: "Advanced FFI Types" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Advanced FFI Types} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) tcc_bind <- Rtinycc::tcc_bind tcc_callback <- Rtinycc::tcc_callback tcc_callback_async_drain <- Rtinycc::tcc_callback_async_drain tcc_callback_close <- Rtinycc::tcc_callback_close tcc_callback_ptr <- Rtinycc::tcc_callback_ptr tcc_compile <- Rtinycc::tcc_compile tcc_ffi <- Rtinycc::tcc_ffi tcc_global <- Rtinycc::tcc_global tcc_source <- Rtinycc::tcc_source tcc_struct <- Rtinycc::tcc_struct ``` This article covers the more structured parts of the FFI surface: array-return specifications, callbacks, globals, and generated struct helpers. ## Array Returns Array returns use a structured return specification rather than a bare type string. The implemented form is: - `type`: one of the supported array types - `length_arg`: which argument provides the returned length - `free`: whether the wrapper should `free()` the returned C buffer after copying ```{r} ffi <- tcc_ffi() |> tcc_source( " #include int* dup_array(int* x, int n) { if (n <= 0) return NULL; int* out = (int*)malloc(sizeof(int) * n); for (int i = 0; i < n; ++i) out[i] = x[i] * 2; return out; } " ) |> tcc_bind( dup_array = list( args = list("integer_array", "i32"), returns = list(type = "integer_array", length_arg = 2, free = TRUE) ) ) |> tcc_compile() ffi$dup_array(as.integer(c(1, 2, 3)), 3L) ``` The wrapper copies the returned buffer into a fresh R vector. It does not hand that C buffer to R by reference. ## Callbacks Callback arguments use `callback:` or `callback_async:`. ```{r} cb <- tcc_callback(function(x) x * 3, "double (*)(double)") cb_ptr <- tcc_callback_ptr(cb) ffi_cb <- tcc_ffi() |> tcc_source( " double apply_cb(double (*cb)(void* ctx, double), void* ctx, double x) { return cb(ctx, x); } " ) |> tcc_bind( apply_cb = list( args = list("callback:double(double)", "ptr", "f64"), returns = "f64" ) ) |> tcc_compile() ffi_cb$apply_cb(cb, cb_ptr, 5) tcc_callback_close(cb) ``` The callback object owns the registered R function. The callback pointer is the user-data token passed into the generated trampoline. For `callback_async:` specifically: - the registry currently supports up to 256 live callbacks at once - `i64`, `u32`, and `u64` async arguments and returns travel through R numeric (`double`), so only exact integer values up to `2^53` round-trip exactly - async callbacks run when the main thread services R's event loop, or when you call `tcc_callback_async_drain()` explicitly in tight loops and tests ## Globals `tcc_global()` generates getter and setter wrappers for C globals. ```{r} ffi_global <- tcc_ffi() |> tcc_source( " int global_counter = 7; " ) |> tcc_global("global_counter", "i32") |> tcc_compile() ffi_global$global_global_counter_get() ffi_global$global_global_counter_set(9L) ffi_global$global_global_counter_get() ``` Globals are limited to scalar FFI types. Array globals are rejected by the API. ## Struct Helpers For C structs, `Rtinycc` can generate allocation, getter, setter, and free helpers. ```{r} ffi_struct <- tcc_ffi() |> tcc_source( " struct point { double x; double y; }; " ) |> tcc_struct("point", accessors = c(x = "f64", y = "f64")) |> tcc_compile() pt <- ffi_struct$struct_point_new() pt <- ffi_struct$struct_point_set_x(pt, 1.5) pt <- ffi_struct$struct_point_set_y(pt, 2.5) ffi_struct$struct_point_get_x(pt) ffi_struct$struct_point_free(pt) ``` These helpers are separate from `tcc_bind()` because they expose storage and layout rather than just call signatures.