You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 19:07:09