《CLR via C#》读书笔记 第2章 生成、打包、部署和管理应用程序及类型

首先了解几个命令:

  • 路径:%SystemRoot%\Microsoft.NET\Framework(64)\version

    • csc.exe 通过在命令提示符处键入 C# 编译器的可执行文件名称 (csc.exe ),可调用该编译器。
  • 路径:%ProgramFiles%\Microsoft SDKs\Windows\version\bin

    • ildasm.exe IL反汇编程序,它是 IL 汇编程序 (ilasm.exe) 的配套工具。 ildasm.exe可利用包含中间语言(IL)代码的可移植可执行(PE)文件,并创建适合输入到ilasm.exe的文本文件(*.il文件)。
    • al.exe
      程序集链接器从一个或多个文件(这些文件可以是模块或资源文件)生成一个具有程序集清单的文件。 模块是不含程序集清单的中间语言 (IL) 文件。(*只能打包没有程序集清单的文件)

将类型生成到模块中(csc应用)

  • 创建Progress.cs文件
    public sealed class Program {
       public static void Main() {
          System.Console.WriteLine("Hi");
       }
    }
    
    
  • 执行csc命令,生成可执行文件
    csc.exe /out:Progress.exe /t:exe /r:MSCorLib.dll Progress.cs
    

至此C#编译器生成Progress.exe文件,它是标准PE(可移植执行体,Portable Executable)文件。

解释一下几个参数:(更多指令含义查看csc -help)

  • /out:<文件> 指定输出文件夹(默认值:包含主类文件或第一个文件的基名称)
  • /t(arget):exe生成控制台可执行文件(默认)
  • /r(eference):MSCorLib 从指定程序集文件引用元数据

响应文件:是包含一组编译器命令行开关的文本文件,编译器打开响应文件,并使用其中包含的所有开发,感觉就是这些开关直接在命令行上传递给CSC.exe。使用步骤如下:

  • 创建响应文件 MyProject.rsp
    /out:MyProject.exe
    /target:exe
    
  • 执行命令行,生成可执行文件
    csc.exe @MyProject.rsp Progress.cs
    

.NET Framework安装时会在%SystemRoot%\Microsoft.NET\Framework(64)\version下安装默认全局CSC.rsp文件。由于全局CSC.rsp引用列出了所有程序集,所以我们不用使用/reference开关显示引用这些程序集。

元数据概述

托管PE文件由4部分构成:PE32(+)头、CLR头、元数据、以及IL

  • PE32(+)头:Windows要求的标准信息

  • CLR头:小的信息块,是需要CLR的模块(托管模块)特有的,这个头包含模块生成时面向CLR的major(主)和minor(次)版本号;一些标志(flag);一个MethodDef token,该token指定了模块的入口方法;一个可选的强名称数字签名,最后还包含模块内部的一些元数据表的大小和偏移。

  • 元数据:由几个表构成的二进制数据块。有三种表,分别是定义表(definition table)、引用表(reference table)和清单表(manifest table)。


    定义表

在编译器编译源代码时,代码中定义任何东西都可能导致在上面列出的某个表中创建一条记录,而编译器还会检测代码所引用的类型、字段、方法等,并创建相应的元数据引用记录,下面总结了常用的引用元数据表。


引用表

引用表续
元数据清单表

首先看一下定义表和引用表,查看PE文件中的元数据使用到了ILDasm.exe(IL反汇编器),打开ILDasm.exe、将上一节生成的可执行文件拖进去。要查看元数据可以使用“视图”|“元信息”|“显示”菜单项(或者Ctrl+M)下面摘抄部分信息,从下面的信息我们可以看到TypeDef中由我们定义的Progress以及两个方法Main、.ctor。而TypeRef中由我们引用的模块比如System.Object等

