C #에서 구조를 바이트 배열로 변환하는 방법은 무엇입니까?


83

C #에서 구조를 바이트 배열로 어떻게 변환합니까?

다음과 같은 구조를 정의했습니다.

public struct CIFSPacket
{
    public uint protocolIdentifier; //The value must be "0xFF+'SMB'".
    public byte command;

    public byte errorClass;
    public byte reserved;
    public ushort error;

    public byte flags;

    //Here there are 14 bytes of data which is used differently among different dialects.
    //I do want the flags2. However, so I'll try parsing them.
    public ushort flags2;

    public ushort treeId;
    public ushort processId;
    public ushort userId;
    public ushort multiplexId;

    //Trans request
    public byte wordCount;//Count of parameter words defining the data portion of the packet.
    //From here it might be undefined...

    public int parametersStartIndex;

    public ushort byteCount; //Buffer length
    public int bufferStartIndex;

    public string Buffer;
}

내 주요 방법에서는 인스턴스를 만들고 값을 할당합니다.

CIFSPacket packet = new CIFSPacket();
packet.protocolIdentifier = 0xff;
packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
packet.errorClass = 0xff;
packet.error = 0;
packet.flags = 0x00;
packet.flags2 = 0x0001;
packet.multiplexId = 22;
packet.wordCount = 0;
packet.byteCount = 119;

packet.Buffer = "NT LM 0.12";

이제이 패킷을 소켓으로 보내고 싶습니다. 이를 위해 구조를 바이트 배열로 변환해야합니다. 어떻게하니?

내 전체 코드는 다음과 같습니다.

static void Main(string[] args)
{

  Socket MyPing = new Socket(AddressFamily.InterNetwork,
  SocketType.Stream , ProtocolType.Unspecified ) ;


  MyPing.Connect("172.24.18.240", 139);

    //Fake an IP Address so I can send with SendTo
    IPAddress IP = new IPAddress(new byte[] { 172,24,18,240 });
    IPEndPoint IPEP = new IPEndPoint(IP, 139);

    //Local IP for Receiving
    IPEndPoint Local = new IPEndPoint(IPAddress.Any, 0);
    EndPoint EP = (EndPoint)Local;

    CIFSPacket packet = new CIFSPacket();
    packet.protocolIdentifier = 0xff;
    packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
    packet.errorClass = 0xff;
    packet.error = 0;
    packet.flags = 0x00;
    packet.flags2 = 0x0001;
    packet.multiplexId = 22;
    packet.wordCount = 0;
    packet.byteCount = 119;

    packet.Buffer = "NT LM 0.12";

    MyPing.SendTo(It takes byte array as parameter);
}

코드 조각은 무엇입니까?


마지막 줄에서 한 번 수정 MyPing.Send (바이트 배열을 매개 변수로 사용); 그것은하지의 SendTo ...... 보내기되어
Swapnil 굽타

안녕하세요 페 타르, 나는 ... 당신을 얻을하지 않았다
Swapnil 굽타에게

3
이전 질문에 대한 답변을 수락하는 것이 좋습니다.
jnoss

1
나는 그것이 당신이 기대하는 출력에 대해 좀 더 구체적으로 설명하는 것이 도움이 될 것이라고 생각합니다. 그것을 byte []로 바꾸는 많은 방법이 있습니다. 우리는 아마 대부분에 대해 몇 가지 가정을 할 수 있습니다. 여러분은 필드의 필드 순서 네트워크 바이트 순서 고정 크기 표현을 원합니다. 문자열?
Marc Gravell

Marshall 옵션을 선택하는 경우 Grand Endian 및 Little endian과 약 32 비트 / 64 비트에주의하십시오.
x77

답변:


126

마샬링을 사용하면 상당히 쉽습니다.

파일 상단

using System.Runtime.InteropServices

함수

byte[] getBytes(CIFSPacket str) {
    int size = Marshal.SizeOf(str);
    byte[] arr = new byte[size];

    IntPtr ptr = Marshal.AllocHGlobal(size);
    Marshal.StructureToPtr(str, ptr, true);
    Marshal.Copy(ptr, arr, 0, size);
    Marshal.FreeHGlobal(ptr);
    return arr;
}

