简体   繁体   English

如何在 C# 中将结构转换为字节数组?

[英]How to convert a structure to a byte array in C#?

How do I convert a structure to a byte array in C#?如何在 C# 中将结构转换为字节数组?

I have defined a structure like this:我已经定义了一个这样的结构:

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;
}

In my main method, I create an instance of it and assign values to it:在我的 main 方法中,我创建它的一个实例并为其赋值:

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";

Now I want to send this Packet by socket.现在我想通过套接字发送这个数据包。 For that, I need to convert the structure to a byte array.为此,我需要将结构转换为字节数组。 How can I do it?我该怎么做?

My full code is as follows.我的完整代码如下。

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);
}

What would a code snippet be?代码片段是什么?

This is fairly easy, using marshalling.这很容易,使用编组。

Top of file文件顶部

using System.Runtime.InteropServices

Function功能

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

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

And to convert it back:并将其转换回来:

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

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

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

        str = (CIFSPacket)Marshal.PtrToStructure(ptr, str.GetType());
    }
    finally
    {
        Marshal.FreeHGlobal(ptr);
    }
    return str;
}

In your structure, you will need to put this before a string在您的结构中,您需要将其放在字符串之前

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

And make sure SizeConst is as big as your biggest possible string.并确保 SizeConst 与您最大的字符串一样大。

And you should probably read this: http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx你可能应该读到这个:http: //msdn.microsoft.com/en-us/library/4ca6d5z7.aspx

If you really want it to be FAST on Windows, you can do it using unsafe code with CopyMemory.如果您真的希望它在 Windows 上快速运行,您可以使用带有 CopyMemory 的不安全代码来实现。 CopyMemory is about 5x faster (eg 800MB of data takes 3s to copy via marshalling, while only taking .6s to copy via CopyMemory). CopyMemory 大约快 5 倍(例如,通过编组复制 800MB 的数据需要 3 秒,而通过 CopyMemory 复制只需 0.6 秒)。 This method does limit you to using only data which is actually stored in the struct blob itself, eg numbers, or fixed length byte arrays.此方法确实限制您仅使用实际存储在结构 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;
    }

Have a look at these methods:看看这些方法:

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);
}

This is a shameless copy of another thread which I found upon Googling!这是我在谷歌上找到的另一个线程的无耻副本!

Update : For more details, check the source更新:有关更多详细信息,请检查

Variant of the code of Vicent with one less memory allocation: 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;
}

I use GCHandle to "pin" the memory and then I use directly its address with h.AddrOfPinnedObject() .我使用GCHandle来“固定”内存,然后直接将其地址与h.AddrOfPinnedObject()一起使用。

I know this is really late, but with C# 7.3 you can do this for unmanaged structs or anything else that's unmanged (int, bool etc...):我知道这已经很晚了,但是使用 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;
    }

Then use like this:然后像这样使用:

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

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

As the main answer is using CIFSPacket type, which is not (or no longer) available in C#, I wrote correct methods:由于主要答案是使用 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;
    }

Tested, they work.经过测试,它们有效。

You can use Marshal (StructureToPtr, ptrToStructure), and Marshal.copy but this is plataform dependent.您可以使用 Marshal (StructureToPtr, ptrToStructure) 和 Marshal.copy,但这取决于平台。


Serialization includes Functions to Custom Serialization.序列化包括自定义序列化的函数。

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

SerializationInfo include functions to serialize each member. SerializationInfo 包括用于序列化每个成员的函数。


BinaryWriter and BinaryReader also contains methods to Save / Load to Byte Array (Stream). BinaryWriter 和 BinaryReader 还包含保存/加载到字节数组(流)的方法。

Note that you can create a MemoryStream from a Byte Array or a Byte Array from a MemoryStream.请注意,您可以从字节数组创建 MemoryStream 或从 MemoryStream 创建字节数组。

You can create a method Save and a method New on your structure:您可以在结构上创建方法 Save 和方法 New:

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

Then you select members to Save / Load to Stream -> Byte Array.然后选择要保存/加载到流 -> 字节数组的成员。

This can be done very straightforwardly.这可以非常简单地完成。

Define your struct explicitly with [StructLayout(LayoutKind.Explicit)]使用[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;
}

This code can only be written in an unsafe context.此代码只能在不安全的上下文中编写。 You have to free addr when you're done with it.完成后,您必须释放addr

Marshal.FreeHGlobal(addr);

I've come up with a different approach that could convert any struct without the hassle of fixing length, however the resulting byte array would have a little bit more overhead.我想出了一种不同的方法,可以转换任何struct而无需固定长度的麻烦,但是生成的字节数组会有更多的开销。

Here is a sample struct :这是一个示例struct

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

As you can see, all those structures would require adding the fixed length attributes.如您所见,所有这些结构都需要添加固定长度属性。 Which could often ended up taking up more space than required.这通常最终会占用比所需更多的空间。 Note that the LayoutKind.Sequential is required, as we want reflection to always gives us the same order when pulling for FieldInfo .请注意, LayoutKind.Sequential是必需的,因为我们希望反射在拉取FieldInfo时始终为我们提供相同的顺序。 My inspiration is from TLV Type-Length-Value.我的灵感来自TLV Type-Length-Value。 Let's have a look at the code:让我们看一下代码:

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();
    }
}

The above function simply uses the BinaryFormatter to serialize the unknown size raw object , and I simply keep track of the size as well and store it inside the output MemoryStream too.上面的函数简单地使用BinaryFormatter序列化未知大小的原始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));
            }
        }
    }
}

When we want to convert it back to its original struct we simply read the length back and directly dump it back into the BinaryFormatter which in turn dump it back into the struct .当我们想要将其转换回其原始struct时,我们只需读取长度并将其直接转储回BinaryFormatter ,然后将其转储回struct

These 2 functions are generic and should work with any struct , I've tested the above code in my C# project where I have a server and a client, connected and communicate via NamedPipeStream and I forward my struct as byte array from one and to another and converted it back.这两个函数是通用的,应该适用于任何struct ,我已经在我的C#项目中测试了上面的代码,我有一个服务器和一个客户端,通过NamedPipeStream连接和通信,我将我的struct作为字节数组从一个转发到另一个并将其转换回来。

I believe my approach might be better, since it doesn't fix length on the struct itself and the only overhead is just an int for every fields you have in your struct.我相信我的方法可能会更好,因为它不固定struct本身的长度,唯一的开销只是结构中每个字段的int There are also some tiny bit overhead inside the byte array generated by BinaryFormatter , but other than that, is not much. BinaryFormatter生成的字节数组内部也有一些微小的开销,但除此之外,并不多。

@Abdel Olakara answer donese not work in .net 3.5, should be modified as below: @Abdel Olakara 回答 doense 在 .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);
    }
        Header header = new Header();
        Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
        Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);

This should do the trick quickly, right?这应该很快就能解决问题,对吧?

I would take a look at the BinaryReader and BinaryWriter classes.我会看一下 BinaryReader 和 BinaryWriter 类。 I recently had to serialize data to a byte array (and back) and only found these classes after I'd basically rewritten them myself.我最近不得不将数据序列化为字节数组(然后返回),并且只有在我自己基本上重写它们之后才发现这些类。

http://msdn.microsoft.com/en-us/library/system.io.binarywriter.aspx http://msdn.microsoft.com/en-us/library/system.io.binarywriter.aspx

There is a good example on that page too.该页面上也有一个很好的例子。

Looks like a predefined (C level) structure for some external library.看起来像是某些外部库的预定义(C 级)结构。 Marshal is your friend.元帅是你的朋友。 Check:查看:

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

for a starter how to deal with this.对于初学者如何处理这个问题。 Note that you can - with attributes - define things like byte layout and string handling.请注意,您可以 - 使用属性 - 定义字节布局和字符串处理等内容。 VERY nice approach, actually.非常好的方法,实际上。

