public struct Foo
{
public byte A { get; set; }
public UInt16 B { get; set; }
public UInt32 C { get; set; }
public bool D { get; set; }
public string E { get; set; }
public UInt32[] F { get; set; }
}
static byte[] ConverterFoo2ByteArray(Foo f)
{
using (MemoryStream ms = new MemoryStream())
{
using (BinaryWriter bw = new BinaryWriter(ms))
{
bw.Write(f.A);
bw.Write(f.B);
bw.Write(f.C);
bw.Write(f.D);
//bw.Write(f.E);//使用Write(string)的方法时,字节中会包含一个编码方式信息
byte[] str = new byte[128];
Encoding.ASCII.GetBytes(f.E).CopyTo(str, 0);
bw.Write(str);
for (int i = 0; i < f.F.Length; i++)
{
bw.Write(f.F[i]);//没有Write(UInt32[])的重载,所以枚举每一个元素单独写
}
}
return ms.ToArray();
}
}
static Foo ConverterByteArray2Foo(byte[] buffer)
{
using (MemoryStream ms = new MemoryStream(buffer))
{
using (BinaryReader br = new BinaryReader(ms))
{
Foo foo = new Foo();
foo.A = br.ReadByte();
foo.B = br.ReadUInt16();
foo.C = br.ReadUInt32();
foo.D = br.ReadBoolean();
foo.E = Encoding.ASCII.GetString(br.ReadBytes(128));
foo.F = new UInt32[3];
foo.F[0] = br.ReadUInt32();
foo.F[1] = br.ReadUInt32();
foo.F[2] = br.ReadUInt32();
return foo;
}
}
}
显然,上面的例子存在一个很严重的问题,如果Foo结构体中的成员有变动(增删或者顺序调整),那么就不得不修改ConverterFoo2ByteArray()方法。
那么我们可以这样做:
static byte[] ConverterFoo2ByteArray(Foo f)
{
using (MemoryStream ms = new MemoryStream())
{
using (BinaryWriter bw = new BinaryWriter(ms))
{
Type type = f.GetType();
var properties = type.GetProperties();
foreach (var pro in properties)
{
object o = pro.GetValue(f);
if (o.GetType() == typeof(byte))
bw.Write((byte)o);
else if (o.GetType() == typeof(UInt16))
bw.Write((UInt16)o);
else if (o.GetType() == typeof(UInt32))
bw.Write((UInt32)o);
else if (o.GetType() == typeof(bool))
bw.Write((bool)o);
else if (o.GetType() == typeof(string))
{
byte[] str = new byte[128];
Encoding.ASCII.GetBytes((string)o).CopyTo(str, 0);
bw.Write(str);
}
else if (o.GetType() == typeof(UInt32[]))
{
for (int i = 0; i < ((UInt32[])o).Length; i++)
{
bw.Write(((UInt32[])o)[i]);//没有Write(UInt32[])的重载,所以枚举每一个元素单独写
}
}
}
}
return ms.ToArray();
}
}
利用反射遍历成员中的所有属性,然后判断其Type,写入流中。
但是问题又来了,读怎么办呢?好像还得一个一个读。。。没有办法归一化。。
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct Foo
{
private byte a;
public byte A { get => a; set => a = value; }
private UInt16 b;
public UInt16 B { get => b; set => b = value; }
private UInt32 c;
public UInt32 C { get => c; set => c = value; }
private bool d;
public bool D { get => d; set => d = value; }
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
private string e;
public string E { get => e; set => e = value; }
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
private UInt32[] f;
public UInt32[] F { get => f; set => f = value; }
}
先定义好结构体,由于有些特性不能用在属性上,所以把隐藏的字段手动写出来了。
//使用这个方法将你的结构体转化为bytes数组
public static byte[] Struct2Bytes(Foo f)
{
int size = Marshal.SizeOf(f);
byte[] bytes = new byte[size];
try
{
//分配结构体大小的内存空间
IntPtr ptr = Marshal.AllocHGlobal(size);
//将结构体转换为分配好的内存空间
Marshal.StructureToPtr(f, ptr, false);
//将内存空间拷贝到字节数组
Marshal.Copy(ptr, bytes, 0, size);
//释放内存空间
Marshal.FreeHGlobal(ptr);
return bytes;
}
catch (Exception ee)
{
Console.WriteLine(ee.Message);
return bytes;
}
}
//使用这个方法将byte数组转化为结构体
public static T BytesToStuct2<T>(byte[] data)
{
//得到结构体的大小
int size = Marshal.SizeOf(typeof(T));
//byte数组长度小于结构体的大小
if (size > data.Length)
{
//返回空
return default;
}
//分配结构体大小的内存空间
IntPtr structPtr = Marshal.AllocHGlobal(size);
//将byte数组拷到分配好的内存空间
Marshal.Copy(data, 0, structPtr, size);
//将内存空间转换为目标结构体
object obj = Marshal.PtrToStructure(structPtr, typeof(T));
//释放内存空间
Marshal.FreeHGlobal(structPtr);
//返回结构体
return (T)obj;
}
static void Main(string[] args)
{
Foo foo = new Foo
{
A = 0x11,
B = 0x22,
C = 0x33,
D = true,
E = "zhanghanyun",
F = new UInt32[] { 0x44, 0x55, 0x66 },
};
Console.WriteLine(BitConverter.ToString(Encoding.UTF8.GetBytes(foo.E)));
byte[] buffer = Struct2Bytes(foo);
Console.WriteLine(BitConverter.ToString(buffer));
Foo f = BytesToStuct2<Foo>(buffer);
}
如果对上面代码有不理解的地方,应该是StructLayout特性和Marshal类。这里简单说一下,StructLayout特性就是为了控制结构体的内存分配,不然默认的字节对齐方式可能不是我们期待的,可以参考这里。
Marshal类主要是操作结构体和内存之间的转化,可在后期单独讲解。官方文档:https://docs.microsoft.com/zh-cn/dotnet/standard/native-interop/customize-struct-marshaling
还是使用StructLayout特性和Marshal类进行结构体和字节流之间的转换比较合适。最后,将struct换为class也是成立的。另外推荐一个开源库也可以简单实现上述的需求:BinarySerializer