如何在 C# 中使用 Swig 包装 UTF-8 编码C++ std::strings

How to wrap UTF-8 encoded C++ std::strings with Swig in C#?

本文关键字:编码 UTF-8 C++ std strings 包装 Swig      更新时间:2023-10-16

我的问题与这个问题几乎相同,只是链接的问题处理char*,而我在代码中使用std::string。就像链接的问题一样,我也使用 C# 作为我的目标语言。

我有一个用C++写的类:

class MyClass
{
public:
    const std::string get_value() const; // returns utf8-string
    void set_value(const std::string &value); // sets utf8-string
private:
    // ...
};

SWIG 在 C# 中包装了这个 get,如下所示:

public class MyClass
{
    public string get_value();
    public void set_value(string value);
}

SWIG 为我做了一切,除了在调用 MyClass 期间它不会进行 utf8 到 utf16 字符串的转换。如果字符串可以用 ASCII 表示,我的字符串就可以通过,但是如果我尝试通过"set_value"和"get_value"往返传递带有非 ASCII 字符的字符串,我最终会得到难以理解的字符。

如何使 SWIG 在 C# 中包装 UTF-8 编码C++字符串? 注意我使用的是 std::string,而不是 std::wstring,也不是 char*。

SWIG sourceforge 站点上有一个部分解决方案,但它处理的是 char* 而不是 std::string,并且它使用(可配置的)固定长度缓冲区。

在链接的代码项目文章中David Jeske的帮助下(阅读:天才!),我终于能够回答这个问题。

C# 库中需要此类(来自 David Jeske 的代码)。

public class UTF8Marshaler : ICustomMarshaler {
    static UTF8Marshaler static_instance;
    public IntPtr MarshalManagedToNative(object managedObj) {
        if (managedObj == null)
            return IntPtr.Zero;
        if (!(managedObj is string))
            throw new MarshalDirectiveException(
                   "UTF8Marshaler must be used on a string.");
        // not null terminated
        byte[] strbuf = Encoding.UTF8.GetBytes((string)managedObj); 
        IntPtr buffer = Marshal.AllocHGlobal(strbuf.Length + 1);
        Marshal.Copy(strbuf, 0, buffer, strbuf.Length);
        // write the terminating null
        Marshal.WriteByte(buffer + strbuf.Length, 0); 
        return buffer;
    }
    public unsafe object MarshalNativeToManaged(IntPtr pNativeData) {
        byte* walk = (byte*)pNativeData;
        // find the end of the string
        while (*walk != 0) {
            walk++;
        }
        int length = (int)(walk - (byte*)pNativeData);
        // should not be null terminated
        byte[] strbuf = new byte[length];  
        // skip the trailing null
        Marshal.Copy((IntPtr)pNativeData, strbuf, 0, length); 
        string data = Encoding.UTF8.GetString(strbuf);
        return data;
    }
    public void CleanUpNativeData(IntPtr pNativeData) {
        Marshal.FreeHGlobal(pNativeData);            
    }
    public void CleanUpManagedData(object managedObj) {
    }
    public int GetNativeDataSize() {
        return -1;
    }
    public static ICustomMarshaler GetInstance(string cookie) {
        if (static_instance == null) {
            return static_instance = new UTF8Marshaler();
        }
        return static_instance;
    }
}

然后,在 Swig 的"std_string.i"中,在第 24 行替换这一行:

%typemap(imtype) string "string"

用这一行:

%typemap(imtype, inattributes="[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]", outattributes="[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]") string "string"

在第 61 行,替换此行:

%typemap(imtype) const string & "string"

用这一行:

%typemap(imtype, inattributes="[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]", outattributes="[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]") string & "string"

你瞧,一切正常。阅读链接的文章,以很好地理解其工作原理。