【问题标题】:How to use from C# a Rust function that has another function as a parameter?如何从 C# 使用具有另一个函数作为参数的 Rust 函数?
【发布时间】:2022-01-01 17:35:00
【问题描述】:

以下示例说明了我想要做什么。 操作函数接收一个值和一个函数作为参数,然后返回函数的执行。到目前为止,一切都很完美。现在我想在库中使用该函数 operation 在 C# 中使用。

fn operation(number: i32, f: &dyn Fn(i32) -> i32) -> i32 {
    f(number)
}
fn square(number: i32) -> i32 {
    number * number
}
fn cube(number: i32) -> i32 {
    number * number * number
}

fn test() {// passing a function as parameter, OK :)
    println!("{}", operation(5, &square));
    println!("{}", operation(7, &square));
    println!("{}", operation(3, &cube));
}

// Tried without success :_(
/*
#[no_mangle] 
pub extern "C" c_operation(number: i32, f: &dyn Fn(i32) -> i32) -> *mut i32 {
    unsafe {
     &mut f(number)
    }
}
*/

函数c_operation的正确签名应该是什么?

我认为当签名在库中定义良好时,以下 C# 代码应该可以工作。

const string RSTLIB = "../../PathOfDLL";

public delegate int Fn(int number);

[DllImport(RSTLIB)] static extern int c_operation(int x, Fn fn);

int Square(int number) => number * number;
int Cube(int number) => number * number * number;

void Test()
{
    Console.WriteLine("{0}", c_operation(5, Square));
    Console.WriteLine("{0}", c_operation(7, Square));
    Console.WriteLine("{0}", c_operation(3, Cube));
}

感谢您的关注

【问题讨论】:

  • 你有一个涉及 Rust 和 C# 的问题,但是 C 和 C++ 是如何出现的呢?请不要发送不相关的标签。
  • 感谢您的评论。添加这些标签是因为与来自 Rust、C 或 C++ 库的 C# 的互操作性在 C# 代码中没有太大区别。

标签: c# rust language-interoperability


【解决方案1】:

对于从 C 中使用,声明 c_operation 并根据 operation 定义它的一种方法是:

#[no_mangle]
pub extern "C" fn c_operation(
    number: i32,
    f: unsafe extern "C" fn(i32) -> i32
) -> i32 {
    operation(number, &|n| unsafe { f(n) })
}

【讨论】:

  • 是的! c_operation(5, Square) : 25 c_operation(7, Square) : 49 c_operation(3, Cube) : 27
【解决方案2】:

dyn 指针不是 FFI 安全的,因此它们不能通过 extern "C" 屏障。您实际上会收到关于此的警告:

警告:extern fn 使用类型 dyn Fn(i32) -> i32,这不是 FFI 安全的。

但是你可以传递一个普通的函数指针,只要它也是extern "C",当然):

#[no_mangle] 
pub extern "C" fn c_operation(number: i32, f: extern "C" fn(i32) -> i32) -> *mut i32 {
    unsafe {
        &mut f(number)
    }
}

(您正在返回一个指向本地临时的悬空指针,但我猜这只是因为您正在编写虚拟代码。)

然后,从 C 开始,您可以使用普通函数,但 closure 数据没有位置。这对于 C 或 C++ 来说是个问题,但对于 C# 来说不是,因为它会从任何委托创建原始的普通函数指针,只要您正确地装饰委托类型:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int Fn(int number)

然后,您的 c_operation() 声明应该可以正常工作,除了您在 C# 中的返回类型 int 而 Rust 版本返回一个指针。那对应于IntPtr

[DllImport(RSTLIB)] static extern IntPtr c_operation(int x, Fn fn);

另外,请注意,如果 c_operation() 存储指针 fn 以便稍后调用它,那么在 C# 中您必须保持委托对象处于活动状态,要么将其保存在变量中,要么分配给它一个GCHandle

    Fn fSquare = Square;
    //keep fSquare somewhere
    c_operation(5, fSquare);

如果你没有这样做,垃圾收集器会收集自动临时的 delgate(编译器为你创建的),然后 Rust 代码会尝试调用存储的函数指针......好吧,这不会很好.

【讨论】:

  • 考虑将此知识添加到github.com/shepmaster/rust-ffi-omnibus
  • @Stargateur:我不知道那个页面,它很不错,但很抱歉,我不太熟悉这个网站使用的 Jekyll 东西,或者许多语言涉及。但请随意复制我的答案中的任何一段文字。
【解决方案3】:

User4815162342 的回答正是我想要的。我想补充一点,令人惊讶的是,您可以将 C# 函数作为参数传递给 Rust 函数,并获得结果。同样,我们可以在 C# 中通过回调将 Rust 函数传递给同一个 Rust 库。为了结束这件事,我将展示结果。

//  user4815162342 answer:
#[no_mangle]
pub extern "C" fn c_operation(number: i32, f: unsafe extern "C" fn(i32) -> i32) -> i32 {
    operation(number, &|n| unsafe { f(n) })
}

// by example
#[no_mangle]
fn cube(number: i32) -> i32 {
    number * number * number
}

C# 代码:

const string RSTLIB = "...\release\rstlib.dll";

public delegate int Fn(int number);

[DllImport(RSTLIB)] static extern int c_operation(int number, Fn fn);
[DllImport(RSTLIB)] static extern int cube(int number);

int Square(int number) => number * number;
int Cube(int number) => number * number * number;

public void Test()
{
    // passing a C# function as parameter of Rust function
    Console.WriteLine("c_operation(5, Square) : {0}", c_operation(5, Square));
    Console.WriteLine("c_operation(3, Cube)   : {0}", c_operation(3, Cube));

    // passing a Rust function as callback inside the same Rust function
    Console.WriteLine("c_operation(5, cube)   : {0}", c_operation(5, cube));

    // Output
    // c_operation(5, Square) : 25
    // c_operation(3, Cube)   : 27
    // c_operation(5, cube)   : 125
}

感谢您的关注。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-27
    • 2018-11-13
    • 1970-01-01
    • 2013-07-10
    • 2016-09-23
    • 1970-01-01
    • 1970-01-01
    • 2021-04-29
    相关资源
    最近更新 更多