<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0">
<channel>
	<title><![CDATA[iceting's Blog]]></title>
	<link>http://blog.verycd.com/iceting/req=showblog</link>
	<description><![CDATA[iceting's Blog Syndication]]></description>
	<pubDate>Wed, 16 Dec 2009 13:30:26 +0800</pubDate>
	<webMaster>no-reply@mail.verycd.com (VeryCD.com 分享互联网社区)</webMaster>
	<generator>Invision Community Blog</generator>
	<ttl>60</ttl>
	<item>
		<title>《C# to IL》第一章 IL入门</title>
		<link>http://blog.verycd.com/iceting/showentry=55428</link>
		<category>C# 3.0</category>
		<description><![CDATA[-1-<br /><br />      我们用C#、VB.NET语言编写的代码最终都会被编译成程序集或IL。因此用VB.NET编写的代码可以在C#中修改，随后在COBOL中使用。因此，理解IL是非常有必要的。<br /><br />      一旦熟悉了IL，理解.NET技术就不会有障碍了，因为所有的.NET语言都会编译为IL。IL是一门中性语言。IL是先发明的，随后才有了C#、VB.NET等语言。<br /><br />      我们将在一个短而精辟的程序中展示IL。我们还假设读者至少熟悉一门.NET语言。<br /><br />a.il<br /><br />.method void vijay() <br />{<br /><br />}<br />      随后，我们用IL编写了一个非常短小的IL程序——它显然是不能工作的，并将它命名为a.il。那么我们怎么才能把它编译为一个可执行程序呢？不需要为此而焦急，Microsoft提供了一个ilasm程序，它的唯一任务就是从IL文件中创建可执行文件。<br /><br />      在允许这个命令之前，要确保你的变量路径被设置为framework中的bin子目录。如果不是，请输入命令如下：<br /><br />            set path=c:&#092;progra~1&#092;microsoft.net&#092;frameworksdk&#092;bin;%PATH%<br /><br />      现在，我们使用如下命令：<br /><br />            c:&#092;il&gt;ilasm /nologo /quiet a.il<br /><br />      这样做会生成下面的错误：<br /><br />            Source file is ANSI<br /><br />            Error: No entry point declared for executable<br /><br />            ***** FAILURE ***** <br /><br />      将来，我们将不会显示由ilasm生成的输出的第一行和最后一行。我们还将移除非空白行之间的空白行。<br /><br />      在IL中，允许我们使用句点.作为一行的开始，这是一条指令，要求编译器执行某个功能，如创建一个函数或类，等等。任何开始于句点的语句都是一条实际俄编译器指令。<br /><br />      .method表示创建一个名为vijay的函数（或方法），并且这个函数返回void，即它不返回任何值。因为缺少较好的命名法则，函数名称vijay显得很随意。<br /><br />      汇编器显然理解不了这个程序，从而会显示“no entry point”的消息。这个错误信息的生成是因为IL文件能够包括无数的函数，而汇编器无法区分哪个会被首先被执行。<br /><br />      在IL中，首先被执行的函数被称为进入点（entrypoint）函数。在C#中，这个函数是Main。函数的语法是，名称之后是一对圆括号（）。函数代码的开始和结束用花括号{}来表示。<br /><br />a.il<br /><br />.method void vijay() <br />{<br />      .entrypoint<br />}<br />      c:&#092;il&gt;ilasm /nologo /quiet a.il<br /><br />      Source file is ANSI<br /><br />      Creating PE file<br /><br />      Emitting members:<br /><br />      Global Methods: 1;<br /><br />      Writing PE file<br /><br />      Operation completed successfully<br />      现在不会生成任何错误了。伪指令（directive）entrypoint表示程序执行必须开始于这个函数。在这个例子中，我们不得不使用这个伪指令，虽然事实上这个程序只有一个函数。当在DOS提示符中给出dir命令后，我们看到有3个文件会被创建。a.exe是一个可执行文件，现在可以执行它来看到程序的输出。<br /><br />      C:&#092;il&gt;a<br /><br />      Exception occurred: System.BadImageFormatException: Exception from HRESULT: 0x8007000B. Failed to load C:&#092;IL&#092;A.EXE.<br /><br />      当我们试图执行上面的程序时，我们的运气似乎不太好，因为会生成上面的运行时错误。一个可能的原因是，这个函数是不完整的，每个函数都应当具有一个“函数结束”指令在函数体中。我们匆忙之中显然没有注意到这个事实。<br /><br />a.il<br /><br />.method void vijay() <br />{<br />      .entrypoint<br />      ret<br />}<br />      “函数结束”指令被称为ret。前面所有的函数都必须以这个指令作为结束。<br /><br />Output<br /><br />      Exception occurred: System.BadImageFormatException: Exception from HRESULT: 0x8007000B. Failed to load C:&#092;IL&#092;A.EXE.<br /><br />      在执行这个程序时，我们再次得到了相同的错误。这次我们的问题又在哪里呢？<br /><br />a.il<br /><br />.assembly mukhi {}<br />.method void vijay() <br />{<br />      .entrypoint<br />      ret<br />}<br />      错误在于我们忘记在名称后面使用必不可少的伪指令assembly。我们将其合成在上面的代码中，并在一对空的花括号之后使用了名称mukhi。这个程序集伪指令用于给出程序的名称。它又被称为一个部署单元。<br /><br />      上面的代码是可以汇编而没有任何错误的最小的程序，虽然它在执行时并没有做什么有用的事情。它没有任何名为Main的函数。它只有一个带有entrypoint伪指令的函数vijay。现在汇编这个程序并运行而根本不会有任何错误。<br /><br />      在.NET中，程序集的概念是极其重要的，应该对其有彻底的认识。我们将在本章后半部分使用这个伪指令。<br /><br />a.il<br /><br />.assembly mukhi {}<br />.method void vijay() <br />{<br />      .entrypoint<br />      ret<br />}<br />.method void vijay1() <br />{<br />      .entrypoint<br />      ret<br />}<br />Error<br /><br />      ***** FAILURE *****<br /><br />      上面错误信息的原因是，上面的程序有2个函数，vijay和vijay1，每个函数都包括了.entrypoint伪指令。正如前面提到的那样，这个指令指定了关于那个函数会被首先执行。<br /><br />      因此，在功能上，它类似于C#中的Main函数。当C#代码被转换为IL代码时，在Main函数中包含的代码会被转换为IL中的函数中并包括.entrypoint伪指令。例如，如果在COBOL程序中执行的第一个函数被称为abc，那么在IL中生成的代码就会在这个函数中插入.entrypoint伪指令。<br /><br />      在常规的程序语言中，首先被执行的函数必须有一个特定的名称，例如Main，但是在IL中，只需要一个.entrypoint伪指令。因此，因为一个程序只能由一个开始点，所以在IL代码中只允许一个函数包括.entrypoint伪指令。<br /><br />      迫切地看到，没有生成任何错误消息编号或说明，使得调试这个错误非常困难。<br /><br />a.il<br /><br />.assembly mukhi {}<br />.method void vijay() <br />{<br />      ret<br />      .entrypoint<br />}<br />      .entrypoint伪指令需要被定位为函数中的第一个指令或最后一个指令。它仅出现在函数体中，从而将它的状态宣布为第一个被执行的函数。伪指令不是程序集指令，甚至可以被放置在任何ret指令之后。提醒你一下，ret表示函数代码的结束。<br /><br />a.il<br /><br />.assembly mukhi {}<br />.method void vijay() <br />{<br />      .entrypoint<br />      call void System.Console::WriteLine()<br />      ret<br />}<br />      我们可能有一个用C#、VB.NET编写的函数，但是在IL中执行这个函数的机制是相同的。如下所示：<br /><br />      我们必须使用汇编指令调用。调用指令之后，按照给定的顺序，为以下详细内容：<br /><br />函数的返回类型（void）<br />命名空间(System)<br />类 (Console)<br />函数名称 (WriteLine())<br />      函数被调用但不会生成任何输出。因为，我们传递一个参数到WriteLine函数中。<br /><br />a.il<br /><br />.assembly mukhi {}<br />.method void vijay() <br />{<br />      .entrypoint<br />      call void System.Console::WriteLine(class System.String)<br />      ret<br />}<br />      上面的代码有一处“闪光点”。当一个函数在IL中被调用时，除了它的返回类型之外，被传递的参数的数据类型，也必须被指定。我们将Writeline设置为——希望得到一个System.String类型作为参数，但是由于没有字符串被传递到这个函数中，所以它会生成一个运行时错误。<br /><br />      因此，在调用一个函数时，在IL和其他程序语言之间有一个明显的区别。在IL中，当我们调用一个函数，我们必须指定关于该函数我们所知道的任何内容，包括它的返回类型和它的参数的数据类型。通过在运行期间进行恰当的检查，保证了汇编器能够在语法上验证代码的有效性。<br /><br />      现在我们将看到如何将参数传递到一个函数中。<br /><br />a.il<br /><br />.assembly mukhi {}<br />.method void vijay() <br />{<br />      .entrypoint<br />      ldstr "hell"<br />      call void System.Console::WriteLine(class System.String)<br />      ret<br />}<br />Output<br /><br />      hell<br /><br />      汇编器指令ldstr把字符串放到栈上。Ldstr的名称是文本"load a string on the stack"的缩写版本。栈是一块内存区域，它用来传递参数到函数中。所有的函数从栈上接收它们的参数。因此，像ldstr这样的指令是必不可少的。<br /><br />a.il<br /><br />.assembly mukhi {}<br />.method public hidebysig static void vijay()il managed<br />{<br />      .entrypoint<br />      ldstr "hell"<br />      call void System.Console::WriteLine(class System.String)<br />      ret<br />}<br />Output<br /><br />      hell<br /><br />      我们在方法vijay上添加了一些特性。接下来我们将逐个讲解它们。<br /><br />      public：被称为可访问特性，它决定了都有谁可以访问一个方法。public意味着这个方法可以被程序的其他任何部分所访问。<br /><br />      hidebysig：类可以从其它多个类中派生。hidebysig特性保证了父类中的函数在具有相同名称或签名的派生类中会被隐藏。在这个例子中，它保证了如果函数vijay出现在基类中，那么它在派生类中就是不可见的。<br /><br />      static：方法可以是静态的或非静态的。静态方法属于一个类而不属于一个实例。因此， 就像我们只有一个单独的类，我们不能拥有一个静态函数的多份复制。静态函数可以在哪里创建是没有约束的。带有entrypoint指令的函数必须是静态的。静态函数必须具有相关联的实体或者源代码，并且使用类型名称而不是实例名称来引用它们。<br /><br />      il managed:由于它的复杂性质，我们将关于这个特性的解释延后。当时机成熟时，它的功能将会被解释清楚。<br /><br />      上面涉及的特性并没有修改函数的输出。 稍后，你将明白为什么我们要提供这些特性的解释。<br /><br />      无论何时我们用C#语言编写一个程序，我们首先在类的名称前指定关键字class，随后，我们将源代码封闭在一对花括号内。示范如下：<br /><br />a.cs<br /><br />class zzz<br />{<br /><br />}<br />      让我们引进称为class的IL指令：<br /><br />a.il<br /><br />.assembly mukhi {}<br />.class zzz<br />{<br />      .method public hidebysig static void vijay()il managed<br />      {<br />            .entrypoint<br />            ldstr "hell"<br />            call void System.Console::WriteLine(class System.String)<br />            ret<br />      }<br />}<br />      注意到，汇编器输出中的改变： Class 1 Methods: 1;<br /><br /><br />Output<br /><br />      hell<br /><br />      伪指令.class之后是类的名称。它在IL中是可选的，让我们通过添加一些类的特性来增强这个类的功能。<br /><br />a.il<br /><br />.assembly mukhi {}<br />.class private auto ansi zzz<br />{<br />      .method public hidebysig static void vijay()il managed<br />      {<br />            .entrypoint<br />            ldstr "hell"<br />            call void System.Console::WriteLine(class System.String)<br />            ret<br />      }<br />}<br />Output<br /><br />      hell<br /><br />      我们添加了 3个特性到类的伪指令中。<br /><br />private：这表示了对类的成员的访问被约束为只能在当前类中。<br />auto：这表示类在内存中的布局将只由运行时来决定，而不是由我们的程序决定。<br />ansi：源代码通常被划分为两个主要的类别：托管代码和非托管代码。<br />      以诸如C语言编写的代码被称为非托管代码或不可信任的代码。我们需要一个特性来处理非托管代码和托管代码之间的互操作。例如，当我们想要在托管和非托管代码之间转移字符串时，这个特性会被使用到。<br /><br />      如果我们跨越托管代码的边界并钻进非托管代码的领域，那么一个字符串——由2字节Unicode字符组成的数组，将会被转换为一个ANSI字符串——由1字节ANSI字符组成的数组；反之亦然。修饰符ansi用于消除托管和非托管代码之间的转换。<br /><br />a.il<br /><br />.assembly mukhi {}<br />.class private auto ansi zzz extends System.Object<br />{<br />      .method public hidebysig static void vijay()il managed<br />      {<br />            .entrypoint<br />            ldstr "hell"<br />            call void System.Console::WriteLine(class System.String)<br />            ret<br />      }<br />}<br />Output<br /><br />      hell <br /><br />      类zzz从System.Object中派生。在.NET中，为了定义类型的一致性，所有的类型最终都派生于System.Object。因此，所有的对象都有一个共同的基类Object。在IL中，类从其它类中派生，与C++、C#和Java的表现方式相同，<br /><br />a.il<br /><br />.module aa.exe<br />.subsystem 3 <br />.corflags 1<br /><br />.assembly extern mscorlib<br />{<br />      .originator = (03 68 91 16 D3 A4 AE 33 )<br />      .hash = (52 44 F8 C9 55 1F 54 3F 97 D7 AB AD E2 DF 1D E0 <br />            F2 9D 4F BC )<br />      .ver 1:0:2204:21<br />}<br /><br />.assembly a as "a"<br />{<br />      .hash algorithm 0x00008004<br />      .ver 0:0:0:0<br />}<br /><br />.class private auto ansi zzz extends System.Object<br />{<br />      .method public hidebysig static void vijay() il managed<br />      {<br />            .entrypoint<br />            ldstr "hell"<br />            call void System.Console::WriteLine(class System.String)<br />            ret<br />}<br /><br />      .method public hidebysig specialname rtspecialname instance void .ctor() il managed<br />      {<br />            .maxstack 8<br />            ldstr "hell1"<br />            call void System.Console::WriteLine(class System.String)<br />            ldarg.0<br />            call instance void [mscorlib]System.Object::.ctor()<br />      ret<br />      }<br />}<br />Output<br /><br />      hell<br /><br />      你一定想知道为什么我们会编写出这么难看的程序。在迷雾驱散之前你需要保持耐心，所有的一切就要开始有意义了。我们将逐个解释新引进的函数和特性。<br /><br />      .ctor: 我们引进了一个新的函数.ctor，它调用了WriteLine函数来显示hell1，但是它没有被调用。.ctor涉及到了构造函数。<br /><br />      rtspecialname: 这个特性会告诉运行时——函数的名称是特殊的，它会以一种特殊的方式被对待。<br /><br />      specialname: 这个特性会提示编译器和工具——函数是特殊的。运行时可能选择忽略这个特性。<br /><br />      instance: 一个常规的函数会被一个实例函数调用。这样一个函数与一个对象关联，不同于静态方法，后者关联到一个类。<br /><br />      在合适的时候，为函数选择特定名称的原因会变得明朗。<br /><br />      ldarg.0: 这是一个汇编器指令，它加载this指针或第0个参数的地址到执行栈上。我们随后将详细解释ldarg.0。<br /><br />      mscorlib: 在上面的程序中，函数.ctor会被基类System.Object调用。通常，函数的名称以包括代码的库的名称作为前缀。这个库的名称被放置在方括号中。在这个例子中，它是可选的——因为mscorlib.dll是默认的库，并且它包括了.NET所需要的大部分类。<br /><br />      .maxstack: 这个伪指令指定了在一个方法被调用时，能够出现在计算栈上的元素的最大数量。<br /><br />      .module: 所有的IL文件必须是一个逻辑实体的一部分，或它们的组合体，我们将这些实体称为模块（module）。文件被添加到使用了.module伪指令的模块中。模块的名称可能被规定为aa.exe，但是可执行文件的名称和前面保持一样，即a.exe。<br /><br />      .subsystem: 这个指令用于指定可执行体运行在什么操作系统上。这是另一种指定可执行体所代表的种类的方式。一些数字值和它们对应的操作系统如下所示：<br /><br />            2 - A Windows Character 子系统。<br /><br />            3 - A Windows GUI 子系统。<br /><br />            5 – 像OS/2这样的老系统。<br /><br />      .corsflags: 这个伪指令用于指定对于64位计算机唯一的标志。值1表示它是从il中创建的可执行文件，而值64表示一个库。<br /><br />      .assembly: 在前面，我们曾经简单涉及过一个名为.assembly的指令。现在让我们进行深入的研究。<br /><br />      无论我们创建了什么，都是一个称为清单（manifest）的实体的一部分。.assembly伪指令标注了一个清单的开始位置。在层次上，模块是清单最小的实体。.assembly伪指令指定了这个模块属于哪个程序集。模块只能包括一个单独的.assembly伪指令。<br /><br />      对于exe文件，这个伪指令的存在是必须的，但是，对于.dll中的模块，则是可选的。这是因为，我们需要使用这个伪指令来创建一个程序集。这是.NET的基本需要。程序集伪指令包括了其它伪指令。<br /><br />      .hash: 散列计算是一门在计算机世界中通用的技术，这里有大量使用到的散列方法或算法。这个伪指令用于散列计算。<br /><br />      .ver: .ver:伪指令包括了4个由冒号分割的数字。按照下面给定的顺序，它们代表了下面的信息：<br /><br />主版本编号<br />次版本编号<br />内部版本号<br />修订版本号<br />      extern: 如果有涉及到其它程序集的需求，就要使用到extern伪指令。.NET核心类的代码位于mscorlib.dll中。除了这个dll之外，当我们的程序需要涉及到大量其它的dll时，extern伪指令就要排上用场了。<br /><br />      originator: 在转移到解释上面程序的本质和意义之前，这是我们要研究的最后一个伪指令。这个伪指令揭示了创建该dll的标识。它包括了dll的所有者公钥的8个字节。它显然是一个散列值。<br /><br />      让我们以一种不同的方式一步一步地温习到目前为止我们所做的事情。<br /><br />（a）我们开始于一个我们能够编写的最简单的程序。这个程序被称为a.cs，并包括了下面的代码：<br />a.cs<br /><br />class zzz<br />{<br />      public static void Main() <br />      {<br />            System.Console.WriteLine("hi");<br />      } <br />}<br />（b）然后我们使用下面的命令运行C#编译器。<br />            &gt;csc a.cs<br /><br />      因此，会创建名为a.exe的exe文件。<br /><br />（c）在可执行体中，我们运行一个名为ildasm的程序，它是由Microsoft提供的：<br />            &gt;ildasm /out=a.txt a.exe<br /><br />      这就创建了一个txt文件，具有下面的内容：<br /><br />a.txt<br /><br />//  Microsoft &reg; .NET Framework IL Disassembler.  Version 1.0.2204.21<br />//  Copyright &copy; Microsoft Corp. 1998-2000<br /> <br />// VTableFixup Directory:<br />// No data.<br />.subsystem 0x00000003<br />.corflags 0x00000001<br />.assembly extern mscorlib<br />{<br />  .originator = (03 68 91 16 D3 A4 AE 33 )        // .h..3<br />  .hash = (52 44 F8 C9 55 1F 54 3F 97 D7 AB AD E2 DF 1D E0   <br />           F2 9D 4F BC )                        // RD..U.T?O.<br />  .ver 1:0:2204:21<br />}<br />.assembly a as "a"<br />{<br />  .hash algorithm 0x00008004<br />  .ver 0:0:0:0<br />}<br />.module aa.exe<br />// MVID: {89CFAD60-F5BD-11D4-A55A-96B5C7D61E7B}<br />.class private auto ansi zzz<br />       extends System.Object<br />{<br />  .method public hidebysig static void vijay() il managed<br />  {<br />    .entrypoint<br />    // Code size       11 (0xb)<br />    .maxstack  8<br />    IL_0000:  ldstr      "hell"<br />    IL_0005:  call       void System.Console::WriteLine(class System.String)<br />    IL_000a:  ret<br />  } // end of method zzz::vijay<br /> <br />  .method public hidebysig specialname rtspecialname <br />          instance void .ctor() il managed<br />  {<br />    // Code size       17 (0x11)<br />    .maxstack  8<br />    IL_0000:  ldstr      "hell"<br />    IL_0005:  call       void System.Console::WriteLine(class System.String)<br />    IL_000a:  ldarg.0<br />    IL_000b:  call       instance void [mscorlib]System.Object::.ctor()<br />    IL_0010:  ret<br />  } // end of method zzz::.ctor<br /> <br />} // end of class zzz<br /> <br />//*********** DISASSEMBLY COMPLETE ***********************<br /><br />      当我们阅读上面的文件时，你将明白它的所有内容都已经在前面解释过了。我们开始于一个简单的C#程序，然后将它编译到一个可执行文件中。在正常的环境下，它将被转换为机器语言或这个程序运行在所在的计算机/微处理器的汇编程序。一旦创建了可执行体，我们就使用ildasm来反汇编它。反汇编输出被保存到一个新的文件a.txt中。这个文件可能被命名为a.il，然后我们可以通过对其运行ilasm反过来再次创建这个可执行体。<br /><br />      让我们看一下最小的VB.NET程序。我们将它命名为one.vb，而它的源代码如下所示：<br /><br />one.vb<br /><br />Public Module modmain<br />    Sub Main()<br />        System.Console.WriteLine("hell")<br />    End Sub<br />End Module<br /><br />      在编写完上述的代码后，我们运行Visual.Net编译器vbc如下：<br /><br />            &gt;vbc one.vb<br /><br />      这就产生了文件one.exe。<br /><br />      下面，我们执行ildasm如下所示：<br /><br />            &gt;ildasm /out=a.txt one.exe<br /><br />      这就生成了下面的文件a.txt:<br /><br />a.txt<br /><br />//  Microsoft &reg; .NET Framework IL Disassembler.  Version 1.0.2204.21<br />//  Copyright &copy; Microsoft Corp. 1998-2000<br /> <br />// VTableFixup Directory:<br />// No data.<br />.subsystem 0x00000003<br />.corflags 0x00000001<br />.assembly extern mscorlib<br />{<br />  .originator = (03 68 91 16 D3 A4 AE 33 )              // .h..3<br />  .hash = (52 44 F8 C9 55 1F 54 3F 97 D7 AB AD E2 DF 1D E0 <br />  F2 9D 4F BC )                                  // RD..U.T?.O.<br />  .ver 1:0:2204:21<br />}<br />.assembly extern Microsoft.VisualBasic<br />{<br />  .originator = (03 68 91 16 D3 A4 AE 33 )      // .h..3<br />  .hash = (5B 42 1F D2 5E 1A 42 83 F5 90 B2 29 9F 35 A1 BE   <br />           E5 5E 0D E4 )                  // [B..^.B.).5.<br />  .ver 1:0:0:0<br />}<br />.assembly one as "one"<br />{<br />  .hash algorithm 0x00008004<br />  .ver 1:0:0:0<br />}<br />.module one.exe<br />// MVID: {1ED19820-F5C2-11D4-A55A-96B5C7D61E7B}<br />.class public auto ansi modmain<br />       extends [mscorlib]System.Object<br />{<br />  .custom instance void [Microsoft.VisualBasic]Microsoft.VisualBasic.Globals/Globals$StandardModuleAttribute::.ctor() = ( 01 00 00 00 ) <br />  .method public static void Main() il managed<br />  {<br />    // Code size       11 (0xb)<br />    .maxstack  1<br />    .locals init (class System.Object[] V_0)<br />    IL_0000:  ldstr      "hell"<br />    IL_0005:  call       void [mscorlib]System.Console::WriteLine(class System.String)<br />    IL_000a:  ret<br />  } // end of method modmain::Main<br /> <br />} // end of class modmain<br /> <br />.class private auto ansi _vbProject<br />       extends [mscorlib]System.Object<br />{<br />  .custom instance void [Microsoft.VisualBasic]Microsoft.VisualBasic.Globals/Globals$StandardModuleAttribute::.ctor() = ( 01 00 00 00 )<br />.method public static void  _main(class System.String[] _s) il managed<br />  {<br />    .entrypoint<br />    // Code size       6 (0x6)<br />    .maxstack  8<br />    IL_0000:  call       void modmain::Main()<br />    IL_0005:  ret<br />  } // end of method _vbProject::_main<br />} // end of class _vbProject<br />//*********** DISASSEMBLY COMPLETE ***********************<br />      你将惊讶地看到由两个不同的编译器所生成的输出几乎是相同的。我向你展示了这个示例用以证实——语言的无关性，最终，源代码将会被转换为IL代码。无论我们使用VB.NET或C#，都会调用相同的WriteLine函数。<br /><br />      因此，程序语言间的不同现在是表面上的问题。无休止的争论那个语言是最优的是没有意义的。从而，IL使得程序员可以自由使用他们所选择的语言。<br /><br />      让我们揭开上面给出的代码的神秘面纱。<br /><br />      每个VB.NET程序都需要被包括在一个模块中。我们称之为modmain。Visual Basic中的所有模块都是以关键字End结束的，从而我们会看到End Module。这是VB在语法上不区别于C#的地方——C#不理解模块是什么。<br /><br />      在VB.NET中，函数被称为子程序。我们需要子程序来标注程序执行的开始位置。这个子程序被称为Main。<br /><br />      VB.NET代码不仅关联到mscorlib.dll，还使用了文件Microsoft.VisualBasic。<br /><br />      在IL中会创建一个名为_vbProject的类，因为在VB中类的名称不是必须的。<br /><br />      称为_main的函数是子函数的开始，因为它具有entrypoint伪指令。它的名称前面有一个下划线。这些名称是由VB编译器选择用来生成IL代码的。<br /><br />      这个函数会传递一个字符串数组作为参数。它具有一个自定义伪指令来处理元数据的概念。<br /><br />      接下来，我们具有这个函数的完整原型，以一系列可选的字节作为终结。这些字节是元数据规范中的一部分。<br /><br />      模块modmain被转换为一个具有相同名称的类。和之前一样，这个类还具有相同的伪指令.custom和一个Main函数。该函数使用了名为.locals的伪指令在栈上创建一个只能在这个方法中使用变量。这个变量只存在于方法执行期间，当方法停止运行时，它就会“消亡”。<br /><br />      字段还存储在内存中，但是需要更长的时间来为它们分配内存。关键字init表示在创建期间，这些变量应该被初始化为它们的默认值。默认值依赖于变量的类型。数值总是被初始化为值ZERO。关键字init之后是这些变量的数据类型和它的名称。<br /><br />]]></description>
		<pubDate>Fri, 29 May 2009 17:07:39 +0800</pubDate>
		<guid>http://blog.verycd.com/iceting/showentry=55428</guid>
	</item>
	<item>
		<title>范型真的会降低性能吗？</title>
		<link>http://blog.verycd.com/iceting/showentry=55427</link>
		<category>C# 3.0</category>
		<description><![CDATA[　　在<a href="http://www.cnblogs.com/xinyuperfect/archive/2009/05/29/1491686.html" target="_blank">《.NET,你忘记了么？（八）—— 从dynamic到特性误用》</a>一文中，飞林沙同学提到，使用范型会略微降低程序性能，因此在程序中使用List&lt;Ojbect&gt;是不合理的行为，应该使用ArrayList。这一点和老赵平时的观点相悖，老赵一直提倡，在.NET 2.0之后，要尽可能使用List&lt;T&gt;，情愿是List&lt;Object&gt;也不要使用ArrayList。不过个中原因与性能无关，我们稍候再叙述。飞同学的文章让我有了将范型与非范型进行性能比较的想法。这个比较非常容易，不过也得出了一些非常有意思的结论。<br /><br />范型容器与非范型容器的性能比较<br />　　首先，我们来比较一种最“纯粹”的范型容器，它的目的是避免程序的其他方面对性能的影响。因此，这里我们构造两个最简单的容器，就是简单的模仿ArrayList和List&lt;T&gt;：<br /><br />public class MyArrayList<br />{<br />    public MyArrayList(int length)<br />    {<br />        this.m_items = new object[length];<br />    }<br /><br />    public object[] m_items;<br /><br />    public object this[int index]<br />    {<br />        get<br />        {<br />            return this.m_items[index];<br />        }<br />        set<br />        {<br />            this.m_items[index] = value;<br />        }<br />    }<br /><br />    public IEnumerable InnerContainer<br />    {<br />        get<br />        {<br />            return this.m_items;<br />        }<br />    }<br />}<br /><br />public class MyList&lt;T&gt;<br />{<br />    public MyList(int length)<br />    {<br />        this.m_items = new T[length];<br />    }<br /><br />    public T[] m_items;<br /><br />    public T this[int index]<br />    {<br />        get<br />        {<br />            return this.m_items[index];<br />        }<br />        set<br />        {<br />            this.m_items[index] = value;<br />        }<br />    }<br /><br />    public IEnumerable&lt;T&gt; InnerContainer<br />    {<br />        get<br />        {<br />            return this.m_items;<br />        }<br />    }<br />}<br /><br />　　MyArrayList为直接使用Object类型的容器，而MyList&lt;T&gt;则是范型容器。老赵为他们实现了两种操作，一是下标访问，二是直接把内部的数组容器作为可遍历的对象释放出来。这两种都是平时编程中最经常使用的操作。<br /><br />　　于是我们就可以编写测试代码了。首先，我们初始化两种容器，并进行“预热”：<br /><br />int length = 1000;<br />object value = new object();<br /><br />MyArrayList myArrayList = new MyArrayList(length);<br />for (int i = 0; i &lt; length; i++)<br />{<br />    myArrayList[i] = myArrayList[i] ?? value;<br />}<br /><br />MyList&lt;object&gt; myList = new MyList&lt;object&gt;(length);<br />for (int i = 0; i &lt; length; i++)<br />{<br />    myList[i] = myList[i] ?? value;<br />}<br /><br />　　然后我们使用<a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/03/10/CodeTimer.html" target="_blank">CodeTimer</a>来进行统计：<br /><br />CodeTimer.Initialize();<br />int iteration = 300 * 1000;<br /><br />CodeTimer.Time("MyArrayList下标访问", iteration, () =&gt;<br />{<br />    for (int i = 0; i &lt; length; i++)<br />    {<br />        var o = myArrayList[i];<br />    }<br />});<br /><br />CodeTimer.Time("MyList下标访问", iteration, () =&gt;<br />{<br />    for (int i = 0; i &lt; length; i++)<br />    {<br />        var o = myList[i];<br />    }<br />});<br /><br />　　Release Build并运行。猜猜看，结果是什么？<br /><br />MyArrayList下标访问<br />        Time Elapsed:   2,398ms<br />        CPU Cycles:     5,042,561,997<br />        Gen 0:          0<br />        Gen 1:          0<br />        Gen 2:          0<br /><br />MyList下标访问<br />        Time Elapsed:   2,285ms<br />        CPU Cycles:     4,935,066,741<br />        Gen 0:          0<br />        Gen 1:          0<br />        Gen 2:          0<br />　　以上是在老赵的机器上得到的结果，从结果上看，范型的MyList性能甚至略比MyArrayList有所提高。当然测试的结果其实是略有胜负，但是事实上，MyList的获胜的次数甚至还略有领先。<br /><br />　　那么我们再来看看“遍历”的性能如何：<br /><br />CodeTimer.Time("MyArrayList遍历", iteration, () =&gt;<br />{<br />    foreach (object o in myArrayList.InnerContainer)<br />    {<br />        var o1 = o;<br />    }<br />});<br /><br />CodeTimer.Time("MyList遍历", iteration, () =&gt;<br />{<br />    foreach (object o in myList.InnerContainer)<br />    {<br />        var o1 = o;<br />    }<br />});<br /><br />　　运行的结果颇有意思：<br /><br />MyArrayList遍历<br />        Time Elapsed:   21,367ms<br />        CPU Cycles:     46,023,627,496<br />        Gen 0:          2<br />        Gen 1:          1<br />        Gen 2:          0<br /><br />MyList遍历<br />        Time Elapsed:   3,463ms<br />        CPU Cycles:     7,448,928,223<br />        Gen 0:          2<br />        Gen 1:          0<br />        Gen 2:          0<br />　　直接使用Object的MyArrayList性能居然差了这么多！各种原因老赵有所猜测，在得到明确答案之后会接着与大家分享。<br /><br />ArrayList和List&lt;T&gt;的性能<br />　　刚才比较了最“纯粹”的性能，那么我们再来比较ArrayList和List&lt;T&gt;。因为我们其实不知道它俩具体在实现上的细节是如何的，还是比较一下这两个容器具体的性能比较好。比较的内容还是两项：下标访问及遍历。代码如下：<br /><br />int length = 1000;<br />object value = new object();<br /><br />ArrayList arrayList = new ArrayList(length);<br />for (int i = 0; i &lt; length; i++)<br />{<br />    arrayList.Add(value);<br />    arrayList[i] = arrayList[i] ?? value;<br />}<br /><br />List&lt;object&gt; list = new List&lt;object&gt;(length);<br />for (int i = 0; i &lt; length; i++)<br />{<br />    list.Add(value);<br />    list[i] = list[i] ?? value;<br />}<br /><br />CodeTimer.Initialize();<br />int iteration = 300 * 1000;<br /><br />CodeTimer.Time("ArrayList下标访问", iteration, () =&gt;<br />{<br />    for (int i = 0; i &lt; length; i++)<br />    {<br />        var o = arrayList[i];<br />    }<br />});<br /><br />CodeTimer.Time("List下标访问", iteration, () =&gt;<br />{<br />    for (int i = 0; i &lt; length; i++)<br />    {<br />        var o = list[i];<br />    }<br />});<br /><br />CodeTimer.Time("ArrayList遍历", iteration, () =&gt;<br />{<br />    foreach (object o in arrayList)<br />    {<br />        var o1 = o;<br />    }<br />});<br /><br />CodeTimer.Time("List遍历", iteration, () =&gt;<br />{<br />    foreach (object o in list)<br />    {<br />        var o1 = o;<br />    }<br />});<br /><br />　　结果如下：<br /><br />ArrayList下标访问<br />        Time Elapsed:   2,282ms<br />        CPU Cycles:     4,838,476,797<br />        Gen 0:          0<br />        Gen 1:          0<br />        Gen 2:          0<br /><br />List下标访问<br />        Time Elapsed:   2,302ms<br />        CPU Cycles:     4,979,267,920<br />        Gen 0:          0<br />        Gen 1:          0<br />        Gen 2:          0<br /><br />ArrayList遍历<br />        Time Elapsed:   5,187ms<br />        CPU Cycles:     11,145,830,014<br />        Gen 0:          4<br />        Gen 1:          0<br />        Gen 2:          0<br /><br />List遍历<br />        Time Elapsed:   2,989ms<br />        CPU Cycles:     6,459,825,955<br />        Gen 0:          0<br />        Gen 1:          0<br />        Gen 2:          0<br />　　现在您还觉得范型会降低性能，List&lt;Object&gt;比ArrayList的性能差吗？<br /><br />总结<br />　　从结果上已经可以看出，范型并不会影响性能，而List&lt;T&gt;的性能也不比ArrayList要差。因此老赵继续坚持：在有范型支持的情况下，尽量使用范型容器。例如使用List&lt;Object&gt;而不是ArrayList。除了“性能”之外，老赵的还有其他一些理由。例如使用List&lt;Object&gt;的话就可以使用框架内部所定义的各种有用的辅助方法（要知道在.NET框架中，现在几乎都是在针对IEnumerable&lt;T&gt;进行开发）；而我们平时写程序时，也可以统一的针对范型编程，如IList&lt;T&gt;，IEnumerable&lt;T&gt;，不必考虑List或Enumerable等非范型元素。<br /><br />　　放心使用List&lt;Object&gt;吧。]]></description>
		<pubDate>Fri, 29 May 2009 17:04:04 +0800</pubDate>
		<guid>http://blog.verycd.com/iceting/showentry=55427</guid>
	</item>
	<item>
		<title>《WF编程》2 - 漫游工作流:活动与自定义活动</title>
		<link>http://blog.verycd.com/iceting/showentry=47806</link>
		<category>WF</category>
		<description><![CDATA[1.2 漫游工作流<br />Windows Workflow Foundation是.NET 3.0中新增的一部分,新的.NET 3.0还包括Windows Presentation  Foundation(WPF)和Windows Communication Foundation(WCF)这两项新技术.<br /><br />支持Windows Workflow (WF)的操作系统包含Windows XP, Windows Server 2003和最新的Windows Vista.<br /><br />WF的使用场景可以是智能客户端应用程序;可以是简单的控制台程序;也可以是包含Windows 服务,ASP.NET Web应用程序和Web Service的服务器端应用程序.WF还出现在微软自己的一些产品中,包括Windows SharePoint Services 和 Microsoft Biztalk Server.现在我们来简单的看看Windows Workflow的基本特点. <br /><br />1.2.1 活动<br />Windows Workflow 主要的组成部件是活动(Activity),活动组成了工作流中的步骤(或任务),也可以说活动定义了工作流.我们按顺序和层次组织活动,然后这些活动将作为指令供工作流引擎执行.<br /><br />WF中所有的活动都继承一个基类-Activity,Activity类定义了所有活动的公共方法(比如Execute和Cancel),公共属性(比如Name和Parent)和公共事件(如Executing和Closed).<br /><br />WF就运作在这些基本活动库中的现成活动之上.基本活动库主要包括流程控制(如IfElseActivity和WhileActivity),等待事件,调用Web Service和执行规则引擎等等的活动. <br /><br />1.2.1.1自定义活动<br />Windows Workflow允许开发人员扩展基本活动库的功能,通过创建自定义活动来解决特定领域的问题.例如,送餐工作流如果使用了SendOrderToKitchen 和NotifyCustomer这两个活动就会方便很多.<br /><br />自定义活动也继承自Activity,工作流引擎并不会区别一个活动是Microsoft的活动还是第三方自定义活动.<br /><br />我们可以使用自定义活动来创建应用于特定领域的工作流解决方案.这样做可以极大的简化问题.例如, SendOrderToKitchen活动调用Web Service并进行其他内部逻辑操作,而且很明显,这个活动只针对餐馆这个领域定制.开发人员使用这个自定义活动会比使用基本活动库中的活动更有效率.不仅开发人员,餐馆管理者也会很容易理解SendOrderToKitchen并会在可视化工作流设计器上设计工作流(但餐馆管理者不一定对WhileActivity和InvokeWebServiceActivity感兴趣).<br />]]></description>
		<pubDate>Thu, 01 Nov 2007 18:08:18 +0800</pubDate>
		<guid>http://blog.verycd.com/iceting/showentry=47806</guid>
	</item>
	<item>
		<title><![CDATA[《WF编程》1 - Hello,Workflow & 创建工作流解决方案]]></title>
		<link>http://blog.verycd.com/iceting/showentry=47805</link>
		<category>WF</category>
		<description><![CDATA[1. Hello,Workflow<br />什么是工作流?有一个简单的定义:工作流是完成特定任务所需的一系列步骤,决策和规则.<br /><br />举一个现实生活中的例子,在饭店里,我们告诉服务员想要的菜,服务员记录然后交给厨师,厨师把做好的饭菜递给服务员,服务员再端给我们.<br /><br />这个工作流的步骤是:我们(食客) &gt; 服务员 &gt;厨师 &gt; 服务员 &gt; 食客.<br /><br />工作流的每一个步骤中都包含着制定决策.还是上面的例子,厨师必须比较服务员拿来的订单和库存的食材,而服务员则必须检查我们出示的优惠卷和钞票.<br /><br />当然,并不是每个工作流都需要人类参与(人类会把最简单的过程变得很复杂).工作流可以在两个分布式软件之间进行.例如,两个内容管理程序在同步内容的时候或许需要遵循一组特定的步骤和规则.<br /><br />大多数工作流都被状态化,并且经常运行相当长的时间.就像我们点的菜可能半小时后才被送上,在这半小时里,订单的状态信息必须保持有效.蔬菜供应商也许在30小时之后才能提供蔬菜,饭店也有可能在30天之后才能和蔬菜供应商结算.在这30天里,一些信息必须维持其状态直到结算为止.<br /><br />如上所述, 也许工作流耗费了大部分时间来等待事件发生.但是当工作流在等待时,它是空闲的并且不需要任何资源.<br /><br />最后再来归纳一下,工作流,是为了完成任务的一系列步骤.工作流经常长时间运行并且状态化.经常需要等待事件并且与人类交互.你可以在世界的任何角落看到工作流,作为软件开发人员的我们经常会为身边的流程编写工作流应用软件.<br /><br />1.1 创建工作流解决方案<br />我们都从事改善业务流程的软件项目,无论这些业务涉及到外卖订单,金融交易或者卫生保健等等,谈论这些项目的时候,总是会出现一个词:工作流. <br /> <br /><br />工作流听起来可能很简单,但是恐怖东西往往会在你深究之后出现:我们需要数据库和数据访问类来管理工作流状态;我们需要组件来发送E-mail或等待队列里的消息到达;我们还需要让计算机识别并运行工作流.理论上工作流是这样实现的:<br /><br /> <br /><br />// 最近提交的订单工作流 <br /><br />     class PurchaseOrderWorkflow <br /><br />     { <br /><br />         public void Execute(PurchaseOrder order) <br /><br />         { <br /><br />             WaitForManagerApproval(order); <br /><br />             NotifyPurchaseManager(order); <br /><br />             WaitForGoods(order); <br /><br />         }<br /><br />}<br /><br /><br />假定我们为这三个方法都定义了内部实现,那么工作流真的就这么简单吗?<br /><br />答案是”NO!”.<br /><br />我们还需要为异常处理和日志等等添加代码,还需要去触发事件,提供钩子(Hook)来跟踪并取消正在运行的工作流.有时候,工作流会变成空闲状态并等待外部事件的发生,就像你在淘宝购买的货物终于到了,虽然等了很久.但我们不能把一个正在运行的应用程序线程挂起来数日或数周来等待交付.我们需要提供这样一个机制:可以把工作流的执行状态保存到持久的数据库中,然后从内存中删除工作流实例,当等待的事件发生后,恢复工作流状态并继续执行.<br /><br />不幸的是,围绕工作流将会有大量的代码,以至于我们在代码中找啊找啊都找不到工作流本身.这些代码正式工作流模块化的杀手.一个外行可能永远都看不到被深深埋藏在代码中的工作流,就算是开发人员,也需要狠狠的掘”码”三尺才能找到内部的工作流.<br /><br />一个改进的工作流设计模式将把工作流定义从引擎和执行工作流的代码中分离出来.这种方法让开发人员,甚至是外行都知道该如何表示工作流,而工作流引擎只需关心如何执行工作流.下面来看看理论上的工作流定义XML:<br /><br />&lt;Workflow Name="PurchaseOrderWorkflow"&gt;<br /><br />                 &lt;Steps&gt;<br /><br />                        &lt;WaitForTask Event="ManagerApproval"/&gt;<br /><br />                        &lt;NotifyTask Target="PurchaseManager"/&gt;<br /><br />                        &lt;WaitForTask Event="Delivery"/&gt;<br /><br />                 &lt;/Steps&gt;<br /><br />                 &lt;Parameters&gt;<br /><br />                        &lt;Parameter Type="PurchaseOrder" Name="order"/&gt;<br /><br />                  &lt;/Parameters&gt;<br /><br />&lt;/Workflow&gt;<br /><br /><br />   <br />让我们再问同样的问题,工作流真的这么简单吗?<br /><br />答案是”YES!”.<br /><br />我们想要的工作流引擎应该具备这样的能力:理解XML,而且可以把XML转换为计算机指令.引擎还应该包含所有我们需要的功能,例如异常处理和允许取消工作流.<br /><br />使用XML的好处是有大量的工具可以去读写并转换XML.相比处理C#代码,处理XML更加容易.我们可以让用户在一个可视化设计器中将块用线条连接起来,画出工作流图表,然后生成XML.<br /><br />我们的解决方案中需要实现怎样的工作流?为了清晰明了的表示工作流,我们需要借助可视化设计器.将工作流定义内置到工作流引擎中,让引擎去管理错误,事件,跟踪,激活和反激活.<br /><br />接下来,进入Windows Workflow Foundation吧.<br />]]></description>
		<pubDate>Thu, 01 Nov 2007 18:06:44 +0800</pubDate>
		<guid>http://blog.verycd.com/iceting/showentry=47805</guid>
	</item>
</channel>
</rss>