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

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

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

  • 相关阅读:
    第11组 Beta版本演示
    第11组 Beta冲刺(5/5)
    第11组 Beta冲刺(4/5)
    第11组 Beta冲刺(3/5)
    第11组 Beta冲刺(2/5)
    第11组 Beta冲刺(1/5)
    第11组 Alpha冲刺(2/6)
    第11组 Alpha冲刺(1/6)
    第11组 团队Git现场编程实战
    团队项目-需求分析报告
  • 原文地址:https://www.cnblogs.com/HopeGi/p/3536742.html
Copyright © 2011-2022 走看看