.NET 脚本

** .NET ** ** 脚本(一) **
** 作者: ** ** jconwell **


简介

你知道我对于前 .NET 时代有什么留恋吗?脚本!我喜欢创建一个小巧的脚本文件为我完成一些小任务,或者为了测试一小段代码而无需创建一项工程或是解决方案。我喜欢处理和清除的仅仅是一个小巧的文件而不是一个解决方案文件夹,工程文件夹和附带的 bin 和 obj 文件夹。我怀念那些时光,这正是我创建 .NET 脚本的原因。

什么是 .NET 脚本呢?基本上,它就是一个简单的控制台应用程序,从 .dnml 文件( Dot Net Markup Language, .NET 标记语言, 这是我定义的,哈哈)中读取 XML 文档。这个 XML 文档包含如下子元素,存储程序集引用,编写的代码所属的语言以及实际的要编译和执行的代码。那个控制台应用程序,我称之为脚本引擎,读取 XML 文本并分析出需要的数据。然后它利用 CSharp, VisualBasic, 和 CodeDom 命名空间中的类编译代码并将作为结果的程序集装载到内存中。教本引擎利用反射机制执行生成的程序集中的入口函数。当用户关闭控制台窗口时,脚本引擎被关闭,在内存中的程序集将不复存在,它将被垃圾回收器清理掉。没有任何的库或可执行程序生成。

Dot Net 标记语言

让我们来看看 .NET 标记语言是什么模样的。它其实非常简单。下面就是一个它的例子。我会一一说明 XML 文档中的每个元素。

 1<dnml>
 2<reference assembly="System.Windows.Forms.dll"></reference>
 3<language name="C#"></language>
 4<scriptcode><![CDATA[
 5    
 6    
 7    using System.Windows.Forms;
 8    
 9    
10    public class Test
11    
12    
13    {
14    
15    
16       public static void Main()
17    
18    
19       {
20    
21    
22           Console.WriteLine("This is a test");
23    
24    
25           MessageBox.Show("This is another test");
26    
27    
28           Test2 two = new Test2();
29    
30    
31           two.Stuff();
32    
33    
34       }
35    
36    
37    }
38    
39    
40    public class Test2
41    
42    
43    {
44    
45    
46     public void Stuff()
47    
48    
49     {
50    
51    
52      Console.WriteLine("Instance call");
53    
54    
55     }
56    
57    
58    }
59    
60    
61    ]]></scriptcode>
62</dnml>

