如何在Rust中接收C#接口实例(以ILogger为例)
问题:在Rust中通过FFI接收C#接口实例(以Serilog ILogger为例)
我希望在Rust中定义函数,按引用接收实现C#接口的对象(以Serilog的ILogger接口为例,理想情况可适配任意接口),让C#前端调用可能长时间运行的Rust后端,并传递包含该接口实例在内的若干数据。
现有实现(单方法接口可行)
目前已实现直接传递ILogger.Write方法的指针而非对象本身,对应C#和Rust代码如下:
C# 代码
const string dll = "..."; [DllImport(dll)] static extern void foo_fn(IntPtr log, [MarshalAs(UnmanagedType.LPUTF8Str)] string input); public delegate void LogFunc(LogEventLevel level, string message); public static void FooFn(LogFunc log, string input) { foo_fn(Marshal.GetFunctionPointerForDelegate(log), input); } public static void FooInterface(ILogger log, string input) { FooFn(log.Write, input); }
Rust 代码
use std::ffi::{c_char, CStr, CString}; #[no_mangle] pub extern "C" fn foo_fn( log: extern "stdcall" fn(log::Level, *const c_char), input: *const c_char, ) { let input = unsafe { CStr::from_ptr(input) }.to_str().unwrap(); foo_internal(&log, input) } impl log::Logger for extern "stdcall" fn(log::Level, *const c_char) { fn write(&self, level: log::Level, msg: &str) { let msg = CString::new(msg).unwrap(); self(level, msg.as_ptr()); } } mod log { #[repr(C)] pub enum Level { Verbose, Debug, Information, Warning, Error, Fatal, } pub trait Logger { fn write(&self, level: Level, msg: &str); fn verbose(&self, msg: &str) { self.write(Level::Verbose, msg); } fn debug(&self, msg: &str) { self.write(Level::Debug, msg); } fn information(&self, msg: &str) { self.write(Level::Information, msg); } fn warning(&self, msg: &str) { self.write(Level::Warning, msg); } fn error(&self, msg: &str) { self.write(Level::Error, msg); } fn fatal(&self, msg: &str) { self.write(Level::Fatal, msg); } } } fn foo_internal(log: &dyn log::Logger, input: &str) { log.information(&format!("received input \"{}\"", input)); log.information(&format!("how's this for output")); }
现有方案的局限性
该方案对ILogger可行,因接口仅基于单个方法/函数指针,但无法适配其他多方法接口。
期望实现目标
期望能传递包含相关vtable的(假定为不透明的)对象指针,在Rust中为其实现对应trait,仅采用借用语义(不转移所有权),仅耦合于接口本身。
未实现的示例代码
C# 代码
[DllImport(dll)] private static extern void foo_interface([SomethingToMarshalTheVtable]ILogger log, [MarshalAs(UnmanagedType.LPUTF8Str)] string input); public static void FooInterface(ILogger log, string input) { foo_interface(log, input); }
Rust 代码
#[repr(C)] pub struct AbstractLogger { pub object: *const Object, pub vtable: *const LoggerVTable, } extern "C" { type Object; } #[repr(C)] pub struct LoggerVTable { pub write: fn(*const Object, log::Level, &str), } impl log::Logger for AbstractLogger { fn write(&self, level: log::Level, msg: &str) { let vt = unsafe { &*self.vtable }; (vt.write)(self.object, level, msg) } } #[no_mangle] pub extern "C" fn act_interface(log: AbstractLogger, input: *const c_char) { let input = unsafe { CStr::from_ptr(input) }.to_str().unwrap(); foo(&log, input) }
注:仅希望借用C#对象,无意转移所有权;已知FFI中unwrap会导致unsound unwind,后续会修复。
内容的提问来源于stack exchange,提问作者Zoey Hewll
相关产品推荐
相关产品推荐

