从不安全的字节指针获取字符串到固定的字符数组



我试图了解如何从以下结构中的不安全字节指针获取字符串。SDL_TEXTINPUTEVENT_TEXTSIZE是32岁。

[StructLayout(LayoutKind.Sequential)]
public unsafe struct SDL_TextInputEvent
{
    public SDL_EventType type;
    public UInt32 timestamp;
    public UInt32 windowID;
    public fixed byte text[SDL_TEXTINPUTEVENT_TEXT_SIZE];
}

我试过:

byte[] rawBytes = new byte[SDL_TEXTINPUTEVENT_TEXT_SIZE];
unsafe
{
    Marshal.Copy((IntPtr)rawEvent.text.text, rawBytes, 0, SDL_TEXTINPUTEVENT_TEXT_SIZE);
}
string text = System.Text.Encoding.UTF8.GetString(rawBytes);

哪种有效,但给了我一个字符串,除了实际输入的字符之外,还有很多额外的字节。我应该解析字节数组并搜索以 0 结尾的字符以避免过多吗?

我完全误解了什么吗?

作为参考,封送到 .NET 运行时的原始 C 结构是:

typedef struct SDL_TextInputEvent
{
    Uint32 type;
    Uint32 timestamp;
    Uint32 windowID;
    char text[SDL_TEXTINPUTEVENT_TEXT_SIZE];
} SDL_TextInputEvent;

您确实需要找到空终止符。Marshal.Copy不会那样做。如果您的文本是 ANSI 编码的,则可以使用 Marshal.PtrToStringAnsi。但是 UTF-8 没有这样的功能。因此,您需要遍历数组以查找零字节。当您遇到已知缓冲区的实际长度时,您可以修改现有代码以使用该长度而不是最大可能长度。

我刚刚在 .NET Core 上遇到了同样的问题。幸运的是,从.NET Core 1.1/.NET Standard 2.1开始,有一个方法Marshal.PtrToStringUTF8,它提供了本机UTF-8字符串的转换。

给定此结构:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct NativeType
{
    public int SomeNumber;
    public unsafe fixed byte SomeString[16];
}

我们可以将二进制数据解码为 ASCII 和 UTF-8,如下所示:

var byteArrayAscii = new byte[] { 0x78, 0x56, 0x34, 0x12, 0x41, 0x53, 0x43, 0x49, 0x49, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
var byteArrayUtf8 = new byte[] { 0xef, 0xcd, 0xab, 0x89, 0x45, 0x6d, 0x6f, 0x6a, 0x69, 0x3a, 0x20, 0xf0, 0x9f, 0x91, 0x8d, 0x21, 0x00, 0x00, 0x00, 0x00 };
using var outputStream = File.OpenWrite("output.txt");
using var outputWriter = new StreamWriter(outputStream);
unsafe
{
    var decoded1 = MemoryMarshal.Read<NativeType>(byteArrayAscii);
    outputWriter.WriteLine($"Number 1: {decoded1.SomeNumber:x8}");
    outputWriter.WriteLine($"String 1: {Marshal.PtrToStringAnsi(new IntPtr(decoded1.SomeString))}");
}
unsafe
{
    var decoded2 = MemoryMarshal.Read<NativeType>(byteArrayUtf8);
    outputWriter.WriteLine($"Number 2: {decoded2.SomeNumber:x8}");
    outputWriter.WriteLine($"String 2: {Marshal.PtrToStringUTF8(new IntPtr(decoded2.SomeString))}");
}

输出:

Number 1: 12345678
String 1: ASCII!
Number 2: 89abcdef
String 2: Emoji: 👍!

(包含"大拇指"表情符号,某些浏览器可能呈现不正确)

笔记:

  • 本机字符串必须以 0 结尾。
  • 对本机字符串使用 char 不适用于 ASCII 或 UTF-8 编码的数据,因为在 C# 中,char 的大小始终为 16 位 (UTF-16):

    无论编码如何,固定大小的字符缓冲区始终为每个字符占用两个字节。即使将 char 缓冲区封送到具有 CharSet = CharSet.AutoCharSet = CharSet.Ansi 的 API 方法或结构,也是如此。

最新更新