【问题标题】:How to pass c# strings through p/invoke to linux/glibc wchar_t * parameters?如何通过 p/invoke 将 c# 字符串传递给 linux/glibc wchar_t * 参数?
【发布时间】:2019-02-14 17:47:06
【问题描述】:

我有一个 .NET Core 2.2 C# 应用程序,它使用 DllImport 在 CentOS 7.5 上提取本机共享库(使用 gcc 编译的 C++ extern "C" 接口)。 C++ 库中的函数需要 wchar_t * 参数,但这些参数似乎被编组为 UTF16 字符串,而不是 gcc/glibc 中实现的 UTF32 字符串。这是(我的)程序员错误还是应该向 .NET Core 团队提出?

这是我试图调用的高度复杂的方法:

void wchar_tTest(const wchar_t *arg1, const wchar_t *arg2)
{
    std::wcout << L"wchar_tTest: arg1: " << arg1 << L", arg2: " << arg2 << std::endl;

    char *s = (char *)arg1;
    for (int i = 0; i < 12; i++)
    {
        printf("%d: %c\n", i, s[i]);
    }
}

我尝试在托管端的 DllImport 上使用 MarshalAs(UnmanagedType.LPWSTR) 和/或 CharSet.Unicode 无济于事。这些都产生相似的结果:

[DllImport("cover", EntryPoint = "wchar_tTest", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void LPWSTRStringTest([MarshalAs(UnmanagedType.LPWStr)] string arg1, [MarshalAs(UnmanagedType.LPWStr)] string arg2);

[DllImport("cover", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void wchar_tTest(string arg1, string arg2);

调用看起来像这样(stringTest() 是一个类似的调用,但调用的是带有 char * 参数的函数):

string arg1 = "Hello!";
string arg2 = "Goodbye!";

stringTest(arg1, arg2);

wchar_tTest(arg1, arg2);

LPWSTRStringTest(arg1, arg2);

当通过wcout转储参数时,Hello!变为HloGoodbye!变为Gobe。当您逐个字符地遍历时,输出看起来很像 UTF16...看起来wchar_t * 跳过了所有其他 UTF16 字符(我假设将其视为 UTF32 字符串)。

wchar_tTest: arg1: Hlo, arg2: Gobe
0: H
1: 
2: e
3: 
4: l
5: 
6: l
7: 
8: o
9: 
10: !
11: 

有没有办法在不进行自定义编组的情况下解决这个问题?毕竟我读过这似乎应该是一个简单的任务,但我在这里。

【问题讨论】:

    标签: c# linux gcc .net-core pinvoke


    【解决方案1】:

    文本按照预期和设计被编组为 UTF16。您需要:

    • 调整您的 C++ 代码以在 UTF16 上运行,或
    • 使用其他编码的自定义编组,例如UTF8 或 UTF32。

    【讨论】:

    • 我不认为这是互操作人员的想法,必须有一个更清晰的答案。字符串被称为单独的用例,它们非常特别。 :)
    • 为什么必须有?为什么你认为 .net 同时支持 UTF32 和 UTF16?
    • .NET 框架在内部使用 UTF-16,但这里的问题是本机互操作如何与本机库交互。如果本机库使用 UTF-32,则互操作似乎会编组为 UTF-32。现在我认为这个问题最好问 .NET Core corefx 的人。
    • 欢迎向 .net core 的开发者提交功能请求。我刚刚通过说明库的设计方式和行为方式回答了您提出的问题。您不太关心该设计的事实并不会使这成为一个糟糕的答案。你似乎在向信使开枪。
    【解决方案2】:

    考虑到我所看到的没有好的答案的流量,我将发布我用来解决这个问题的短期黑客,因为世界上的 C++/本机库一侧无法更改...

    我修改了 DllImport 以声明 byte[] 参数

    [DllImport("cover", EntryPoint = "wchar_tTest", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    public static extern void utf32Test(byte[] arg1, byte[] arg2);
    

    并创建了 .NET 字符串的 UTF32 编码版本

    string arg1 = "Hello!";
    byte[] arg1UTF32 = Encoding.UTF32.GetBytes(arg1);
    string arg2 = "Goodbye!";
    byte[] arg2UTF32 = Encoding.UTF32.GetBytes(arg2);
    
    utf32Test(arg1UTF32, arg2UTF32);
    

    瞧,你得到了预期的输出字符串和数组内容

    wchar_tTest: arg1: Hello!, arg2: Goodbye!
    0: H
    1: 
    2: 
    3: 
    4: e
    5: 
    6: 
    7: 
    8: l
    9: 
    10: 
    11: 
    

    虽然这很难移植,当你在 Windows 系统上运行它时当然会失败。我希望有更好的答案。

    【讨论】:

    • 编写一个自定义封送器(参见ICustomMarshaler)并让它根据平台在两种编码之间切换。
    猜你喜欢
    • 2020-02-27
    • 2020-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-04
    • 2023-03-11
    相关资源
    最近更新 更多