实现目的
最近一直在用Rust做一些底层库,性能用起来也很顺手,但是碍于Rust目前极差的GUI生态,还是决定将Rust导出为DLL然后用C#来调用,用C#做个GUI的壳给用户使用,所有的内容还是用Rust实现。值得一看的是关于中文字符串参数的处理
Rust部分
定义个函数,负责读入一个字符串然后写到文件里。
1
2
3
4
5
6
7
8
9
10
11
12
| //lib.rs
use std::os::raw::c_char;
use std::ffi::CStr;
use std::fs::File;
use std::io::Write;
#[no_mangle]
pub fn get_str(s:*const c_char){
let mut f = File::create("test.file").unwrap();
let s = unsafe{CStr::from_ptr(s)}.to_bytes().to_vec();
let s = String::from_utf8(s).unwrap();
f.write(s.as_bytes());
}
|
这里no_mangle的意思是在编译期间保留这个函数名,让编译器不要改掉,这样C#才能顺利找到入口。后面的Cstr::from_ptr是从参数里读出字符串,读的类型按照的是C标准,详情请看Rust的FFI文档。这里转换了一下utf-8,如果单纯是为了写在文件里,倒是不必,直接写字节即可。我是为了其他功能必须读到中文。
1
2
3
4
5
6
7
8
9
10
11
12
| //Cargo.toml
[package]
name = "hello"
version = "0.1.0"
authors = ["chuxiuhong <chuxiuhong@chuxiuhong.com>"]
edition = "2018"
[dependencies]
[lib]
name = "dexterlib"
crate-type=["cdylib"]
|
在Cargo.toml里定义出我们要编译的类型是C标准的动态链接库,这里注意你要把需要导出的函数像前文一样处理然后放到lib.rs里。
C#部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace testRustDll
{
class Program
{
[DllImport("dexterlib.dll")]
static extern void get_str(byte[] s);
static void Main(string[] args)
{
var s = "艾欧尼亚,昂扬不灭";
var encoder = new UTF8Encoding();
var s_utf_8 = encoder.GetBytes(s);
get_str(s_utf_8);
}
}
}
|
这种C#调用DLL的代码到处都有介绍,我主要介绍的是解决中文乱码问题。C#的Winform从外界读回来的字符串貌似是UTF-16编码(对C#不是很精通),如果按照正常的思路来调用,那么定义应该是下面这种
1
2
| [DllImport("dexterlib.dll")]
static extern void get_str(string s);
|
但是这种定义传到Rust里在转换UTF-8编码时十有八九会出问题,所以要单独再先做一个UTF-8的字节转换,将原来的字符串转换为一个UTF-8的字节数组传进去。
参阅
有大神专门做了Rust与c/c#/js/python/ruby/julia的FFI样例代码,并且是针对不同数据结构的样板,https://github.com/shepmaster/rust-ffi-omnibus