mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-06-20 08:19:55 +00:00
feat(codegen): native Rust generator handles seq / Option / nested
Extends the native Rust marshalling to sequences and optionals (the Rust
counterpart of the cpp_native increment): a field maps to Vec<T> / Option<T>,
the repr(C) mirror gains the matching `{ ptr, len }` / `{ present, value }`
fields, and `to_c` now returns a holder owning the CStrings + C-array backing
(Vec/CString live on the heap, so the C struct's raw pointers survive the move
and the call). `from_c` reads seq/Option back out via slice + present-flag.
Unblocks the timer's complex (seq-of-structs / seq-of-strings / two optionals)
and schedule (three struct params). Verified end-to-end — the demo round-trips
typed ComplexResponse / ScheduleResult.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
23152d4fe7
commit
99fdcfdff7
@ -21,8 +21,12 @@ Regenerate with `nimble genbindings_rust_native`.
|
||||
|
||||
## Status
|
||||
|
||||
First cut — scalar / string / bool / float / nested-struct fields (create,
|
||||
version, echo). Methods taking sequences or optionals (complex, schedule) are
|
||||
`// SKIPPED`; those plus native typed events are the next increments. Linking is
|
||||
left to the consumer (`-L <dir> -l my_timer` + an rpath, as in `examples/demo.rs`);
|
||||
a build.rs that compiles the dylib (like the CBOR crate) can be added later.
|
||||
Requests are fully supported: scalar / string / bool / float / nested struct
|
||||
**and now sequences (`Vec`) and optionals (`Option`)** — create, version, echo,
|
||||
complex, schedule all generate and round-trip typed values. `to_c` returns a
|
||||
holder that owns the `CString`s and C-array backing (heap, so the C struct's raw
|
||||
pointers stay valid across the move and for the call).
|
||||
|
||||
Still to come: native typed events and the native-bare / `_cbor` reconciliation.
|
||||
Linking is left to the consumer (`-L <dir> -l my_timer` + rpath, as in
|
||||
`examples/demo.rs`); a build.rs that compiles the dylib can be added later.
|
||||
|
||||
@ -4,4 +4,20 @@ fn main() {
|
||||
println!("version: {}", node.version().unwrap());
|
||||
let r = node.echo(EchoRequest { message: "hello from generated Rust".into(), delay_ms: 5 }).unwrap();
|
||||
println!("echo: echoed={} timer_name={}", r.echoed, r.timer_name);
|
||||
|
||||
let c = node.complex(ComplexRequest {
|
||||
messages: vec![EchoRequest { message: "one".into(), delay_ms: 0 },
|
||||
EchoRequest { message: "two".into(), delay_ms: 0 }],
|
||||
tags: vec!["a".into(), "b".into()],
|
||||
note: Some("a note".into()),
|
||||
retries: Some(3),
|
||||
}).unwrap();
|
||||
println!("complex: item_count={} has_note={} summary={:?}", c.item_count, c.has_note, c.summary);
|
||||
|
||||
let s = node.schedule(
|
||||
JobSpec { name: "nightly".into(), payload: vec!["x".into()], priority: 5 },
|
||||
RetryPolicy { max_attempts: 3, backoff_ms: 100, retry_on: vec!["timeout".into()] },
|
||||
ScheduleConfig { start_at_ms: 1000, interval_ms: 5000, jitter: Some(50) },
|
||||
).unwrap();
|
||||
println!("schedule: job_id={:?} will_run_count={}", s.job_id, s.will_run_count);
|
||||
}
|
||||
|
||||
@ -31,6 +31,20 @@ unsafe extern "C" fn cb_my_timer_echo(ret: c_int, msg: *const c_char, len: usize
|
||||
} else { Err(err_text(msg, len)) };
|
||||
let _ = tx.send(r);
|
||||
}
|
||||
unsafe extern "C" fn cb_my_timer_complex(ret: c_int, msg: *const c_char, len: usize, ud: *mut c_void) {
|
||||
let tx = Box::from_raw(ud as *mut SyncSender<Result<ComplexResponse, String>>);
|
||||
let r = if ret == RET_OK && !msg.is_null() {
|
||||
Ok(ComplexResponse::from_c(&*(msg as *const ffi::ComplexResponse)))
|
||||
} else { Err(err_text(msg, len)) };
|
||||
let _ = tx.send(r);
|
||||
}
|
||||
unsafe extern "C" fn cb_my_timer_schedule(ret: c_int, msg: *const c_char, len: usize, ud: *mut c_void) {
|
||||
let tx = Box::from_raw(ud as *mut SyncSender<Result<ScheduleResult, String>>);
|
||||
let r = if ret == RET_OK && !msg.is_null() {
|
||||
Ok(ScheduleResult::from_c(&*(msg as *const ffi::ScheduleResult)))
|
||||
} else { Err(err_text(msg, len)) };
|
||||
let _ = tx.send(r);
|
||||
}
|
||||
|
||||
pub struct MyTimerNode { ctx: *mut c_void }
|
||||
unsafe impl Send for MyTimerNode {}
|
||||
@ -38,29 +52,25 @@ unsafe impl Sync for MyTimerNode {}
|
||||
|
||||
impl MyTimerNode {
|
||||
pub fn new(config: TimerConfig) -> Result<Self, String> {
|
||||
let mut strings = Vec::new();
|
||||
let c_config = config.to_c_inner(&mut strings);
|
||||
let c_config = config.to_c();
|
||||
let (tx, rx) = sync_channel::<Result<(), String>>(1);
|
||||
let raw = Box::into_raw(Box::new(tx)) as *mut c_void;
|
||||
let ctx = unsafe { ffi::my_timer_create(c_config, ack_cb, raw) };
|
||||
let ctx = unsafe { ffi::my_timer_create(c_config.c, ack_cb, raw) };
|
||||
let res = rx.recv().map_err(|_| String::from("callback channel closed"))?;
|
||||
res?;
|
||||
if ctx.is_null() { return Err(String::from("my_timer_create returned null")); }
|
||||
let _ = strings;
|
||||
Ok(MyTimerNode { ctx })
|
||||
}
|
||||
|
||||
pub fn echo(&self, req: EchoRequest) -> Result<EchoResponse, String> {
|
||||
let mut strings = Vec::new();
|
||||
let c_req = req.to_c_inner(&mut strings);
|
||||
let c_req = req.to_c();
|
||||
let (tx, rx) = sync_channel::<Result<EchoResponse, String>>(1);
|
||||
let raw = Box::into_raw(Box::new(tx)) as *mut c_void;
|
||||
let rc = unsafe { ffi::my_timer_echo(self.ctx, cb_my_timer_echo, raw, c_req) };
|
||||
let rc = unsafe { ffi::my_timer_echo(self.ctx, cb_my_timer_echo, raw, c_req.c) };
|
||||
if rc != RET_OK {
|
||||
drop(unsafe { Box::from_raw(raw as *mut SyncSender<Result<EchoResponse, String>>) });
|
||||
return Err(String::from("my_timer_echo dispatch failed"));
|
||||
}
|
||||
let _ = strings;
|
||||
rx.recv().map_err(|_| String::from("callback channel closed"))?
|
||||
}
|
||||
|
||||
@ -75,8 +85,32 @@ impl MyTimerNode {
|
||||
rx.recv().map_err(|_| String::from("callback channel closed"))?
|
||||
}
|
||||
|
||||
// SKIPPED my_timer_complex: seq/Option params not yet supported by native Rust codegen
|
||||
// SKIPPED my_timer_schedule: seq/Option params not yet supported by native Rust codegen
|
||||
pub fn complex(&self, req: ComplexRequest) -> Result<ComplexResponse, String> {
|
||||
let c_req = req.to_c();
|
||||
let (tx, rx) = sync_channel::<Result<ComplexResponse, String>>(1);
|
||||
let raw = Box::into_raw(Box::new(tx)) as *mut c_void;
|
||||
let rc = unsafe { ffi::my_timer_complex(self.ctx, cb_my_timer_complex, raw, c_req.c) };
|
||||
if rc != RET_OK {
|
||||
drop(unsafe { Box::from_raw(raw as *mut SyncSender<Result<ComplexResponse, String>>) });
|
||||
return Err(String::from("my_timer_complex dispatch failed"));
|
||||
}
|
||||
rx.recv().map_err(|_| String::from("callback channel closed"))?
|
||||
}
|
||||
|
||||
pub fn schedule(&self, job: JobSpec, retry: RetryPolicy, schedule: ScheduleConfig) -> Result<ScheduleResult, String> {
|
||||
let c_job = job.to_c();
|
||||
let c_retry = retry.to_c();
|
||||
let c_schedule = schedule.to_c();
|
||||
let (tx, rx) = sync_channel::<Result<ScheduleResult, String>>(1);
|
||||
let raw = Box::into_raw(Box::new(tx)) as *mut c_void;
|
||||
let rc = unsafe { ffi::my_timer_schedule(self.ctx, cb_my_timer_schedule, raw, c_job.c, c_retry.c, c_schedule.c) };
|
||||
if rc != RET_OK {
|
||||
drop(unsafe { Box::from_raw(raw as *mut SyncSender<Result<ScheduleResult, String>>) });
|
||||
return Err(String::from("my_timer_schedule dispatch failed"));
|
||||
}
|
||||
rx.recv().map_err(|_| String::from("callback channel closed"))?
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Drop for MyTimerNode {
|
||||
|
||||
@ -25,6 +25,19 @@ pub struct EchoResponse {
|
||||
pub timer_name: *const c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ComplexRequest {
|
||||
pub messages: *const EchoRequest,
|
||||
pub messages_len: usize,
|
||||
pub tags: *const *const c_char,
|
||||
pub tags_len: usize,
|
||||
pub note_present: c_int,
|
||||
pub note: *const c_char,
|
||||
pub retries_present: c_int,
|
||||
pub retries: i64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ComplexResponse {
|
||||
@ -40,6 +53,33 @@ pub struct EchoEvent {
|
||||
pub echo_count: i64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct JobSpec {
|
||||
pub name: *const c_char,
|
||||
pub payload: *const *const c_char,
|
||||
pub payload_len: usize,
|
||||
pub priority: i64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RetryPolicy {
|
||||
pub max_attempts: i64,
|
||||
pub backoff_ms: i64,
|
||||
pub retry_on: *const *const c_char,
|
||||
pub retry_on_len: usize,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ScheduleConfig {
|
||||
pub start_at_ms: i64,
|
||||
pub interval_ms: i64,
|
||||
pub jitter_present: c_int,
|
||||
pub jitter: i64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ScheduleResult {
|
||||
@ -54,5 +94,7 @@ extern "C" {
|
||||
pub fn my_timer_create(config: TimerConfig, callback: FFICallback, user_data: *mut c_void) -> *mut c_void;
|
||||
pub fn my_timer_echo(ctx: *mut c_void, callback: FFICallback, user_data: *mut c_void, req: EchoRequest) -> c_int;
|
||||
pub fn my_timer_version(ctx: *mut c_void, callback: FFICallback, user_data: *mut c_void) -> c_int;
|
||||
pub fn my_timer_complex(ctx: *mut c_void, callback: FFICallback, user_data: *mut c_void, req: ComplexRequest) -> c_int;
|
||||
pub fn my_timer_schedule(ctx: *mut c_void, callback: FFICallback, user_data: *mut c_void, job: JobSpec, retry: RetryPolicy, schedule: ScheduleConfig) -> c_int;
|
||||
pub fn my_timer_destroy(ctx: *mut c_void) -> c_int;
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
// Generated by nim-ffi native Rust codegen. Do not edit by hand.
|
||||
#![allow(dead_code)]
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
use super::ffi;
|
||||
@ -13,14 +14,18 @@ pub struct TimerConfig {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub struct TimerConfigC {
|
||||
pub c: ffi::TimerConfig,
|
||||
_s: Vec<CString>,
|
||||
}
|
||||
|
||||
impl TimerConfig {
|
||||
pub fn to_c_inner(&self, strings: &mut Vec<CString>) -> ffi::TimerConfig {
|
||||
let name_c = CString::new(self.name.as_str()).unwrap_or_default();
|
||||
let name_p = name_c.as_ptr();
|
||||
strings.push(name_c);
|
||||
ffi::TimerConfig {
|
||||
name: name_p,
|
||||
}
|
||||
pub fn to_c(&self) -> TimerConfigC {
|
||||
#[allow(unused_mut)]
|
||||
let mut h = TimerConfigC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
|
||||
};
|
||||
{ let cs = CString::new(self.name.as_str()).unwrap_or_default(); h.c.name = cs.as_ptr(); h._s.push(cs); }
|
||||
h
|
||||
}
|
||||
pub fn from_c(c: &ffi::TimerConfig) -> Self {
|
||||
TimerConfig {
|
||||
@ -35,15 +40,19 @@ pub struct EchoRequest {
|
||||
pub delay_ms: i64,
|
||||
}
|
||||
|
||||
pub struct EchoRequestC {
|
||||
pub c: ffi::EchoRequest,
|
||||
_s: Vec<CString>,
|
||||
}
|
||||
|
||||
impl EchoRequest {
|
||||
pub fn to_c_inner(&self, strings: &mut Vec<CString>) -> ffi::EchoRequest {
|
||||
let message_c = CString::new(self.message.as_str()).unwrap_or_default();
|
||||
let message_p = message_c.as_ptr();
|
||||
strings.push(message_c);
|
||||
ffi::EchoRequest {
|
||||
message: message_p,
|
||||
delay_ms: self.delay_ms,
|
||||
}
|
||||
pub fn to_c(&self) -> EchoRequestC {
|
||||
#[allow(unused_mut)]
|
||||
let mut h = EchoRequestC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
|
||||
};
|
||||
{ let cs = CString::new(self.message.as_str()).unwrap_or_default(); h.c.message = cs.as_ptr(); h._s.push(cs); }
|
||||
h.c.delay_ms = self.delay_ms;
|
||||
h
|
||||
}
|
||||
pub fn from_c(c: &ffi::EchoRequest) -> Self {
|
||||
EchoRequest {
|
||||
@ -59,18 +68,19 @@ pub struct EchoResponse {
|
||||
pub timer_name: String,
|
||||
}
|
||||
|
||||
pub struct EchoResponseC {
|
||||
pub c: ffi::EchoResponse,
|
||||
_s: Vec<CString>,
|
||||
}
|
||||
|
||||
impl EchoResponse {
|
||||
pub fn to_c_inner(&self, strings: &mut Vec<CString>) -> ffi::EchoResponse {
|
||||
let echoed_c = CString::new(self.echoed.as_str()).unwrap_or_default();
|
||||
let echoed_p = echoed_c.as_ptr();
|
||||
strings.push(echoed_c);
|
||||
let timer_name_c = CString::new(self.timer_name.as_str()).unwrap_or_default();
|
||||
let timer_name_p = timer_name_c.as_ptr();
|
||||
strings.push(timer_name_c);
|
||||
ffi::EchoResponse {
|
||||
echoed: echoed_p,
|
||||
timer_name: timer_name_p,
|
||||
}
|
||||
pub fn to_c(&self) -> EchoResponseC {
|
||||
#[allow(unused_mut)]
|
||||
let mut h = EchoResponseC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
|
||||
};
|
||||
{ let cs = CString::new(self.echoed.as_str()).unwrap_or_default(); h.c.echoed = cs.as_ptr(); h._s.push(cs); }
|
||||
{ let cs = CString::new(self.timer_name.as_str()).unwrap_or_default(); h.c.timer_name = cs.as_ptr(); h._s.push(cs); }
|
||||
h
|
||||
}
|
||||
pub fn from_c(c: &ffi::EchoResponse) -> Self {
|
||||
EchoResponse {
|
||||
@ -80,6 +90,61 @@ impl EchoResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ComplexRequest {
|
||||
pub messages: Vec<EchoRequest>,
|
||||
pub tags: Vec<String>,
|
||||
pub note: Option<String>,
|
||||
pub retries: Option<i64>,
|
||||
}
|
||||
|
||||
pub struct ComplexRequestC {
|
||||
pub c: ffi::ComplexRequest,
|
||||
_s: Vec<CString>,
|
||||
_a_messages: Vec<ffi::EchoRequest>,
|
||||
_h_messages: Vec<EchoRequestC>,
|
||||
_a_tags: Vec<*const c_char>,
|
||||
}
|
||||
|
||||
impl ComplexRequest {
|
||||
pub fn to_c(&self) -> ComplexRequestC {
|
||||
#[allow(unused_mut)]
|
||||
let mut h = ComplexRequestC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
|
||||
_a_messages: Vec::new(),
|
||||
_h_messages: Vec::new(),
|
||||
_a_tags: Vec::new(),
|
||||
};
|
||||
for x in &self.messages {
|
||||
let eh = x.to_c();
|
||||
h._a_messages.push(eh.c);
|
||||
h._h_messages.push(eh);
|
||||
}
|
||||
h.c.messages = if h._a_messages.is_empty() { std::ptr::null() } else { h._a_messages.as_ptr() };
|
||||
h.c.messages_len = h._a_messages.len();
|
||||
for x in &self.tags {
|
||||
let cs = CString::new(x.as_str()).unwrap_or_default();
|
||||
h._a_tags.push(cs.as_ptr());
|
||||
h._s.push(cs);
|
||||
}
|
||||
h.c.tags = if h._a_tags.is_empty() { std::ptr::null() } else { h._a_tags.as_ptr() };
|
||||
h.c.tags_len = h._a_tags.len();
|
||||
if let Some(x) = &self.note {
|
||||
let cs = CString::new(x.as_str()).unwrap_or_default();
|
||||
h.c.note = cs.as_ptr(); h.c.note_present = 1; h._s.push(cs);
|
||||
}
|
||||
if let Some(v) = self.retries { h.c.retries_present = 1; h.c.retries = v; }
|
||||
h
|
||||
}
|
||||
pub fn from_c(c: &ffi::ComplexRequest) -> Self {
|
||||
ComplexRequest {
|
||||
messages: if c.messages.is_null() { Vec::new() } else { unsafe { std::slice::from_raw_parts(c.messages, c.messages_len) }.iter().map(|e| EchoRequest::from_c(e)).collect() },
|
||||
tags: if c.tags.is_null() { Vec::new() } else { unsafe { std::slice::from_raw_parts(c.tags, c.tags_len) }.iter().map(|&p| cstr(p)).collect() },
|
||||
note: if c.note_present != 0 { Some(cstr(c.note)) } else { None },
|
||||
retries: if c.retries_present != 0 { Some(c.retries) } else { None },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ComplexResponse {
|
||||
pub summary: String,
|
||||
@ -87,16 +152,20 @@ pub struct ComplexResponse {
|
||||
pub has_note: bool,
|
||||
}
|
||||
|
||||
pub struct ComplexResponseC {
|
||||
pub c: ffi::ComplexResponse,
|
||||
_s: Vec<CString>,
|
||||
}
|
||||
|
||||
impl ComplexResponse {
|
||||
pub fn to_c_inner(&self, strings: &mut Vec<CString>) -> ffi::ComplexResponse {
|
||||
let summary_c = CString::new(self.summary.as_str()).unwrap_or_default();
|
||||
let summary_p = summary_c.as_ptr();
|
||||
strings.push(summary_c);
|
||||
ffi::ComplexResponse {
|
||||
summary: summary_p,
|
||||
item_count: self.item_count,
|
||||
has_note: if self.has_note { 1 } else { 0 },
|
||||
}
|
||||
pub fn to_c(&self) -> ComplexResponseC {
|
||||
#[allow(unused_mut)]
|
||||
let mut h = ComplexResponseC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
|
||||
};
|
||||
{ let cs = CString::new(self.summary.as_str()).unwrap_or_default(); h.c.summary = cs.as_ptr(); h._s.push(cs); }
|
||||
h.c.item_count = self.item_count;
|
||||
h.c.has_note = if self.has_note { 1 } else { 0 };
|
||||
h
|
||||
}
|
||||
pub fn from_c(c: &ffi::ComplexResponse) -> Self {
|
||||
ComplexResponse {
|
||||
@ -113,15 +182,19 @@ pub struct EchoEvent {
|
||||
pub echo_count: i64,
|
||||
}
|
||||
|
||||
pub struct EchoEventC {
|
||||
pub c: ffi::EchoEvent,
|
||||
_s: Vec<CString>,
|
||||
}
|
||||
|
||||
impl EchoEvent {
|
||||
pub fn to_c_inner(&self, strings: &mut Vec<CString>) -> ffi::EchoEvent {
|
||||
let message_c = CString::new(self.message.as_str()).unwrap_or_default();
|
||||
let message_p = message_c.as_ptr();
|
||||
strings.push(message_c);
|
||||
ffi::EchoEvent {
|
||||
message: message_p,
|
||||
echo_count: self.echo_count,
|
||||
}
|
||||
pub fn to_c(&self) -> EchoEventC {
|
||||
#[allow(unused_mut)]
|
||||
let mut h = EchoEventC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
|
||||
};
|
||||
{ let cs = CString::new(self.message.as_str()).unwrap_or_default(); h.c.message = cs.as_ptr(); h._s.push(cs); }
|
||||
h.c.echo_count = self.echo_count;
|
||||
h
|
||||
}
|
||||
pub fn from_c(c: &ffi::EchoEvent) -> Self {
|
||||
EchoEvent {
|
||||
@ -131,6 +204,115 @@ impl EchoEvent {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct JobSpec {
|
||||
pub name: String,
|
||||
pub payload: Vec<String>,
|
||||
pub priority: i64,
|
||||
}
|
||||
|
||||
pub struct JobSpecC {
|
||||
pub c: ffi::JobSpec,
|
||||
_s: Vec<CString>,
|
||||
_a_payload: Vec<*const c_char>,
|
||||
}
|
||||
|
||||
impl JobSpec {
|
||||
pub fn to_c(&self) -> JobSpecC {
|
||||
#[allow(unused_mut)]
|
||||
let mut h = JobSpecC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
|
||||
_a_payload: Vec::new(),
|
||||
};
|
||||
{ let cs = CString::new(self.name.as_str()).unwrap_or_default(); h.c.name = cs.as_ptr(); h._s.push(cs); }
|
||||
for x in &self.payload {
|
||||
let cs = CString::new(x.as_str()).unwrap_or_default();
|
||||
h._a_payload.push(cs.as_ptr());
|
||||
h._s.push(cs);
|
||||
}
|
||||
h.c.payload = if h._a_payload.is_empty() { std::ptr::null() } else { h._a_payload.as_ptr() };
|
||||
h.c.payload_len = h._a_payload.len();
|
||||
h.c.priority = self.priority;
|
||||
h
|
||||
}
|
||||
pub fn from_c(c: &ffi::JobSpec) -> Self {
|
||||
JobSpec {
|
||||
name: cstr(c.name),
|
||||
payload: if c.payload.is_null() { Vec::new() } else { unsafe { std::slice::from_raw_parts(c.payload, c.payload_len) }.iter().map(|&p| cstr(p)).collect() },
|
||||
priority: c.priority,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct RetryPolicy {
|
||||
pub max_attempts: i64,
|
||||
pub backoff_ms: i64,
|
||||
pub retry_on: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct RetryPolicyC {
|
||||
pub c: ffi::RetryPolicy,
|
||||
_s: Vec<CString>,
|
||||
_a_retry_on: Vec<*const c_char>,
|
||||
}
|
||||
|
||||
impl RetryPolicy {
|
||||
pub fn to_c(&self) -> RetryPolicyC {
|
||||
#[allow(unused_mut)]
|
||||
let mut h = RetryPolicyC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
|
||||
_a_retry_on: Vec::new(),
|
||||
};
|
||||
h.c.max_attempts = self.max_attempts;
|
||||
h.c.backoff_ms = self.backoff_ms;
|
||||
for x in &self.retry_on {
|
||||
let cs = CString::new(x.as_str()).unwrap_or_default();
|
||||
h._a_retry_on.push(cs.as_ptr());
|
||||
h._s.push(cs);
|
||||
}
|
||||
h.c.retry_on = if h._a_retry_on.is_empty() { std::ptr::null() } else { h._a_retry_on.as_ptr() };
|
||||
h.c.retry_on_len = h._a_retry_on.len();
|
||||
h
|
||||
}
|
||||
pub fn from_c(c: &ffi::RetryPolicy) -> Self {
|
||||
RetryPolicy {
|
||||
max_attempts: c.max_attempts,
|
||||
backoff_ms: c.backoff_ms,
|
||||
retry_on: if c.retry_on.is_null() { Vec::new() } else { unsafe { std::slice::from_raw_parts(c.retry_on, c.retry_on_len) }.iter().map(|&p| cstr(p)).collect() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ScheduleConfig {
|
||||
pub start_at_ms: i64,
|
||||
pub interval_ms: i64,
|
||||
pub jitter: Option<i64>,
|
||||
}
|
||||
|
||||
pub struct ScheduleConfigC {
|
||||
pub c: ffi::ScheduleConfig,
|
||||
_s: Vec<CString>,
|
||||
}
|
||||
|
||||
impl ScheduleConfig {
|
||||
pub fn to_c(&self) -> ScheduleConfigC {
|
||||
#[allow(unused_mut)]
|
||||
let mut h = ScheduleConfigC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
|
||||
};
|
||||
h.c.start_at_ms = self.start_at_ms;
|
||||
h.c.interval_ms = self.interval_ms;
|
||||
if let Some(v) = self.jitter { h.c.jitter_present = 1; h.c.jitter = v; }
|
||||
h
|
||||
}
|
||||
pub fn from_c(c: &ffi::ScheduleConfig) -> Self {
|
||||
ScheduleConfig {
|
||||
start_at_ms: c.start_at_ms,
|
||||
interval_ms: c.interval_ms,
|
||||
jitter: if c.jitter_present != 0 { Some(c.jitter) } else { None },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ScheduleResult {
|
||||
pub job_id: String,
|
||||
@ -139,17 +321,21 @@ pub struct ScheduleResult {
|
||||
pub effective_backoff_ms: i64,
|
||||
}
|
||||
|
||||
pub struct ScheduleResultC {
|
||||
pub c: ffi::ScheduleResult,
|
||||
_s: Vec<CString>,
|
||||
}
|
||||
|
||||
impl ScheduleResult {
|
||||
pub fn to_c_inner(&self, strings: &mut Vec<CString>) -> ffi::ScheduleResult {
|
||||
let job_id_c = CString::new(self.job_id.as_str()).unwrap_or_default();
|
||||
let job_id_p = job_id_c.as_ptr();
|
||||
strings.push(job_id_c);
|
||||
ffi::ScheduleResult {
|
||||
job_id: job_id_p,
|
||||
will_run_count: self.will_run_count,
|
||||
first_run_at_ms: self.first_run_at_ms,
|
||||
effective_backoff_ms: self.effective_backoff_ms,
|
||||
}
|
||||
pub fn to_c(&self) -> ScheduleResultC {
|
||||
#[allow(unused_mut)]
|
||||
let mut h = ScheduleResultC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
|
||||
};
|
||||
{ let cs = CString::new(self.job_id.as_str()).unwrap_or_default(); h.c.job_id = cs.as_ptr(); h._s.push(cs); }
|
||||
h.c.will_run_count = self.will_run_count;
|
||||
h.c.first_run_at_ms = self.first_run_at_ms;
|
||||
h.c.effective_backoff_ms = self.effective_backoff_ms;
|
||||
h
|
||||
}
|
||||
pub fn from_c(c: &ffi::ScheduleResult) -> Self {
|
||||
ScheduleResult {
|
||||
|
||||
@ -30,6 +30,14 @@ proc isStructT(t: string, types: seq[FFITypeMeta]): bool =
|
||||
return true
|
||||
false
|
||||
|
||||
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 rustScalar(t: string): string =
|
||||
case t.strip()
|
||||
of "int", "int64", "clong": "i64"
|
||||
@ -47,35 +55,28 @@ proc rustScalar(t: string): string =
|
||||
|
||||
proc rustIdiomatic(t: string): string =
|
||||
let s = t.strip()
|
||||
if isStringT(s): "String"
|
||||
if isSeqT(s): "Vec<" & rustIdiomatic(seqElem(s)) & ">"
|
||||
elif isOptT(s): "Option<" & rustIdiomatic(optElem(s)) & ">"
|
||||
elif isStringT(s): "String"
|
||||
else: rustScalar(s)
|
||||
|
||||
proc rustCField(t: string, types: seq[FFITypeMeta]): string =
|
||||
## repr(C) field type, matching codegen/c.emitCStructs.
|
||||
proc rustCElem(t: string, types: seq[FFITypeMeta], qualify = false): string =
|
||||
## repr(C) type for one seq element / option payload (matches emitCStructs).
|
||||
## `qualify` prefixes struct names with `ffi::` (needed outside the ffi module).
|
||||
let s = t.strip()
|
||||
if isStringT(s): "*const c_char"
|
||||
elif s == "bool": "c_int"
|
||||
elif isStructT(s, types): capitalizeFirstLetter(s) # sibling repr(C) struct
|
||||
elif isStructT(s, types): (if qualify: "ffi::" else: "") & capitalizeFirstLetter(s)
|
||||
else: rustScalar(s)
|
||||
|
||||
proc typeSimple(t: FFITypeMeta): bool =
|
||||
for f in t.fields:
|
||||
if isSeqT(f.typeName) or isOptT(f.typeName):
|
||||
return false
|
||||
true
|
||||
|
||||
proc procSimple(p: FFIProcMeta, types: seq[FFITypeMeta]): bool =
|
||||
## A proc is "simple" if no param is a raw pointer, a bare seq/Option, or a
|
||||
## struct that itself carries seq/Option fields (those structs aren't emitted
|
||||
## yet, so referencing them would not compile).
|
||||
## All scalar / string / struct params are supported (structs may carry
|
||||
## seq/Option fields). Bare seq/Option top-level params and raw pointers are
|
||||
## not (the native ABI doesn't expose them either).
|
||||
for ep in p.extraParams:
|
||||
let t = ep.typeName.strip()
|
||||
if ep.isPtr or isSeqT(t) or isOptT(t):
|
||||
return false
|
||||
if isStructT(t, types):
|
||||
for ty in types:
|
||||
if ty.name == t and not typeSimple(ty):
|
||||
return false
|
||||
true
|
||||
|
||||
# ── ffi.rs ──────────────────────────────────────────────────────────────────
|
||||
@ -90,15 +91,22 @@ proc emitFfiRs(
|
||||
L.add("pub type FFICallback =")
|
||||
L.add(" unsafe extern \"C\" fn(ret: c_int, msg: *const c_char, len: usize, user_data: *mut c_void);")
|
||||
L.add("")
|
||||
# repr(C) POD mirrors.
|
||||
# repr(C) POD mirrors — field layout mirrors codegen/c.emitCStructs.
|
||||
for t in types:
|
||||
if not typeSimple(t):
|
||||
continue
|
||||
L.add("#[repr(C)]")
|
||||
L.add("#[derive(Clone, Copy)]")
|
||||
L.add("pub struct " & capitalizeFirstLetter(t.name) & " {")
|
||||
for f in t.fields:
|
||||
L.add(" pub " & camelToSnakeCase(f.name) & ": " & rustCField(f.typeName, types) & ",")
|
||||
let snake = camelToSnakeCase(f.name)
|
||||
let ft = f.typeName.strip()
|
||||
if isSeqT(ft):
|
||||
L.add(" pub " & snake & ": *const " & rustCElem(seqElem(ft), types) & ",")
|
||||
L.add(" pub " & snake & "_len: usize,")
|
||||
elif isOptT(ft):
|
||||
L.add(" pub " & snake & "_present: c_int,")
|
||||
L.add(" pub " & snake & ": " & rustCElem(optElem(ft), types) & ",")
|
||||
else:
|
||||
L.add(" pub " & snake & ": " & rustCElem(ft, types) & ",")
|
||||
L.add("}")
|
||||
L.add("")
|
||||
L.add("#[link(name = \"" & libName & "\")]")
|
||||
@ -108,7 +116,7 @@ proc emitFfiRs(
|
||||
of FFIKind.CTOR:
|
||||
var ps: seq[string] = @[]
|
||||
for ep in p.extraParams:
|
||||
ps.add(camelToSnakeCase(ep.name) & ": " & rustCField(ep.typeName, types))
|
||||
ps.add(camelToSnakeCase(ep.name) & ": " & rustCElem(ep.typeName, types))
|
||||
ps.add("callback: FFICallback")
|
||||
ps.add("user_data: *mut c_void")
|
||||
L.add(" pub fn " & p.procName & "(" & ps.join(", ") & ") -> *mut c_void;")
|
||||
@ -117,7 +125,7 @@ proc emitFfiRs(
|
||||
continue
|
||||
var ps = @["ctx: *mut c_void", "callback: FFICallback", "user_data: *mut c_void"]
|
||||
for ep in p.extraParams:
|
||||
ps.add(camelToSnakeCase(ep.name) & ": " & rustCField(ep.typeName, types))
|
||||
ps.add(camelToSnakeCase(ep.name) & ": " & rustCElem(ep.typeName, types))
|
||||
L.add(" pub fn " & p.procName & "(" & ps.join(", ") & ") -> c_int;")
|
||||
of FFIKind.DTOR:
|
||||
L.add(" pub fn " & p.procName & "(ctx: *mut c_void) -> c_int;")
|
||||
@ -128,6 +136,7 @@ proc emitFfiRs(
|
||||
proc emitTypesRs(types: seq[FFITypeMeta]): string =
|
||||
var L: seq[string] = @[]
|
||||
L.add("// Generated by nim-ffi native Rust codegen. Do not edit by hand.")
|
||||
L.add("#![allow(dead_code)]")
|
||||
L.add("use std::ffi::{CStr, CString};")
|
||||
L.add("use std::os::raw::c_char;")
|
||||
L.add("use super::ffi;")
|
||||
@ -138,55 +147,122 @@ proc emitTypesRs(types: seq[FFITypeMeta]): string =
|
||||
L.add("}")
|
||||
L.add("")
|
||||
for t in types:
|
||||
if not typeSimple(t):
|
||||
continue
|
||||
let rn = capitalizeFirstLetter(t.name)
|
||||
let hn = rn & "C"
|
||||
# idiomatic struct
|
||||
L.add("#[derive(Clone, Debug, Default)]")
|
||||
L.add("pub struct " & rn & " {")
|
||||
for f in t.fields:
|
||||
L.add(" pub " & camelToSnakeCase(f.name) & ": " & rustIdiomatic(f.typeName) & ",")
|
||||
L.add("}")
|
||||
L.add("")
|
||||
L.add("impl " & rn & " {")
|
||||
# to_c_inner: build the C struct, pushing owned CStrings into `strings`.
|
||||
L.add(" pub fn to_c_inner(&self, strings: &mut Vec<CString>) -> ffi::" & rn & " {")
|
||||
# holder: owns the C struct + all heap backing (CStrings, arrays, nested
|
||||
# holders). Returned by value; the backing lives on the heap, so the C
|
||||
# struct's raw pointers stay valid across the move and for the call.
|
||||
L.add("pub struct " & hn & " {")
|
||||
L.add(" pub c: ffi::" & rn & ",")
|
||||
L.add(" _s: Vec<CString>,")
|
||||
for f in t.fields:
|
||||
let snake = camelToSnakeCase(f.name)
|
||||
let s = camelToSnakeCase(f.name)
|
||||
let ft = f.typeName.strip()
|
||||
if isStringT(ft):
|
||||
L.add(" let " & snake & "_c = CString::new(self." & snake &
|
||||
".as_str()).unwrap_or_default();")
|
||||
L.add(" let " & snake & "_p = " & snake & "_c.as_ptr();")
|
||||
L.add(" strings.push(" & snake & "_c);")
|
||||
L.add(" ffi::" & rn & " {")
|
||||
for f in t.fields:
|
||||
let snake = camelToSnakeCase(f.name)
|
||||
let ft = f.typeName.strip()
|
||||
if isStringT(ft):
|
||||
L.add(" " & snake & ": " & snake & "_p,")
|
||||
elif ft == "bool":
|
||||
L.add(" " & snake & ": if self." & snake & " { 1 } else { 0 },")
|
||||
if isSeqT(ft):
|
||||
L.add(" _a_" & s & ": Vec<" & rustCElem(seqElem(ft), types, qualify = true) & ">,")
|
||||
if isStructT(seqElem(ft), types):
|
||||
L.add(" _h_" & s & ": Vec<" & capitalizeFirstLetter(seqElem(ft)) & "C>,")
|
||||
elif isOptT(ft) and isStructT(optElem(ft), types):
|
||||
L.add(" _h_" & s & ": Vec<" & capitalizeFirstLetter(optElem(ft)) & "C>,")
|
||||
elif isStructT(ft, types):
|
||||
L.add(" " & snake & ": self." & snake & ".to_c_inner(strings),")
|
||||
L.add(" _h_" & s & ": Vec<" & capitalizeFirstLetter(ft) & "C>,")
|
||||
L.add("}")
|
||||
L.add("")
|
||||
L.add("impl " & rn & " {")
|
||||
L.add(" pub fn to_c(&self) -> " & hn & " {")
|
||||
L.add(" #[allow(unused_mut)]")
|
||||
L.add(" let mut h = " & hn & " { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),")
|
||||
for f in t.fields:
|
||||
let s = camelToSnakeCase(f.name)
|
||||
let ft = f.typeName.strip()
|
||||
if isSeqT(ft):
|
||||
L.add(" _a_" & s & ": Vec::new(),")
|
||||
if isStructT(seqElem(ft), types):
|
||||
L.add(" _h_" & s & ": Vec::new(),")
|
||||
elif (isOptT(ft) and isStructT(optElem(ft), types)) or isStructT(ft, types):
|
||||
L.add(" _h_" & s & ": Vec::new(),")
|
||||
L.add(" };")
|
||||
for f in t.fields:
|
||||
let s = camelToSnakeCase(f.name)
|
||||
let ft = f.typeName.strip()
|
||||
if isSeqT(ft):
|
||||
let e = seqElem(ft)
|
||||
L.add(" for x in &self." & s & " {")
|
||||
if isStringT(e):
|
||||
L.add(" let cs = CString::new(x.as_str()).unwrap_or_default();")
|
||||
L.add(" h._a_" & s & ".push(cs.as_ptr());")
|
||||
L.add(" h._s.push(cs);")
|
||||
elif isStructT(e, types):
|
||||
L.add(" let eh = x.to_c();")
|
||||
L.add(" h._a_" & s & ".push(eh.c);")
|
||||
L.add(" h._h_" & s & ".push(eh);")
|
||||
elif e == "bool":
|
||||
L.add(" h._a_" & s & ".push(if *x { 1 } else { 0 });")
|
||||
else:
|
||||
L.add(" h._a_" & s & ".push(*x);")
|
||||
L.add(" }")
|
||||
L.add(" h.c." & s & " = if h._a_" & s & ".is_empty() { std::ptr::null() } else { h._a_" & s & ".as_ptr() };")
|
||||
L.add(" h.c." & s & "_len = h._a_" & s & ".len();")
|
||||
elif isOptT(ft):
|
||||
let e = optElem(ft)
|
||||
if isStringT(e):
|
||||
L.add(" if let Some(x) = &self." & s & " {")
|
||||
L.add(" let cs = CString::new(x.as_str()).unwrap_or_default();")
|
||||
L.add(" h.c." & s & " = cs.as_ptr(); h.c." & s & "_present = 1; h._s.push(cs);")
|
||||
L.add(" }")
|
||||
elif isStructT(e, types):
|
||||
L.add(" if let Some(x) = &self." & s & " {")
|
||||
L.add(" let eh = x.to_c(); h.c." & s & " = eh.c; h.c." & s & "_present = 1; h._h_" & s & ".push(eh);")
|
||||
L.add(" }")
|
||||
elif e == "bool":
|
||||
L.add(" if let Some(v) = self." & s & " { h.c." & s & "_present = 1; h.c." & s & " = if v { 1 } else { 0 }; }")
|
||||
else:
|
||||
L.add(" if let Some(v) = self." & s & " { h.c." & s & "_present = 1; h.c." & s & " = v; }")
|
||||
elif isStringT(ft):
|
||||
L.add(" { let cs = CString::new(self." & s & ".as_str()).unwrap_or_default(); h.c." & s & " = cs.as_ptr(); h._s.push(cs); }")
|
||||
elif ft == "bool":
|
||||
L.add(" h.c." & s & " = if self." & s & " { 1 } else { 0 };")
|
||||
elif isStructT(ft, types):
|
||||
L.add(" h._h_" & s & ".push(self." & s & ".to_c()); h.c." & s & " = h._h_" & s & "[0].c;")
|
||||
else:
|
||||
L.add(" " & snake & ": self." & snake & ",")
|
||||
L.add(" }")
|
||||
L.add(" h.c." & s & " = self." & s & ";")
|
||||
L.add(" h")
|
||||
L.add(" }")
|
||||
# from_c
|
||||
L.add(" pub fn from_c(c: &ffi::" & rn & ") -> Self {")
|
||||
L.add(" " & rn & " {")
|
||||
for f in t.fields:
|
||||
let snake = camelToSnakeCase(f.name)
|
||||
let s = camelToSnakeCase(f.name)
|
||||
let ft = f.typeName.strip()
|
||||
if isStringT(ft):
|
||||
L.add(" " & snake & ": cstr(c." & snake & "),")
|
||||
if isSeqT(ft):
|
||||
let e = seqElem(ft)
|
||||
var conv = "|&v| v"
|
||||
if isStringT(e): conv = "|&p| cstr(p)"
|
||||
elif isStructT(e, types): conv = "|e| " & capitalizeFirstLetter(e) & "::from_c(e)"
|
||||
elif e == "bool": conv = "|&v| v != 0"
|
||||
L.add(" " & s & ": if c." & s & ".is_null() { Vec::new() } else { unsafe { std::slice::from_raw_parts(c." & s & ", c." & s & "_len) }.iter().map(" & conv & ").collect() },")
|
||||
elif isOptT(ft):
|
||||
let e = optElem(ft)
|
||||
var val = "c." & s
|
||||
if isStringT(e): val = "cstr(c." & s & ")"
|
||||
elif isStructT(e, types): val = capitalizeFirstLetter(e) & "::from_c(&c." & s & ")"
|
||||
elif e == "bool": val = "c." & s & " != 0"
|
||||
L.add(" " & s & ": if c." & s & "_present != 0 { Some(" & val & ") } else { None },")
|
||||
elif isStringT(ft):
|
||||
L.add(" " & s & ": cstr(c." & s & "),")
|
||||
elif ft == "bool":
|
||||
L.add(" " & snake & ": c." & snake & " != 0,")
|
||||
L.add(" " & s & ": c." & s & " != 0,")
|
||||
elif isStructT(ft, types):
|
||||
L.add(" " & snake & ": " & capitalizeFirstLetter(ft) &
|
||||
"::from_c(&c." & snake & "),")
|
||||
L.add(" " & s & ": " & capitalizeFirstLetter(ft) & "::from_c(&c." & s & "),")
|
||||
else:
|
||||
L.add(" " & snake & ": c." & snake & ",")
|
||||
L.add(" " & s & ": c." & s & ",")
|
||||
L.add(" }")
|
||||
L.add(" }")
|
||||
L.add("}")
|
||||
@ -257,11 +333,12 @@ proc emitApiRs(
|
||||
for ep in p.extraParams:
|
||||
params.add(camelToSnakeCase(ep.name) & ": " & capitalizeFirstLetter(ep.typeName))
|
||||
L.add(" pub fn new(" & params.join(", ") & ") -> Result<Self, String> {")
|
||||
L.add(" let mut strings = Vec::new();")
|
||||
var args: seq[string] = @[]
|
||||
for ep in p.extraParams:
|
||||
L.add(" let c_" & camelToSnakeCase(ep.name) & " = " & camelToSnakeCase(ep.name) & ".to_c_inner(&mut strings);")
|
||||
args.add("c_" & camelToSnakeCase(ep.name))
|
||||
# The holder must outlive the call (it owns the CStrings/arrays the C
|
||||
# struct points into); the native ctor deep-copies before returning.
|
||||
L.add(" let c_" & camelToSnakeCase(ep.name) & " = " & camelToSnakeCase(ep.name) & ".to_c();")
|
||||
args.add("c_" & camelToSnakeCase(ep.name) & ".c")
|
||||
L.add(" let (tx, rx) = sync_channel::<Result<(), String>>(1);")
|
||||
L.add(" let raw = Box::into_raw(Box::new(tx)) as *mut c_void;")
|
||||
let cargs = (args & @["ack_cb", "raw"]).join(", ")
|
||||
@ -269,7 +346,6 @@ proc emitApiRs(
|
||||
L.add(" let res = rx.recv().map_err(|_| String::from(\"callback channel closed\"))?;")
|
||||
L.add(" res?;")
|
||||
L.add(" if ctx.is_null() { return Err(String::from(\"" & p.procName & " returned null\")); }")
|
||||
L.add(" let _ = strings;")
|
||||
L.add(" Ok(" & nodeT & " { ctx })")
|
||||
L.add(" }")
|
||||
L.add("")
|
||||
@ -288,12 +364,10 @@ proc emitApiRs(
|
||||
var callArgs: seq[string] = @["self.ctx", cbName, "raw"]
|
||||
for ep in p.extraParams:
|
||||
params.add(camelToSnakeCase(ep.name) & ": " & capitalizeFirstLetter(ep.typeName))
|
||||
conv.add(" let c_" & camelToSnakeCase(ep.name) & " = " & camelToSnakeCase(ep.name) & ".to_c_inner(&mut strings);")
|
||||
callArgs.add("c_" & camelToSnakeCase(ep.name))
|
||||
conv.add(" let c_" & camelToSnakeCase(ep.name) & " = " & camelToSnakeCase(ep.name) & ".to_c();")
|
||||
callArgs.add("c_" & camelToSnakeCase(ep.name) & ".c")
|
||||
L.add(" pub fn " & rustMethod(p.procName, libName) & "(&self" & (if params.len > 0: ", " & params.join(", ") else: "") & ") -> Result<" & retT & ", String> {")
|
||||
if conv.len > 0:
|
||||
L.add(" let mut strings = Vec::new();")
|
||||
for c in conv: L.add(c)
|
||||
for c in conv: L.add(c)
|
||||
L.add(" let (tx, rx) = sync_channel::<Result<" & retT & ", String>>(1);")
|
||||
L.add(" let raw = Box::into_raw(Box::new(tx)) as *mut c_void;")
|
||||
L.add(" let rc = unsafe { ffi::" & p.procName & "(" & callArgs.join(", ") & ") };")
|
||||
@ -301,8 +375,6 @@ proc emitApiRs(
|
||||
L.add(" drop(unsafe { Box::from_raw(raw as *mut SyncSender<Result<" & retT & ", String>>) });")
|
||||
L.add(" return Err(String::from(\"" & p.procName & " dispatch failed\"));")
|
||||
L.add(" }")
|
||||
if conv.len > 0:
|
||||
L.add(" let _ = strings;")
|
||||
L.add(" rx.recv().map_err(|_| String::from(\"callback channel closed\"))?")
|
||||
L.add(" }")
|
||||
L.add("")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user