没有结构的对象的序列化
Serialization of object without structure
我正在开发一个可以与西门子 PLC(工业控制器)交换数据的软件。
为了让它工作,我需要能够序列化和反序列化一个只包含变量当前值的字节数组。
我面临的问题是 Serialize/Deserialize 方法添加了很多超出变量当前值的信息。
下面的实例class:
[Serializable]
public class VarMap
{
public byte var1;
public int64 var2;
public int32 var3;
}
序列化后,需要是一个字节数组,包含一个接一个的值,每个值以字节为单位占用它们的大小:
[var1 字节 1][var2 字节 1][var2 字节 2][var2 字节 3][var2 字节 4][var3 字节 1][var3 字节 2].
有什么想法可以根据 class 的声明以动态方式实现这一点吗?
对于引用其他引用类型的复杂引用类型,没有一种简单的方法可以将它们扁平化为纯字节序列,并且序列化程序必须使用更复杂的格式来处理此类情况是有原因的。
也就是说,你显示的类型就是所谓的blittable type。
Blittable 类型只包含可以直接复制位模式的其他原始类型的字段。
通过将 class 更改为结构,您通常可以将此类类型直接保存到磁盘。这个确实需要用到unsafe block,但是代码比较简单
有很多方法可以在不使用不安全代码的情况下做同样的事情,但使用不安全代码是最多的straight-forward,因为您可以只获取一个指针并使用它直接写入磁盘、网络等。
首先,更改为结构:
[StructLayout(LayoutKind.Sequential)]
public struct VarMap
{
public byte var1;
public long var2;
public int var3;
}
然后,创建一个并直接写入磁盘:
VarMap vm = new VarMap
{
var1 = 254,
var2 = 1234,
var3 = 5678
};
unsafe
{
VarMap* p = &vm;
int sz = sizeof(VarMap);
using FileStream fs = new FileStream(@"out.bin", FileMode.Create);
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(p, sz);
fs.Write(span);
}
但是,如果您在十六进制查看器中查看结果,您会注意到一些事情:
24 个字节被写入磁盘,而不是我们根据您的数据结构中的字段类型假设的 13 个字节。
这是因为为了对齐目的在内存中填充了结构。
根据您将这些字节发送到实际期望的设备(并且 endian-ness 也可能是一个问题),这种技术可能有效也可能无效。
如果您需要绝对控制,您可能希望自己 hand-write 序列化代码,而不是试图找到一些通用机制来执行此操作。使用反射可能是可能的,但直接将您的字段转换为原始字节表示形式可能更简单。
我正在开发一个可以与西门子 PLC(工业控制器)交换数据的软件。 为了让它工作,我需要能够序列化和反序列化一个只包含变量当前值的字节数组。 我面临的问题是 Serialize/Deserialize 方法添加了很多超出变量当前值的信息。 下面的实例class:
[Serializable]
public class VarMap
{
public byte var1;
public int64 var2;
public int32 var3;
}
序列化后,需要是一个字节数组,包含一个接一个的值,每个值以字节为单位占用它们的大小: [var1 字节 1][var2 字节 1][var2 字节 2][var2 字节 3][var2 字节 4][var3 字节 1][var3 字节 2].
有什么想法可以根据 class 的声明以动态方式实现这一点吗?
对于引用其他引用类型的复杂引用类型,没有一种简单的方法可以将它们扁平化为纯字节序列,并且序列化程序必须使用更复杂的格式来处理此类情况是有原因的。
也就是说,你显示的类型就是所谓的blittable type。
Blittable 类型只包含可以直接复制位模式的其他原始类型的字段。
通过将 class 更改为结构,您通常可以将此类类型直接保存到磁盘。这个确实需要用到unsafe block,但是代码比较简单
有很多方法可以在不使用不安全代码的情况下做同样的事情,但使用不安全代码是最多的straight-forward,因为您可以只获取一个指针并使用它直接写入磁盘、网络等。
首先,更改为结构:
[StructLayout(LayoutKind.Sequential)]
public struct VarMap
{
public byte var1;
public long var2;
public int var3;
}
然后,创建一个并直接写入磁盘:
VarMap vm = new VarMap
{
var1 = 254,
var2 = 1234,
var3 = 5678
};
unsafe
{
VarMap* p = &vm;
int sz = sizeof(VarMap);
using FileStream fs = new FileStream(@"out.bin", FileMode.Create);
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(p, sz);
fs.Write(span);
}
但是,如果您在十六进制查看器中查看结果,您会注意到一些事情:
24 个字节被写入磁盘,而不是我们根据您的数据结构中的字段类型假设的 13 个字节。
这是因为为了对齐目的在内存中填充了结构。
根据您将这些字节发送到实际期望的设备(并且 endian-ness 也可能是一个问题),这种技术可能有效也可能无效。
如果您需要绝对控制,您可能希望自己 hand-write 序列化代码,而不是试图找到一些通用机制来执行此操作。使用反射可能是可能的,但直接将您的字段转换为原始字节表示形式可能更简单。