Add ffiDtor concept

This commit is contained in:
Ivan FB 2026-05-06 23:09:30 +02:00
parent 863b15d2dc
commit 729801b999
No known key found for this signature in database
GPG Key ID: DF0C67A04C543270
4 changed files with 119 additions and 5 deletions

View File

@ -109,7 +109,7 @@ proc generateCppHeader*(
lines.add("")
for p in procs:
var params: seq[string] = @[]
if p.kind == ffiFfiKind:
if p.kind in {ffiFfiKind, ffiDtorKind}:
params.add("void* ctx")
params.add("FfiCallback callback")
params.add("void* user_data")
@ -121,8 +121,6 @@ proc generateCppHeader*(
params.add("FfiCallback callback")
params.add("void* user_data")
lines.add("int $1($2);" % [p.procName, params.join(", ")])
# Destroy is a plain synchronous call — no callback needed
lines.add("void $1_destroy(void* ctx);" % [libName])
lines.add("} // extern \"C\"")
lines.add("")

View File

@ -10,6 +10,7 @@ type
FFIProcKind* = enum
ffiCtorKind
ffiFfiKind
ffiDtorKind
FFIProcMeta* = object
procName*: string # e.g. "nimtimer_echo"

View File

@ -179,8 +179,8 @@ proc generateFfiRs*(procs: seq[FFIProcMeta]): string =
for p in procs:
var params: seq[string] = @[]
if p.kind == ffiFfiKind:
# Method: ctx comes first
if p.kind in {ffiFfiKind, ffiDtorKind}:
# Method/destructor: ctx comes first
params.add("ctx: *mut c_void")
params.add("callback: FfiCallback")
params.add("user_data: *mut c_void")

View File

@ -1468,6 +1468,121 @@ macro ffiCtor*(prc: untyped): untyped =
when defined(ffiDumpMacros):
echo result.repr
# ---------------------------------------------------------------------------
# ffiDtor — destructor macro
# ---------------------------------------------------------------------------
macro ffiDtor*(prc: untyped): untyped =
## Defines a C-exported destructor. Works like {.ffi.} but also tears down
## the FFIContext after the body runs.
##
## The annotated proc must have exactly one parameter of the library type.
## The body contains any library-level cleanup to run before context teardown.
##
## Example:
## proc waku_destroy*(w: Waku) {.ffiDtor.} =
## w.cleanup()
##
## The generated C-exported proc has the signature:
## cint waku_destroy(void* ctx, FfiCallback callback, void* userData)
##
## It extracts the library value from ctx, runs the body, then calls
## destroyFFIContext to tear down the FFI thread and free the context.
let procName = prc[0]
let formalParams = prc[3]
let bodyNode = prc[^1]
if formalParams.len < 2:
error("ffiDtor: proc must have exactly one parameter (w: LibType)")
let libParamName = formalParams[1][0] # e.g. w
let libTypeName = formalParams[1][1] # e.g. Waku
let procNameStr = block:
let raw = $procName
if raw.endsWith("*"): raw[0 ..^ 2] else: raw
let cExportName = nimNameToCExport(procNameStr)
let exportedProcName =
if procName.kind == nnkPostfix: procName[1] else: procName
let destroyResIdent = genSym(nskLet, "destroyRes")
let ffiBody = newStmtList()
ffiBody.add quote do:
when declared(initializeLibrary):
initializeLibrary()
ffiBody.add quote do:
if ctx.isNil or cast[ptr FFIContext[`libTypeName`]](ctx)[].myLib.isNil:
if not callback.isNil:
let errStr = "context not initialized"
callback(RET_ERR, unsafeAddr errStr[0], cast[csize_t](errStr.len), userData)
return RET_ERR
# Extract the library value so the user body can reference it by name
ffiBody.add quote do:
let `libParamName` = cast[ptr FFIContext[`libTypeName`]](ctx)[].myLib[]
# Append the user body if it is not a bare discard
let isNoop =
bodyNode.kind == nnkEmpty or
(bodyNode.kind == nnkStmtList and bodyNode.len == 1 and
bodyNode[0].kind == nnkDiscardStmt)
if not isNoop:
ffiBody.add(bodyNode)
ffiBody.add quote do:
let `destroyResIdent` =
destroyFFIContext[`libTypeName`](cast[ptr FFIContext[`libTypeName`]](ctx))
if `destroyResIdent`.isErr():
if not callback.isNil:
let errStr = "destroy failed: " & $`destroyResIdent`.error
callback(RET_ERR, unsafeAddr errStr[0], cast[csize_t](errStr.len), userData)
return RET_ERR
ffiBody.add quote do:
if not callback.isNil:
callback(RET_OK, nil, 0, userData)
return RET_OK
let ffiProc = newProc(
name = exportedProcName,
params = @[
ident("cint"),
newIdentDefs(ident("ctx"), ident("pointer")),
newIdentDefs(ident("callback"), ident("FFICallBack")),
newIdentDefs(ident("userData"), ident("pointer")),
],
body = ffiBody,
pragmas = newTree(
nnkPragma,
ident("dynlib"),
newTree(nnkExprColonExpr, ident("exportc"), newStrLitNode(cExportName)),
ident("cdecl"),
newTree(nnkExprColonExpr, ident("raises"), newTree(nnkBracket)),
),
)
ffiProcRegistry.add(
FFIProcMeta(
procName: cExportName,
libName: currentLibName,
kind: ffiDtorKind,
libTypeName: $libTypeName,
extraParams: @[],
returnTypeName: "",
returnIsPtr: false,
isAsync: false,
)
)
result = ffiProc
when defined(ffiDumpMacros):
echo result.repr
# ---------------------------------------------------------------------------
# genBindings — Rust crate generator
# ---------------------------------------------------------------------------