```
===========================================================
ScopeName : Progress.exe
MVID      : {E0D3E63C-F15E-4D4B-A808-A932764E9CC9}
===========================================================
Global functions
-------------------------------------------------------

Global fields
-------------------------------------------------------

Global MemberRefs
-------------------------------------------------------

TypeDef #1 (02000002)
-------------------------------------------------------
    TypDefName: Progress  (02000002)
    Flags     : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass] [BeforeFieldInit]  (00100101)
    Extends   : 01000001 [TypeRef] System.Object
    Method #1 (06000001) [ENTRYPOINT]
    -------------------------------------------------------
        MethodName: Main (06000001)
        Flags     : [Public] [Static] [HideBySig] [ReuseSlot]  (00000096)
        RVA       : 0x00002050
        ImplFlags : [IL] [Managed]  (00000000)
        CallCnvntn: [DEFAULT]
        ReturnType: Void
        No arguments.

    Method #2 (06000002) 
    -------------------------------------------------------
        MethodName: .ctor (06000002)
        Flags     : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor]  (00001886)
        RVA       : 0x0000205e
        ImplFlags : [IL] [Managed]  (00000000)
        CallCnvntn: [DEFAULT]
        hasThis 
        ReturnType: Void
        No arguments.


TypeRef #1 (01000001)
-------------------------------------------------------
Token:             0x01000001
ResolutionScope:   0x23000001
TypeRefName:       System.Object
    MemberRef #1 (0a000004)
    -------------------------------------------------------
        Member: (0a000004) .ctor: 
        CallCnvntn: [DEFAULT]
        hasThis 
        ReturnType: Void
        No arguments.

(...省略部分信息...)

Assembly
-------------------------------------------------------
    Token: 0x20000001
    Name : Progress
    Public Key    :
    Hash Algorithm : 0x00008004
    Version: 0.0.0.0
    Major Version: 0x00000000
    Minor Version: 0x00000000
    Build Number: 0x00000000
    Revision Number: 0x00000000
    Locale: <null>
    Flags : [none] (00000000)
    CustomAttribute #1 (0c000001)
    -------------------------------------------------------
        CustomAttribute Type: 0a000001
        CustomAttributeName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute :: instance void .ctor(int32)
        Length: 8
        Value : 01 00 08 00 00 00 00 00                          >                <
        ctor args: (8)

    CustomAttribute #2 (0c000002)
    -------------------------------------------------------
        CustomAttribute Type: 0a000002
        CustomAttributeName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute :: instance void .ctor()
        Length: 30
        Value : 01 00 01 00 54 02 16 57  72 61 70 4e 6f 6e 45 78 >    T  WrapNonEx<
                      : 63 65 70 74 69 6f 6e 54  68 72 6f 77 73 01       >ceptionThrows   <
        ctor args: ()


AssemblyRef #1 (23000001)
-------------------------------------------------------
    Token: 0x23000001
    Public Key or Token: b7 7a 5c 56 19 34 e0 89 
    Name: mscorlib
    Version: 4.0.0.0
    Major Version: 0x00000004
    Minor Version: 0x00000000
    Build Number: 0x00000000
    Revision Number: 0x00000000
    Locale: <null>
    HashValue Blob:
    Flags: [none] (00000000)


User Strings
-------------------------------------------------------
70000001 : ( 2) L"Hi"


Coff symbol name overhead:  0
===========================================================
===========================================================
===========================================================
```

我们还可以通过“视图”|“统计”看到程序集的统计信息。

 File size            : 3584
 PE header size       : 512 (496 used)    (14.29%)
 PE additional info   : 1423              (39.70%)
 Num.of PE sections   : 3
 CLR header size     : 72                 ( 2.01%)
 CLR meta-data size  : 608                (16.96%)
 CLR additional info : 0                  ( 0.00%)
 CLR method headers  : 2                  ( 0.06%)
 Managed code         : 20                ( 0.56%)
 Data                 : 2048              (57.14%)
 Unaccounted          : -1101             (-30.72%)

 Num.of PE sections   : 3
   .text    - 1024
   .rsrc    - 1536
   .reloc   - 512

 CLR meta-data size  : 608
   Module        -    1 (10 bytes)
   TypeDef       -    2 (28 bytes)      0 interfaces, 0 explicit layout
   TypeRef       -    4 (24 bytes)
   MethodDef     -    2 (28 bytes)      0 abstract, 0 native, 2 bodies
   MemberRef     -    4 (24 bytes)
   CustomAttribute-    2 (12 bytes)
   Assembly      -    1 (22 bytes)
   AssemblyRef   -    1 (20 bytes)
   Strings       -   178 bytes
   Blobs         -    68 bytes
   UserStrings   -     8 bytes
   Guids         -    16 bytes
   Uncategorized -   170 bytes

 CLR method headers : 2
   Num.of method bodies  - 2
   Num.of fat headers    - 0
   Num.of tiny headers   - 2

 Managed code : 20
   Ave method size - 10

