.NET 4.0面向对象编程漫谈:基础篇
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.2 初探.NET程序运行原理

1.2.1 手工开发.NET程序请读者事先安装好Visual Studio 2010。

几乎所有的程序设计书,第一个程序都是一个“Hello,World!”程序,以之向世界庄严宣告读者要开始学习程序设计,即将进入到无比神奇的软件世界中。

本书也未能免俗,第一个程序就是C#版的“Hello,World!”。

请打开Windows附件中的记事本,手动输入以下程序:

class MyFirstProgram
{
    static void Main(string[] args)
    {
        System.Console.WriteLine("Hello,world!");
    }
}

给文件取名“MyFirstProgram.cs”,并以纯文本格式将其保存到硬盘上。

从Windows“开始”菜单中按以下顺序选取菜单命令:

开始→所有程序→Micrsoft Visual Studio 2010→Visual Studio Tools→Visual studio命令提示(2010)

提示

其实并不需要安装Visual Studio,只需安装有“.NET Framework SDK(Software Development Kit,软件开发包)”就可以手工编译一个.NET程序,但须设置好相应的环境变量。为简单起见,本书直接使用“Visual Studio命令提示(2010)”所启动的控制台窗口,以省去建立各种环境变量的麻烦。

这将打开一个黑底白字的命令提示窗口。

使用DOS命令“cd”转入到保存“MyFirstProgram.cs”的文件夹下,输入以下命令:

csc/target:exe MyFirstProgram.cs

csc.exe是C#语言编译器,如果MyFirstProgram.cs中没有语法错误,将会产生一个可执行的MyFirstProgram.exe文件,此程序运行后将在控制台窗口输出字符串“Hello,World!”(见图1-5)。

图1-5 手工编译并运行C#程序

.NET下运行的程序它的可执行程序文件被称为“程序集(assembly)”。与传统的Windows应用程序指直接运行于Win32平台上的应用程序。不同,后者在可执行程序文件中包含的是可以在本地CPU上直接执行的机器指令,而.NET程序集中包含的是独立于特定CPU的IL指令。

.NET 4.0 SDK提供了一个查看程序集IL指令的反汇编工具ildasm.exe(默认情况下安装于“\Program Files\Microsoft SDKs\Windows\v7.0A\bin\”文件夹下),可以使用它来查看生成的MyFirstProgram.exe程序集中所包容的IL指令代码(见图1-6)。

目前读者可以不去理会这些指令代码的含义,只需知道有这么一个工具就行了。关键是要明白:.NET应用程序编译之后会生成“程序集”,其中包含了IL指令。

图1-6 使用ildasm查看.NET程序集中的IL指令代码

提示

如果想深入地掌握.NET Framework的技术内幕,经常使用ildasm工具是一个好方法。美国著名的.NET技术专家Jeffrey RichterJeffrey Richter有多本著作,他的“CLR via C#”是.NET技术领域的名著。就有这么一句名言:“如果你对IL反汇编工具显示的内容了解得越多,那么你对.NET框架的理解也会越深”

除了可以使用ildasm工具“反汇编”.NET程序集而得到应用程序的IL代码之外,我们还可以直接使用另一个名为Reflector在前言中给出了Reflector的下载网址。的工具,直接看到转换为C#代码的程序集反汇编结果。

请运行Reflector工具,从File菜单中选择“Open”命令,打开编译好的示例程序集“MyFirstProgram.exe”(见图1-7)。

如图1-7所示,选中“MyFirstProgram”节点,右击,从弹出菜单中选择“Disassemble”命令,就可以看到反汇编得到的C#代码(见图1-8)。

图1-7 使用Reflector反汇编示例程序

图1-8 Reflector反汇编程序集得到的C#代码

在探索.NET技术内幕的过程中,Reflector是一个强有力的工具。

扩充阅读

如何保护自己的代码不被恶意利用?

Reflector是一个免费的共享软件,任何一个人都可以轻易地使用它反汇编一个.NET程序集,而它的功能是如此强大,反汇编出来的代码非常接近于真实的源代码。一个问题出现了:如何保护开发者的劳动成果不被不怀好意的人利用?

