# C bindings — native (same-process) example Generated C headers for the timer library plus a small driver that links the library directly and calls the **native** (zero-serialization) ABI. > **Which ABI?** The library exports **both** ABIs from the same shared object, > side by side: the native `` symbols and the CBOR `_cbor` symbols. > Use the **native (pure-C) ABI for same-process / local calls** — it passes > flat C structs with zero serialization. Use the **CBOR ABI only for > inter-process communication** (a different process, or a different machine), > where the data has to be serialized to cross the boundary anyway. In one > address space, CBOR is pure overhead — prefer native. See [`../ipc`](../ipc) > for the CBOR/IPC path. ## Files | File | Description | |------|-------------| | `my_timer.h` | Native ABI: each `{.ffi.}` type is a plain C `struct`, passed by value to `int (ctx, cb, ud, )`. Results arrive on the callback. Best for same-process callers — no serialization. | | `my_timer_cbor.h` | CBOR ABI (`_cbor`): request/response as CBOR bytes. Use this when the call crosses a process or machine boundary. See [`../ipc`](../ipc). | | `example.c` | Native same-process driver: create → version → echo → complex → destroy. | | `Makefile` | Builds the Nim dylib (from the repo root) and the driver. | The headers are regenerated by `nimble genbindings_c` (run from the repo root) and overwritten each time — don't edit them by hand. ## Build & run ```sh cd examples/timer/c_bindings make run ``` This compiles `libmy_timer.{dylib,so}` and runs `./example`, which prints the library version and the round-tripped echo/complex responses. Every call is dispatched on the library's FFI thread, so the driver blocks on a condvar-backed callback for each result. ## Native vs CBOR The native path passes `{.ffi.}` structs as flat C-POD values (`const char*` for strings, `{ T* ptr; size_t len }` for sequences, `{ int present; T }` for options). Arguments are **deep-copied** across the FFI-thread boundary, so the C caller's buffers can be freed immediately after the call returns. String returns arrive as raw bytes; **struct returns arrive as a typed `const *`** in the callback (cast and read it there — it is valid only for the callback's lifetime, and the library deep-frees it afterwards, so copy out anything you need). For the cross-process / cross-machine path, the same library is reached over a socket using the CBOR ABI — see [`../ipc`](../ipc).