다시 변환하려면 :

CIFSPacket fromBytes(byte[] arr) {
    CIFSPacket str = new CIFSPacket();

    int size = Marshal.SizeOf(str);
    IntPtr ptr = Marshal.AllocHGlobal(size);

    Marshal.Copy(arr, 0, ptr, size);

    str = (CIFSPacket)Marshal.PtrToStructure(ptr, str.GetType());
    Marshal.FreeHGlobal(ptr);

    return str;
}

구조에서 문자열 앞에 이것을 넣어야합니다.

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string Buffer;

그리고 SizeConst가 가능한 가장 큰 문자열만큼 큰지 확인하십시오.

그리고 아마도 이것을 읽어야합니다 : http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx


감사합니다 Vincet. byte [] ??를 보낸 후 GetBytes ()를 호출해야합니다. frombytes () 메소드가 바이트를 보내고 있습니까? 나는 약간 혼란스러운 친구입니까?
Swapnil Gupta

1
GetBytes는 구조에서 배열로 변환합니다. FromBytes는 바이트에서 구조로 다시 변환합니다. 이는 함수 시그니처에서 분명합니다.
Vincent McNabb

1
@Swapnil 그것은 당신이 별도로 물어봐야 할 또 다른 질문입니다. 소켓에 대한 몇 가지 CE 자습서를 완료하는 것을 고려해야합니다. Google을 검색하세요.
Vincent McNabb

3
fromBytes 메서드에서는 CIFSPacket을 두 번 할당 할 필요가 없습니다. Marshal.SizeOf는 기꺼이 Type을 매개 변수로 사용하고 Marshal.PtrToStructure는 새 관리 개체를 할당합니다.
Jack Ukleja 2014 년

1
일부 상황에서는«StructureToPtr»함수가 예외를 발생시킵니다. 이 문제는«true»대신«false»를에 전달하여 수정할 수 있습니다 Marshal.StructureToPtr(str, ptr, false);. 그러나 필요는 내가하지만, 일반적인 바꿈 기능을 사용하고 있음을 언급 ...
하이 천사

30

Windows에서 FAST를 정말로 원한다면 CopyMemory와 함께 안전하지 않은 코드를 사용하여 수행 할 수 있습니다. CopyMemory는 약 5 배 더 빠릅니다 (예 : 800MB의 데이터는 마샬링을 통해 복사하는 데 3 초가 걸리고 CopyMemory를 통해 복사하는 데는 .6s 만 걸립니다). 이 방법은 실제로 구조체 blob 자체에 저장된 데이터 (예 : 숫자 또는 고정 길이 바이트 배열) 만 사용하도록 제한합니다.

    [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
    private static unsafe extern void CopyMemory(void *dest, void *src, int count);

    private static unsafe byte[] Serialize(TestStruct[] index)
    {
        var buffer = new byte[Marshal.SizeOf(typeof(TestStruct)) * index.Length];
        fixed (void* d = &buffer[0])
        {
            fixed (void* s = &index[0])
            {
                CopyMemory(d, s, buffer.Length);
            }
        }

        return buffer;
    }

4
이 답변을 읽는 사람들에게 머리를 기울이십시오 .. 이것은 크로스 플랫폼 친화적이 아닙니다 (Windows 전용 kernel32.dll 사용). 하지만 다시 2014 년에 작성되었습니다. :)
Raid

24

다음 방법을 살펴보십시오.

byte [] StructureToByteArray(object obj)
{
    int len = Marshal.SizeOf(obj);

    byte [] arr = new byte[len];

    IntPtr ptr = Marshal.AllocHGlobal(len);

    Marshal.StructureToPtr(obj, ptr, true);

    Marshal.Copy(ptr, arr, 0, len);

    Marshal.FreeHGlobal(ptr);

    return arr;
}

void ByteArrayToStructure(byte [] bytearray, ref object obj)
{
    int len = Marshal.SizeOf(obj);

    IntPtr i = Marshal.AllocHGlobal(len);

    Marshal.Copy(bytearray,0, i,len);

    obj = Marshal.PtrToStructure(i, obj.GetType());

    Marshal.FreeHGlobal(i);
}

