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文件里面有多个命名空间,命名空间里面又有多种类型(类,接口,委托),类型里面又包含各自的成员(字段,属性,方法),方法里面包含了语句,语句里面又包含了表达式。
但是这种方式从开发人员而言代码量加大了。
这篇也是营养不多,不上博客园首页了。