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

上一篇中提到了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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值