이것은 인터넷 검색에서 찾은 다른 스레드의 뻔뻔한 사본입니다!

업데이트 : 자세한 내용은 소스 확인


Marshalling을 사용하여 Structure를 바이트 배열로 변환했습니다. 이제 소켓에서 응답을 받고 있는지 여부를 어떻게 확인할 수 있습니까? 확인하는 방법?
Swapnil Gupta

@Alastair, 나는 그것을 놓쳤다!! 지적 해주셔서 감사합니다. 내 답변을 업데이트했습니다.
Abdel Raoof

2
이 옵션은 플랫폼에 따라 다릅니다. Grand Endian 및 Little Endian 및 약 32 비트 / 64 비트에주의하십시오.
x77

@Abdel, 그리고 -1 : 사라입니다
알라 피츠

Alloc을 수행하고 시도에서 중간 비트를 감싼 다음 Free를 마지막으로 넣는 것이 합리적입니까? 일이 실패 할 것 같지는 않지만 실패하면 기억이 해제됩니까?
Casey

17

메모리 할당이 하나 더 적은 Vicent 코드의 변형 :

public static byte[] GetBytes<T>(T str)
{
    int size = Marshal.SizeOf(str);

    byte[] arr = new byte[size];

    GCHandle h = default(GCHandle);

    try
    {
        h = GCHandle.Alloc(arr, GCHandleType.Pinned);

        Marshal.StructureToPtr<T>(str, h.AddrOfPinnedObject(), false);
    }
    finally
    {
        if (h.IsAllocated)
        {
            h.Free();
        }
    }

    return arr;
}

public static T FromBytes<T>(byte[] arr) where T : struct
{
    T str = default(T);

    GCHandle h = default(GCHandle);

    try
    {
        h = GCHandle.Alloc(arr, GCHandleType.Pinned);

        str = Marshal.PtrToStructure<T>(h.AddrOfPinnedObject());

    }
    finally
    {
        if (h.IsAllocated)
        {
            h.Free();
        }
    }

    return str;
}

나는 GCHandle메모리를 "고정"하는데 사용하고 그 주소를 h.AddrOfPinnedObject().


where T : struct그렇지 않으면 제거해야 T통과 에 대한 불만 이 non-nullable type있습니다.
codenamezero

GCHandle.Alloc구조체에 블리트 할 수없는 데이터 (예 : 배열)가 있으면 실패합니다
joe

@joe 맞아요. 코드는 blittable 유형 및 string.
xanatos

5

주된 대답은 C #에서 사용할 수 없거나 더 이상 사용할 수없는 CIFSPacket 유형을 사용하는 것이므로 올바른 메서드를 작성했습니다.

    static byte[] getBytes(object str)
    {
        int size = Marshal.SizeOf(str);
        byte[] arr = new byte[size];
        IntPtr ptr = Marshal.AllocHGlobal(size);

        Marshal.StructureToPtr(str, ptr, true);
        Marshal.Copy(ptr, arr, 0, size);
        Marshal.FreeHGlobal(ptr);

        return arr;
    }

    static T fromBytes<T>(byte[] arr)
    {
        T str = default(T);

        int size = Marshal.SizeOf(str);
        IntPtr ptr = Marshal.AllocHGlobal(size);

        Marshal.Copy(arr, 0, ptr, size);

        str = (T)Marshal.PtrToStructure(ptr, str.GetType());
        Marshal.FreeHGlobal(ptr);

        return str;
    }

테스트를 거쳐 작동합니다.


3

나는 이것이 정말 늦었다는 것을 알고 있지만 C # 7.3을 사용하면 관리되지 않는 구조체 또는 관리되지 않는 다른 모든 것 (int, bool 등)에 대해이 작업을 수행 할 수 있습니다.

public static unsafe byte[] ConvertToBytes<T>(T value) where T : unmanaged {
        byte* pointer = (byte*)&value;

        byte[] bytes = new byte[sizeof(T)];
        for (int i = 0; i < sizeof(T); i++) {
            bytes[i] = pointer[i];
        }

        return bytes;
    }

그런 다음 다음과 같이 사용하십시오.

struct MyStruct {
        public int Value1;
        public int Value2;
        //.. blah blah blah
    }

    byte[] bytes = ConvertToBytes(new MyStruct());

