C# LPUTF8Str 字符串封送处理似乎无法从内存中正确读取字符串

x6h*_*ius 7 c# c++ unicode utf-8

LPUTF8StrC# 中的字符串封送处理对我来说根本不起作用。我觉得我无法正确理解它的用例,但是在仔细研究文档并进行各种其他测试之后,我不确定我做错了什么。

\n

语境

\n

首先,陈述我对字符编码的基本(可能不正确)理解以及为什么 C# 需要转换它们,以防出现问题:

\n
    \n
  • 在 Windows 和其他地方,标准 C/C++ 字符串(const char*std::string)默认使用单字节字符。您可以使用包含两字节字符的字符串,但只有在您选择使用时才会使用这些字符串std::wstring(我没有这样做)。
  • \n
  • 默认的 Windows 单字节字符编码是 ANSI(7 位 ASCII + 使用第 8 位的额外字符集)。
  • \n
  • Unicode 是可打印字符到代码点(即唯一数字)的映射。Unicode 代码点字符串通常使用以下约定进行编码:\n
      \n
    • UTF-8:英语中大多数情况下每个字符一个字节,特殊字节指定多于一个字节的链应在何处形成单个字符(对于更时髦的字符)。7 位 ASCII 是 UTF-8 编码的子集。
    • \n
    • UTF-16:每个字符两个字节,对于真正时髦的字符具有类似(但更罕见)的延续模式。
    • \n
    • UTF-32:每个字符四个字节,基本上从不用于英语和邻近语言,因为它不是一种非常节省内存的编码。
    • \n
    \n
  • \n
  • 要在 C/C++ 字符串中写入非 ASCII 字符,您可以使用 编码文字 UTF-8 字节\\xhh,其中hh是字节的十六进制编码。例如。"\\xF0\\x9F\\xA4\\xA0"相当于“”。
  • \n
  • C# 使用两字节字符对所有托管字符串进行编码 - 我不确定这是否是明确的 UTF-16 或其他某种 Microsoft 编码。当 C/C++ 字符串传递给 C# 时,需要将其从单字节(窄)字符转换为两字节(宽)字符。
  • \n
  • 微软滥用了术语“Unicode”。他们在 C# 文档中将两字节字符串称为“Unicode 字符串”,从而暗示(错误地)每个字符不是两个字节的任何字符串都不是Unicode。正如我们从 UTF-8 编码中知道的那样,这不一定是真的 - 仅仅因为字符串表示为 aconst char*并不意味着它不是由 Unicode 字符组成。给我上色"\\xF0\\x9F\\x98\\x92"=>“”
  • \n
\n

实际问题

\n

因此,对于 C++ 程序,它必须使用const char*指针将字符串公开给 C#,并且 C# 应用程序必须通过将这些字符串转换为宽字符来封送这些字符串。假设我有以下 C++ 函数,为了演示 C# 封送处理,它通过结构体传递数据:

\n
// Header:\nextern "C"\n{\n    struct Library_Output\n    {\n        const char* str;\n    };\n\n    API_FUNC void Library_GetString(Library_Output* out);\n}\n\n// Source:\nextern "C"\n{\n    void Library_GetString(Library_Output* out)\n    {\n        if ( out )\n        {\n            // Static string literal:\n            out->str = "This is a UTF-8 string. \\xF0\\x9F\\xA4\\xA0";\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在 C# 中,我这样调用该函数:

\n
public class Program\n{\n    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]\n    struct Library_Output\n    {\n        // This is where the marshaling type is defined.\n        // C# will convert the const char* pointer to\n        // a string automatically.\n        [MarshalAs(UnmanagedType.LPUTF8Str)]\n        public string str;\n    }\n    \n    [DllImport("Library.dll")]\n    static extern void Library_GetString(IntPtr output);\n\n    private static void Main()\n    {\n        int structSize = Marshal.SizeOf(typeof(Library_Output));\n        IntPtr structPtr = Marshal.AllocHGlobal(structSize);\n\n        Library_GetString(structPtr);\n        \n        // Tell C# to convert the data in the unmanaged memory\n        // buffer to a managed object.\n        Library_Output outputStruct =\n            (Library_Output)Marshal.PtrToStructure(structPtr, typeof(Library_Output));\n            \n        Console.WriteLine(outputStruct.str);\n\n        Marshal.FreeHGlobal(structPtr);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

应用程序实际打印出的内容不是将字符串打印到控制台,而是:

\n
\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbdn\xef\xbf\xbd\n
Run Code Online (Sandbox Code Playgroud)\n

但是,如果我将封送类型更改为 而UnmanagedType.LPStr不是UnmanagedType.LPUTF8Str,我会得到:

\n
This is a UTF-8 string. \n
Run Code Online (Sandbox Code Playgroud)\n

这让我感到困惑,因为结构成员字符串封送的文档指出:

\n
\n

UnmanagedType.LPStr:指向以 null 结尾的 ANSI 字符数组的指针。

\n

UnmanagedType.LPUTF8Str:指向以 null 结尾的 UTF-8 编码字符数组的指针。

\n
\n

那么 ANSI 字符串封送处理会打印 UTF-8(非 ANSI)字符串,但是 UTF-8 字符串封送处理会打印垃圾吗?为了弄清楚垃圾来自哪里,我查看了打印的数据实际上是什么,它似乎是指针本身的值。

\n

要么 UTF-8 封送例程将字符串指针值所在的内存视为字符串本身,要么我误解了有关此过程的一些关键内容。从根本上来说,我的问题是双重的:首先,为什么 UTF-8 封送过程不能正确遵循字符串指针,其次,将 UTF-8 字符串从 C++ 封送到 C# 的正确方法实际上是什么?是为了使用LPUTF8Str,还是别的什么?

\n