在DllImport中使用Unicode字符串和用Rust编写的DLL

Ido*_*nts 2 c# pinvoke dllimport rust

我试图从C#程序调用Rust编写的DLL.DLL有两个简单的函数,它们以不同的方式敲击并打印到控制台.

Rust DLL代码

#![crate_type = "lib"]
extern crate libc;

use libc::{c_char};
use std::ffi::CStr;

#[no_mangle]
pub extern fn printc(s: *const c_char){
    let c_str : &CStr = unsafe {
        assert!(!s.is_null());

        CStr::from_ptr(s)
    };

    println!("{:?}", c_str.to_bytes().len()); //prints "1" if unicode

    let r_str = std::str::from_utf8(c_str.to_bytes()).unwrap();
    println!("{:?}", r_str);
}

#[no_mangle]
pub extern fn print2(string: String) {
    println!("{:?}", string)
}
Run Code Online (Sandbox Code Playgroud)

C#控制台程序代码

[DllImport("lib.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
static extern void print2(ref string str);

[DllImport("lib.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void printc(string str);

static void Main(string[] args)
{
  try
  {
    var graw = "yeyeye";
    printc(graw);
    print2(ref graw);
  }
  catch (Exception ex)
  {
    Console.WriteLine("calamity!, {0}", ex.Message);
  }
  Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)

对于该print2功能,它一直在屏幕上打印垃圾,直到它造成AccessViolationException

第二个printc函数会打印字符串,但仅限于CharSet.Unicode未设置.如果设置,它将只打印第一个字符,因此println!("{:?}", c_str.to_bytes().len());将打印1.

我相信该Cstr::from_ptr函数不支持Unicode,这就是为什么它只返回字符串的第一个字符串.

知道如何将Unicode字符串作为参数传递给Rust DLL吗?是否有可能使功能更简单print2

DK.*_*DK. 5

如果您查看文档CharSet,您将看到CharSet.Unicode告诉.NET将字符串编组为UTF-16(每个代码点两个字节).因此,.NET是试图通过printc应该是什么*const u16,不是一个*const libc::c_char.当CStr计算字符串的长度时,它看到的是以下内容:

b"y\0e\0y\0e\0y\0e\0"
Run Code Online (Sandbox Code Playgroud)

也就是说,它看到一个代码单元,然后是一个空字节,所以它停止; 因此为什么它说长度为"1".

Rust没有对UTF-16字符串的标准支持,但是如果您在Windows上工作,则有一些转换方法:在文档中搜索OsStrExtOsStringExt.请注意,您必须使用随编译器一起安装的文档; 在线的人不会包含它.

遗憾的是,没有什么可以直接处理以null结尾的UTF-16字符串.你需要编写一些不安全的代码来将a *const u16转换成&[u16]你可以传递给的代码OsStringExt::from_wide.

现在,Rust 确实使用Unicode,但它使用UTF-8.遗憾的是,没有直接的方法可以让.NET将字符串编组为UTF-8.使用任何其他编码似乎会丢失信息,因此您必须在Rust端明确处理UTF-16,或者在C#端显式处理UTF-8.

这是简单的重新编码字符串,如C#UTF-8.您可以利用.NET将数组编组为第一个元素的原始指针(就像C)并传递以null结尾的UTF-8字符串这一事实.

首先,一个静态方法,用于获取.NET字符串并生成存储在字节数组中的UTF-8字符串:

byte[] NullTerminatedUTF8bytes(string str)
{
    return Encoding.GetBytes(str + "\0");
}
Run Code Online (Sandbox Code Playgroud)

然后声明Rust函数的签名,如下所示:

[DllImport(dllname, CallingConvention = CallingConvention.Cdecl)]
static extern void printc([In] byte[] str);
Run Code Online (Sandbox Code Playgroud)

最后,这样称呼它:

printc(NullTerminatedUTF8bytes(str));
Run Code Online (Sandbox Code Playgroud)

对于奖励积分,你可以返工printc来取代a *const u8 a u32,传递重新编码的字符串加上它的长度; 那么你不需要null终止符并且可以使用std::slice::from_raw_parts函数重建字符串(但是这开始超出原始问题).

至于print2那个,那是不可行的..NET 对Rust的类型一无所知String,它与.NET字符串完全不兼容.更重要的是,String甚至没有保证的布局,因此安全地绑定它或多或少是不可能的.

所有这一切是说的很啰嗦的方式:不使用String,或者任何其他非FFI安全型的,跨语言的功能,永远.如果您的意图是将"拥有"字符串传递给Rust ...我不知道是否可以与.NET协同工作.

另外:Rust中的"FFI-safe"基本上归结为:是内置的固定大小类型( 不是 usize/isize),或者是#[repr(C)]附加到它的用户定义类型.遗憾的是,文档中不包含类型的"FFI安全".