本文简单介绍写如果通过Emit来在运行时动态的生成对数据对象的protebuf编码解码类。 通过本实例展示下元数据编程的能力。
关于protebuf的编码原理可以参考这里http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/encoding.html
,本文只展示对简单的int32进行varint方式编码和string的编码,。在此基础上可以很容易的实现全部的protobuf的编码逻辑。首先定义两个用于测试的实体类。
public class TestModel1
{
[ProtoMember(1)]
public int UserId { get; set; }
[ProtoMember(2)]
public string Password { get; set; }
[ProtoMember(
3)]public TestModel2 Model2 { get; set; }
[ProtoMember(
4)]public List<TestModel2> Models { get; set; }
}
[ProtoContract]
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
}
{
byte[] Encode(T obj);
T Decode(byte[] data);
}
class TestModel1Codec:ICodec<TestModel1> {....}
{
public byte[] Encode(TestModel1 obj)
{
ProtoStream stream = new ProtoStream();
if (obj.UserId != 0)
{
stream.WriteTag(new Tag(1, WireType.Varint));
stream.WriteInt32(obj.UserId);
}
if (obj.Password != null)
{
stream.WriteTag(new Tag(2, WireType.LengthDelimited));
stream.WriteString(obj.Password);
}
if (obj.Model2 != null)
{
stream.WriteTag(new Tag(3, WireType.LengthDelimited));
stream.WriteBytes(Codec<TestModel2>.Encode(obj.Model2));
}
if (obj.Models != null)
{
for (int i = 0; i < obj.Models.Count; i++ )
{
stream.WriteTag(new Tag(4, WireType.LengthDelimited));
stream.WriteBytes(Codec<TestModel2>.Encode(obj.Models[i]));
}
}
return stream.ToArray();
}
public TestModel1 Decode(byte[] data)
{
TestModel1 result = new TestModel1();
ProtoStream stream = new ProtoStream(data);
while (true)
{
Tag tag = stream.ReadTag();
if (tag.Number == -1)
{
break;
}
if (tag.Number == 1)
{
result.UserId = stream.ReadInt32();
}
if (tag.Number == 2)
{
result.Password = stream.ReadString();
}
if (tag.Number == 3)
{
result.Model2 = Codec<TestModel2>.Decode(stream.ReadBytes());
}
if (tag.Number == 4)
{
if (result.Models == null)
{
result.Models = new List<TestModel2>();
}
result.Models.Add(Codec<TestModel2>.Decode(stream.ReadBytes()));
}
}
return result;
}
}
class TestModel2Codec : ICodec<TestModel2>
{
public byte[] Encode(TestModel2 obj)
{
ProtoStream stream = new ProtoStream();
if (obj.Id != 0)
{
stream.WriteTag(new Tag(1, WireType.Varint));
stream.WriteInt32(obj.Id);
}
if (obj.Name != null)
{
stream.WriteTag(new Tag(2, WireType.LengthDelimited));
stream.WriteString(obj.Name);
}
return stream.ToArray();
}
public TestModel2 Decode(byte[] data)
{
Program.TestModel2 result = new Program.TestModel2();
ProtoStream stream = new ProtoStream(data);
while (true)
{
Tag tag = stream.ReadTag();
if (tag.Number == -1)
{
break;
}
if (tag.Number == 1)
{
result.Id = stream.ReadInt32();
}
if (tag.Number == 2)
{
result.Name = stream.ReadString();
}
}
return result;
}
}
{
private static AssemblyBuilder codecAssmblyBuilder = System.AppDomain.CurrentDomain
.DefineDynamicAssembly(new AssemblyName { Name = "Codec" }, AssemblyBuilderAccess.RunAndSave);
private static ModuleBuilder codecModuleBuilder = codecAssmblyBuilder.DefineDynamicModule("Codec", "Codec.dll");
public static Type CreateCodec<T>()
{
Type messageType = typeof(T);
TypeBuilder typeBuilder = codecModuleBuilder.DefineType(messageType.Name + "Codec",
TypeAttributes.Class | TypeAttributes.Public);
typeBuilder.AddInterfaceImplementation(
typeof(ICodec<T>));MethodBuilder encodeMethodBuilder
= typeBuilder.DefineMethod("Encode", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
CallingConventions.Standard,typeof(byte[]),new Type[]{ typeof(T)}
);
MethodBuilder decodeMethodBuilder
= typeBuilder.DefineMethod("Decode", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual, CallingConventions.Standard, typeof(T), new Type[]
{
typeof(byte[]) });ILGenerator encodeMethodBody
= encodeMethodBuilder.GetILGenerator();CreateEncodeMethodBody(encodeMethodBody, messageType);
ILGenerator decodeMethodBody
= decodeMethodBuilder.GetILGenerator();CreateDecodeMethodBody(decodeMethodBody, messageType);
return typeBuilder.CreateType();
}
private static ConstructorInfo proteStreamConstructorWithArgs = typeof(ProtoStream).GetConstructor(new Type[1] { typeof(byte[]) });
private static ConstructorInfo proteStreamDefaultConstructor = typeof(ProtoStream).GetConstructor(new Type[0]);
private static MethodInfo writeTagMethod = typeof(ProtoStream).GetMethod("WriteTag");
private static MethodInfo writeInt32Method = typeof(ProtoStream).GetMethod("WriteInt32");
private static MethodInfo writeStringMethod = typeof(ProtoStream).GetMethod("WriteString");
private static MethodInfo toArrayMethod = typeof(ProtoStream).GetMethod("ToArray");
private static MethodInfo writeBytesMethod = typeof(ProtoStream).GetMethod("WriteBytes");
private static void CreateEncodeMethodBody(ILGenerator il, Type messageType)
{
il.DeclareLocal(typeof(ProtoStream));
il.DeclareLocal(typeof(Tag));
il.DeclareLocal(typeof(byte[]));
//stream = new ProtoStream();
il.Emit(OpCodes.Newobj, proteStreamDefaultConstructor);
il.Emit(OpCodes.Stloc_0);
//
foreach (var p in messageType.GetProperties())
{
if (p.IsDefined(typeof(ProtoMemberAttribute), false))
{
ProtoMemberAttribute protoMember = p.GetCustomAttributes(false)[0] as ProtoMemberAttribute;
if (p.PropertyType == typeof(int))
{
//if(value != 0) { write(value) }
Label skipLabel = il.DefineLabel();
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, p.GetGetMethod());
il.Emit(OpCodes.Brfalse, skipLabel);
//write tag
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldc_I4, protoMember.TagNumber);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Newobj, tagConstructor);
il.Emit(OpCodes.Call, writeTagMethod);
//write int value
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, p.GetGetMethod());
il.Emit(OpCodes.Call, writeInt32Method);
il.MarkLabel(skipLabel);
}
{
Label skipLabel = il.DefineLabel();
//if(str != null) { write str}
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, p.GetGetMethod());
il.Emit(OpCodes.Brfalse, skipLabel);
//write tag
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldc_I4, protoMember.TagNumber);
il.Emit(OpCodes.Ldc_I4_2);
il.Emit(OpCodes.Newobj, tagConstructor);
il.Emit(OpCodes.Call, writeTagMethod);
//write string value
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, p.GetGetMethod());
il.Emit(OpCodes.Call, writeStringMethod);
il.MarkLabel(skipLabel);
}
{
....... }
}
}
//return stream.ToArray()
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Call, toArrayMethod);
il.Emit(OpCodes.Ret);
}
private static MethodInfo getTagNumberMethod = typeof(Tag).GetProperty("Number").GetGetMethod();
private static MethodInfo readTagMethod = typeof(ProtoStream).GetMethod("ReadTag");
private static MethodInfo readInt32Method = typeof(ProtoStream).GetMethod("ReadInt32");
private static MethodInfo readStringMethod = typeof(ProtoStream).GetMethod("ReadString");
private static MethodInfo readBytesMethod = typeof(ProtoStream).GetMethod("ReadBytes");
private static void CreateDecodeMethodBody(ILGenerator il, Type messageType)
{
il.DeclareLocal(messageType); //result
il.DeclareLocal(typeof(ProtoStream)); //stream
il.DeclareLocal(typeof(Tag)); // tag
il.DeclareLocal(typeof(byte[])); //tempData
Label beginWhile = il.DefineLabel();
Label endWhile = il.DefineLabel();
//result = new T()
il.Emit(OpCodes.Newobj, messageType.GetConstructor(new Type[0]));
il.Emit(OpCodes.Stloc_0);
//stream = new ProtoStream(data)
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Newobj, proteStreamConstructorWithArgs);
il.Emit(OpCodes.Stloc_1);
il.MarkLabel(beginWhile);
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Call, readTagMethod);
il.Emit(OpCodes.Stloc_2);
il.Emit(OpCodes.Ldloc_2);
il.Emit(OpCodes.Call, getTagNumberMethod);
il.Emit(OpCodes.Ldc_I4_M1);
il.Emit(OpCodes.Beq, endWhile);
foreach (var p in messageType.GetProperties())
{
if (p.IsDefined(typeof(ProtoMemberAttribute), false))
{
ProtoMemberAttribute protoMember = p.GetCustomAttributes(false)[0] as ProtoMemberAttribute;
Label skipLabel
= il.DefineLabel();il.Emit(OpCodes.Ldloc_2);
il.Emit(OpCodes.Call, getTagNumberMethod);
il.Emit(OpCodes.Ldc_I4, protoMember.TagNumber);
il.Emit(OpCodes.Bne_Un, skipLabel);
if (p.PropertyType == typeof(int))
{
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Call, readInt32Method);
il.Emit(OpCodes.Call, p.GetSetMethod());
}
if (p.PropertyType == typeof(string))
{
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Call, readStringMethod);
il.Emit(OpCodes.Call, p.GetSetMethod());
}
if (p.PropertyType.IsDefined(typeof(ProtoMessageAttribute), false))
{
....... }
il.MarkLabel(skipLabel);
}
}
il.Emit(OpCodes.Br, beginWhile);
il.MarkLabel(endWhile);
//return result
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ret);
}
public static void Save()
{
codecAssmblyBuilder.Save("Codec.dll");
}
}
然后就是运用一点反射来查看元数据信息,并将重复il片段写到for循环中就ok了。调试的时候如果发现有什么异常,可以通过Save方法,把动态创建的assembly写到磁盘上
然后ildasm打开或者用reflector(现在不是免费的了,不过可以试用)和手工写的反汇编代码对比着查看,很容易能找到问题。ProtoStream ,Tag类是具体对protobuf编码算法的一个封装。
最后在封装一个factory类来简化对Codec类的实例创建
{
static ICodec<T> codec = (ICodec<T>)Activator.CreateInstance(CodecTypeGenerator.CreateCodec<T>());
public static byte[] Encode(T obj)
{
return codec.Encode(obj);
}
public static T Decode(byte[] data)
{
return codec.Decode(data);
}
}
然后写个简单的小测试
{
byte[] data = Codec<TestModel1>.Encode(new TestModel1
{
UserId = 11,
Password = "fetion123",
Model2 = new TestModel2 { Id = 4, Name = "test" },
Models = new List<TestModel2> { new TestModel2 { Id = 5, Name = "asdfasfd" },
new TestModel2 { Id = 4, Name = "vvv" } }
});
TestModel1 aa
= Codec<TestModel1>.Decode(data);Console.WriteLine(aa.Model2.Name);
Console.ReadKey();
CodecTypeGenerator.Save();
}