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文件里面有多个命名空间,命名空间里面又有多种类型(类,接口,委托),类型里面又包含各自的成员(字段,属性,方法),方法里面包含了语句,语句里面又包含了表达式。

但是这种方式从开发人员而言代码量加大了。

  这篇也是营养不多,不上博客园首页了。

posted @ 2014-01-31 10:47 猴健居士 阅读(...) 评论(...) 编辑 收藏