如何在 Rust 中并行创建一个数组并将其 return 到 C#?

How to create an array in Rust in parallel and return it to C#?

我尝试在 Rust 中并行创建一个数组,然后 return 通过 DLL 绑定将其转换为 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# 绑定和函数调用

[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 barrier的后半部分是C。我没有合适的环境来展示我的C#技能。

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

生锈的一半实际上是这样的:

#[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 时,我可以用这段代码重现错误

这里是 "fixed" 版本:

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(),这样的好处是您不必记住 forget 切片。