该文档 XML 元素称为

  1<dnml> (  你能猜出它代表什么吗?  )  。在这个元素内部有三个不同的子元素,你能用它们来定义脚本如何编译。 
  2
  3首先是  <reference> 元素,它只有一个属性,叫“  Assembly  ”。“  Assembly  ”属性包含你要引用的程序集名称(包含文件扩展名)。一个  .dnml  文档可以包含许多  <reference> 元素,它对应于你在  VS.NET  中向工程中添加的引用列表。  对于每一个你的代码执行所需要的引用,都必须添加一个  <reference assembly=""></reference> 元素。 
  4
  5基于程序集探测的考虑,任何你所引用的  GAC  程序集都会被  CLR  自动找到。但是如果你引用了一个不是  GAC  中的程序集,情况就不同了。假设你引用了一个称为  Common.dll  的非  GAC  程序集。为了让您的  .NET  脚本正确执行,  Common.dll  必须放在两个地方。首先它必须放在你的  .dnml  文件所属的文件夹中。其次,它必须放在脚本引擎所在的文件夹中。我正在试图解决这个问题,但是就目前来说非  GAC  程序集必须存放在两个不同的文件夹中。 
  6
  7下一个元素是  <language> ,它有一个属性,称为  'name'  。一个  .dnml  文件只能有一个语言元素。对于  ’name’  属性两个可能的值是  'C#'  和  'VB'  ,  我希望他们是自描述的。 
  8
  9最后一个元素是  <scriptcode> ,  它含有一个  CDATA XML  元素。这个元素里包含了当你执行该  .dnml  文件时将要执行的代码。但是为了使用它你必须遵循一些接口规则。首先,它实际上只是普通的内嵌  C#  和  VB.NET  ,  所有的方法和字段都必须放在类里面。其次,你可以定义任意多个类,但是必须有一个类拥有一个公有静态函数,称为  “Main”  ,  没有任何输入参数,也不返回结果。只是脚本引擎通过反射搜索到的入口方法;如果找到,将会调用它。当然,“  Main  ”方法放在哪个类中是无关紧要的,因为脚本引擎将会遍历定义的每个类型,直到它找到  Main  方法为止。 
 10
 11##  脚本引擎是怎样工作的  ? 
 12
 13脚本引擎的大多数代码都是很直观的,因此我会一一描述它的每个方面。当中有一个非常有趣的部分就是一个名为  ` AssemblyGenerator  ` 的类,它只有一个方法,称为  ` CreateAssembly()  ` 。这个方法将完成所有的工作,编译并生成一个新的程序集,正如下面所看到的  . 
 14    
 15    
 16    _//Create an instance of the C# compiler   _
 17    
 18    
 19    CodeDomProvider codeProvider = null;
 20    
 21    
 22    if (code.IsCSharp)
 23    
 24    
 25       codeProvider = new CSharpCodeProvider();
 26    
 27    
 28    else
 29    
 30    
 31       codeProvider = new VBCodeProvider(); 
 32    
 33    
 34    ICodeCompiler compiler = codeProvider.CreateCompiler();
 35
 36首先我需要声明一个类  ` CodeDomProvider  ` 的实例。它是类  ` CSharpCodeProvider  ` 和类  ` VBCodeProvider  ` 的基类。你可以使用这些语言的特定  XxxProvider  对象来创建一个  ` CodeGenerator  ` 对象,它将被用来根据它包含的你创建的  CodeDom  对象图生成代码。你可以创建一个  ` CodeParser  ` 对象,它将根据你传入的源代码字符串生成一个  ` CodeDom  ` 对象图  (  在当前  1.1 .NET Framework  版本中  ,  它将返回空值  )  。  XxxProvider  对象也可以用来创建一个  ` CodeCompiler  ` ,  这正是我这里所使用的。  CodeCompiler  类就是我用来编译  .dnml  文件中的代码,并生成新的程序集的类。 
 37
 38所以,基于  .dnml  文件中所定义的语言类型,我创建一个合适的  XxxCodeProvider  对象。从这个对象,我请求一个  ` CodeCompiler  ` 实例,它将是基于语言不同而不同的。 
 39    
 40    
 41    _//add compiler parameters_
 42    
 43    
 44    CompilerParameters compilerParams = new CompilerParameters();
 45    
 46    
 47    compilerParams.CompilerOptions = "/target:library /optimize";
 48    
 49    
 50    compilerParams.GenerateExecutable = false;
 51    
 52    
 53    compilerParams.GenerateInMemory = true;
 54    
 55    
 56    compilerParams.IncludeDebugInformation = false;
 57    
 58    
 59    compilerParams.ReferencedAssemblies.Add("mscorlib.dll");
 60    
 61    
 62    compilerParams.ReferencedAssemblies.Add("System.dll"); 
 63    
 64    
 65       
 66    
 67    
 68    _//add any aditional references needed_
 69    
 70    
 71    foreach (string refAssembly in code.References)
 72    
 73    
 74       compilerParams.ReferencedAssemblies.Add(refAssembly);
 75
 76下一步,我创建一个  ` CompilerParameters  ` 对象。这个类基本上包装了当你手工通过  csc.exe (C#  编译器  )  和  vbc.exe (VB.NET  编译器  )  编译一个程序集时所用的所有命令行参数。一个特别重要的参数就是属性  ` GenerateInMemory  ` ,  这里我用到了它。它能确保当代码编译时,生成的程序集只会驻存在内存中,而不会作为结果创建任何文件。 
 77
 78该代码的最后一部分将脚本代码所需要的所有引用添加到  ` CompilerParameters  ` 中。  默认情况下,我添加了对于  mscorlib.dll  和  system.dll  的引用。然后我添加了对于  .dnml  文件中每一个  <reference> 元素所标明的程序集的引用。 
 79    
 80    
 81    _//actually compile the code_
 82    
 83    
 84    CompilerResults results = compiler.CompileAssemblyFromSource(
 85    
 86    
 87                                 compilerParams,  code.SourceCode)); 
 88    
 89    
 90    _//Do we have any compiler errors_
 91    
 92    
 93    if (results.Errors.Count &gt; 0)
 94    
 95    
 96    {
 97    
 98    
 99       foreach (CompilerError error in results.Errors)
100    
101    
102          DotNetScriptEngine.LogAllErrMsgs("Compine Error:"+error.ErrorText); 
103    
104    
105       return null;
106    
107    
108    }
109
110然后,我调用了  ` CodeCompiler.CompileAssemblyFromSource  ` ,  它传入  ` CompilerParameters  ` 对象和包含所要编译的实际代码的字符串变量。返回的对象属于类  ` CompilerResults  ` .  当编译出现错误时,这个对象包含一个  ` CompileError  ` 对象的集合,我将用它显示给用户当编译时那些地方出错了。 
111    
112    
113    _//get a hold of the actual assembly that was generated_
114    
115    
116    Assembly generatedAssembly = results.CompiledAssembly; 
117    
118    
119    _//return the assembly_
120    
121    
122    return generatedAssembly;
123    
124    
125    }
126
127如果脚本代码编译成功,  ` CompilerReslts  ` 对象将包含一个对于新编译和创建的程序集的引用。我保留该对象并返回给调用方法。 
128
129一旦程序集成功创建并返回,脚本引擎将利用反射遍历每一个生成的类型,寻找一个叫  'Main'  的静态方法。如果找到了,就再次利用反射执行它。如果没有找到,它将返回给用户一个错误,用来解释发生的问题。 
130
131##  最后一步 
132
133.NET  脚本引擎还能够添加和删除  .dnml  文件的关联。这意味着一旦文件关联好,你为了执行  .dnml  文件只需双击它就可以了。  当你做个时,脚本引擎将会执行,一个相关路径的命令行参数和该  .dnml  文件名将传递给它。接着教本引擎将读取该文件并处理相应的  XML  。 
134
135为了创建  .dnml  文件和  .NET  脚本引擎之间的关联,你只需双击  DotNetScriptEngine.exe  就可以了。当它不带任何命令行参数执行时,它将在您的服务器上创建文件关联。如果你在控制台运行  DotNetScriptEngine.exe  ,  并传入  'remove'  参数,该引擎将会在您的服务器上删除文件关联。</reference></scriptcode></language></reference></reference></dnml>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus