一篇文章彻底搞懂Assembly和Module

定义

废话不多说,这里先直接给出 Assembly和Module的相关定义。

权威的定义

An assembly is a deployment unit, a building block of a managed application. Assemblies are reusable, allowing different applications to use the same assembly.
Assemblies carry a full self-description in their metadata, including version information that allows the common language runtime to use a specific version of an assembly for a particular application ——《Net IL assembler》 Chapter 6

程序集是一个部署单元,也是托管应用程序的构建块。程序集是可重用的,允许不同的应用程序使用同一个程序集。程序集在其元数据中包含了完整的自我描述信息,其中包括版本信息,这使得公共语言运行时能够为特定应用程序使用特定版本的程序集。

The managed executable files are referred to as modules

托管可执行文件被称为Module。

方便理解,但不那么权威的定义

好吧,可能书上的定义还是有些模糊,这里作者会尝试根据个人经验以及书中的其他内容,给出更具体,更直观的解释。

程序集Assembly,其实是一个抽象的概念,这也是为什么程序集的定义那么抽象,因为它本身就是一个抽象的概念。这个概念就是上面提到的程序集的定义。

所以,如果想获得一个直观的,具体的,清晰的理解,我们不能从程序集入手。 这里我们从Module入手。

Module是什么?

Module在Common Intermediate Language里面就是指一个托管/非托管的文件。
也就是说Module本质上就是包含了代码的文件,这个代码可能是二进制的或者是IL代码。

  • 非托管文件:我们用C++编译出一个二进制的Dll,然后它被C#代码引用了。那么它就是一个Module。
  • 托管文件: 我们使用csc生成netmodule文件,最后被程序集引用。那么它也是一个Module。
  • 托管文件: 我们用C#生成了一个dll文件,这个dll文件既是一个程序集,也是一个Module。

这点我们可以在《.Net IL assembler》第6章的ModuleRef Metadata Table and Declaration小节和File Metadata Table and Declaration小节得到验证。

The ModuleRef metadata table contains descriptors of other modules referenced in the current module.
The relevant managed modules are the other modules of the current assembly. In ILAsm, they should be
declared explicitly, and their declarations should be paired with File declarations

The File metadata table describes other files of the same assembly that are referenced in the current module.

ModuleRef表中的数据 总是要和File表中的数据配对, 说明了Module就是一个File。

Assembly是什么?

Assembly对外,是一个包含完整自我描述信息的部署单位。
Assembly对内,对应到一个或多个Module。
可以理解为Assembly对这些包含代码的Module文件进行来一个打包处理。这些Module里面会有个Prime Module包含Assembly的信息。

对于single-module Assembly,prime module既是module,也是Assembly。

对于multi-module Assembly,prime module是包含了Assembly元数据的module,它和其他的module共同组成了 这个Assembly.

综上,一个Assembly可能是

  • 一个Prime Module
  • 一个Prime Module + 多个Module

image

生成并解析一个multi-module assembly

生成 multi-module Assembly

我们可以根据 https://learn.microsoft.com/en-us/dotnet/framework/app-domains/build-multifile-assembly 链接中的 指令生成一个 multi-module的Assembly

这里我们可以通过下面指令,来生成 Stringer.netmodule 以及 Client.exe

csc /t:module Stringer.cs
csc Client.cs /addmodule:Stringer.netmodule

If this module contains a row in the Assembly table (that is, if this module ―holds the manifest‖) then there shall not be any row in the File table for this module

这里 Client.exeStringer.netmodule 合起来被称为程序集Assembly,其中Client.exe是prime module.

prime module 会比其他module 多出Assembly表, ExportedType表。 具体情况我们可以通过ILSpy打开对应的module查看其中的metadata。

用ILSpy,CFF Explorer工具查看

用ILSpy打开Client.exe,我们可以观察到Assembly表,这标志着当前module是该Assembly的prime module.

The .assembly directive declares the manifest and specifies to which assembly the current module belongs

