.Net应用程序由一个或多个可执行程序组成,每个可执行程序中都有元数据和可管理的代码。.Net应用程序通常被称为程序集。一个程序集由一个或多个部署在一起的文件组成,它通常保存一份清单,该清单确定程序集标识,指定组成程序集实现的文件,指定组成程序集的类型和资源,列举对其他程序集的编译时依赖项,并指定为保证程序集正确运行所需要的权限集。在运行时使用此信息来解析引用,强制版本绑定策略,并验证已加载的程序集的完整性。
不含程序集清单的中间语言文件被称为模块。程序集可以是单模块的,也可以是多模块的。每个程序集只能够有一个清单,该清单驻留在拥有入口函数的模块中。图一显示了一个单模块程序集的结构:
图一 单模块程序集的结构
从图一中我们可以看到程序集中包含了程序集标识段,元数据段和中间语言代码段。让我们来看一下HelloWorld中的代码,其中的assembly命令代表是程序集标识段,但是在其中没有包含版本、名称、区域性、安全性和模块信息。让我们在代码中加入下面的行(代码重用黑体标出):
.assembly DemystifyingILChapter1
{
.hash algorithm 0x00008004
.ver 1:0:0:0}
.class public auto ansi HelloWorld extends [mscorlib]System.Object
{
…
} |
上面的代码扩充了assembly命令的内容。事实上assembly命令可以包含很多其它的命令,在上面的代码中使用了hash和ver命令。
· hash:该命令告诉VSE实现安全性所使用的哈希算法。数字0x00008004表示使用SHA1,这也是系统的缺省设置。
· ver:程序集的版本号,由四个32位整数组成。
前面在讨论.Net应用程序的格式的时候,曾提到在一个可执行的应用程序中可以包含对其它模块的引用。到目前为止我们还没有使用任何命令来告知程序集应该生成哪一个模块。在HelloWorld例子中我们引用了一个外部程序mscorlib。那么汇编工具正确编译了代码吗?答案是肯定的。ILAsm能够自动将HelloWorld中的代码定义为基本模块,并在其中引用mscorlib程序集。在下面的代码中我们将使用命令告诉编译器如何集成模块。我们使用的命令仍然是assembly,不过现在带上了extern属性。为了正确地引用一个程序集,至少需要创作者的公钥或公钥Token以及程序集的版本信息。公钥Token是SHA1哈希码的低八位字节,它能够唯一确定一个程序集。我们可以在C:\Winnt\assembly目录下找到程序集的相关信息(如图二所示)。
图二 程序集的相关信息
在C:\Winnt\assembly中可以看到计算机上安装的mscorlib版本是1.0.3300.0。公钥Token是B77A5C561934E089。也许你计算机上安装了不同版本的mscorlib。在下面的代码中你需要用正确的版本号替代1.0.3300.0。
.module Hello.exe
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89)
.ver 1:0:3300:0
}
.assembly DemystifyingILChapter1
{
…
}
.class public auto ansi HelloWorld extends [mscorlib]System.Object
{
…
} |
现在我们拥有了一个正确的.Net应用程序,该程序中提供了.Net框架所有必要的信息。下面我们将用C#和VB.Net中编写HelloWorld程序,并将它们编译成中间代码。
在高级语言中实现HelloWorld例子程序
我们将在C#和VB.Net中编写HelloWorld程序,将它们编译成可执行程序,然后用反汇编工具ildasm.exe将执行程序反汇编为中间代码,把它们和上面的例子进行比较。
C#
public class HelloWorld
{
public static void Main()
{
System.Console.WriteLine("Hello World.");
}
} |
当生成可执行文件后,用ildasm.exe来反汇编才生成的HelloWorld.exe。在命令行中输入:
ildasm /out=HelloWorld.txt HelloWorld.exe |
查看生成的HelloWorld.txt文件,我们可以看到:
// Microsoft (R) .NET Framework IL Disassembler. Version 1.0.3705.0
// Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:3300:0
}
.assembly HelloWorld
{
// --- The following custom attribute is added automatically, ---
// --- do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::
// .ctor(bool, bool) = ( 01 00 00 01 00 00 )
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module HelloWorld.exe
// MVID: {E63F9CA9-D4C4-4826-9BE1-2B0EE3694289}
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000001
// Image base: 0x03000000
//
// ============== CLASS STRUCTURE DECLARATION ==================
//
.class public auto ansi beforefieldinit HelloWorld
extends [mscorlib]System.Object
{
} // end of class HelloWorld
// =============== CLASS MEMBERS DECLARATION ===================
// note that class flags, 'extends' and 'implements' clauses
// are provided here for information only
.class public auto ansi beforefieldinit HelloWorld
extends [mscorlib]System.Object
{
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 11 (0xb)
.maxstack 1
IL_0000: ldstr "Hello World."
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method HelloWorld::Main
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 1
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method HelloWorld::.ctor
} // end of class HelloWorld
// =============================================================
//*********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file HelloWorld.res |
如果仔细察看一下上面的文件,我们会发现其中大部分的命令和指令在前面已经作了阐述。如果用VB.Net来编写HelloWorld程序,编译器的输出基本上和C#一样。因此虽然使用的语言不一样,但是源代码最终会编译成相同的中间代码,因此由于语言之间的差别产生的种种问题在.Net中都不足为道了。
小结
本文我们延续使用了《解密微软中间语言MSIL之中间语言概述 》Hello
World例子程序,详细分析了.net应用程序的格式和结构。接下来,我们将在下一篇文章中完成对程序的调试工作。
|