菜鸟CLR VIA C#之旅—开始旅行:千里之行始于足下
菜鸟踏上CLR VIA C#之旅,小学成绩一般般,初中成绩普普通通,高中成绩勉勉强强,三流大学凑活毕业。偶然间踏上了北京之旅,也开始了他作为程序员的梦想。就这么一个摆在大街上不会有人再看第二眼的普通到无法再普通的普通人,就是本次旅行的菜鸟了。希望《CLR VIA C#》这本圣经能带菜鸟踏上更高的台阶!。
顺便说下菜鸟学习.net的经过。菜鸟是09年10月份(大三上学期)接触C#的,自己买了本《C#入门经典》自己看的。然后又买了本《C#高级编程》,就这样走上了.net之路,在刚开始自学的时候,面向对象的基础知识就像一道门槛时时阻碍着菜鸟,在此要特别感谢菜鸟的室友,大家都叫他果爷,就菜鸟个人认为,他是我们专业技术最好的,不管菜鸟的问题多幼稚或简单,他都会认真解答。
到了今年3月份时,菜鸟进入了北京一家金融软件开发公司实习,一直到现在。在工作中发现了自己有很大的弊病,由于自己刚刚毕业,觉得很多事情自己不懂是理所当然。我发现自己错了,错的离谱,所以请记住,学习不是被动的,当任何事务变成被动的时候,那件事已经不再是你的事了!
菜鸟从小就是一个有理想的人,但目前菜鸟必须先在北京把自己养活,然后才能发展,随着工作越深,菜鸟心中的担心、空虚和迷茫就越多!但菜鸟相信,任何东西,只要一个人认真地去学,投入很多时间和精力,肯定是能够干好的。在学C#的过程中真的体会到了“学的越多,才知道会的越少”这句话的含义。学了语法之后会发现很多Framework中的类不太会用,学了一些类库的用法又发现还有MSIL、CLR和JIT、GC这些高深的东西。这才发现.NET的宽广和驳杂,对同学们的“其实.NET就这点儿东西,拖拖控件而已”的说法颇不赞同。就在意识到.NET的博大精深的同时,菜鸟发现自己一直受困在目前的水平中,想要自我提高,很难很难。知道要学的东西有一大堆,但菜鸟却是根本不知道到底要学些什么东西知识才能让我得到快速进步,!这才是让人最痛苦的事!菜鸟也经常查阅这方面的信息,但似乎总是很乱,从没有一个人能够详细透彻地给菜鸟以明确的指引。
在偶然的一个晚上,菜鸟躺在床上的时候,突然有件事物触动了菜鸟的灵感,对啊,怎么把它给忘了,它就是一直默默守候在菜鸟的床头桌上的《CLR VIA C#》,记得周公说过:“如果你没有时间的话,那么学新技术不如学基础、学应用不如学思想。技术虽然一直在变,但是越是基础和越是抽象的技术变化越慢,越是偏向应用越是具体的技术变化越是快,从性价比上说,学习基础知识性价比更高。”说实话,这本书菜鸟买了有段时间了,一直没有静心下来认真学习。菜鸟准备系统学习这本伟大的书,并将自己的心得体会写下来,权当学习笔记,加深印象及理解,技术的深度来自于总结。菜鸟知识有限,文章中难免会有很多低级可笑的错误,希望得到各位高人的指正批评,这样菜鸟进步的会更快!
好了,上面废话了这么多,菜鸟给大家演示一个最简单的例子—Hello,word。1988年Brian W. Kernighan和Dennis M. Ritchie合著了软件史上的经典巨著《The C programming Language》,从那时起,Hello, world示例就作为了几乎所有实践型程序设计书籍的开篇代码,一直延续至今,除了表达对巨人与历史的尊重,本文也以Hello, world示例作为我们扣开学习的大门,开始菜鸟循序渐进的CLR VIA C#认识之旅。
从Hello world开始:
1 using System;
2
3 class HelloWord
4 {
5 static void Main(string[] args)
6 {
7 Console.WriteLine("Hello,word!");
8 }
9 }
对于开发中我们可能会经常用到一些外部工具,比如ILDASM和ILASM等,我们可以配置成Visual Studio 2010的一个菜单项,需要的时候点击一下就可以打开,而不必每次都费时费力找到该文件所在的位置再双击打开。配置过程如下:
从工具栏上找到“工具”-“外部工具”,如下图:
将ildasm.exe路径设置下(电脑中ildasm.exe文件位置),比如菜鸟电脑:
C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\ildasm.exe,标题随便自己设置,如下:
配置好以后就可以直接在工具栏中运行了:
好了,说远了,言归正传,还是来看我们的 Hello word的MSIL编码,用VS自带的ILDasm.exe反编译工具打开后如下:
由上图可知,编译后的IL结构中,包含了MANIFEST和HelloWorld类,其中MANIFEST是个附加信息列表,主要包含了程序集的一些属性,例如程序集名称、版本号、哈希算法、程序集模块等,以及对外部引用程序集的引用项;而Hello_world类则是我们下面介绍的主角。
首先我们来看一下MANIFEST程序集清单分析:
1 // Metadata version: v4.0.30319
2 .assembly extern mscorlib
3 {
4 .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
5 .ver 4:0:0:0
6 }
7 .assembly 'Hello word'
8 {
9 .custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 29 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B // ..).NETFramework
10 2C 56 65 72 73 69 6F 6E 3D 76 34 2E 30 2C 50 72 // ,Version=v4.0,Pr
11 6F 66 69 6C 65 3D 43 6C 69 65 6E 74 01 00 54 0E // ofile=Client..T.
12 14 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 // .FrameworkDispla
13 79 4E 61 6D 65 1F 2E 4E 45 54 20 46 72 61 6D 65 // yName..NET Frame
14 77 6F 72 6B 20 34 20 43 6C 69 65 6E 74 20 50 72 // work 4 Client Pr
15 6F 66 69 6C 65 ) // ofile
16 .custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 0A 48 65 6C 6C 6F 20 77 6F 72 64 00 00 ) // ...Hello word..
17 .custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 )
18 .custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 )
19 .custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 0C E5 BE AE E8 BD AF E4 B8 AD E5 9B BD 00
20 00 )
21 .custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 0A 48 65 6C 6C 6F 20 77 6F 72 64 00 00 ) // ...Hello word..
22 .custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 1E 43 6F 70 79 72 69 67 68 74 20 C2 A9 20 // ...Copyright ..
23 E5 BE AE E8 BD AF E4 B8 AD E5 9B BD 20 32 30 31 // ............ 201
24 31 00 00 ) // 1..
25 .custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 )
26 .custom instance void [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::.ctor(bool) = ( 01 00 00 00 00 )
27 .custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 38 64 66 39 39 65 34 64 2D 66 38 64 66 // ..$8df99e4d-f8df
28 2D 34 62 62 34 2D 61 61 63 34 2D 35 35 31 38 34 // -4bb4-aac4-55184
29 39 63 31 65 62 64 62 00 00 ) // 9c1ebdb..
30 .custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 ) // ...1.0.0.0..
31
32 // --- 下列自定义属性会自动添加,不要取消注释 -------
33 // .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 )
34
35 .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
36 .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
37 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
38 .hash algorithm 0x00008004
39 .ver 1:0:0:0
40 }
41 .module 'Hello word.exe'
42 // MVID: {6CF52DF9-4A2D-4815-91B4-D85F23F6E333}
43 .imagebase 0x00400000
44 .file alignment 0x00000200
45 .stackreserve 0x00100000
46 .subsystem 0x0003 // WINDOWS_CUI
47 .corflags 0x00000003 // ILONLY 32BITREQUIRED
48 // Image base: 0x04C90000
从这段清单代码中,我们可以得到如下信息:
- .assembly指令用于定义编译目标或者加载外部库。在IL清单中可见,.assembly extern mscorlib表示外部加载了外部核心库mscorlib,而.assembly HelloWorld则表示了定义的编译目标。值得注意的是,.assembly将只显示程序中实际应用到的程序集列表,而对于加入using引用的程序集,如果并未在程序中引用,则编译器会忽略多加载的程序集,例如System.Data将被忽略,这样就有效避免了过度加载引起的代码膨胀。
- 我们知道mscorlib.dll程序集定义managed code依赖的核心数据类型,属于必须加载项。 例如接下来要分析的.ctor指令表示构造函数,从代码中我们知道没有为HelloWord类提供任何显示的构造函数,因此可以肯定其继承自基类System.Object,而这个System.Object就包含在mscorlib程序集中。
- 在外部指令中还会指明了引用版本(.ver);应用程序实际公钥标记(.publickeytoken),公钥Token是SHA1哈希码的低8位字节的反序(如下图所示),用于唯一的确定程序集;还包括其他信息如语言文化等。
- HelloWorld程序集中包括了.hash algorithm指令,表示实现安全性所使用的哈希算法,系统缺省为0x00008004,表明为SHA1算法;.ver则表示了HelloWorld程序集的版本号;
- 程序集由模块组成, .module为程序集指令,表明定义的模块的元数据,以指定当前模块。
- 其他的指令还有:imagebase为影像基地址;.file alignment为文件对齐数值;.subsystem为连接系统类型,0x0003表示从控制台运行;.corflags为设置运行库头文件标志,默认为1;这些指令不是我们研究的重点,详细的信息请参考MSDN相关信息。
指令
说明
.assembly extern <程序集名称>
指定包含当前模块所引用项目的另一程序集(在此示例中为 mscorlib)。
.publickeytoken <标记>
指定所引用程序集的实际密钥的标记。
.ver <版本号>
指定引用程序集的版本号。
.assembly <程序集名称>
指定程序集名称。
.hash algorithm <int32 值>
指定使用的哈希算法。
.ver <版本号>
指定程序集的版本号。
.module <文件名>
指定组成程序集的模块名称, 在此示例中,程序集只包含一个文件。
.subsystem <值>
指定程序要求的应用程序环境。 在此示例中,值 3 表示该可执行文件从控制台运行。
.corflags
当前是元数据中的一个保留字段。
下面是重点的Hello_word类分析:
首先我们看看定义HelloWord类是IL是如何处理的
1 .class private auto ansi beforefieldinit HelloWord
2 extends [mscorlib]System.Object
3 {
4 } // end of class HelloWord
Ø .class表明了HelloWorld是一个private类(默认私有),该类继承自外部程序集mscorlib的System.Object类。
Ø private为访问控制权限,这点很容易理解。
Ø auto表明程序加载时内存的布局是由CLR决定的,而不是程序本身
Ø ansi属性则为了在没有被管理和被管理代码间实现无缝转换。没有被管理的代码,指的是没有运行在CLR运行库之上的代码,例如原来的C,C++代码等。
Ø beforefieldinit属性为HelloWorld提供了一个附加信息,用于标记运行库可以在任何时候执行类型构造函数方法,只要该方法在第一次访问其静态字段之前执行即可。如果没有beforefieldinit则运行库必须在某个精确时间执行类型构造函数方法,从而影响性能优化。
然后是.ctor方法,代码为:
1 .method public hidebysig specialname rtspecialname
2 instance void .ctor() cil managed
3 {
4 // 代码大小 7 (0x7)
5 .maxstack 8
6 IL_0000: ldarg.0
7 IL_0001: call instance void [mscorlib]System.Object::.ctor()
8 IL_0006: ret
9 } // end of method HelloWord::.ctor
Ø cil managed 说明方法体中为IL代码,指示编译器编译为托管代码。
Ø .maxstack表明执行构造函数.ctor期间的评估堆栈(Evaluation Stack)可容纳数据项的最大个数。关于评估堆栈,其用于保存方法所需变量的值,并在方法执行结束时清空,或者存储一个返回值。
Ø IL_0000,是一个标记代码行开头,一般来说,IL_之前的部分为变量的声明和初始化。
Ø ldarg.0 表示装载第一个成员参数,在实例方法中指的是当前实例的引用,该引用将用于在基类构造函数中调用。
Ø call指令一般用于调用静态方法,因为静态方法是在编译期指定的,而在此调用的是构造函数.ctor()也是在编译期指定的;而另一个指令callvirt则表示调用实例方法,它的调用过程有异于call,函数的调用是在运行时确定的,首先会检查被调用函数是否为虚函数,如果不是就直接调用,如果是则向下检查子类是否有重写,如果有就调用重写实现,如果没有还调用原来的函数,依次类推直到找到最新的重写实现。
Ø ret表示执行完毕,返回。
最后是Main方法,代码为:
1 .method private hidebysig static void Main(string[] args) cil managed
2 {
3 .entrypoint
4 // 代码大小 13 (0xd)
5 .maxstack 8
6 IL_0000: nop
7 IL_0001: ldstr "Hello,word!"
8 IL_0006: call void [mscorlib]System.Console::WriteLine(string)
9 IL_000b: nop
10 IL_000c: ret
11 } // end of method HelloWord::Main
Ø hidebysig属性用于表示如果当前类作为父类时,类中的方法不会被子类继承,因此HelloWorld子类中不会看到Main方法。
Ø .entrypoint指令表明了CLR加载程序HelloWorld.exe时,是首先从.entrypoint方法开始执行的,也就是表明Main方法将作为程序的入口函数。每个托管程序必须有并且只有一个入口点。这区别于将Main函数作为程序入口标志。
Ø ldstr指令表示将字符串压栈,"Hello, world."字符串将被移到stack顶部。CLR通过从元数据表中获得文字常量来构造string对象,值得注意的是,在此构造string对象并未出现newobj指令。
关于字符串string的却比较特殊,String类型直接派生自Object,所以它是引用类型,总是存在于堆上,永远不会跑到线程栈,但必须注意的是不能使用new操作符来从一个文本常量字符串来构造一个String对象
1 using System;
2
3 class HelloWord
4 {
5 static void Main(string[] args)
6 {
7 //string a="abc";
8 string a = new string("abc"); //错误
9 }
10 }
相反,必须使用代码中注释的简化语法来构造,为什么?我们知道IL指令newobj用于构造一个对象的新实例,然而,在实例化String对象时IL代码中并不会出现newobj指令,只有一个ldstr(即load string)指令,它用从元数据获得的一个文本常量字符串构造一个String对象,这证明CLR事实使用一种特殊的方式来构造文本常量String对象,或许这就是String与众不同(不变性、字符串驻留)的根源所在吧!
好了,这篇不是文章的文章就写到这了,菜鸟的CLR VIA C#之旅开始了,菜鸟已经准备好了,你呢?
最后,引用周公的一段话
浮躁的人容易问:我到底该学什么;----别问,学就对了;
浮躁的人容易问:学脚本有钱途吗;----建议你去抢银行;
浮躁的人容易说:我要中文版!我英文不行!----不行?学呀!
浮躁的人分两种:只观望而不学的人;只学而不坚持的人;
浮躁的人永远不是一个高手。