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

4
我试图在Rust中并行创建一个数组,并通过DLL绑定将其返回给C#。但前4个元素是无效的。
如果没有线程池和同步,我会得到正确的结果。实际计算更加复杂,但以下代码是一个简化版本,不涉及真正的计算。我还尝试过使用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

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

你能检查一下指针地址在跨越边界时是否实际上是相同的吗? - Sébastien Renauld
@SébastienRenauld 在C#中,resultPtr为0x000002404d35d480,而println!("Pointer address {:p}",result);则打印出“Pointer address 0x410437d650”——不一样,或者我需要以不同的方式获取值吗?但是result_data与C#中的相同。 - MiBa
是的,那与此无关,我想。正在撰写解决方案。 - Sébastien Renauld
这篇文章与问题看起来不同,但是否有帮助?Rust FFI Omnibus - kunif
1个回答

4

首先,抱歉,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(),这样做的好处是不需要记得forget slice。


太好了。很高兴能帮忙! :-) - Sébastien Renauld
3
你应该使用result_slice.into_raw()而不是as_mut_ptr,这意味着你不能忘记mem::forget result_slice - trent
@trentcl 我想强调一下这个问题,所以故意保留了mem::forget的调用,因为那里是问题所在。不过我会添加一个小免责声明。 - Sébastien Renauld

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接