由于当前文件是整个Assembly中的一个module,所以我们也可以观察到Module表。

The Module metadata table contains a single record that provides the identification of the current module.

接着,我们可以看到Client.exe的metadata里面还有ModuleRef,TypeDefsMemberRef表,这是因为Client.exe中引用了Stringer.netmodule中的Stringer类里的StringerMethod函数。

An entry is made into the MemberRef table whenever a reference is made in the CIL code to a method or field which is defined in another module or assembly. ——《Common Language Infrastructure》22.25

The ModuleRef metadata table contains descriptors of other modules referenced in the current module. The set of “other modules” includes both managed and unmanaged modules. ——《.Net IL Assembler》ModuleRef Metadata Table and Declaration

ModuleRef表中存储了当前Module引用的其他Module,如果是托管的module,那么就是同一Assembly下的托管Module。 如果是非托管Module,那么就是通过 DllImport 引入的native module.

TypeDefs and related metadata describe the types declared in the current module, whereas TypeRefs describe references to types that are declared somewhere else.

这里具体提一下 TypeRef 表的ResolutionScope列,因为等下源码会用到。
这里TypeRef表中的第一列ResolutionScope指示了Type Definition的位置,这一列是ResolutionScope类型的Coded Token
Tag位会被放置在最低位,Tag位站几位,RID就会左移几位。 查看ResolutionScopeTag可知,Tag这里占2位,

ResolutionScope (75): 4 referenced tables, tag size 2
Module 0
ModuleRef 1
AssemblyRef 2
TypeRef 3

并且,这里我们需要用CFF Explorer查看对应的表数据,

image

可以发现myStringer.Stringer类的ResolutionScope的值是9,也就是0b1001,

  • Tag:这里末2位是tag位,对应值0b01表示这是一个ModuleRef表中的索引。
  • RID:去掉末2位后剩下的值0b10,转化为十进制是2,表示ModuleRef表中的第2行数据。

image

这意味着 myStringer.Stringer 位于 Stringer.netmodule 这个文件中。

如果用ILSpy或者DotPeek查看,这里ResolutionScope的值会直接显示为对应ModuleRef或者AssemblyRef表中对应行的Token列的值。
image

mono运行Client.exe 并调试源码

mono源码中MonoAssembly对应Assembly,MonoImage对应着托管可执行文件,也就是managed module.

我们可以在mono中使用mono_domain_assembly_open加载Client.exe,然后调用mono_jit_exec运行程序集。

调试前,我们需要在mono_class_from_typeref_checked函数的MONO_RESOLUTION_SCOPE_MODULEREF case中下断点。

mono_class_from_typeref_checked函数用于加载当前module中TypeRef表中的类, MONO_RESOLUTION_SCOPE_MODULEREF对应了同一Assembly其他Module中的类的情况。

	case MONO_RESOLUTION_SCOPE_MODULEREF:
		module = mono_image_load_module_checked (image, idx, error); //这里下断点
		if (module)
			res = mono_class_from_name_checked (module, nspace, name, error);
		goto done;

mono_image_load_module_checked会去加载ModuleRef表中对应的索引的module文件,更准确的说是Managed Module

首先它会加载File表中的值,存储到valid_modules数组中,表示合法的托管Module.
这里这么处理是因为ModuleRef中引用的可能是托管Module也可能是native的module. 如果引用的是托管的Module,那么File表中也会插入一个同名的数据。

The relevant managed modules are the other modules of the current assembly. In ILAsm, they should be declared explicitly, and their declarations should be paired with File declarations ——《.Net IL Assembler》Chapter6 ModuleRef Metadata Table and Declaration

如果要加载的module出现在valid_modules中,也就是说,如果要加载的module是一个托管module,mono_image_load_module_checked才会接着调用 mono_image_open_a_lot_parameterized去加载它。

参考资料

build-multifile-assembly
.NET IL Assembler

Common Language Infrastructure

posted @ 2026-04-27 16:51  dewxin  阅读(2)  评论(0)    收藏  举报