从上面可以看出文件大小以及文件各部分的大小比重。可以看到PE头和元数据占用了相当大的比重,而IL代码只占用了区区20字节。

将模块合并成程序集(重点)

程序集相关定义及解释

上一节的Progress.exe并非只是含由元数据的PE文件,它还是程序集(assembly)。程序集是一个或多个类型定义文件及资源的集合。在程序集的所有文件中,由一个文件容纳了清单。清单也是一个源数据表集合,表中主要包含程序集组成部分的文件名称。此外还描述了程序集的版本、语言文件、发布者、公开到处类型以及构成程序集的所有文件。

CLR操作的是程序集。换言之,CLR总是首先加载包含“清单”元数据表的文件,然后再根据“清单”来获取程序集中其他文件的名称。程序集的特点:

  • 程序集定义了可重用的类型
  • 程序集用一个版本号标记
  • 程序集可以关联安全信息

类型为了顺利打包、版本控制、安全保护以及使用,必须放在程序集一部分的模块中。程序集可由多个文件构成:一些是含有元数据的PE文件、还有资源文件,为了便于理解,可将程序集是为一个逻辑EXE或DLL(作为库文件使用)。

为什么要引入“程序集”?可重用类型的逻辑表示于物理表示可以分来,例如程序集中可能包含多个类型,可以将常用类型放在一个文件,不常用的放在一个文件,对于不常用的文件,等到使用的是由再去下载部署它,不使用就永远不会下载该模块。

为什么要使用多程序集?

  • 不同类型用不同文件
  • 可在程序集中添加资源或数据文件(源代码资源分开)
  • 不同程序集可用不同编程语言来实现

清单元数据表的应用

生成程序集要么选择现有的PE文件作为“清单”的宿主,要么创建单独的PE文件并只在其中包含清单。

使用/t[arget]:module开关,这个开发指示编译器生成一个不包含清单元数据的PE文件。这样生成的肯定是一个DLL PE文件。CLR要想访问其中的任何类型必须将其添加到一个程序集中。

选择现有的PE文件作为“清单”生成程序集

  • 首先生成两个源代码文件PUT.cs(包含不常用类型),FUT.cs包含常用类型
  • 编译PUT.cs到一个不含清单的模块,RUT.netmodule
csc /t:module RUT.cs
  • 编译FUT.cs,将其作为程序集清单的宿主,并
    将刚刚生成的模块添加到清单元数据表
csc /out:MultiFileLibrary.dll /t:library /addmodule:RUT.netmodule FUT.cs
image.png

上述命令执行完成后将生成MultiFileLibrary.dll库文件(客户端可用/r:MultiFileLibrary来引用程序集的类型),我们可以用ILDasm分别打开这两个文件并查看元数据,可以发现只有MultiFileLibrary.dll中含有元数据清单,并且包含了RUT.netmodule的所有公开导出类型。

