Ivan FB 918dd72390
feat(ffi): native event delivery + dual-ABI event symbol naming
Events now mirror the native/CBOR split already in place for requests, with the
same symbol-naming convention:

- `<lib>_add_event_listener`      -> NATIVE listener (typed `<T>Pod` by pointer)
- `<lib>_add_event_listener_cbor` -> CBOR listener (EventEnvelope bytes)

Framework: `FFIEventListener` gains a `native` flag; `addEventListener` a
`native` param; a new `dispatchFFIEventDual` template builds the `<T>Pod` once
for native listeners (`nimToPod`/`freePod`) and the CBOR envelope once for the
rest, fanning each out — so a single `{.ffiEvent.}` dispatch serves both kinds.
`declareLibrary` exports both registration entry points.

Generators: the bare `<lib>_add_event_listener` is the native symbol; every
CBOR consumer (C/C++/Go/Rust) now targets `<lib>_add_event_listener_cbor`. The
rename and the generator updates ship together so the bare name is never briefly
broken. Bindings regenerated.

Validated: native-event unit test (typed POD to native + CBOR to cbor listener,
orc/refc/ASAN); full unit suite; C++ e2e 19/19; Go example; existing event
tests unchanged. The per-event *typed* native callback + wildcard router (the
ergonomic consumer surface) is a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:37:27 +02:00
..

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 <name> symbols and the CBOR <name>_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 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 <name>(ctx, cb, ud, <args…>). Results arrive on the callback. Best for same-process callers — no serialization.
my_timer_cbor.h CBOR ABI (<name>_cbor): request/response as CBOR bytes. Use this when the call crosses a process or machine boundary. See ../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

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 <Type>* 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.