上一篇中提到了JSB中要想使用protobuff-net生成的C#代码需要进行的工作,可以看到,对生成的代码改动量还是蛮大的,
手动非常不现实,其实写个简单的基于.proto文件的分析程序,把类中的变量名修改下,然后去掉特定变量,修改函数实现
也不是什么难事,都是字符串替换而已,殊途同归,所以这篇文章就当是protobuff-net的原理介绍吧
先放上protobuff-net的github
https://github.com/mgravell/protobuf-net
protobuff-net分为两部分,运行库和生成工具,生成工具负责生成具有描述性质的C#代码,在运行库中序列化和反序列化需要用到
既然说要改变生成的C#代码,那肯定是改生成工具了
https://github.com/mgravell/protobuf-net/tree/master/ProtoGen
这是对应的protogen的子工程,点进去发现代码很少
CommandLineOptions.cs负责命令行解析
InputFileLoader.cs 负责protobuff的.proto文件加载
注意,后边代码贴的比较多,如果懒得看代码解释过程,直接看最后的结论即可
public static int Main(params string[] args)
{
CommandLineOptions opt = null;
try
{
opt = Parse(Console.Out, args);
opt.Execute();
return opt.ShowHelp ? 1 : 0; // count help as a non-success (we didn't generate code)
}
catch (Exception ex)
{
Console.Error.Write(ex.Message);
return 1;
}
}
只能顺着程序入口找逻辑了,打开Execute函数
public void Execute()
{
StringBuilder errors = new StringBuilder();
string oldDir = Environment.CurrentDirectory;
Environment.CurrentDirectory = WorkingDirectory;
try
{
if (string.IsNullOrEmpty(OutPath))
{
WriteErrorsToFile = false; // can't be
}
else if (WriteErrorsToFile)
{
ErrorWriter = new StringWriter(errors);
}
try
{
if (ShowLogo)
{
messageOutput.WriteLine(Properties.Resources.LogoText);
}
if (ShowHelp)
{
messageOutput.WriteLine(Properties.Resources.Usage);
return;
}
string xml = LoadFilesAsXml(this);
Code = ApplyTransform(this, xml);
if (this.OutPath == "-") { }
else if (!string.IsNullOrEmpty(this.OutPath))
{
File.WriteAllText(this.OutPath, Code);
}
可以看到,核心功能就俩函数,一个是LoadFileAsXml,一个是 ApplyTransform
<span style="color:#333333;">private static string LoadFilesAsXml(CommandLineOptions options)
{
FileDescriptorSet set = new FileDescriptorSet();
foreach (string inPath in options.InPaths)
{
</span><span style="color:#ff0000;">InputFileLoader.Merge</span><span style="color:#333333;">(set, inPath, options.ErrorWriter, options.Arguments.ToArray());
}
XmlSerializer xser = new XmlSerializer(typeof(FileDescriptorSet));
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = " ";
settings.NewLineHandling = NewLineHandling.Entitize;
StringBuilder sb = new StringBuilder();
using (XmlWriter writer = XmlWriter.Create(sb, settings))
{
xser.Serialize(writer, set);
}
return sb.ToString();
}</span>
可以看到
FileDescriptorSet
这个类,这个类是protobuff的meta数据的描述类,再看下merge函数做的事情
public static void Merge(FileDescriptorSet files, string path, TextWriter stderr, params string[] args)
{
if (stderr == null) throw new ArgumentNullException("stderr");
if (files == null) throw new ArgumentNullException("files");
if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path");
bool deletePath = false;
if(!IsValidBinary(path))
{ // try to use protoc
path = <span style="color:#ff0000;">CompileDescriptor</span>(path, stderr, args);
deletePath = true;
}
try
{
using (FileStream stream = File.OpenRead(path))
{
Serializer.Merge(stream, files);
}
}
finally
{
if(deletePath)
{
File.Delete(path);
}
}
}
通过生成的xml,生成了一个
FileDescriptorSet类,并且把生成的xml删除了
private static string CompileDescriptor(string path, TextWriter stderr, params string[] args)
{
string tmp = Path.GetTempFileName();
string tmpFolder = null, protocPath = null;
try
{
protocPath = GetProtocPath(out tmpFolder);
ProcessStartInfo psi = new ProcessStartInfo(
protocPath,
string.Format(@"""--descriptor_set_out={0}"" ""--proto_path={1}"" ""--proto_path={2}"" ""--proto_path={3}"" --error_format=gcc ""{4}"" {5}",
tmp, // output file
Path.GetDirectoryName(path), // primary search path
Environment.CurrentDirectory, // primary search path
Path.GetDirectoryName(protocPath), // secondary search path
Path.Combine(Environment.CurrentDirectory, path), // input file
string.Join(" ", args) // extra args
)
);
Debug.WriteLine(psi.FileName + " " + psi.Arguments, "protoc");
psi.CreateNoWindow = true;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.WorkingDirectory = Environment.CurrentDirectory;
psi.UseShellExecute = false;
psi.RedirectStandardOutput = psi.RedirectStandardError = true;
using (Process proc = Process.Start(psi))
可以看到,生成描述文件的其实就是google的protoc.exe,
private static string ApplyTransform(CommandLineOptions options, string xml)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.ConformanceLevel = ConformanceLevel.Auto;
settings.CheckCharacters = false;
StringBuilder sb = new StringBuilder();
using (XmlReader reader = XmlReader.Create(new StringReader(xml)))
using (TextWriter writer = new StringWriter(sb))
{
XslCompiledTransform xslt = new XslCompiledTransform();
string xsltTemplate = Path.ChangeExtension(options.Template, "xslt");
if (!File.Exists(xsltTemplate))
{
string localXslt = InputFileLoader.CombinePathFromAppRoot(xsltTemplate);
if (File.Exists(localXslt))
xsltTemplate = localXslt;
}
try
{
xslt.Load(xsltTemplate);
}
catch (Exception ex)
{
throw new InvalidOperationException("Unable to load tranform: " + options.Template, ex);
}
options.XsltOptions.RemoveParam("defaultNamespace", "");
if (options.DefaultNamespace != null)
{
options.XsltOptions.AddParam("defaultNamespace", "", options.DefaultNamespace);
}
xslt.Transform(reader, options.XsltOptions, writer);
}
return sb.ToString();
}
}
最后看下,ApplyTransform做的事情就是通过xslt模版将生成的xml字符串转换为C#代码
梳理下过程作为 结论,protobuff-net的工作流程就是
1.protoc.exe生成单独的一个个类的描述文件xml
2.loader合并类描述并且生成一个总的xml字符串
3.xslt转换生成xml字符串生成代码
4.要改生成规则,改xlst即可,就是那个不适宜人类阅读的csharp.xslt
关于xslt的介绍就不多说了,实施这个事情的是组里的晓川同学,我看着有点晕,他也在群里,有兴趣的同学可以跟他交流
http://download.csdn.net/detail/bn0305/9664369
对应的xslt的下载,替换protogen目录下的文件即可改变生成的C#代码