Neither BinaryFormatter Nor MemoryStream are done for that. BinaryFormatter 和 MemoryStream 都不是为此而做的。

This example here is only applicable to pure blittable types, eg, types that can be memcpy'd directly in C.这里的这个例子只适用于纯 blittable 类型,例如,可以直接在 C 中 memcpy 的类型。

Example - well known 64-bit struct示例 - 众所周知的 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;
}

Defined exactly like this, the struct will be automatically packed as 64-bit.完全像这样定义,结构将自动打包为 64 位。

Now we can create volume of voxels:现在我们可以创建体素体积:

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

And save them all to a byte array:并将它们全部保存到一个字节数组中:

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.

However, since the OP wants to know how to convert the struct itself, our Voxel struct can have following method ToBytes :但是,由于 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();

Almost all of the answers here use Marshal.StructureToPtr , which might be good for P/Invoke but it is very slow, and doesn't even always represent the actual raw content of the value.这里几乎所有的答案都使用Marshal.StructureToPtr ,这可能对 P/Invoke 有好处,但速度很慢,甚至并不总是代表值的实际原始内容。 @Varscott128's answer is much better but it also contains an explicit byte copying, which is not necessary. @ Varscott128 的答案要好得多,但它也包含显式字节复制,这不是必需的。

For unmanaged structs (structs without managed references) all you need is to reinterpret the allocated result array so a simple assignment does the trick (works even for huge structs):对于非托管结构(没有托管引用的结构),您只需要重新解释分配的结果数组,这样一个简单的赋值就可以解决问题(即使对于巨大的结构也有效):

.NET (Core) Solution: .NET(核心)解决方案:

If you can use the Unsafe class, then the solution is really easy.如果您可以使用Unsafe类,那么解决方案真的很简单。 The unsafe modifier is required only due to sizeof(T) .仅由于sizeof(T)才需要unsafe修饰符。

public static unsafe byte[] SerializeValueType<T>(in T value) where T : unmanaged
{
    byte[] result = new byte[sizeof(T)];
    Unsafe.As<byte, T>(ref result[0]) = value;
    return result;
}

// Note: Validation is omitted for simplicity
public static T DeserializeValueType<T>(byte[] data) where T : unmanaged
    => return Unsafe.As<byte, T>(ref data[0]);

.NET Framework/Standard Solution: .NET 框架/标准解决方案:

public static unsafe byte[] SerializeValueType<T>(in T value) where T : unmanaged
{
    byte[] result = new byte[sizeof(T)];
    fixed (byte* dst = result)
        *(T*)dst = value;
    return result;
}

// Note: Validation is omitted for simplicity
public static unsafe T DeserializeValueType<T>(byte[] data) where T : unmanaged
{
    fixed (byte* src = data)
        return *(T*)src;
}

See the complete code with validations here .此处查看带有验证的完整代码。

Remarks:评论:

The OP's example contains a string , which is a reference type so the solution above cannot be used for that. OP 的示例包含一个string ,它是一个引用类型,因此上述解决方案不能用于此。 And if you can't use generic methods for some reason things start to get more complicated, especially for .NET Framework (but non-generic size calculation is a pain also on the Core platform).如果由于某种原因不能使用泛型方法,事情就会变得更加复杂,尤其是对于.NET Framework (但非泛型大小计算在 Core 平台上也是一种痛苦)。 If performance does not matter, then you can revert to Marshal.SizeOf and StructureToPtr as suggested by several other answers, or feel free to use the BinarySerializer.SerializeValueType method from my library that I linked also for the examples above ( NuGet ).如果性能无关紧要,那么您可以按照其他几个答案的建议恢复为Marshal.SizeOfStructureToPtr ,或者随意使用我的中的BinarySerializer.SerializeValueType方法,我也为上面的示例( NuGet )链接了该方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM