C#调用Rust dll,重点在于字符串传递,其他类型比较自然。可以给函数传递json字符串,在传出json字符串,两端通过json序列化、反序列化,可以方便处理参数数据。也可以传递不带字符串的结构体(结构体内含字符串的情况没有验证)。

小的结构体实例序列化反序列化速度很快,C#端序列化后,调用10000次Rust端函数,执行反序列化后修改对象成员,再次序列化,仅耗时36ms。开发中使用字符串传递参数和传出处理结果,完全可行。

没什么难点,直接上码:

Rust:

use std::thread;
use libc::{c_char, uint32_t};
use std::ffi::{CStr, CString};
use std::str;
extern crate serde;
extern crate serde_json;

#[macro_use] //引入serde_derive中的宏
extern crate serde_derive;

#[derive(Debug, Serialize, Deserialize)]
struct Address{
    street: String,
    city: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct Person {
    name: String,
    age: u8,
    address: Address,
    phones: Vec<String>,
}

impl Person{
    fn default() -> Self {
        Person{
            name: "zhangsan".to_string(),
            age: 18u8,
            address: Address{
                street: "East nanjing road".to_string(), 
                city: "shanghai".to_string(),
            },
            phones: vec!["12345678".to_string(), "23456789".to_string()],
        }
    }
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Kind {
    Ok,

    Done,
    BufferTooSmall,

    ArgumentNull,
    InternalError,
}

#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DbResult {
    kind: Kind,
    id: u32,
}

#[no_mangle]
pub extern fn GetDbResult() -> DbResult{
    DbResult{
        kind: Kind::Done,
        id: 100u32,
    }
}

#[no_mangle]
pub extern fn process(){
    let handles: Vec<_> = (0..10).map(|_|{
        thread::spawn(||{
            let mut x = 0;
            for _ in(0..5_000_000){
                x+=1
            }
            x
        })
    }).collect();

    for h in handles{
        println!("thread finished with count={}", 
        h.join().map_err(|_| "Could not join a thread!").unwrap());
    }
    println!("done!");
}

fn mkstr(s: *const c_char) -> String {
    let c_str = unsafe {
        assert!(!s.is_null());
        CStr::from_ptr(s)
    };

    let r_str = c_str.to_str().expect("Could not successfully convert string form foreign code!");
    String::from(r_str)
}

#[no_mangle]
pub extern fn free_string(s: *mut c_char){
    unsafe{
        if s.is_null(){return}
        CString::from_raw(s)
    };
}

#[no_mangle]
pub extern fn result(istr: *const c_char) -> *mut c_char{
    let s = mkstr(istr);

    let values: Person = serde_json::from_str(&s).unwrap();
    println!("Hello,{},welcome to rust world,you name change to {}-rust!",values.name,values.name);
    let values2 = Person{name:values.name + "-rust", ..values};
    let json = serde_json::to_string_pretty(&values2).unwrap();
    let cex = CString::new(json).expect("Failed to create CString!");

    cex.into_raw() //转移了所有权,rust不再释放内容,需要C端显式释放
}

Cargo.toxml 

[package]
name = "rust_code"
version = "0.1.0"
authors = ["henreash <nmhys@126.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
libc = "0.2.48"
serde = "*"
serde_derive = "*"
serde_json = "*"

[lib]
name="rust_code"
crate-type=["dylib"]

C#代码:

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SharpProcessor
{
    public partial class Form1 : Form
    {
        [DllImport("rust_code.dll")]
        internal static extern void process();

        [DllImport("rust_code.dll")]
        internal static extern void free_string(IntPtr str);

        [DllImport("rust_code.dll")]
        internal static extern StringHandle result(string inputs);

        [DllImport("rust_code.dll")]
        internal static extern DbResult GetDbResult();

        public Form1()
        {
            InitializeComponent();
        }

        private Person GetTestData()
        {
            return new Person {
                Name = "zhangsan",
                Age = 18,
                Addresss = new Address {
                    Street = "East nanjing road",
                    City = "Shanghai",
                },
                Phones = new List<string> {
                    "12345678",
                    "23456789",
                }
            };
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //process();
            //var str = "hello world, i come form c sharp";
            var str = JsonConvert.SerializeObject(GetTestData());
            using (var str2 = result(str))
            {
                MessageBox.Show(str2.AsString());
            }
            var dbResult = GetDbResult();
            MessageBox.Show($"{dbResult._result}  {dbResult._id}");
        }
    }


    internal class StringHandle : SafeHandle
    {
        public StringHandle() : base(IntPtr.Zero, true) { }

        public override bool IsInvalid
        {
            get { return false; }
        }

        public string AsString()
        {
            int len = 0;
            while (Marshal.ReadByte(handle, len) != 0) { ++len; }
            byte[] buffer = new byte[len];
            Marshal.Copy(handle, buffer, 0, buffer.Length);
            return Encoding.UTF8.GetString(buffer);
        }

        protected override bool ReleaseHandle()
        {
            Form1.free_string(handle);
            return true;
        }
    }

    public class Person
    {
        [JsonProperty("name")]
        public string Name { get; set; }
        [JsonProperty("age")]
        public byte Age { get; set; }
        [JsonProperty("address")]
        public Address Addresss { get; set; } = new Address();
        [JsonProperty("phones")]
        public List<string> Phones { get; set; } = new List<string>();
    }

    public class Address
    {
        [JsonProperty("street")]
        public string Street { get; set; }
        [JsonProperty("city")]
        public string City { get; set; }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct DbResult
    {
        public enum Kind : uint
        {
            Ok,

            Done,
            BufferTooSmall,

            ArgumentNull,
            InternalError
        }

        public readonly Kind _result;
        public readonly uint _id;

        public bool IsSuccess()
        {
            return _result == Kind.Ok || _result == Kind.Done;
        }

        public bool IsDone()
        {
            return _result == Kind.Done;
        }

        public bool IsBufferTooSmall()
        {
            return _result == Kind.BufferTooSmall;
        }
    }
}

运行结果:

耗时测试(首次执行耗时稍长,后面在执行,稳定在36ms左右):

        private void button1_Click(object sender, EventArgs e)
        {
            var stop = new Stopwatch();
            stop.Start();
            var str = JsonConvert.SerializeObject(GetTestData());
            for (var i = 0; i < 10000; i++)
            {
                using (var str2 = result(str))
                {
                }
            }
            stop.Stop();
            MessageBox.Show(stop.ElapsedMilliseconds.ToString());
        }
Rust ffi和外部系统传递数据:
1、获取C端数组:

使用slice::from_raw_parts获取切片,在做进一步处理。

pub extern "C" fn sum_of_array(array: *const u32, len: usize) -> u32 {
     let array = unsafe {
         assert!(!array.is_null());
         slice::from_raw_parts(array, len)
     };
     array.iter().sum()
}
2、获取C端字符串:

使用CStr::from_ptr(raw_string)方法

pub extern fn result(istr: *const c_char){
        let c_str = unsafe {
        assert!(!s.is_null());
        CStr::from_ptr(s)
    };
    
    let r_str = c_str.to_str().expect("Could not successfully convert string form foreign code!");
    let input = String::from(r_str);
    //c_str.to_string_lossy().into_owned();//简化写法
    println!("{}", input);
}
3、给C返回字符串:

CString::new(“Hello, world!”).as_ptr() //将RustCString指针类型转化为C的原始指针类型

CString::new(“Hello world!”).into_raw() //将RustCString指针类型转化为C的原始指针类型,同时转移所有权,Rust不释放内存

CString::from_raw(s) //C端用完时Rust端来释放内存

use std::ffi::CString;
use std::os::raw::c_char;
extern {
    fn my_printer(s: *const c_char);
}
 
let c_to_print = CString::new("Hello, world!").unwrap();
unsafe {
    my_printer(c_to_print.as_ptr()); // 使用 as_ptr 将CString转化成char指针传给c函数
}

上面代码不牵涉释放,C用完c_to_print的内存,c_to_print才离开作用域释放。但如果使用into_raw,则需要from_raw配对调用。

Logo

开放原子旋武开源社区(简称“旋武社区”)是由开放原子开源基金会孵化及运营的技术社区,致力于在中国推广和发展Rust编程语言生态,推动Rust在操作系统、终端设备、安全技术、基础软件等关键领域的产业落地,构建安全、可靠、高效的软件基础设施。

更多推荐