c# - 如何在 Rust 中并行创建一个数组并将其返回给 C#?

标签 c# rust interop

我尝试在 Rust 中并行创建一个数组,并通过 DLL 绑定(bind)将其返回给 C#。前 4 个元素无效。

没有 ThreadPool 和同步,我得到了正确的结果。真正的计算比较复杂,但是下面的代码是没有真正计算的简化版。 我也尝试过 int* 而不是 IntPtr 但得到了相同的无效结果。

最后,我是 Rust 的新手,欢迎任何改进代码的建议。

简化的 Rust 计算

#[no_mangle]
pub extern "C" fn create_array(len: libc::c_int, result:*mut *mut libc::c_int){
    let mut result_vec: Vec<libc::c_int> = vec![0;len as usize];
    let sync_result=Arc::new(Mutex::new(result_vec));
    let pool=ThreadPool::new(6);

    println!("From Thread");
    for i in 0..(len){
        pool.execute({
            let clone = Arc::clone(&sync_result);
            move||{
            let mut result_vec = clone.lock().unwrap();
            result_vec[i as usize]=i;
            if i<10{
                println!("{}:{}",i,result_vec[i as usize]);
            }
        }});
    } 
    pool.join();

    let  mut result_vec = Arc::try_unwrap(sync_result).unwrap().into_inner().unwrap();

    println!("Unwrapped Vector");
    for i in 0..10{
        println!("{}:{}",i,result_vec[i as usize]);
    }
    let result_data = result_vec.as_mut_ptr();
    unsafe{
        println!("Raw data");
        *result=result_data;
        for i in 0..10 as isize{
            println!("{}:{}",i,ptr::read(result_data.offset(i)));
        }
    }
    std::mem::forget(result_data);
}

C#绑定(bind)和函数调用

[DllImport(@"libs\OptimizationRust.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void create_array(int len, out IntPtr result);
public void RustCpuSerial()
{
    IntPtr resultPtr;
    int len = 10000;
    create_array(len,out resultPtr);

    int[] results = new int[len];
    Marshal.Copy(resultPtr, results, 0, results.Length);
}

Rust 输出:

From Thread
0:0
5:5
7:7
8:8
9:9
1:1
3:3
4:4
6:6
2:2

Unwrapped Vector
0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:8
9:9

Raw data
0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:8
9:9

C# 输出:

0:-314008176
1:672
2:-314139296
3:672
4:4
5:5
6:6
7:7
8:8
9:9

任何想法,是什么导致了这种行为?

最佳答案

首先,抱歉,FFI 障碍的后半部分将是 C。我没有合适的环境来展示我的 C# 技能。

让我们先稍微总结一下您的代码,然后放弃所有线程(因为它什么都不做,只是在问题不存在时让我们感到困惑)

使用rust 的一半实际上是这样的:

#[no_mangle]
pub extern "C" fn create_array(len: libc::c_int, result:*mut *mut libc::c_int){ // You're passing a pointer to a collection
    let mut result_vec: Vec<i32> = vec![];  // You create a Vec<>
    for i in 0..(len){
        result_vec.push(i); // You fill it...
    }
    let result_data = result_vec.as_mut_ptr(); // You then get a *mut ptr to it
    unsafe{
        *result=result_data; // You then assign the content of the pointer-to-a-pointer of what you received to the result ptr you just acquired
    }
    std::mem::forget(result_data); // And then you forget your data, because it is referenced elsewhere
}

这是在任何更改之前。我添加了评论以总结您最终所做的事情。

果然,当从 C 执行 FFI 时,我可以使用这段代码重现错误:

enter image description here

这是“固定”版本:

pub extern "C" fn create_array(len: libc::c_int,result:*mut *mut libc::c_int){
    let mut results_vec: Vec<i32> = vec![];
    for i in 0..(len) {
      results_vec.push(i);
    }

    let mut result_slice = results_vec.into_boxed_slice(); // Change #1: Vec -> Box<[i32]>
    let result_data = result_slice.as_mut_ptr(); // We then get a *mut out of it
    unsafe {
        *result = result_data; // Same step as yours, we then overwrite the old pointer with its address
        // Do note that you may have leaked memory there if the pointer is not null.
        for i in 0..10 as isize{
            println!("{}:{}",i,ptr::read((*result).offset(i)));
        }
    }
    // We then forget *the boxed slice*. This is the important bit.
    std::mem::forget(result_slice);
}

这行得通,至少有少量用 C 编写的测试代码。它行得通而原始版本不行的原因是你没有忘记正确的事情,主要是 - 你忘记了 指针Vec,而不是 Vec 本身。结果,整个内存块在技术上未被初始化,并且显然在 FFI 使用和您打印出数据之间用于其他事情。

在实践中,您将调用 Box::into_raw(result_slice),而不是调用 result_slice.as_mut_ptr(),它的优点是不会让您必须记住忘记切片。

关于c# - 如何在 Rust 中并行创建一个数组并将其返回给 C#?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57773532/

相关文章:

c++ - 如何在 C++ 中使用 MATLAB 函数 randn?

c# - 我应该如何设置我的集成测试以将测试数据库与 Entity Framework 一起使用?

rust - 使用 Rust 裸机显示的输出

loops - 如何在for循环中跟踪先前的索引? ( rust )

java - 当第一个参数是具有一个方法的类时,无法用 lambda 替换 SAM 构造函数

asp.net - Microsoft.Office.Interop.Excel 的 Excel dll

c# - 有没有办法从 COM Interop 调用 C# 委托(delegate)?

c# - 动态添加行到datagridview

c# - C# 中的两个枚举何时相等?

rust - 如何将 impl Fn 存储在枚举类型中?