Skip to content

Commit a6bc9c0

Browse files
committed
fix(docs): complete story
1 parent 3e57751 commit a6bc9c0

1 file changed

Lines changed: 83 additions & 6 deletions

File tree

README.md

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ worker thread, a request channel, CBOR (de)serialization, an event queue and a
77
context/handle registry — and generates the foreign-language bindings for you.
88
No 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
8492
the 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

88117
An 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.
122169
An 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

Comments
 (0)