如何在C#中释放从Rust返回的字符串的内存?

5

我有这两个函数从Rust暴露出来

extern crate libc;

use std::mem;
use std::ffi::{CString, CStr};

use libc::c_char;

pub static FFI_LIB_VERSION: &'static str = env!("CARGO_PKG_VERSION"); // '

#[no_mangle]
pub extern "C" fn rustffi_get_version() -> *const c_char {
    let s = CString::new(FFI_LIB_VERSION).unwrap();
    let p = s.as_ptr();
    mem::forget(s);

    p as *const _
}

#[no_mangle]
pub extern "C" fn rustffi_get_version_free(s: *mut c_char) {
    unsafe {
        if s.is_null() {
            return;
        }

        let c_str: &CStr = CStr::from_ptr(s);
        let bytes_len: usize = c_str.to_bytes_with_nul().len();
        let temp_vec: Vec<c_char> = Vec::from_raw_parts(s, bytes_len, bytes_len);
    }
}

fn main() {}

他们被 C# 导入如下:
namespace rustFfiLibrary
{
    public class RustFfiApi
    {
        [DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version")]
        public static extern string rustffi_get_version();

        [DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version_free")]
        public static extern void rustffi_get_version_free(string s);

    }
}

rustffi_get_version返回的字符串内存已经不再由Rust管理,因为已经调用了mem::forget。在C#中,我想调用获取版本的函数,获取字符串,然后像下面这样将其传回给Rust以进行内存释放。

public class RustService
{
    public static string GetVersion()
    {
        string temp = RustFfiApi.rustffi_get_version();
        string ver = (string)temp.Clone();

        RustFfiApi.rustffi_get_version_free(temp);

        return ver ;
    }
}

但是,当运行 rustffi_get_version_free(temp) 时,C# 程序崩溃了。如何在 C# 中释放被遗忘的字符串内存?需要传递什么值回 Rust 进行 deallocation?
与其在 C# extern 中将参数定义为 string,我将其改为 pointer
[DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version")]
public static extern System.IntPtr rustffi_get_version();

[DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version_free")]
public static extern void rustffi_get_version_free(System.IntPtr s);

public static string GetVersion()
{
    System.IntPtr tempPointer = RustFfiApi.rustffi_get_version();
    string tempString = Marshal.PtrToStringAnsi(tempPointer);

    string ver = (string)tempString.Clone();

    RustFfiApi.rustffi_get_version_free(tempPointer);

    return ver ;
}
rustffi_get_version方法返回的IntPtr可以成功转换为C#中的托管字符串类型。tempStringver都是好的选择。
当运行rustffi_get_version_free(tempPointer)时,会抛出一个异常,其中包含“堆栈未平衡”的信息:

PInvoke函数'rustFfiLibrary!rustFfiLibrary.RustFfiApi :: rustffi_get_version_free'的调用未平衡堆栈。这很可能是因为托管PInvoke签名与非托管目标签名不匹配。请检查PInvoke签名的调用约定和参数是否与目标非托管签名匹配。

在我的系统上,sizeof(IntPtr)sizeof(char *)都是4。此外,IntPtr可用于返回值;为什么它不能作为输入参数?

你返回的字符串一旦进入.NET环境,就会变成托管字符串...因此,你不能只是将指针传回Rust,因为.NET垃圾回收器可能会移动它。我想知道是否需要添加MarshalAs属性来解决这个问题...不幸的是,我现在无法测试它(我还没有做过C#->Rust互操作!)。 - Simon Whitehead
@Simon,是的,我也这么认为。那么,你认为我应该将C#中的接口函数更改为[DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version")] public static extern const char* rustffi_get_version();吗?然后我自己进行char*到string的显式转换。 - palazzo train
那肯定是一个值得探索的选项。 - Simon Whitehead
@尝试过,但出现了另一个错误。我编辑了问题以包含新的信息。 - palazzo train
1个回答

8

extern "C" fn 在 Rust 中表示函数使用 C 调用约定。 C# 默认情况下期望 P/Invoke 函数使用 stdcall 调用约定。

你可以告诉 C# 使用 C 调用约定:

[DllImport("rustffilib.dll", CallingConvention = CallingConvention.Cdecl)]

或者在 Rust 端使用 extern "stdcall" fn


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