2017-10-24 10 views
2

배열을 포함하는 구조체를 C#의 바이트 배열로 변환하려면 어떻게해야합니까?배열이 들어있는 구조체를 바이트 배열로 변환하는 방법은 무엇입니까?

어레이가없는 구조체에 대한 질문이 here입니다.

그러나

구조체는 다음과 같이 배열을 포함하는 경우 :

바이트의 구조체를 변환 할 때 액세스 위반 예외 결과
public struct DiObject 
{ 
    public byte Command; 
    public byte ErrorClass; 
    public byte Reserved; 
    public byte Flags; 
} 

public struct MyPacket 
{ 
    public uint ProtocolIdentifier; 
    public uint NumDi;  
    public DiObject[] Di; 
} 

:

private static byte[] GetBytes(MyPacket packet, int packetSize) 
{ 
    var data = new byte[packetSize]; 
    var ptr = Marshal.AllocHGlobal(packetSize); 

    // ==== Access violation exception occurs here ==== 
    Marshal.StructureToPtr(packet, ptr, true); 

    Marshal.Copy(ptr, data, 0, packetSize); 
    Marshal.FreeHGlobal(ptr); 
    return data; 
} 

내 목표는 메시지를 보내는 것입니다 MSMQ가있는 메시지 큐의 바이트 단위.

여기에서 문제를 컴파일하고 재현하는 전체 코드입니다.

using System; 
//using System.IO; 
//using System.Messaging; 
using System.Runtime.InteropServices; 

namespace StructToBytes 
{ 
    // 4 bytes 
    [Serializable] 
    public struct DiObject 
    { 
     public byte Command; 
     public byte ErrorClass; 
     public byte Reserved; 
     public byte Flags; 
    } 

    // 8 + (numDi*4) bytes 
    [Serializable] 
    public struct MyPacket 
    { 
     public uint ProtocolIdentifier; 
     public uint NumDi;  
     public DiObject[] Di; 
    } 

    internal class Program 
    { 
     private static byte[] GetBytes(MyPacket packet, int packetSize) 
     { 
      var data = new byte[packetSize]; 
      var ptr = Marshal.AllocHGlobal(packetSize); 

      // ==== Access violation exception occurs here ==== 
      Marshal.StructureToPtr(packet, ptr, true); 

      Marshal.Copy(ptr, data, 0, packetSize); 
      Marshal.FreeHGlobal(ptr); 
      return data; 
     } 

     private static MyPacket FromBytes(byte[] data) 
     { 
      var packet = new MyPacket(); 
      var dataSize = Marshal.SizeOf(packet); 
      var ptr = Marshal.AllocHGlobal(dataSize); 
      Marshal.Copy(data, 0, ptr, dataSize); 
      packet = (MyPacket) Marshal.PtrToStructure(ptr, packet.GetType()); 
      Marshal.FreeHGlobal(ptr); 
      return packet; 
     } 

     private static void Main(string[] args) 
     { 
      const string queuePath = @".\private$\test_msmq"; 

      // Create the packet 
      var packet = new MyPacket(); 

      // 8 bytes 
      packet.ProtocolIdentifier = 1; 
      packet.NumDi = 2; 

      // 8 bytes 
      packet.Di = new DiObject[packet.NumDi]; 
      packet.Di[0].Command = 2; 
      packet.Di[0].ErrorClass = 3; 
      packet.Di[0].Flags = 4; 
      packet.Di[0].Reserved = 5; 
      packet.Di[1].Command = 6; 
      packet.Di[1].ErrorClass = 7; 
      packet.Di[1].Flags = 8; 
      packet.Di[1].Reserved = 9; 

      // Convert the struct in bytes 
      const int packetSize = 16; 
      var packetBytes = GetBytes(packet, packetSize); 

      // Create the message 
      /* 
      var msg = new Message(); 
      msg.BodyStream = new MemoryStream(packetBytes); 

      // Open or create the message queue 
      if (!MessageQueue.Exists(queuePath)) 
       MessageQueue.Create(queuePath); 

      // Open the queue 
      var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()}; 

      // Send the message to the queue 
      q.Send(msg); 
      */ 
     } 
    } 
} 
+0

'BinaryMessageFormatter'가 이것을 직렬화하지 않는 이유는 무엇입니까? 메시지를 작게하려고하는 것입니까? MSMQ 메시지의 오버 헤드는 이미 메시지 내용 자체보다 커질 것이므로이 작은 것으로는 큰 차이를 만들 가능성이 없습니다. –