2

Marshal (StructureToPtr, ptrToStructure) 및 Marshal.copy를 사용할 수 있지만 이것은 plataform에 따라 다릅니다.


직렬화에는 사용자 정의 직렬화에 대한 함수가 포함됩니다.

public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) 

SerializationInfo에는 각 멤버를 직렬화하는 함수가 포함됩니다.


BinaryWriter 및 BinaryReader에는 또한 바이트 배열 (Stream)에 저장 /로드하는 메서드가 포함되어 있습니다.

바이트 배열에서 MemoryStream을 만들거나 MemoryStream에서 바이트 배열을 만들 수 있습니다.

구조에 Save 메소드와 New 메소드를 생성 할 수 있습니다.

   Save(Bw as BinaryWriter)
   New (Br as BinaryReader)

그런 다음 Save / Load to Stream-> Byte Array 할 멤버를 선택합니다.


1

이것은 매우 간단하게 할 수 있습니다.

다음을 사용하여 구조체를 명시 적으로 정의하십시오. [StructLayout(LayoutKind.Explicit)]

int size = list.GetLength(0);
IntPtr addr = Marshal.AllocHGlobal(size * sizeof(DataStruct));
DataStruct *ptrBuffer = (DataStruct*)addr;
foreach (DataStruct ds in list)
{
    *ptrBuffer = ds;
    ptrBuffer += 1;
}

이 코드는 안전하지 않은 컨텍스트에서만 작성할 수 있습니다. 당신이 addr그것으로 끝났을 때 자유로 워야 합니다.

Marshal.FreeHGlobal(addr);

고정 된 크기의 컬렉션에 대해 명시 적으로 정렬 된 작업을 수행 할 때 배열과 for 루프를 사용해야합니다. 배열은 크기가 고정되어 있고 for-loop는 foreach가 예상 한 순서대로 보장되지 않기 때문에 목록 유형과 열거 자의 기본 구현을 알지 못하고 절대 변경되지 않기 때문입니다. 예를 들어 끝에서 시작하여 뒤로 이동하도록 열거자를 정의 할 수 있습니다.

1

길이 고정의 번거 로움없이 어떤 것도 변환 수있는 다른 접근 방식을 생각해 struct냈지만 결과 바이트 배열은 약간 더 많은 오버 헤드를 가질 것입니다.

다음은 샘플입니다 struct.

[StructLayout(LayoutKind.Sequential)]
public class HelloWorld
{
    public MyEnum enumvalue;
    public string reqtimestamp;
    public string resptimestamp;
    public string message;
    public byte[] rawresp;
}

보시다시피 이러한 모든 구조는 고정 길이 속성을 추가해야합니다. 종종 필요한 것보다 더 많은 공간을 차지할 수 있습니다. 가 있습니다 LayoutKind.Sequential에 대한 당기는 때 항상 우리에게 같은 순서를 제공에 우리가 반사를 원하는 필요합니다 FieldInfo. 제 영감은 TLV유형-길이-가치입니다. 코드를 살펴 보겠습니다.

public static byte[] StructToByteArray<T>(T obj)
{
    using (MemoryStream ms = new MemoryStream())
    {
        FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
        foreach (FieldInfo info in infos)
        {
            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream inms = new MemoryStream()) {

                bf.Serialize(inms, info.GetValue(obj));
                byte[] ba = inms.ToArray();
                // for length
                ms.Write(BitConverter.GetBytes(ba.Length), 0, sizeof(int));

                // for value
                ms.Write(ba, 0, ba.Length);
            }
        }

        return ms.ToArray();
    }
}

위의 함수는 단순히를 사용하여 BinaryFormatter알 수없는 크기의 raw를 직렬화하고 object, 크기도 추적하고 출력 내부에도 저장합니다 MemoryStream.

public static void ByteArrayToStruct<T>(byte[] data, out T output)
{
    output = (T) Activator.CreateInstance(typeof(T), null);
    using (MemoryStream ms = new MemoryStream(data))
    {
        byte[] ba = null;
        FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
        foreach (FieldInfo info in infos)
        {
            // for length
            ba = new byte[sizeof(int)];
            ms.Read(ba, 0, sizeof(int));

            // for value
            int sz = BitConverter.ToInt32(ba, 0);
            ba = new byte[sz];
            ms.Read(ba, 0, sz);

            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream inms = new MemoryStream(ba))
            {
                info.SetValue(output, bf.Deserialize(inms));
            }
        }
    }
}

