現在也接觸一下動態編譯吧!去年也聽說過了,但是只瞄了一眼,沒去實踐,不久前有同事在介紹動態編譯,那時我因為某些原因沒法去聽聽。現在就看一下
整個編譯過程最基本用到兩個類CodeDomProvider類和CompilerParameters 類。前者就充當一個編譯器,后者則是用於記錄傳遞給編譯器的一些參數。在最初學習C#的使用,鄙人沒有用得上VS,只能靠CSC,那么CSC就類似於CodeDomProvider這個類,而CSC本身會有不少命令參數,CompilerParameters 類就能為CSC傳遞一些編譯信息(生成類型,引用程序集等)。那么下面則嘗試用最簡單的方式看看這個動態編譯。
1 public static void TestMain() 2 { 3 _default = new CompilTest(); 4 _default.SimpleCompile(code); 5 } 6 7 static CompilTest _default; 8 9 10 CodeDomProvider compiler; 11 CompilerParameters comPara; 12 const string code=@"using System; 13 14 class Test 15 { 16 static void Main() 17 { 18 Console.WriteLine(""Hello world""); 19 Console.ReadLine(); 20 } 21 }"; 22 23 private CompilTest() 24 { 25 compiler = new CSharpCodeProvider(); 26 comPara = new CompilerParameters(); 27 } 28 29 30 31 public void SimpleCompile(string code) 32 { 33 comPara.GenerateExecutable = true; 34 comPara.GenerateInMemory = false; 35 comPara.OutputAssembly = "SimpleCompile.exe"; 36 37 compiler.CompileAssemblyFromSource(comPara, code); 38 }
然后跑到當前運行程序的目錄下就能找到生成的可執行文件SimpleCompile.exe。這個就是最簡單的動態編譯。
上面CompilerParameters 類的示例設置了三個屬性,GenerateExecutable是設置編譯后生成的是dll還是exe,true是dll,false是exe,默認是生成dll的。OutputAssembly則是設置生成文件的文件名。對於GenerateInMemory這個屬性,MSDN上說的是true就把編譯的生成的程序集保留在內存中,通過CompilerResults實例的CompiledAssembly可以獲取。如果設為false則是生成文件保存在磁盤上,通過CompilerResults實例的PathToAssembly實例獲取程序集的路徑。但是經我實踐,無論GenerateInMemory設置哪個值,都會在硬盤上生成相應的文件,區別在於OutputAssembly設置了相應的文件名的話,生成的文件會存在指定路徑上,否則會存放在系統的臨時文件夾里面。都可以通過CompiledAssembly獲取生存的程序集。GenerateInMemory設值區別在於設置了true,PathToAssembly的值為null,false就能獲取生成文件的路徑。不過該類還有些比較有用的屬性沒用上,ReferencedAssemblies屬性設置編譯時要引用的dll;MainClass屬性設置主類的名稱,假設要編譯的代碼中包含了多個帶有Main方法的類,生成的程序采用就近原則只執行第一個Main方法,如果要執行別的類的Main方法的時候,就可以通過MainClass來設置。
CodeDomProvider只是一個抽象類而已,對於不同的程序語言,有相應的類去繼承這個抽象類,C#的就是CSharpCodeProvider類。用於編譯的方法有三個,方法的聲明如下
public virtual CompilerResults CompileAssemblyFromDom(CompilerParameters options, params CodeCompileUnit[] compilationUnits); public virtual CompilerResults CompileAssemblyFromFile(CompilerParameters options, params string[] fileNames); public virtual CompilerResults CompileAssemblyFromSource(CompilerParameters options, params string[] sources);
上面用到的是CompileAssemblyFromSource,傳進去第二個參數是需要編譯的代碼字符串。而第二個方法傳入的參數是需要編譯的代碼文件名。以上三個方法都返回同一個值——一個CompilerResults的實例。通常這個示例可以知道編譯是否通過,如果失敗了錯誤的代碼的位置,更重要的是可以獲取編譯成功的程序集。當編譯成功之后,要么就通過反射來調用生成好的東西,要么就直接開啟進程去執行exe。
那么上面還有一個方法沒介紹,這個方法的參數是CodeCompileUnit,這個類MSDN上的解釋是為 CodeDOM 程序圖形提供容器。但個人感覺它可以說是一個代碼的構造器,CompileAssemblyFromFile方法和CompileAssemblyFromSource方法無論代碼來自文件還是字符串,它都是確確實實的C#代碼,但是用了CodeCompileUnit就感覺通過配置的形式來經行編程,而不是逐行逐行地寫。先看看下面代碼,定義了三個方法,一個是構造CodeCompileUnit實例,其余兩個方法是生成C#代碼並輸出到文件和編譯生成
1 private CodeCompileUnit CreateUnitTest() 2 { 3 CodeCompileUnit unit = new CodeCompileUnit(); 4 5 //命名空間設置 6 CodeNamespace codeNamespace = new CodeNamespace("TestNameSpace");//設置命名空間名字 7 codeNamespace.Imports.Add(new CodeNamespaceImport("System"));//引用的命名空間 8 unit.Namespaces.Add(codeNamespace); 9 10 //類的定義 11 CodeTypeDeclaration testClass = new CodeTypeDeclaration("TestClass");//類名 12 testClass.Attributes= MemberAttributes.Public; 13 testClass.CustomAttributes.Add(new CodeAttributeDeclaration("Serializable"));//類的Attributes 14 codeNamespace.Types.Add(testClass); 15 16 //字段定義 17 CodeMemberField strMember = new CodeMemberField("String", "str1"); 18 strMember.Attributes= MemberAttributes.Private; 19 testClass.Members.Add(strMember); 20 21 CodeMemberField _default = new CodeMemberField("TestClass", "_default"); 22 _default.Attributes = MemberAttributes.Private | MemberAttributes.Static; 23 _default.InitExpression = new CodeSnippetExpression("new TestClass(\"hello world\")"); 24 testClass.Members.Add(_default); 25 26 //構造函數定義 27 CodeConstructor constructor = new CodeConstructor(); 28 constructor.Attributes = MemberAttributes.Public; 29 constructor.Parameters.Add(new CodeParameterDeclarationExpression("String", "para1")); 30 constructor.Statements.Add(new CodeSnippetStatement("str1=para1;")); 31 testClass.Members.Add(constructor); 32 33 //方法定義 34 CodeMemberMethod method = new CodeMemberMethod(); 35 method.Name = "Print"; 36 method.Attributes = MemberAttributes.Static | MemberAttributes.Public; 37 CodeParameterDeclarationExpression para1 = new CodeParameterDeclarationExpression("String", "str"); 38 method.Parameters.Add(para1); 39 method.ReturnType = new CodeTypeReference(typeof(void)); 40 CodeTypeReferenceExpression csSystemConsoleType = new CodeTypeReferenceExpression("System.Console"); 41 CodeMethodInvokeExpression cs1 = new CodeMethodInvokeExpression( 42 csSystemConsoleType, "WriteLine", 43 new CodeArgumentReferenceExpression("str")); 44 method.Statements.Add(cs1); 45 testClass.Members.Add(method); 46 47 //程序入口定義 Main方法 48 CodeEntryPointMethod mainMethod = new CodeEntryPointMethod(); 49 mainMethod.Attributes = MemberAttributes.Public; 50 CodeTypeReferenceExpression csMethodCall = new CodeTypeReferenceExpression("TestNameSpace.TestClass"); 51 cs1 = new CodeMethodInvokeExpression(csMethodCall, "Print", new CodeTypeReferenceExpression("_default.str1")); 52 mainMethod.Statements.Add(cs1); 53 testClass.Members.Add(mainMethod); 54 55 return unit; 56 } 57 58 private void Compile(CodeCompileUnit unit) 59 { 60 comPara.GenerateExecutable = true; 61 comPara.GenerateInMemory = true; 62 comPara.OutputAssembly = "SimpleCompile.exe"; 63 //comPara.MainClass = "Test2"; 64 65 CompilerResults result = compiler.CompileAssemblyFromDom(comPara, unit); 66 67 if (result.Errors.Count == 0) 68 { 69 Assembly assembly = result.CompiledAssembly; 70 Type AType = assembly.GetType("TestNameSpace.TestClass"); 71 MethodInfo method = AType.GetMethod("Main", BindingFlags.Static | BindingFlags.Public); 72 Console.WriteLine(method.Invoke(null, null)); 73 } 74 else 75 { 76 foreach (CompilerError item in result.Errors) 77 { 78 Console.WriteLine(item.ErrorText); 79 } 80 } 81 } 82 83 private void CreteCodeFile(CodeCompileUnit unit, string fileName) 84 { 85 StringBuilder sb=new StringBuilder(); 86 using (StringWriter tw=new StringWriter(sb)) 87 { 88 compiler.GenerateCodeFromCompileUnit(unit, tw, new CodeGeneratorOptions()); 89 } 90 File.WriteAllText(fileName, sb.ToString()); 91 }
上面代碼我覺得的重點在於構造CodeCompileUnit,在MSDN上對GenerateCodeFromCompileUnit的描述是:基於包含在 CodeCompileUnit 對象的指定數組中的 System.CodeDom 樹,使用指定的編譯器設置編譯程序集。這里有個DOM我想起了JS對HTML的DOM樹,不過在構造整個CodeCompileUnit過程中,也感覺到樹的層次結構,一個code文件里面有多個命名空間,命名空間里面又有多種類型(類,接口,委托),類型里面又包含各自的成員(字段,屬性,方法),方法里面包含了語句,語句里面又包含了表達式。

但是這種方式從開發人員而言代碼量加大了。
這篇也是營養不多,不上博客園首頁了。
