【问题标题】:How to free the memory of a string returned from Rust in C#?如何在 C# 中释放从 Rust 返回的字符串的内存?
【发布时间】:2016-01-12 07:19:09
【问题描述】:

我从 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# 中,我想调用 get version 函数,获取字符串,然后将其传递回 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 ;
    }
}

但是 C# 程序在运行 rustffi_get_version_free(temp) 时会崩溃。如何在 C# 中释放被遗忘的字符串内存?应该将什么传回 Rust 进行释放?

我没有将 string 定义为 C# extern 中的参数,而是将其更改为 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) 运行时,它会抛出异常stack unbalanced

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

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

【问题讨论】:

  • 你返回的字符串一旦进入 .NET 领域就会是一个托管字符串......因此你不能只将指向它的指针传递回 Rust,因为 .NET 垃圾收集器可以移动它。我想知道您是否需要添加 MarshalAs 属性来帮助解决这个问题......不幸的是我现在无法测试它(而且我还没有完成任何 C#->Rust 互操作!)。跨度>
  • @simon,是的,我就是这么想的。那么,您认为我应该将 C# 中的接口函数更改为 [DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version")] public static extern const char* rustffi_get_version(); ?然后我自己将 char* 显式转换为字符串
  • 这当然是一个值得探索的选择。
  • @tried,但得到另一个错误。我编辑问题以包含新信息。

标签: c# memory-management memory-leaks rust ffi


【解决方案1】:

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

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

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

或者,您可以在 Rust 端使用 extern "stdcall" fn

【讨论】:

    猜你喜欢
    • 2021-05-23
    • 1970-01-01
    • 1970-01-01
    • 2018-05-11
    • 1970-01-01
    • 2015-07-07
    • 2011-11-11
    • 1970-01-01
    相关资源
    最近更新 更多