diff --git a/examples/timer/cpp_native_bindings/README.md b/examples/timer/cpp_native_bindings/README.md index 40c0dc3..bb7bbb5 100644 --- a/examples/timer/cpp_native_bindings/README.md +++ b/examples/timer/cpp_native_bindings/README.md @@ -28,10 +28,12 @@ make run ## Status -First cut. Methods whose params/returns use only scalar / string / bool / -nested-struct fields are generated (create, version, echo). Methods using -**sequences or optionals** are emitted as `// SKIPPED` for now (complex, -schedule) — those plus **native typed events** are the next increments. The -native-bare / `_cbor` filename reconciliation (matching the C headers) is also a -follow-up; today this emits `my_timer_native.hpp` so it coexists with the CBOR -`my_timer.hpp`. +Requests are fully supported: scalar / string / bool / nested struct **and now +sequences (`std::vector`) and optionals (`std::optional`)** — create, version, +echo, complex, schedule all generate and round-trip typed values (ASAN-clean). +`toC` uses a holder that owns the C-array backing while string pointers borrow +the C++ argument (valid for the call's duration; the library deep-copies). + +Still to come: **native typed events** (`On` handlers) and the +native-bare / `_cbor` filename reconciliation (matching the C headers). Today +this emits `my_timer_native.hpp` so it coexists with the CBOR `my_timer.hpp`. diff --git a/examples/timer/cpp_native_bindings/main.cpp b/examples/timer/cpp_native_bindings/main.cpp index 7555e22..ef3d72e 100644 --- a/examples/timer/cpp_native_bindings/main.cpp +++ b/examples/timer/cpp_native_bindings/main.cpp @@ -1,22 +1,34 @@ // Driver for the GENERATED native C++ bindings (my_timer_native.hpp). -// Uses the native (zero-serialization) ABI: typed structs in, typed structs out. #include "my_timer_native.hpp" - #include - int main() { try { my_timer::My_timerNode node(my_timer::TimerConfig{"cpp-native-gen"}); std::cout << "version: " << node.Version() << "\n"; auto r = node.Echo(my_timer::EchoRequest{"hello from generated C++", 5}); - std::cout << "echo: echoed=" << r.echoed << " timerName=" << r.timerName - << "\n"; + std::cout << "echo: echoed=" << r.echoed << " timerName=" << r.timerName << "\n"; + + // seq + Option params (ComplexRequest), typed ComplexResponse return. + my_timer::ComplexRequest creq; + creq.messages = {{"one", 0}, {"two", 0}}; + creq.tags = {"a", "b"}; + creq.note = "a note"; + creq.retries = 3; + auto c = node.Complex(creq); + std::cout << "complex: itemCount=" << c.itemCount << " hasNote=" << c.hasNote + << " summary=\"" << c.summary << "\"\n"; + + // Multiple struct params (JobSpec, RetryPolicy, ScheduleConfig). + my_timer::JobSpec job; job.name = "nightly"; job.payload = {"x"}; job.priority = 5; + my_timer::RetryPolicy retry; retry.maxAttempts = 3; retry.backoffMs = 100; retry.retryOn = {"timeout"}; + my_timer::ScheduleConfig sched; sched.startAtMs = 1000; sched.intervalMs = 5000; sched.jitter = 50; + auto s = node.Schedule(job, retry, sched); + std::cout << "schedule: jobId=\"" << s.jobId << "\" willRunCount=" << s.willRunCount << "\n"; std::cout << "done.\n"; return 0; } catch (const std::exception &e) { - std::cerr << "error: " << e.what() << "\n"; - return 1; + std::cerr << "error: " << e.what() << "\n"; return 1; } } diff --git a/examples/timer/cpp_native_bindings/my_timer_native.hpp b/examples/timer/cpp_native_bindings/my_timer_native.hpp index 56b741b..0cc42ef 100644 --- a/examples/timer/cpp_native_bindings/my_timer_native.hpp +++ b/examples/timer/cpp_native_bindings/my_timer_native.hpp @@ -8,18 +8,23 @@ #include "my_timer.h" #include #include +#include #include #include +#include namespace my_timer { struct TimerConfig { std::string name{}; }; -inline ::TimerConfig toC(const TimerConfig& v) { +struct TimerConfigC { ::TimerConfig c{}; - c.name = v.name.c_str(); - return c; +}; +inline TimerConfigC toC(const TimerConfig& v) { + TimerConfigC h; + h.c.name = v.name.c_str(); + return h; } inline TimerConfig fromC(const ::TimerConfig& c) { TimerConfig v{}; @@ -31,11 +36,14 @@ struct EchoRequest { std::string message{}; int64_t delayMs{}; }; -inline ::EchoRequest toC(const EchoRequest& v) { +struct EchoRequestC { ::EchoRequest c{}; - c.message = v.message.c_str(); - c.delayMs = v.delayMs; - return c; +}; +inline EchoRequestC toC(const EchoRequest& v) { + EchoRequestC h; + h.c.message = v.message.c_str(); + h.c.delayMs = v.delayMs; + return h; } inline EchoRequest fromC(const ::EchoRequest& c) { EchoRequest v{}; @@ -48,11 +56,14 @@ struct EchoResponse { std::string echoed{}; std::string timerName{}; }; -inline ::EchoResponse toC(const EchoResponse& v) { +struct EchoResponseC { ::EchoResponse c{}; - c.echoed = v.echoed.c_str(); - c.timerName = v.timerName.c_str(); - return c; +}; +inline EchoResponseC toC(const EchoResponse& v) { + EchoResponseC h; + h.c.echoed = v.echoed.c_str(); + h.c.timerName = v.timerName.c_str(); + return h; } inline EchoResponse fromC(const ::EchoResponse& c) { EchoResponse v{}; @@ -61,17 +72,68 @@ inline EchoResponse fromC(const ::EchoResponse& c) { return v; } +struct ComplexRequest { + std::vector messages{}; + std::vector tags{}; + std::optional note{}; + std::optional retries{}; +}; +struct ComplexRequestC { + ::ComplexRequest c{}; + std::vector<::EchoRequest> _messages; + std::vector _messagesH; + std::vector _tags; +}; +inline ComplexRequestC toC(const ComplexRequest& v) { + ComplexRequestC h; + for (const auto& it : v.messages) { + h._messagesH.push_back(toC(it)); + } + for (const auto& hh : h._messagesH) h._messages.push_back(hh.c); + h.c.messages = h._messages.empty() ? nullptr : h._messages.data(); + h.c.messages_len = h._messages.size(); + for (const auto& it : v.tags) { + h._tags.push_back(it.c_str()); + } + h.c.tags = h._tags.empty() ? nullptr : h._tags.data(); + h.c.tags_len = h._tags.size(); + if (v.note.has_value()) { + h.c.note_present = 1; + h.c.note = (*v.note).c_str(); + } + if (v.retries.has_value()) { + h.c.retries_present = 1; + h.c.retries = (*v.retries); + } + return h; +} +inline ComplexRequest fromC(const ::ComplexRequest& c) { + ComplexRequest v{}; + for (std::size_t i = 0; i < c.messages_len; i++) + v.messages.push_back(fromC(c.messages[i])); + for (std::size_t i = 0; i < c.tags_len; i++) + v.tags.push_back((c.tags[i] ? std::string(c.tags[i]) : std::string())); + if (c.note_present) + v.note = (c.note ? std::string(c.note) : std::string()); + if (c.retries_present) + v.retries = c.retries; + return v; +} + struct ComplexResponse { std::string summary{}; int64_t itemCount{}; bool hasNote{}; }; -inline ::ComplexResponse toC(const ComplexResponse& v) { +struct ComplexResponseC { ::ComplexResponse c{}; - c.summary = v.summary.c_str(); - c.itemCount = v.itemCount; - c.hasNote = (v.hasNote ? 1 : 0); - return c; +}; +inline ComplexResponseC toC(const ComplexResponse& v) { + ComplexResponseC h; + h.c.summary = v.summary.c_str(); + h.c.itemCount = v.itemCount; + h.c.hasNote = v.hasNote ? 1 : 0; + return h; } inline ComplexResponse fromC(const ::ComplexResponse& c) { ComplexResponse v{}; @@ -85,11 +147,14 @@ struct EchoEvent { std::string message{}; int64_t echoCount{}; }; -inline ::EchoEvent toC(const EchoEvent& v) { +struct EchoEventC { ::EchoEvent c{}; - c.message = v.message.c_str(); - c.echoCount = v.echoCount; - return c; +}; +inline EchoEventC toC(const EchoEvent& v) { + EchoEventC h; + h.c.message = v.message.c_str(); + h.c.echoCount = v.echoCount; + return h; } inline EchoEvent fromC(const ::EchoEvent& c) { EchoEvent v{}; @@ -98,19 +163,107 @@ inline EchoEvent fromC(const ::EchoEvent& c) { return v; } +struct JobSpec { + std::string name{}; + std::vector payload{}; + int64_t priority{}; +}; +struct JobSpecC { + ::JobSpec c{}; + std::vector _payload; +}; +inline JobSpecC toC(const JobSpec& v) { + JobSpecC h; + h.c.name = v.name.c_str(); + for (const auto& it : v.payload) { + h._payload.push_back(it.c_str()); + } + h.c.payload = h._payload.empty() ? nullptr : h._payload.data(); + h.c.payload_len = h._payload.size(); + h.c.priority = v.priority; + return h; +} +inline JobSpec fromC(const ::JobSpec& c) { + JobSpec v{}; + v.name = c.name ? std::string(c.name) : std::string(); + for (std::size_t i = 0; i < c.payload_len; i++) + v.payload.push_back((c.payload[i] ? std::string(c.payload[i]) : std::string())); + v.priority = c.priority; + return v; +} + +struct RetryPolicy { + int64_t maxAttempts{}; + int64_t backoffMs{}; + std::vector retryOn{}; +}; +struct RetryPolicyC { + ::RetryPolicy c{}; + std::vector _retryOn; +}; +inline RetryPolicyC toC(const RetryPolicy& v) { + RetryPolicyC h; + h.c.maxAttempts = v.maxAttempts; + h.c.backoffMs = v.backoffMs; + for (const auto& it : v.retryOn) { + h._retryOn.push_back(it.c_str()); + } + h.c.retryOn = h._retryOn.empty() ? nullptr : h._retryOn.data(); + h.c.retryOn_len = h._retryOn.size(); + return h; +} +inline RetryPolicy fromC(const ::RetryPolicy& c) { + RetryPolicy v{}; + v.maxAttempts = c.maxAttempts; + v.backoffMs = c.backoffMs; + for (std::size_t i = 0; i < c.retryOn_len; i++) + v.retryOn.push_back((c.retryOn[i] ? std::string(c.retryOn[i]) : std::string())); + return v; +} + +struct ScheduleConfig { + int64_t startAtMs{}; + int64_t intervalMs{}; + std::optional jitter{}; +}; +struct ScheduleConfigC { + ::ScheduleConfig c{}; +}; +inline ScheduleConfigC toC(const ScheduleConfig& v) { + ScheduleConfigC h; + h.c.startAtMs = v.startAtMs; + h.c.intervalMs = v.intervalMs; + if (v.jitter.has_value()) { + h.c.jitter_present = 1; + h.c.jitter = (*v.jitter); + } + return h; +} +inline ScheduleConfig fromC(const ::ScheduleConfig& c) { + ScheduleConfig v{}; + v.startAtMs = c.startAtMs; + v.intervalMs = c.intervalMs; + if (c.jitter_present) + v.jitter = c.jitter; + return v; +} + struct ScheduleResult { std::string jobId{}; int64_t willRunCount{}; int64_t firstRunAtMs{}; int64_t effectiveBackoffMs{}; }; -inline ::ScheduleResult toC(const ScheduleResult& v) { +struct ScheduleResultC { ::ScheduleResult c{}; - c.jobId = v.jobId.c_str(); - c.willRunCount = v.willRunCount; - c.firstRunAtMs = v.firstRunAtMs; - c.effectiveBackoffMs = v.effectiveBackoffMs; - return c; +}; +inline ScheduleResultC toC(const ScheduleResult& v) { + ScheduleResultC h; + h.c.jobId = v.jobId.c_str(); + h.c.willRunCount = v.willRunCount; + h.c.firstRunAtMs = v.firstRunAtMs; + h.c.effectiveBackoffMs = v.effectiveBackoffMs; + return h; } inline ScheduleResult fromC(const ::ScheduleResult& c) { ScheduleResult v{}; @@ -159,6 +312,20 @@ inline void my_timer_native_my_timer_echo(int ret, const char* msg, std::size_t else c->err = detail::rawText(msg, len); c->done.set_value(); } +inline void my_timer_native_my_timer_complex(int ret, const char* msg, std::size_t len, void* ud) { + auto* c = static_cast*>(ud); + c->ret = ret; + if (ret == RET_OK) c->value = fromC(*reinterpret_cast(msg)); + else c->err = detail::rawText(msg, len); + c->done.set_value(); +} +inline void my_timer_native_my_timer_schedule(int ret, const char* msg, std::size_t len, void* ud) { + auto* c = static_cast*>(ud); + c->ret = ret; + if (ret == RET_OK) c->value = fromC(*reinterpret_cast(msg)); + else c->err = detail::rawText(msg, len); + c->done.set_value(); +} } // extern "C" class My_timerNode { @@ -167,7 +334,7 @@ class My_timerNode { detail::AckCapture cap; auto fut = cap.done.get_future(); auto c_config = toC(config); - ctx_ = my_timer_create(c_config, my_timer_native_ack, &cap); + ctx_ = my_timer_create(c_config.c, my_timer_native_ack, &cap); if (!ctx_) throw std::runtime_error("my_timer_create returned null"); fut.wait(); if (cap.ret != RET_OK) throw std::runtime_error(cap.err); @@ -177,7 +344,7 @@ class My_timerNode { detail::Capture cap; auto fut = cap.done.get_future(); auto c_req = toC(req); - if (my_timer_echo(ctx_, my_timer_native_my_timer_echo, &cap, c_req) != RET_OK) + if (my_timer_echo(ctx_, my_timer_native_my_timer_echo, &cap, c_req.c) != RET_OK) throw std::runtime_error("my_timer_echo dispatch failed"); fut.wait(); if (cap.ret != RET_OK) throw std::runtime_error(cap.err); @@ -194,8 +361,30 @@ class My_timerNode { return cap.value; } - // SKIPPED my_timer_complex: seq/Option/multi-struct params not yet supported by native C++ codegen - // SKIPPED my_timer_schedule: seq/Option/multi-struct params not yet supported by native C++ codegen + ComplexResponse Complex(const ComplexRequest& req) { + detail::Capture cap; + auto fut = cap.done.get_future(); + auto c_req = toC(req); + if (my_timer_complex(ctx_, my_timer_native_my_timer_complex, &cap, c_req.c) != RET_OK) + throw std::runtime_error("my_timer_complex dispatch failed"); + fut.wait(); + if (cap.ret != RET_OK) throw std::runtime_error(cap.err); + return cap.value; + } + + ScheduleResult Schedule(const JobSpec& job, const RetryPolicy& retry, const ScheduleConfig& schedule) { + detail::Capture cap; + auto fut = cap.done.get_future(); + auto c_job = toC(job); + auto c_retry = toC(retry); + auto c_schedule = toC(schedule); + if (my_timer_schedule(ctx_, my_timer_native_my_timer_schedule, &cap, c_job.c, c_retry.c, c_schedule.c) != RET_OK) + throw std::runtime_error("my_timer_schedule dispatch failed"); + fut.wait(); + if (cap.ret != RET_OK) throw std::runtime_error(cap.err); + return cap.value; + } + ~My_timerNode() { if (ctx_) my_timer_destroy(ctx_); } My_timerNode(const My_timerNode&) = delete; My_timerNode& operator=(const My_timerNode&) = delete; diff --git a/ffi/codegen/cpp_native.nim b/ffi/codegen/cpp_native.nim index 1b01d48..6ecb58d 100644 --- a/ffi/codegen/cpp_native.nim +++ b/ffi/codegen/cpp_native.nim @@ -14,10 +14,32 @@ import std/[os, strutils] import ./meta, ./string_helpers import ./c as cgen -proc cppType(t: string): string = - ## Idiomatic C++ type for an `{.ffi.}` field / scalar. +proc isSeqT(t: string): bool = + t.strip().startsWith("seq[") and t.strip().endsWith("]") + +proc isOptT(t: string): bool = + let s = t.strip() + (s.startsWith("Option[") or s.startsWith("Maybe[")) and s.endsWith("]") + +proc seqElem(t: string): string = + t.strip()["seq[".len .. ^2].strip() + +proc optElem(t: string): string = + let s = t.strip() + let p = if s.startsWith("Maybe["): "Maybe[".len else: "Option[".len + s[p .. ^2].strip() + +proc isStringT(t: string): bool = + t.strip() in ["string", "cstring"] + +proc isStructT(t: string, types: seq[FFITypeMeta]): bool = + for ty in types: + if ty.name == t.strip(): + return true + false + +proc scalarCpp(t: string): string = case t.strip() - of "string", "cstring": "std::string" of "int", "int64", "clong": "int64_t" of "int32", "cint": "int32_t" of "int16": "int16_t" @@ -29,94 +51,122 @@ proc cppType(t: string): string = of "bool": "bool" of "float", "float32": "float" of "float64": "double" - else: t.strip() # nested {.ffi.} struct -> its C++ name + else: t.strip() # nested struct -> its C++ name -proc isSeqT(t: string): bool = - t.strip().startsWith("seq[") and t.strip().endsWith("]") - -proc isOptT(t: string): bool = +proc cppType(t: string): string = + ## Idiomatic C++ type for an `{.ffi.}` field / param. let s = t.strip() - (s.startsWith("Option[") or s.startsWith("Maybe[")) and s.endsWith("]") + if isSeqT(s): "std::vector<" & cppType(seqElem(s)) & ">" + elif isOptT(s): "std::optional<" & cppType(optElem(s)) & ">" + elif isStringT(s): "std::string" + else: scalarCpp(s) -proc isStringT(t: string): bool = - t.strip() in ["string", "cstring"] +proc cElemCpp(t: string, types: seq[FFITypeMeta]): string = + ## The C type used for one seq element / option payload (matches emitCStructs). + let s = t.strip() + if isStringT(s): "const char*" + elif isStructT(s, types): "::" & s + elif s == "bool": "int" + else: scalarCpp(s) -proc isStructT(t: string, types: seq[FFITypeMeta]): bool = - for ty in types: - if ty.name == t.strip(): - return true - false +# Element-granular converters. `src` yields one element on the source side. +proc toCElem(t, src: string, types: seq[FFITypeMeta]): string = + let s = t.strip() + if isStringT(s): src & ".c_str()" + elif s == "bool": "(" & src & " ? 1 : 0)" + elif isStructT(s, types): "toC(" & src & ").c" + else: src -proc isSimpleType(t: FFITypeMeta, types: seq[FFITypeMeta]): bool = - ## True if every field is scalar / string / bool / nested *simple* struct - ## (no sequences or optionals yet). - for f in t.fields: - let ft = f.typeName.strip() - if isSeqT(ft) or isOptT(ft): - return false - if isStructT(ft, types): - for inner in types: - if inner.name == ft and not isSimpleType(inner, types): - return false - true - -proc paramSimple(typeName: string, types: seq[FFITypeMeta]): bool = - let t = typeName.strip() - if isStringT(t) or t == "bool": - return true - if cppType(t) != t: # mapped scalar - return true - if isStructT(t, types): - for ty in types: - if ty.name == t: - return isSimpleType(ty, types) - false - -# --- toC / fromC field converters ------------------------------------------- -proc toCField(f: FFIFieldMeta, types: seq[FFITypeMeta]): string = - let t = f.typeName.strip() - if isStringT(t): - "v." & f.name & ".c_str()" - elif t == "bool": - "(v." & f.name & " ? 1 : 0)" - elif isStructT(t, types): - "toC(v." & f.name & ")" - else: - "v." & f.name - -proc fromCField(f: FFIFieldMeta, types: seq[FFITypeMeta]): string = - let t = f.typeName.strip() - if isStringT(t): - "c." & f.name & " ? std::string(c." & f.name & ") : std::string()" - elif t == "bool": - "c." & f.name & " != 0" - elif isStructT(t, types): - "fromC(c." & f.name & ")" - else: - "c." & f.name +proc fromCElem(t, src: string, types: seq[FFITypeMeta]): string = + let s = t.strip() + if isStringT(s): "(" & src & " ? std::string(" & src & ") : std::string())" + elif s == "bool": "(" & src & " != 0)" + elif isStructT(s, types): "fromC(" & src & ")" + else: src proc emitTypes(types: seq[FFITypeMeta]): seq[string] = var L: seq[string] = @[] for t in types: - if not isSimpleType(t, types): - continue + # C++ struct. L.add("struct " & t.name & " {") for f in t.fields: L.add(" " & cppType(f.typeName) & " " & f.name & "{};") L.add("};") - # toC: build the C-POD struct (strings borrow the C++ object's storage, - # valid for the duration of the call). - L.add("inline ::" & t.name & " toC(const " & t.name & "& v) {") + + # Holder: owns the backing storage for any seq fields and exposes the C-POD + # struct. Returned by value from `toC`; the std::vector buffers survive the + # move/NRVO, and string pointers borrow the source `v` (kept alive by the + # caller for the duration of the call). + L.add("struct " & t.name & "C {") L.add(" ::" & t.name & " c{};") for f in t.fields: - L.add(" c." & f.name & " = " & toCField(f, types) & ";") - L.add(" return c;") + if isSeqT(f.typeName): + L.add(" std::vector<" & cElemCpp(seqElem(f.typeName), types) & "> _" & + f.name & ";") + if isStructT(seqElem(f.typeName), types): + L.add(" std::vector<" & cppType(seqElem(f.typeName)) & "C> _" & + f.name & "H;") + L.add("};") + + L.add("inline " & t.name & "C toC(const " & t.name & "& v) {") + L.add(" " & t.name & "C h;") + for f in t.fields: + let ft = f.typeName.strip() + if isSeqT(ft): + let e = seqElem(ft) + L.add(" for (const auto& it : v." & f.name & ") {") + if isStructT(e, types): + # Keep element holders alive, then collect their C structs. + L.add(" h._" & f.name & "H.push_back(toC(it));") + else: + L.add(" h._" & f.name & ".push_back(" & toCElem(e, "it", types) & ");") + L.add(" }") + if isStructT(e, types): + L.add(" for (const auto& hh : h._" & f.name & "H) h._" & f.name & + ".push_back(hh.c);") + L.add(" h.c." & f.name & " = h._" & f.name & ".empty() ? nullptr : h._" & + f.name & ".data();") + L.add(" h.c." & f.name & "_len = h._" & f.name & ".size();") + elif isOptT(ft): + let e = optElem(ft) + L.add(" if (v." & f.name & ".has_value()) {") + L.add(" h.c." & f.name & "_present = 1;") + L.add(" h.c." & f.name & " = " & toCElem(e, "(*v." & f.name & ")", types) & ";") + L.add(" }") + elif isStringT(ft): + L.add(" h.c." & f.name & " = v." & f.name & ".c_str();") + elif ft == "bool": + L.add(" h.c." & f.name & " = v." & f.name & " ? 1 : 0;") + elif isStructT(ft, types): + L.add(" h.c." & f.name & " = toC(v." & f.name & ").c;") + else: + L.add(" h.c." & f.name & " = v." & f.name & ";") + L.add(" return h;") L.add("}") - # fromC: copy out of a C-POD struct into C++-owned values. + L.add("inline " & t.name & " fromC(const ::" & t.name & "& c) {") L.add(" " & t.name & " v{};") for f in t.fields: - L.add(" v." & f.name & " = " & fromCField(f, types) & ";") + let ft = f.typeName.strip() + if isSeqT(ft): + let e = seqElem(ft) + L.add(" for (std::size_t i = 0; i < c." & f.name & "_len; i++)") + L.add(" v." & f.name & ".push_back(" & + fromCElem(e, "c." & f.name & "[i]", types) & ");") + elif isOptT(ft): + let e = optElem(ft) + L.add(" if (c." & f.name & "_present)") + L.add(" v." & f.name & " = " & + fromCElem(e, "c." & f.name, types) & ";") + elif isStringT(ft): + L.add(" v." & f.name & " = c." & f.name & " ? std::string(c." & f.name & + ") : std::string();") + elif ft == "bool": + L.add(" v." & f.name & " = c." & f.name & " != 0;") + elif isStructT(ft, types): + L.add(" v." & f.name & " = fromC(c." & f.name & ");") + else: + L.add(" v." & f.name & " = c." & f.name & ";") L.add(" return v;") L.add("}") L.add("") @@ -132,8 +182,12 @@ proc methodName(procName, libName: string): string = capitalizeFirstLetter(snakeToPascalCase(bare)) proc procSupported(p: FFIProcMeta, types: seq[FFITypeMeta]): bool = + ## Scalar / string / `{.ffi.}`-struct params are supported (structs may carry + ## seq/Option fields now). Bare seq/Option top-level params and raw pointers + ## are not (the native ABI doesn't expose them either). for ep in p.extraParams: - if ep.isPtr or not paramSimple(ep.typeName, types): + let t = ep.typeName.strip() + if ep.isPtr or isSeqT(t) or isOptT(t): return false true @@ -157,8 +211,10 @@ proc generateCppNativeHeader*( L.add("#include \"" & libName & ".h\"") L.add("#include ") L.add("#include ") + L.add("#include ") L.add("#include ") L.add("#include ") + L.add("#include ") L.add("") L.add("namespace " & libName & " {") L.add("") @@ -240,7 +296,7 @@ proc generateCppNativeHeader*( params.add("const " & cppType(ep.typeName) & "& " & ep.name) if isStructT(ep.typeName, types): conv.add(" auto c_" & ep.name & " = toC(" & ep.name & ");") - args.add("c_" & ep.name) + args.add("c_" & ep.name & ".c") else: args.add(ep.name) let argsStr = if args.len > 0: args.join(", ") & ", " else: "" @@ -273,7 +329,7 @@ proc generateCppNativeHeader*( params.add("const " & cppType(ep.typeName) & "& " & ep.name) if isStructT(ep.typeName, types): conv.add(" auto c_" & ep.name & " = toC(" & ep.name & ");") - args.add("c_" & ep.name) + args.add("c_" & ep.name & ".c") else: args.add(ep.name) let argsStr = if args.len > 0: ", " & args.join(", ") else: ""