원본으로 다시 변환하고 싶을 때 struct길이를 다시 읽어서 직접 BinaryFormatter다시 덤프 한 다음 다시 struct.

이 두 기능은 일반적인 것으로, 어떤 작업을해야한다 struct, 나는 내에서 위의 코드를 테스트 한 C#연결 I는 서버와 클라이언트가 프로젝트와를 통해 통신 NamedPipeStream과 내 앞으로 struct하나와 다른 바이트 배열로 다시 그것을 변환 .

내 접근 방식이 더 나을 것이라고 생각합니다. struct그 자체에 길이를 고정하지 않고 유일한 오버 헤드는 int구조체에있는 모든 필드에 대한 것입니다. 에 의해 생성 된 바이트 배열 내부에도 약간의 오버 헤드가 BinaryFormatter있지만 그 외에는 많지 않습니다.


6
일반적으로 사람들이 이러한 문제를 처리하려고 할 때 직렬화 성능에 대해서도 우려합니다. 이론적으로 모든 구조체 배열은 값 비싼 직렬화 및 복사없이 바이트 배열로 재 해석 할 수 있습니다.
Tanveer Badar


0

일부 외부 라이브러리에 대해 미리 정의 된 (C 레벨) 구조처럼 보입니다. 마샬은 당신의 친구입니다. 검사:

http://geekswithblogs.net/taylorrich/archive/2006/08/21/88665.aspx

우선 이것을 다루는 방법. 속성을 사용하여 바이트 레이아웃 및 문자열 처리와 같은 것을 정의 할 수 있습니다. 실제로 아주 좋은 접근 방식입니다.

BinaryFormatter도 MemoryStream도이를 위해 수행되지 않습니다.


0

@Abdel Olakara 대답은 .net 3.5에서 작동하지 않으며 아래와 같이 수정해야합니다.

    public static void ByteArrayToStructure<T>(byte[] bytearray, ref T obj)
    {
        int len = Marshal.SizeOf(obj);
        IntPtr i = Marshal.AllocHGlobal(len);
        Marshal.Copy(bytearray, 0, i, len);
        obj = (T)Marshal.PtrToStructure(i, typeof(T));
        Marshal.FreeHGlobal(i);
    }

0
        Header header = new Header();
        Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
        Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);

이것은 속임수를 빨리 할 것입니다.


GCHandle 버전이 훨씬 좋습니다.
Петър Петров

0

이 예제는 순수한 blittable 유형에만 적용 할 수 있습니다. 예를 들어 C에서 직접 memcpy 할 수있는 유형입니다.

예-잘 알려진 64 비트 구조체

[StructLayout(LayoutKind.Sequential)]  
public struct Voxel
{
    public ushort m_id;
    public byte m_red, m_green, m_blue, m_alpha, m_matid, m_custom;
}

정확히 이와 같이 정의 된 구조체는 64 비트로 자동 패킹됩니다.

이제 복셀 볼륨을 만들 수 있습니다.

Voxel[,,] voxels = new Voxel[16,16,16];

그리고 그것들을 모두 바이트 배열에 저장합니다.

int size = voxels.Length * 8; // Well known size: 64 bits
byte[] saved = new byte[size];
GCHandle h = GCHandle.Alloc(voxels, GCHandleType.Pinned);
Marshal.Copy(h.AddrOfPinnedObject(), saved, 0, size);
h.Free();
// now feel free to save 'saved' to a File / memory stream.

그러나 OP는 구조체 자체를 변환하는 방법을 알고 싶기 때문에 Voxel 구조체는 다음과 같은 방법을 가질 수 있습니다 ToBytes.

byte[] bytes = new byte[8]; // Well known size: 64 bits
GCHandle h = GCHandle.Alloc(this, GCHandleType.Pinned);
Marshal.Copy(hh.AddrOfPinnedObject(), bytes, 0, 8);
h.Free();
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.