从C#中的Rust DLL获取UTF-8编码的字符串

Getting a UTF-8 encoded String from a Rust DLL in C#

我发现了很多关于C中rust dll实现的US-ANSI字符串的信息,但这并不能解决UTF-8编码字符串的任何问题。

例如,"Br?tchen"一旦在c中调用,就会产生"Br??tchen"

锈病

1
2
3
4
5
6
7
8
9
10
use std::os::raw::c_char;
use std::ffi::CString;

#[no_mangle]
pub extern fn string_test() -> *mut c_char {
    let c_to_print = CString::new("Br?tchen")
        .expect("CString::new failed!");
    let r = c_to_print;
    r.into_raw()  
}

C.*

1
2
3
4
5
6
7
8
9
10
11
12
13
[DllImport(@"C:\Users\User\source
epos\testlib\target\debug\testlib.dll"
)]
private static extern IntPtr string_test();

public static void run()
{
    var s = string_test();
    var res = Marshal.PtrToStringAnsi(s);
    // var res = Marshal.PtrToStringUni(s);
    // var res = Marshal.PtrToStringAuto(s);
    // Are resulting in: ????n
    Console.WriteLine(res); // prints"Br??tchen", expected"Br?tchen"
}

我如何获得想要的结果?

我不认为这是如何用C将字符串转换为UTF-8的副本?因为它的回答方式与Marshal.PtrToStringAuto(s)Marshal.PtrToStringUni(s)相同。


多亏了@e_net4的评论,我建议阅读Rust FFI的综合信息,我得到了一个相当复杂但有效的答案。

我想我必须重写我正在使用的类。此外,我正在使用libc库和CString

卡莫尔

1
2
3
4
5
6
7
8
9
10
11
[package]
name ="testlib"
version ="0.1.0"
authors = ["John Doe <[email protected]>"]
edition ="2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
libc ="0.2.48"

钢骨混凝土

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
extern crate libc;

use libc::{c_char, uint32_t};
use std::ffi::{CStr, CString};
use std::str;

// Takes foreign C# string as input, converts it to Rust String
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)
}


// frees string from ram, takes string pointer as input
#[no_mangle]
pub extern fn free_string(s: *mut c_char) {
    unsafe {
        if s.is_null() { return }
        CString::from_raw(s)
    };
}

// method, that takes the foreign C# string as input,
// converts it to a rust string, and returns it as a raw CString.
#[no_mangle]
pub extern fn result(istr: *const c_char) -> *mut c_char {
    let s = mkstr(istr);
    let cex = CString::new(s)
        .expect("Failed to create CString!");

    cex.into_raw()
}

C级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
using System;
using System.Text;
using System.Runtime.InteropServices;


namespace Testclass
{
    internal class Native
    {
        [DllImport("testlib.dll")]
        internal static extern void free_string(IntPtr str);

        [DllImport("testlib.dll")]
        internal static extern StringHandle result(string inputstr);
    }

    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()
        {
            Native.free_string(handle);
            return true;
        }
    }

    internal class StringTesting: IDisposable
    {
        private StringHandle str;
        private string resString;
        public StringTesting(string word)
        {
            str = Native.result(word);
        }
        public override string ToString()
        {
            if (resString == null)
            {
                resString = str.AsString();
            }
            return resString;
        }
        public void Dispose()
        {
            str.Dispose();
        }
    }

    class Testclass
    {
        public static string Testclass(string inputstr)
        {
            return new StringTesting(inputstr).ToString();
        }

        public static Main()
        {
            Console.WriteLine(new Testclass("Br?tchen")); // output: Br?tchen
        }
    }
}

虽然这会保存期望的结果,但我仍然不确定是什么导致了问题提供的代码的错误解码。