+0

BinaryMessageFormatter를 사용하면 데이터가 16 바이트 대신 340 바이트로 저장됩니다. 가능한 한 가장 작은 패킷을 원합니다. 여기에이 코드가 표시됩니다. // 메시지 큐 열기 또는 만들기 if (! MessageQueue.Exists (queuePath)) MessageQueue.Create (queuePath); // 큐 열기 var q = 새 MessageQueue (queuePath); // {Formatter = new BinaryMessageFormatter()}; var message = 새 메시지 (packet, new BinaryMessageFormatter()); q.Send (message); –

+0

간단히 말해, 디지털 및 아날로그 포인트를 많이 저장하고자하므로 가장 작은 패킷을 사용하고 싶습니다. 이 예제는 간단하게하기 위해 작습니다. –

답변

2

문제는 구조가 public DiObject[] Di 멤버의 크기가 numDi * 4입니다 사실이 아니다 C#을

// 8 + (numDi*4) bytes 
[Serializable] 
public struct MyPacket 
{ 
    public uint ProtocolIdentifier; 
    public uint NumDi; 
    public DiObject[] Di; 
} 

에서 가정을 표현하는 방법에 대한 잘못된 가정하에있다. 이 필드 대신에 구조 배열에 대한 포인터가 있습니다. Array는 .NET의 클래스이며 구조체 선언에 포함되지 않습니다.

이 문제를 해결하려면 고정 배열을 사용할 수 있습니다. 디자인의 기본 아이디어는 가변 길이 배열을 얻는 것이고 다음 코드 목록에 나와 있습니다.

이 코드 executin 동안 AccessViolationException를 발생시키지 않는다 :

using System; 
using System.IO; 
using System.Messaging; 
using System.Runtime.InteropServices; 

namespace StructToBytes 
{ 
    // 4 bytes 
    [Serializable] 
    [StructLayout(LayoutKind.Explicit)] 
    public unsafe struct DiObject 
    { 
     [FieldOffset(0)] 
     public byte Command; 

     [FieldOffset(1)] 
     public byte ErrorClass; 

     [FieldOffset(2)] 
     public byte Reserved; 

     [FieldOffset(3)] 
     public byte Flags; 
    } 

    // 8 + (numDi*4) bytes 
    [Serializable] 
    public unsafe struct MyPacket 
    { 
     public uint ProtocolIdentifier; 
     public uint NumDi; 
     public fixed byte Di[2 * 4]; 
    } 

    internal unsafe class Program 
    { 
     private static byte[] GetBytes(MyPacket packet, int packetSize) 
     { 
      var data = new byte[packetSize]; 
      var ptr = Marshal.AllocHGlobal(packetSize); 

      // ==== Access violation exception occurs here ==== 
      Marshal.StructureToPtr(packet, ptr, true); 

      Marshal.Copy(ptr, data, 0, packetSize); 
      Marshal.FreeHGlobal(ptr); 
      return data; 
     } 

     private static MyPacket FromBytes(byte[] data) 
     { 
      var packet = new MyPacket(); 
      var dataSize = Marshal.SizeOf(packet); 
      var ptr = Marshal.AllocHGlobal(dataSize); 
      Marshal.Copy(data, 0, ptr, dataSize); 
      packet = (MyPacket)Marshal.PtrToStructure(ptr, packet.GetType()); 
      Marshal.FreeHGlobal(ptr); 
      return packet; 
     } 

     private static void Main(string[] args) 
     { 
      const string queuePath = @".\private$\test_msmq"; 

      // Create the packet 
      var packet = new MyPacket(); 

      // 8 bytes 
      packet.ProtocolIdentifier = 1; 
      packet.NumDi = 2; 

      // 8 bytes 
      // packet.Di = new DiObject[packet.NumDi]; 
      packet.Di[0] = 2; 
      packet.Di[1] = 3; 
      packet.Di[2] = 4; 
      packet.Di[3] = 5; 
      packet.Di[4] = 6; 
      packet.Di[5] = 7; 
      packet.Di[6] = 8; 
      packet.Di[7] = 9; 

      // Convert the struct in bytes 
      int packetSize = Marshal.SizeOf<MyPacket>(); 
      var packetBytes = GetBytes(packet, packetSize); 

      // Create the message 

      var msg = new Message(); 
      msg.BodyStream = new MemoryStream(packetBytes); 

      // Open or create the message queue 
      if (!MessageQueue.Exists(queuePath)) 
       MessageQueue.Create(queuePath); 

      // Open the queue 
      var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()}; 

