C# 動態編譯


  現在也接觸一下動態編譯吧!去年也聽說過了,但是只瞄了一眼,沒去實踐,不久前有同事在介紹動態編譯,那時我因為某些原因沒法去聽聽。現在就看一下

  整個編譯過程最基本用到兩個類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文件里面有多個命名空間,命名空間里面又有多種類型(類,接口,委托),類型里面又包含各自的成員(字段,屬性,方法),方法里面包含了語句,語句里面又包含了表達式。

但是這種方式從開發人員而言代碼量加大了。

  這篇也是營養不多,不上博客園首頁了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM