使用 Source Generator 自动生成(反)序列化二进制数据代码
我们在遇到比较罕见/自定义的通信协议的时候,可能nuget上并没有相关的包来处理,需要自己写(反)序列化程序。
此时如果为每个类型的消息都单独解析就显得麻烦了。Source Generator 就能很好解决这个问题。
A Source Generator is a new kind of component that C# developers can write that lets you do two major things:
- Retrieve a compilation object that represents all user code that is being compiled. This object can be inspected, and you can write code that works with the syntax and semantic models for the code being compiled, just like with analyzers today.
- Generate C# source files that can be added to a compilation object during compilation. In other words, you can provide additional source code as input to a compilation while the code is being compiled.
单个消息的封送
拿 SOME/IP(Scalable service-Oriented MiddlewarE over IP) 协议为例:
我们可以很快用struct表示出它的结构:
1 | public partial struct SomeIpMessage |
这里也有一个示例数据(大端存储):
1 | 0000 50 03 80 01 00 00 00 45 00 00 00 00 01 01 02 00 P......E........ |
同时我们也可以很容易写出读写的方法 (以 Header 为例) :
1 | public partial struct Header |
对于 byte[] 类型来说,它的长度可以由 (总大小-其它成员的大小)/sizeof(byte) 取得:
1 | public byte[] ReadFromBytes(byte[] _, byte[] srcBytes, ref int startIndex, bool isBigEndian, object parent) |
制作思路
为了自动生成封送代码,我们可以分成下面几个步骤:
- 寻找所有需要实现的 struct
- 遍历 struct 的每个成员
- 生成实现 (反)序列化 相关的代码
创建二进制处理的辅助项目
创建 .net standard 2.0 类库 StructPacker.Tools
定义封送的接口
1 | public interface IPackable<out T>:ISizeInfo |
定义相关 Attributes:
PackableAttribute 用来表示需要处理的 struct
PackIgnoreAttribute 用于忽略不需要处理的成员
1 | [] |
添加二进制数据读方法
BinaryUtils.Readers.cs
1 | public partial class BinaryUtils |
添加二进制数据写方法
BinaryUtils.Writers.cs
1 | public partial class BinaryUtils |
对于数组类型的数据,由于不同的协议中处理的方法不同,因此可以考虑单独开放自定义数组处理方法
1 | [] |
定义自定义转换器的接口
1 | public interface IArrayBinaryConvertor<T> |
保存自定义转换器 :
BinaryUtils.cs
1 | private static readonly Encoding StringEncoding = Encoding.UTF8; |
下面我们就可以尝试手动实现一下
假设当前有一个测试结构体 StructA
1 | public partial struct StructA |
实现 IPackable<StructA> 接口
1 | public partial struct StructA: IPackable<StructA> |
定义处理自动长度的数组的转换器
1 | public class AutoSizeArrayConvertor:IArrayBinaryConvertor<byte> |
之后就可以尝试编写 Source Generator 了!
创建Source Generator项目
创建 .net standard 2.0 类库 StructPacker 并且添加nuget包:
1 | <ItemGroup> |
The ISyntaxReceiver can record any information about the nodes visited. During Execute(GeneratorExecutionContext) the generator can obtain the created instance via the SyntaxReceiver property. The information contained can be used to perform final generation.
1 | public class StructReceiver:ISyntaxReceiver |
StructPackerGenerator.cs
1 | [] |
使用 SourceGenerator
创建单元测试项目 StructPacker.Tests
引用 刚写好的 StructPacker:
1 | <ItemGroup> |
定义 SomeIpMessage
1 | [] |
1 |
|
恭喜,现在它已经可以自动实现相关的方法了!!
参考链接
source-generators-overview
RudolfKurkaMs/StructPacker
C# Source Generators: Getting Started
Get attribute arguments with roslyn
Finding a type declaration’s namespace and type hierarchy