unity热更方案 java script binding中使用protobuff(三)

来源:互联网 发布:手机地图标注软件 编辑:程序博客网 时间:2024/06/06 04:47

上一篇中提到了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#代码

0 0