序列化/封送C#中的简单对象以通过网络发送,供非托管C++应用程序读取
Serializing / Marshalling simple objects in C# to send over network, for an unmanaged C++ application to read
我正在编写一个C#应用程序,该应用程序需要使用预定义的消息通过网络与非托管C++应用程序进行通信。每条消息都以消息id和长度开头。
Byte 0 MessageId
Byte 1 Length
之后,每条消息都会有所不同。如设置消息的时间,将被
Byte 0 MessageId
Byte 1 Length
Byte 2..5 Time
一开始,我想我只需要创建一个基类,所有其他消息都将从中继承,它将有一个方法来"序列化"它:
public class BaseMessage
{
public virtual byte MessageId { get; }
public virtual byte Length { get; }
public byte[] GetAsMessage()
{
...
}
}
public class SetTimeMessage : BaseMessage
{
public override byte MessageId => 1;
public override byte Length {get; private set; }
public byte[] Time {get; private set; }
public SetTimeMessage(byte[] time)
{
...
}
}
在C++代码中,如果我创建了一个SetTimeMessage
并想通过网络发送它,就会调用一个在基类上定义的类似GetAsMessage
的方法,它只需将对象的内容复制到缓冲区中。这适用于BaseMessage
的所有派生类型。
我如何在C#中做类似的事情?我尝试使用[Serializable]
属性和BinaryFormatter
,但它返回了一个巨大的字节数组,而不仅仅是值实际包含的6个字节。
我也研究了Marshalling
,但它似乎只适用于structs?如果是这样的话,似乎每个消息类型都需要做很多工作,因为结构不支持继承(我需要实现很多消息)。
我还将收到相同格式的消息,这些消息需要反序列化回对象。有人知道实现我追求的目标的巧妙方法吗?
编辑
在Spo1ler的输入之后,我对结构和编组进行了一些实验。
这样做,很容易将任何消息序列化为字节数组
public interface IMsg
{
byte MessageId { get; }
byte Length { get; }
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct SetTime: IMsg
{
public byte Id { get; }
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] Time;
public StartLoggerWithSampleInterval(byte time)
{
Id = (byte) MessageType.SetTime;
Time = time;
}
}
然后我有一个静态类来序列化消息:
public static class MessageSerializer
{
public static byte[] Serialize(IMsg msg)
{
int size = Marshal.SizeOf(msg);
byte[] serialized = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(msg, ptr, true);
Marshal.Copy(ptr, serialized, 0, size);
Marshal.FreeHGlobal(ptr);
return serialized;
}
}
起初,这似乎像我想要的那样工作。这样做会有什么问题吗?
然而,消息的反序列化将是一件很难写的事情,尽管消息中的第一个字节(MessageId)应该告诉我字节数组转换为的结构类型
BinaryFormatter
以.NET稍后可以再次读取的格式保存结构,它保存类型来自哪个程序集等等,所以当您有已知的消息字节结构时,它对您的情况没有用处。
要获得一个可以像Plain Old Data那样成功序列化的结构,您需要在结构上使用StructLayoutAttribute
,如
[StructLayout(LayoutKind.Explicit)]
public class Message
{
[FieldOffset(0)] public byte MessageId;
[FieldOffset(1)] public byte Length;
[FieldOffset(2)] public byte[] Data;
}
StructLayoutAttribute
使用任何其他参数,然后LayoutKind.Auto
告诉编译器不要更改内存中结构的布局,而是使用您提供的布局。在这种情况下,Explicit
布局可以手动告知字段的偏移量。
此外,您可能应该考虑更改消息格式,因为拥有不是机器字大小的FieldOffset
s在性能方面可能会非常昂贵,因为计算机可以更好地处理按机器字大小排列的数据,而不是任意数量的字节。
尝试以这种方式使用基类构建层次结构是一种糟糕的方法,如果你真的想在你的消息类型中建立层次结构,最好使用接口,但你仍然需要正确地布局你的数据。
然后,您可以使用封送将数据序列化到byte
数组,类似于以下
byte[] GetBytes(Message message) {
int size = Marshal.SizeOf(message);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(message, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
如果你想反序列化,那么你需要一个像这样的方法
Message FromBytes(byte[] bytes) {
Message message = new Message();
int size = Marshal.SizeOf(message);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(arr, 0, ptr, size);
message = (Message)Marshal.PtrToStructure(ptr, message.GetType());
Marshal.FreeHGlobal(ptr);
return message;
}
如果您需要在运行时根据收到的消息来决定使用哪个类,那么这些方法中的每一个都可能变得非常大,并且它非常特定于您的层次结构,但您当然可以通过查看第一个字节,然后相应地决定如何反序列化它们来实现这一点。
但从我的角度来看,尝试基于消息的一些常见部分进行分层是一种糟糕的方法。你宁愿做一些已知类型的消息序列来区分下一条消息或类似的消息,但这取决于你自己。我在这里提供的信息应该足以将任何整型序列化为字节数组,稍后可以通过网络发送。
- 从不属于应用程序的闪存读取
- 如何读取windows服务启动的控制台应用程序的输出
- 在应用程序启动时读取文件
- 如何通过C++库读取与安卓应用程序打包的文件?
- 从Qt应用程序读取元数据
- C#类过程 - 控制C 应用程序,不可能读取输出
- 如何使用Qt读取我的应用程序屏幕像素数据
- 如何在Ncurses应用程序中使用读取线
- 要读取一个从一个游戏应用程序(In C )到我的Android应用程序的文件
- 从 MAT 文件中读取 C 应用程序中的自定义类
- 在Qt控制台应用程序中读取和写入QProcess
- AccessViolationException从C++/CLI DLL读取C++应用程序中分配的内存
- 在Qt应用程序中读取python脚本C++输出
- 在C++中读取文件时应用程序冻结
- 从 mfc 应用程序点击网络服务,并希望从网络服务登录方法读取用户代码返回
- 桌面SCADA应用程序-通过C++读取和写入PLC
- 读取应用程序的输出
- 如何在vc++中执行从安装目录中读取数据的应用程序
- 序列化/封送C#中的简单对象以通过网络发送,供非托管C++应用程序读取
- 使CppUnit在netbeans 7.2上读取应用程序类