(...省略定义和引用...)
Assembly
-------------------------------------------------------
    Token: 0x20000001
    Name : MultiFileLibrary
    Public Key    :
    Hash Algorithm : 0x00008004
    Version: 0.0.0.0
    Major Version: 0x00000000
    Minor Version: 0x00000000
    Build Number: 0x00000000
    Revision Number: 0x00000000
    Locale: <null>
    Flags : [none] (00000000)
    CustomAttribute #1 (0c000001)
    -------------------------------------------------------
        CustomAttribute Type: 0a000001
        CustomAttributeName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute :: instance void .ctor()
        Length: 30
        Value : 01 00 01 00 54 02 16 57  72 61 70 4e 6f 6e 45 78 >    T  WrapNonEx<
                      : 63 65 70 74 69 6f 6e 54  68 72 6f 77 73 01       >ceptionThrows   <
        ctor args: ()

    CustomAttribute #2 (0c000002)
    -------------------------------------------------------
        CustomAttribute Type: 0a000002
        CustomAttributeName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute :: instance void .ctor(int32)
        Length: 8
        Value : 01 00 08 00 00 00 00 00                          >                <
        ctor args: (8)

File #1 (26000001)
-------------------------------------------------------
    Token: 0x26000001
    Name : RUT.netmodule
    HashValue Blob : 81 f1 75 f0 0b d6 29 b3  1f 30 52 a5 57 78 38 63  06 24 e4 02 
    Flags : [ContainsMetaData]  (00000000)


ExportedType #1 (27000001)
-------------------------------------------------------
    Token: 0x27000001
    Name: RUT
    Implementation token: 0x26000001
    TypeDef token: 0x02000002
    Flags     : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass] [BeforeFieldInit]  (00100101)

客户端代码方法首次调用时:

  • CLR检测作为参数、返回值或者局部变量而被方法引用的类型。
  • CLR尝试加载所引用程序集中含有清单的文件。
    • 如果要访问的类型恰好在这个文件中,CLR会执行内部登记允许使用该类型。
    • 如果清单指出被引用的类型在不同文件中,CLR会尝试加载需要的文件,同样执行内部登记。

CLR并非一开始就加载所有可能用到的程序集。只有在调用的方法确实引用到了未加载程序集中类型时,才会加载程序集。

创建单独的PE文件只包含清单生成程序集

还是上一节用到的两个类型,RUT.cs、FUT.cs。并使用al命令。

csc /t:module RUT.cs
csc /t:module FUT.cs
al /out:MutilFileLibrary.dll /t:library FUT.netmodule RUT.netmodule
image.png

同时我们也可以使用ILDasm看到生成的out:MutilFileLibrary是只含有清单文件的。


image.png

我们也可以使用/t:exe生成一个可执行文件,但是需要指定程序入口/main

csc /t:module /r:MultiFileLibrary.dll Program.cs
al /out:Program.exe /t:exe /main:Program.Main Program.netmodule
image.png

可以看到生成的可执行文件里生成了一个全局函数__EntryPoint,并包含了以下代码(双击):

.method privatescope static void  __EntryPoint$PST06000001() cil managed
{
  .entrypoint
  // 代码大小       8 (0x8)
  .maxstack  8
  IL_0000:  tail.
  IL_0002:  call       void [.module progress.dll]Program::Main()
  IL_0007:  ret
} // end of method 'Global Functions'::__EntryPoint

可以看到实际上是调用了Program的Main方法。(其他方法其实也是可以的,只是指定一个入口函数而已)。

本章的重点概念主要是程序集。如果生成程序集,为什么要使用多文件程序集,如果生成dll并引用等等。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • 将源程序编译成托管模块 公共语言运行时(CLR) 概念:CLR是一个可由多种编程语言使用的“运行时”。CLR的核心...
    阿飞咯阅读 946评论 0 1
  • [TOC] 内存管理 一、托管堆基础 在面向对象中,每个类型代表一种可使用的资源,要使用该资源,必须为代表资源的类...
    _秦同学_阅读 3,690评论 0 3
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,669评论 0 27
  • 公共语言运行时(CLR),是一个可由多种编程语言使用的“运行时”。包括,内存管理,程序集加载,安全性,异常处理和线...
    何幻阅读 1,845评论 0 0
  • 分享人:傅云特邀嘉宾: 周振涛 原文出处: 链接:https://bbs.kafan.cn/thread-20...
    胡諾阅读 1,362评论 0 0