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

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

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

  • 相关阅读:
    September 17th 2016 Week 38th Saturday
    【2016-09-16】UbuntuServer14.04或更高版本安装问题记录
    September 16th 2016 Week 38th Friday
    September 11th 2016 Week 38th Sunday
    September 12th 2016 Week 38th Monday
    September 10th 2016 Week 37th Saturday
    September 9th 2016 Week 37th Friday
    c++暂停
    八皇后问题
    ( 转转)Android初级开发第九讲--Intent最全用法(打开文件跳转页面等)
  • 原文地址:https://www.cnblogs.com/HopeGi/p/3536742.html
Copyright © 2011-2022 走看看