在DllImport中使用Unicode字符串和用Rust编写的DLL
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在DllImport中使用Unicode字符串和用Rust编写的DLL相关的知识,希望对你有一定的参考价值。
我试图从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)
}
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();
}
对于print2
功能,它一直在屏幕上打印垃圾,直到它导致AccessViolationException
第二个printc
函数会打印字符串,但仅限于未设置CharSet.Unicode
。如果设置,它将只打印第一个字符,因此println!("{:?}", c_str.to_bytes().len());
将打印1
。
我相信Cstr::from_ptr
函数不支持Unicode,这就是为什么它只返回字符串的第一个字符串。
知道如何将Unicode字符串作为参数传递给Rust DLL吗?是否有可能使事情变得更简单,就像在print2
函数中一样?
如果你检查the documentation on CharSet
,你会看到CharSet.Unicode
告诉.NET将字符串编组为UTF-16(即每个代码点两个字节)。因此,.NET试图通过printc
什么应该是*const u16
,而不是*const libc::c_char
。当CStr
去计算字符串的长度时,它看到的是以下内容:
b"y e y e y e "
也就是说,它看到一个代码单元,然后是一个空字节,所以它停止;因此为什么它说长度为“1”。
Rust没有对UTF-16字符串的标准支持,但是如果您在Windows上工作,则有一些转换方法:搜索文档以查找OsStrExt
和OsStringExt
。请注意,您必须使用随编译器一起安装的文档;在线的人不会包含它。
遗憾的是,没有什么可以直接处理以null结尾的UTF-16字符串。你需要写一些不安全的代码来将*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 + " ");
}
然后声明Rust函数的签名,如下所示:
[DllImport(dllname, CallingConvention = CallingConvention.Cdecl)]
static extern void printc([In] byte[] str);
最后,这样称呼它:
printc(NullTerminatedUTF8bytes(str));
对于奖励积分,你可以重新加工printc
来取代*const u8
和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安全”。
以上是关于在DllImport中使用Unicode字符串和用Rust编写的DLL的主要内容,如果未能解决你的问题,请参考以下文章
c# WinForm 全局键盘事件怎么监听。。。。会的童鞋们贴上代码和用例吧,感激
关于VisualStudio2005中MFC通用控件的外观和使用Unicode工程的关系
vs2013编译错误解决: _declspec(dllimport) 动态链接库
在 C# DllImport 中使用 32 位或 64 位 dll