如何在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;
}
在我的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";
现在我想通过套接字发送此数据包。为此,我需要将结构转换为字节数组。我该怎么办?
我的完整代码如下。
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);
}
代码段会是什么?
答案 0 :(得分:109)
使用编组很容易。
文件顶部
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
答案 1 :(得分:25)
如果你真的希望它是快速的,你可以使用CopyMemory使用不安全的代码。 CopyMemory的速度提高了约5倍(例如800MB的数据通过编组复制需要3秒,而只需要通过CopyMemory复制.6s)。此方法确实限制您仅使用实际存储在struct 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;
}
答案 2 :(得分:21)
看看这些方法:
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);
}
这是我在Googling上发现的另一个帖子的无耻副本!
更新:有关详情,请查看source
答案 3 :(得分:14)
包含较少内存分配的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
来&#34; pin&#34;记忆然后我直接使用h.AddrOfPinnedObject()
的地址。
答案 4 :(得分:5)
由于主要答案是使用CIF中没有(或不再)可用的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;
}
经过测试,他们工作。
答案 5 :(得分:4)
我知道这真的很晚,但是使用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());
答案 6 :(得分: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还包含保存/加载到字节数组(流)的方法。
请注意,您可以从字节数组创建MemoryStream或从MemoryStream创建字节数组。
您可以在结构上创建方法Save和方法New:
Save(Bw as BinaryWriter)
New (Br as BinaryReader)
然后选择要保存/加载到流的成员 - &gt;字节数组。
答案 7 :(得分: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);
答案 8 :(得分: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
序列化未知大小的原始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#
项目中测试了上面的代码,我有一个服务器和一个客户端,通过{{1连接和通信我将NamedPipeStream
作为字节数组从一个转发到另一个并转换回来。
我相信我的方法可能会更好,因为它不会修复struct
本身的长度,并且对于结构中的每个字段,唯一的开销只是struct
。在int
生成的字节数组中也有一些微小的开销,但除此之外,并不多。
答案 9 :(得分:0)
我将看一下BinaryReader和BinaryWriter类。我最近不得不将数据序列化为字节数组(并返回),并且在我基本上自己重写之后才发现这些类。
http://msdn.microsoft.com/en-us/library/system.io.binarywriter.aspx
该页面上也有一个很好的例子。
答案 10 :(得分:0)
看起来像某个外部库的预定义(C级)结构。元帅是你的朋友。检查:
http://geekswithblogs.net/taylorrich/archive/2006/08/21/88665.aspx
首发如何处理这个问题。请注意,您可以 - 使用属性 - 定义字节布局和字符串处理等内容。非常好的方法,实际上。
BinaryFormatter Nor MemoryStream都没有为此完成。
答案 11 :(得分: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);
}
答案 12 :(得分:0)
Header header = new Header();
Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);
这应该很快就能解决问题吧?
答案 13 :(得分:0)
此示例仅适用于纯blittable类型,例如可直接在C中记忆的类型。
示例 - 众所周知的64位结构
func split<T>(array: [T], ranges: [CountableClosedRange<T>]) -> [[T]] {
var result = Array(repeating: [T](), count: ranges.count)
for element in array {
if let subIndex = ranges.index(where: { $0 ~= element }) {
result[subIndex].append(element)
}
}
return result
}
let ages = [10, 20, 30, 40, 50, 60]
let subarrays = split(array: ages, ranges: [0...25, 26...50, 51...75, 76...Int.max])
这样定义,结构将自动打包为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;
}
并将它们全部保存为字节数组:
Voxel[,,] voxels = new Voxel[16,16,16];
但是,由于OP想知道如何转换结构本身,我们的Voxel结构可以有以下方法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.
:
ToBytes