解决方案之一是使用“Dotfuscator Software Services”工具来保护程序集。此工具可以加密程序集,从而保护程序集不会被Reflector等工具轻易地反汇编出可以阅读的源代码。

Visual Studio 2010附带了有“Dotfuscator Software Services”,其用法请读者自行阅读其随机手册,或者通过互联网搜索引擎获取相关的资料,本书不再赘述。

下面对比一下ildasm和Reflector这两个工具:

ildasm可以将程序集反汇编为IL代码,偏重“底层”一些,多用于辅助分析编译器和CLR的行为特性,探索CLR的内部机理。它可以直接查看程序集所包容的元数据。开发者可以在ildasm反汇编出来的IL指令代码基础上进行修改,然后使用.NET Framework SDK所提供的另一个配套工具——IL编译器(ilasm.exe)来将修改后的IL代码编译为新的程序集。

Reflector可以将程序集反汇编为多种语言形式(比如C#、IL、Visual Basic等,甚至还有Delphi),比较“高层”一些,人们多将其用于分析.NET Framework类库,以了解特定技术领域的内部技术细节(比如通过反汇编Page类来理解ASP.NET页面的生命周期),Reflector对于.NET程序员深入把握特定技术领域的内幕非常有帮助。

ildasm和Reflector堪称绝配,共同成为探索.NET技术内幕的两把锋利的手术刀,本书在许多地方同时使用这两个工具分析.NET技术内幕。

技术春秋

Lutz Roeder和他的Reflector

Reflector的开发者是Lutz Roeder。

1999年微软宣布启动.NET战略不久,Lutz Roeder就开始了免费工具软件Reflector的开发,这个工具一经推出,就好评如潮。

Lutz Roeder花了8年的时间持续完善Reflector,使之成为了最受欢迎的.NET辅助开发工具之一。

2004年,.NET技术顾问James Avery在MSDN Magazine上写了一篇文章——《每个开发者都需要立即下载的10个.NET开发工具》http://msdn.microsoft.com/en-us/magazine/cc300497.aspx.,Reflector名列其中;Scott Hanselman在其《2007开发者和高级用户的终极工具列表http://www.hanselman.com/blog/ScottHanselmans2007UltimateDeveloperAndPowerUsersToolListForWindows.aspx.》一文中称“Reflector改变了世界”。

其实无需别人的文章“吹捧”,只需看一看安装了Reflector的.NET开发者有多少,再问一问任何一名使用过Reflector的.NET软件工程师的看法,就明白Reflector得享大名实在是理所应当。

商业软件公司Red Gate看中了Reflector的巨大影响力,在2008年与Lutz Roeder达成协议,今后将由Red Gate公司负责Reflector的后续开发工作,Red Gate公司承诺未来的Reflector仍然免费向社区提供,但他们会基于Reflector免费版本开发功能更为强大的商业版本,使用商业版本需要付费。

Lutz Roeder于2002年加盟微软公司,参与开发Microsoft Expression和Silverlight。

1.2.2 托管和非托管的软件运行环境

要想开发.NET应用程序,不可不知其开发与运行背后的故事。

非托管应用程序的执行过程

首先看一下Windows操作系统执行一个普通程序(即非托管程序)的基本过程。

软件工程师写的程序,经过编译器转为机器指令后,一般以文件的方式保存在外部存储器中,当CPU执行程序时,要先把外部存储器中的程序指令代码读到内存。

内存被分成很多块(称为“内存单元”),每个内存单元都有一个唯一的地址,指令就存放在以某个特定的地址开始的内存区域(即“若干个内存单元的集合”)中。保存要执行的第一条机器指令的那个内存单元就是程序的“入口点(Entry Point)”。

当程序执行时,CPU从入口点取出第一条指令,开始执行,然后再取第二条,依次类推……

把一个程序从外部存储器上装入内存执行是一个复杂的过程,这个功能由操作系统实现,开发具体应用程序的软件工程师通常不需要手动去写这部分代码。

由此可知,程序的运行必须依赖于操作系统(如Windows),而且编译器生成的程序文件包含的是仅适用于特定CPU架构的机器指令,由于不同CPU架构的机器指令集不同,所以,这个可执行程序无法不加修改地在拥有不同CPU架构的计算机上运行。

以这种方式生成的机器指令代码称为“非托管代码(Unmanaged Code)”。非托管代码不仅不能在不同CPU架构的计算机上执行,而且通常在不同的操作系统下也不能执行,比如一个Windows应用程序就无法直接在Linux下运行,反之亦然,这说明非托管代码的可移植性是受到较大限制的。

如果需要在拥有不同CPU架构的计算机和多种多样的操作系统上实现同一功能,必须针对每种操作系统和CPU架构编写特定的代码,这明显是一种重复且低效的劳动。

程序能不能只写一次,到处运行?

完全可以的,这就是“跨平台”的设计思想(Java就是一个典范)。.NET也采用了这种设计思想,而且走得更远,.NET在架构设计上不仅允许.NET应用程序在各种操作系统和CPU架构上运行,而且允许在同一个程序中混合使用由不同的编程语言开发出来的软件组件,.NET的这一特性被称为“跨语言”。

要支持跨平台这一特性,软件工程师编写的程序经过编译器生成的结果就不能是依赖于操作系统和特定CPU架构的机器指令了,而必须是一种“中立”的、在各种操作系统和CPU架构上都能执行的代码,这种代码Java称为“Byte Code(字节码)”,.NET称之为“IL(中间语言)”。

但程序最终还是要靠CPU执行的,所以,Java的字节码和.NET的IL代码仍然需要最终被翻译成本地CPU能执行的机器指令,这部分功能由一个运行在特定操作系统之上的软件系统来完成,这个软件系统被称之为“虚拟机(Virtual Machine,VM)”。

只需为每种操作系统和CPU架构提供一个虚拟机,就可以让同样一个应用程序不加修改地“跑”在运行不同操作系统、拥有不同CPU架构的计算机上。

这种运行在虚拟机之上的代码,被称为“托管代码(Managed Code)”,其原理如图1-9所示。

使用C#编译器csc.exe编译生成的可执行程序实际包含的只是IL指令代码,这是一种托管代码,只能运行在.NET虚拟机之上。所以,如果某台计算机上没有安装.NET Framework,就意味着图1-9的“虚拟机”一层不存在,.NET应用程序就无法执行。对于非Windows的操作系统,只要上面有.NET虚拟机,就可以运行.NET程序,通常不需要修改.NET应用程序源代码再重新编译。

图1-9 托管代码运行原理

一个应用程序可以只采用托管代码来构建,完全依赖CLR以及.NET Framework类库,也可以混合使用托管代码和非托管代码进行构建。托管代码调用非托管代码的技术,在.NET中被称为“平台调用(Platform Invoke)”(见图1-10)。

图1-10 托管代码与非托管代码

托管代码执行的过程

.NET下可直接运行的.exe文件包含的是IL指令。IL是微软和第三方编译器供应商磋商而创建的“虚”机器语言,之所以说它是“虚”的,是说它独立于特定架构的CPU,并且引入了许多具有面向对象特征的指令,与传统的直接面向硬件的汇编指令有着很大的不同,可以看成是“面向对象的”汇编指令。

由于IL指令独立于特定架构的CPU,因此它必须经过一个“翻译”过程,转换成本地CPU支持的机器指令,才可以最终执行。这个“翻译者”就是“JIT编译器(Just-In-Time Complier)”,请看图1-11。

如图1-11所示,程序源代码经语言编译器生成程序集,其中包含IL指令代码。当程序运行时,“类装载器(Class Loader)”从外部存储器中将IL指令读入内存,再经过JIT编译器动态地编译为本地CPU指令代码执行。在进行即时编译的过程中,CLR同时检查这些IL指令是否违反了一些安全规则,必要时CLR会停止编译并中断程序的执行。

图1-11 托管代码的执行过程

上述即时编译和代码验证的过程仅仅只是在第一次调用某个方法时发生。CLR会将编译好的本地代码缓存起来,第二次调用时就直接调用缓存中的本地代码,从而避免了再次编译所带来的性能损失。

托管与非托管应用程序的差别,是每一个.NET软件工程师都必须了解的。