编组字符串(但不是字节数组)内部结构导致AccessViolationException

时间:2017-01-08 06:21:33

标签: c# arrays string byte marshalling

我有以下结构,我试图在C#编组:

  • 长度(4个字节)
  • 版本(4字节)
  • MachineID(16字节)

我遇到的问题是在C#中将MachineID作为string。在结构中,我在顶部指定[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Size = 24)],为[MarshalAs(UnmanagedType.LPStr, SizeConst = 16)]指定MachineID。但是当我这样做时,我会抛出System.AccessViolationException

如果我将MachineID更改为byte[]并指定[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)],则可以正常使用。

以下代码适用于byte[]但不适用string。 ASCII格式的MachineIDABCDEFGHIJKLMNO

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

public class Test
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Size = 24)]
    struct Valid
    {
        public uint Length;
        public uint Version;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public byte[] MachineId;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Size = 24)]
    struct Invalid
    {
        public uint Length;
        public uint Version;
        [MarshalAs(UnmanagedType.LPStr, SizeConst = 16)] 
        public string MachineId;
    }

    public static void Main()
    {
        var bytes = new byte[]
        {
            0x58, 0, 0, 0,
            0, 0, 0, 0,
            0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0
        };

        var pinnedBuffer = GCHandle.Alloc(bytes, GCHandleType.Pinned);
        var ptr = pinnedBuffer.AddrOfPinnedObject();

        // Works!
        Debug.Assert(bytes.Length == Marshal.SizeOf<Valid>());

        var trackerData1 = Marshal.PtrToStructure<Valid>(ptr);

        Console.WriteLine("Length: {0}", trackerData1.Length);
        Console.WriteLine("Version: {0}", trackerData1.Version);
        Console.WriteLine("MachineId: {0}", Encoding.ASCII.GetString(trackerData1.MachineId));

        // Doesnt work!
        Debug.Assert(bytes.Length == Marshal.SizeOf<Invalid>());

        // Throws System.AccessViolationException
        var trackerData2 = Marshal.PtrToStructure<Invalid>(ptr);

        Console.WriteLine("Length: {0}", trackerData2.Length);
        Console.WriteLine("Version: {0}", trackerData2.Version);
        Console.WriteLine("MachineId: {0}", trackerData2.MachineId);

        pinnedBuffer.Free();

        Console.In.ReadLine();
    }
}

如果您run this code in IDEOne,当您尝试整理System.AccessViolationException并且它不仅仅是我的电脑时,您会发现它会引发string

我很好奇为什么它适用于字节数组而不是字符串?

1 个答案:

答案 0 :(得分:0)

您需要将其整理为ByValTStr才能实现此目的:

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
public string MachineId;

此处the example