一、啰嗦两句
第一节的时候咱说了C#编译完了之后,就该NEO的编译器搞事情了。我们完全可以按这个节奏搞,手动用NEO的编译器neon编译dll文件生成指令码文件.avm。但是NEO团队给我们写智能合约提供了个插件,咱们就先看看这玩意怎么玩,然后简单的介绍一下NEO的编译器源码。
二、NeoContractPlugin
咱在VS里【工具】-》【扩展和更新】里搜下NeoContractPlugin,安装这个模板。
完事新建项目时候就能新建一个NeoContract的项目了
创建完之后,就是这样滴。
好,按NEO的模板创建出来的东东,多了build.tasks和Neo.ConvertTask.dll这俩文件,除此之外,在项目文件里还导入了一个project和AfterBuild之后的任务。这里做的事情,其实就是告诉项目编译完了之后跑一个ConvertTask的任务,看到项目里的Neo.ConvertTask.dll了么,后面我们介绍它。
ConvertTask这个任务是干嘛的,我们来看一眼源码,源码在项目neo-devpack-dotnet中
using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using System.Diagnostics; namespace Neo { /// <summary> /// this class generate config / xml file by template /// </summary> public class ConvertTask : Task { [Required] public ITaskItem DataSource { get; set; } /// <summary> /// execute replace logic /// </summary> /// <returns>ture successful, false failure</returns> public override bool Execute() { var srcdll = this.DataSource.ToString(); string dllname = System.IO.Path.GetFileName(srcdll); ProcessStartInfo pinfo = new ProcessStartInfo(); pinfo.FileName = "cmd.exe"; pinfo.WorkingDirectory = System.IO.Path.GetDirectoryName(srcdll); pinfo.UseShellExecute = false; pinfo.RedirectStandardInput = true; pinfo.RedirectStandardOutput = true; pinfo.CreateNoWindow = true; pinfo.StandardOutputEncoding = System.Text.Encoding.UTF8; Process p = Process.Start(pinfo); p.StandardInput.AutoFlush = true; p.StandardInput.WriteLine("neon " + dllname); p.StandardInput.WriteLine("exit"); //前四行后一行不要 string lastline = null; int count = 0; bool bSucc = false; while (p.StandardOutput.EndOfStream == false) { var line = p.StandardOutput.ReadLine(); count++; if (count <= 4) continue; if (lastline != null && lastline.Length > 0) { if (lastline[0] == ‘<‘) { if (lastline.IndexOf("<WARN>") == 0) { this.Log.LogWarning(lastline.Substring(6)); lastline = line; continue; } else if (lastline.IndexOf("<ERR>") == 0) { this.Log.LogError(lastline.Substring(5)); lastline = line; continue; } else if (lastline.IndexOf("<WARN|") == 0) { var l = lastline.Substring(6); var ine = lastline.IndexOf(">"); var text = lastline.Substring(ine + 1); var file = lastline.Substring(6, ine - 6); var lines = file.Split(new char[] { ‘(‘, ‘)‘ }); int _line = 0; if (lines.Length > 1) { int.TryParse(lines[1], out _line); } this.Log.LogWarning("", "", "", lines[0], _line, 0, 0, 0, text); lastline = line; continue; } else if (lastline.IndexOf("<ERR|") == 0) { var l = lastline.Substring(5); var ine = lastline.IndexOf(">"); var text = lastline.Substring(ine + 1); var file = lastline.Substring(5, ine - 5); var lines = file.Split(new char[] { ‘(‘, ‘)‘ }); int _line = 0; if (lines.Length > 1) { int.TryParse(lines[1], out _line); } this.Log.LogWarning("", "", "", lines[0], _line, 0, 0, 0, text); lastline = line; continue; } } if (lastline.IndexOf("SUCC") == 0) { bSucc = true; } this.Log.LogMessageFromText(lastline, MessageImportance.High); } lastline = line; //lines.Add(line); } //this.Log.LogMessageFromText(lastline, MessageImportance.High); p.WaitForExit(); return bSucc; } } }
好,先不管细节,就是起了一个cmd进程跑neon,其实就是帮我们调了一下NEO的编译器,妥了,那咱们就看看编译器搞了些撒事情吧。
三、NEO编译器
这一部分最好还是看看源码,我就简单介绍一下。这里有三个类比较关键:
1. ModuleDefination: 这个类是用的库Mono.cecil,这个库可以查看或者修改IL文件,这里是官方地址:http://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/
2. ILModule: 这个是编译器里定义的一个类,类的主要方法就是LoadModule,一看就明白了,其实现正是使用ModuleDefination加载并读取IL文件,将其转化为ILModule。这里其实还是用一种数据类型记录的智能合约代码的元数据信息,方法、字段、属性等的描述信息,可以理解成我们用反射操作类型。
3. NeoModule: 这个是另外一个数据结构,通过一个ModuleConverter的类从ILModule逐条指令转化进NeoModule,这个结构里保持了一个有序字典,value就是转化后的指令码,也就是我们说的OpCode。之后就是把这个NeoModule Build成字段数组写入.avm文件。
理解这三个类的含义就明白编译器的流程了。这里的难点是指令码,非常多,所以需要详细了解的自己看下源码吧。
四、小结
好了,今天就简单讲了一下NEO的智能合约模板背后都搞了啥,以及NEO编译器大概怎么编译的。通过前面几讲,大伙儿应该从工程的角度对NEO的智能合约这部分有些了解了。
后续,准备从NEO的命令行全节点Neo-cli入手讲讲NEO的内部机制。