nim-ffi/examples/host_demo/host_demo.nim
Ivan FB 3240ac0080
feat(host): {.ffiHost.} metadata + Go wrapper (increment 5)
5a: record {.ffiHost.} procs in a compile-time registry (FFIHostMeta /
ffiHostRegistry), populated by the macro, so generators can see host fns.

5b: the Go generator emits an idiomatic wrapper over the host C ABI:
- a single //export cgo trampoline backs every host fn; a cgo.Handle in
  userData selects the Go closure;
- the closure runs on a fresh GOROUTINE so the FFI thread is never blocked
  (the non-blocking contract), then answers via <lib>_host_complete by token;
- a per-host `Set<Name>(func(string) (string, error))` method registers it.

Validated end to end with `go run` (examples/host_demo): Go UseToken -> Nim
{.ffi.} handler -> await fetchToken {.ffiHost.} -> Go trampoline -> goroutine
runs the closure -> host_complete -> future resolves on the loop thread ->
"token[TOK-session]" back in Go. Timer's Go output is unchanged (no host fns);
its regenerated .h just gains the always-exported host ABI decls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:49:36 +02:00

32 lines
1.1 KiB
Nim

## Minimal example exercising a {.ffiHost.} host callback end-to-end from Go.
##
## `fetchToken` is implemented by the *host* (the Go app); `useToken` is a normal
## {.ffi.} method the host calls, which in turn asks the host for a token via
## `fetchToken` and awaits it. This proves the inverted call direction across the
## real FFI boundary with the generated Go wrapper.
import ffi, chronos, results
type Demo = object
declareLibrary("host_demo", Demo)
# Ctor first: the {.ffiCtor.} macro declares the per-lib FFI pool that the
# {.ffi.} method below references.
proc demoCreate(): Future[Result[Demo, string]] {.ffiCtor.} =
return ok(Demo())
proc demoDestroy(d: Demo) {.ffiDtor.} =
discard
# Host-implemented: the Go app registers this with SetFetchToken.
proc fetchToken(key: string): Future[Result[string, string]] {.ffiHost.}
# A {.ffi.} method the host calls; it asks the host for a token and wraps it.
proc useToken(d: Demo, key: string): Future[Result[string, string]] {.ffi.} =
let tok = (await fetchToken(key)).valueOr:
return err("host error: " & error)
return ok("token[" & tok & "]")
genBindings()