      // Send the message to the queue 
      q.Send(msg); 

     } 
    } 
} 

아래 코드 바이트 배열 변수 내부 배열 크기 MyPacket 구조체에 대한 바이트 배열의 변환 효율을 제공한다. 구현은 안전하지 않은 포인터 산술을 사용하여 형변환과 경계 검사를 피합니다.

using System; 
using System.IO; 
using System.Messaging; 
using System.Runtime.InteropServices; 

namespace StructToBytes 
{ 
    // 4 bytes 
    [Serializable] 
    [StructLayout(LayoutKind.Explicit)] 
    public unsafe struct DiObject 
    { 
     [FieldOffset(0)] 
     public byte Command; 

     [FieldOffset(1)] 
     public byte ErrorClass; 

     [FieldOffset(2)] 
     public byte Reserved; 

     [FieldOffset(3)] 
     public byte Flags; 
    } 

    [Serializable] 
    public unsafe struct MyPacket 
    { 
     public uint ProtocolIdentifier; 
     public uint NumDi; 
     public DiObject[] Di; 

     public byte[] ToBytes() 
     { 
      byte[] buffer = new byte[NumDi]; 

      fixed(DiObject* pDi = Di) 
      fixed(byte* pBuff = buffer) 
      { 
       var pBuffDi = (DiObject*)pBuff; 
       var pDiPtr = pDi; 
       for (int i = 0; i < NumDi; i++) 
        *pBuffDi++ = *pDiPtr++; 
      } 
      return buffer; 
     } 

     public static MyPacket Create(byte[] buffer) 
     { 
      // argument checking code here 

      var packet = new MyPacket(); 
      packet.ProtocolIdentifier = buffer[0]; 
      packet.NumDi = buffer[1]; 
      packet.Di = new DiObject[packet.NumDi]; 

      fixed (byte* pBuf = buffer) 
      fixed (DiObject* pDi = packet.Di) 
      { 
       byte* pBufPtr = pBuf; 
       pBufPtr += 2; 
       var pBufDi = (DiObject*)pBufPtr; 
       var pDiPtr = pDi; 

       for (int i = 0; i < packet.NumDi; i++) 
        *pDiPtr++ = *pBufDi++; 
      } 

      return packet; 
     } 
    } 

    internal unsafe class Program 
    { 

     private static void Main(string[] args) 
     { 
      const string queuePath = @".\private$\test_msmq"; 

      // Create the packet 
      var packet = new MyPacket(); 

      // 8 bytes 
      packet.ProtocolIdentifier = 1; 
      packet.NumDi = 5; 

      // 8 bytes 
      packet.Di = new DiObject[packet.NumDi]; 
      packet.Di[0].Command = 2; 
      packet.Di[0].ErrorClass = 3; 
      packet.Di[0].Flags = 4; 
      packet.Di[0].Reserved = 5; 
      packet.Di[1].Command = 6; 
      packet.Di[1].ErrorClass = 7; 
      packet.Di[1].Flags = 8; 
      packet.Di[1].Reserved = 9; 
      packet.Di[2].Command = 6; 
      packet.Di[2].ErrorClass = 7; 
      packet.Di[2].Flags = 8; 
      packet.Di[2].Reserved = 9; 
      packet.Di[3].Command = 6; 
      packet.Di[3].ErrorClass = 7; 
      packet.Di[3].Flags = 8; 
      packet.Di[3].Reserved = 9; 

      // Create the message 

      var msg = new Message(); 
      msg.BodyStream = new MemoryStream(packet.ToBytes()); 

      // Open or create the message queue 
      if (!MessageQueue.Exists(queuePath)) 
       MessageQueue.Create(queuePath); 

      // Open the queue 
      var q = new MessageQueue(queuePath); 

      // Send the message to the queue 
      q.Send(msg); 

     } 
    } 
} 
+0

어레이에 대한 명확한 설명에 감사드립니다. 귀하의 솔루션이 완벽하게 작동하고 C++ 프로그램에 대기열에 연결할 필요가 있음을 잘 알고 있습니다. –