@@ -7,6 +7,14 @@ worker thread, a request channel, CBOR (de)serialization, an event queue and a
77context/handle registry — and generates the foreign-language bindings for you.
88No hand-written ` .h ` files, no manual request enums, no shared-memory plumbing.
99
10+ ## Install
11+
12+ Add nim-ffi to your library's ` .nimble ` , then ` import ffi ` :
13+
14+ ``` nim
15+ requires "https://github.com/logos-messaging/nim-ffi >= 0.2.0"
16+ ```
17+
1018## Mental model
1119
1220- You ** declare a library** once with ` declareLibrary(name, LibType) ` .
@@ -83,6 +91,27 @@ Every `{.ffi.}` / `{.ffiCtor.}` proc must have an explicit
8391` return ok(...) ` without awaiting). The ` Result ` 's error string is delivered to
8492the foreign caller as the failure message.
8593
94+ ### Request timeouts
95+
96+ Every handler runs under a deadline. The default is ` DefaultRequestTimeout `
97+ (5s, ` ffi/ffi_context.nim ` ), applied to every proc so a wedged handler can't
98+ hang a foreign caller forever. On trip the caller is unblocked with an `ffi
99+ request timed out after <n >ms` error; the handler is ** not** cancelled — a
100+ hard cancel mid-call into the underlying library can leave it half-applied — so
101+ it keeps running, and the caller's callback still fires exactly once.
102+
103+ Raise or lower the deadline per proc with a ` "timeout = <ms>" ` spec, parsed
104+ like the ` abi = ... ` spec below:
105+
106+ ``` nim
107+ proc slowOp*(
108+ c: Counter, req: BumpRequest
109+ ): Future[Result[BumpResponse, string]] {.ffi: "timeout = 30000".} =
110+ ...
111+ ```
112+
113+ The timeout is runtime-only; binding codegen ignores it.
114+
86115### Events
87116
88117An event is a proc with an empty body annotated ` {.ffiEvent.} ` . You fire it by
@@ -111,6 +140,24 @@ The default wire format is `cbor`. Override the library default with
111140` declareLibrary("lib", Lib, defaultABIFormat = "c") ` , or per annotation with an
112141` "abi = ..." ` spec, e.g. ` {.ffi: "abi = c".} ` .
113142
143+ ` cbor ` is the fully-supported format: every proc, ctor, dtor and event
144+ serializes through the generic CBOR path, and all four binding backends emit
145+ working callers for it.
146+
147+ ` abi = c ` (flat C-struct wire, generated by ` -d:targetLang=c_abi ` ) is newer and
148+ carries two honest limits today:
149+
150+ - ** Events are CBOR-only.** Applying ` abi = c ` to an ` {.ffiEvent.} ` proc is a
151+ hard compile error; declare events with ` abi = cbor ` (they ride CBOR
152+ internally regardless of the library default).
153+ - ** All-scalar ` abi = c ` procs are dropped from the foreign bindings.** A
154+ ` {.ffi: "abi = c".} ` method whose every param and return is a plain scalar
155+ takes the CBOR-free scalar fast path at runtime, but the foreign codegen for
156+ that inline-args shape is a follow-up — such procs are omitted from the
157+ generated ` .h ` . Give a proc at least one non-scalar (struct / ` seq ` /
158+ ` Option ` ) param or return, or use ` abi = cbor ` , if you need it in the
159+ bindings.
160+
114161## Placement of ` genBindings() `
115162
116163` genBindings() ` reads the compile-time registries that the pragmas populate as
@@ -122,20 +169,50 @@ once at the bottom of the top-level root file.
122169An annotation that expands * after* ` genBindings() ` is now a ** compile error**
123170(previously it was silently dropped from the bindings).
124171
125- ## Generating bindings
172+ ## Building — the two-compile model
173+
174+ A nim-ffi library ships from ** two separate compiles of the same source** ,
175+ because binding emission is gated behind ` -d:ffiGenBindings ` : without that
176+ define ` genBindings() ` is a no-op, so the normal build just produces the shared
177+ library and nothing else.
126178
127- Binding emission is gated behind ` -d:ffiGenBindings ` ; without it, ` genBindings() `
128- is a no-op and the library just builds normally.
179+ ** 1. Build the shared library** (the artifact your host loads):
129180
130181``` sh
131- nim c -d:ffiGenBindings -d:targetLang=c \
132- -d:ffiOutputDir=path/to/output -d:ffiSrcPath=../mylib.nim mylib.nim
182+ nim c --app:lib --noMain --nimMainPrefix:libmylib mylib.nim
133183```
134184
135- - ` -d:targetLang ` — ` rust ` (default), ` cpp ` , ` c ` , or ` cddl ` .
185+ ** 2. Emit the foreign bindings** — same flags, plus the binding defines. This
186+ compile runs the generators as a compile-time side effect and produces no
187+ runnable output, so send the binary to ` /dev/null ` :
188+
189+ ``` sh
190+ nim c --app:lib --noMain --nimMainPrefix:libmylib \
191+ -d:ffiGenBindings -d:targetLang=c \
192+ -d:ffiOutputDir=path/to/output -d:ffiSrcPath=../mylib.nim \
193+ -o:/dev/null mylib.nim
194+ ```
195+
196+ - ` -d:targetLang ` — ` rust ` (default), ` cpp ` , ` c ` , ` c_abi ` , or ` cddl ` .
136197- ` -d:ffiOutputDir ` — where the generated files land.
137198- ` -d:ffiSrcPath ` — the Nim source path embedded in the generated build files.
138199
200+ ### The ` --nimMainPrefix:lib<name> ` rule
201+
202+ ` --app:lib ` builds a shared library; ` --noMain ` hands program entry to the
203+ foreign host rather than Nim's own ` main ` . To initialize the Nim runtime,
204+ ` declareLibrary("<name>", …) ` emits an ` initializeLibrary() ` export that calls
205+ ` lib<name>NimMain() ` — the symbol Nim's ` NimMain ` is renamed to by
206+ ` --nimMainPrefix ` . So the prefix ** must be exactly ` lib ` + the ` declareLibrary `
207+ name** , on * both* compiles above, or the library fails to link. For
208+ ` declareLibrary("my_timer", …) ` that is ` --nimMainPrefix:libmy_timer ` .
209+
210+ ** Library-naming collisions.** When several nim-ffi libraries are loaded into
211+ one process (as the C++ end-to-end test does with ` timer ` + ` echo ` ), each must
212+ use a ** distinct** library name — and therefore a distinct ` --nimMainPrefix ` —
213+ so their exported ` NimMain ` , ` initializeLibrary ` and per-symbol names don't
214+ clash. The example libraries deliberately differ: ` libmy_timer ` vs ` libecho ` .
215+
139216## Examples
140217
141218- ` examples/timer ` — a self-contained Nimble project covering the ctor, sync
0 commit comments