我试图了解如何从以下结构中的不安全字节指针获取字符串。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.Auto
或CharSet = CharSet.Ansi
的 API 方法或结构,也是如此。