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

时间:2014-01-09 00:35:58

标签: c# .net c pinvoke unsafe

我正在尝试了解如何从以下结构中的不安全字节指针获取字符串。 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;

2 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:0)

我刚遇到.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: ?!

(包含“ thumbsup”表情符号,某些浏览器可能无法正确显示)

注意:

  • 本地字符串必须以0结尾。
  • 对本地字符串使用char不适用于ASCII或UTF-8编码的数据,因为在C#char always has a size of 16 bits(UTF-16)中:

    固定大小的char缓冲区每个字符始终占用两个字节,而不管编码如何。即使使用CharSet = CharSet.AutoCharSet = CharSet.Ansi将char缓冲区编组到API方法或结构中,也是这样。