Unity干货

《CLR via C#》读书笔记 第2章 生成、打包、部署和管

2019-09-20  本文已影响0人  阿飞咯

首先了解几个命令:

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

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

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

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

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

元数据概述

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

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


引用表
引用表续 元数据清单表

首先看一下定义表和引用表,查看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文件作为“清单”生成程序集

csc /t:module RUT.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并非一开始就加载所有可能用到的程序集。只有在调用的方法确实引用到了未加载程序集中类型时,才会加载程序集。

创建单独的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并引用等等。

上一篇下一篇

猜你喜欢

热点阅读