如何将结构转换为C#中的字节数组?
如何将结构转换为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; }
在我的主要方法中,我创build一个实例并为其赋值:
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); }
什么是代码片段?
这很简单,使用编组。
文件顶部
using System.Runtime.InteropServices
function
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; }
在你的结构中,你需要把它放在一个string之前
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)] public string Buffer;
确保SizeConst和你最大的string一样大。
你应该读这个: http : //msdn.microsoft.com/en-us/library/4ca6d5z7.aspx
看看这些方法:
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); }
这是我在Google上find的另一个线程的无耻副本!
更新 :有关更多详细信息,请查看源代码
如果你真的希望它是快速的,你可以使用CopyMemory使用不安全的代码。 CopyMemory大约快了5倍(例如800MB的数据通过编组复制需要3s,而只需要通过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; }
Vicent代码的变体,less一个内存分配:
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()
地址。
作为主要的答案是使用CIFSPackettypes,它不是(或不再)在C#中可用,我写了正确的方法:
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; }
经过testing,他们工作。
你可以使用Marshal(StructureToPtr,ptrToStructure)和Marshal.copy,但这是平台依赖的。
序列化包括自定义序列化的function。
public virtual void GetObjectData(SerializationInfo info, StreamingContext context) Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
SerializationInfo包含序列化每个成员的函数。
BinaryWriter和BinaryReader还包含保存/加载到字节数组(stream)的方法。
请注意,您可以从Byte Array或MemoryStream的Byte Array创buildMemoryStream。
你可以创build一个方法Save和一个方法你的结构上的新东西:
Save(Bw as BinaryWriter) New (Br as BinaryReader)
然后,您select成员保存/加载到stream – >字节数组。
这可以非常简单地完成。
用[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);
我会看看BinaryReader和BinaryWriter类。 我最近不得不将数据序列化到一个字节数组(后面),只有在我自己重写了这些类之后才发现这些类。
http://msdn.microsoft.com/en-us/library/system.io.binarywriter.aspx
这个页面上也有一个很好的例子。
看起来像一个外部库的预定义(C级)结构。 元帅是你的朋友。 检查:
http://geekswithblogs.net/taylorrich/archive/2006/08/21/88665.aspx
对于初学者如何处理这个问题。 请注意,您可以使用属性来定义诸如字节布局和string处理之类的内容。 其实很不错。
BinaryFormatter和MemoryStream都没有完成。
@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); }
Header header = new Header(); Byte[] headerBytes = new Byte[Marshal.SizeOf(header)]; Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);
这应该快速做到这一点,对吧?
这里的这个例子只适用于纯粹的blittabletypes,例如,可以直接在C中memcpy的types。
例子 – 众所周知的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位。
现在我们可以创build体素的体积:
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想知道如何转换结构本身,我们的体素结构可以有以下方法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();
我想出了一个不同的方法,可以转换任何 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
是必需的,因为我们希望reflection在拉取FieldInfo
时总是给我们相同的顺序。 我的灵感来自TLV
Type-Length-Value。 让我们看看代码:
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
对未知大小的原始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#
项目中testing了上述代码,我有一个服务器和一个客户端,通过NamedPipeStream
连接和通信,我转发我的struct
作为字节数组从一个到另一个并将其转换回来。
我相信我的方法可能会更好,因为它不固定struct
本身的长度,唯一的开销只是你的结构中的每个字段的int
。 BinaryFormatter
生成的字节数组内部也有一些微小的开销,但除此之外,并不多。