CLR via C# 2.4 将模块组合为程序集(下)
使用Visual Studio IDE为项目添加程序集引用
如果你使用Visual Studio IDE来创建项目,就必须为项目添加所引用的程序集。打开Solution Explorer,右键点项目,然后选择Add Reference,打开Add Reference对话框:
在列表中选择想要的程序集进行引用。如果程序集不在列表中,点击Browse选项卡,找到所需的程序集(包含清单的文件)。COM选项卡允许通过Visual Studio自动生成的托管代理类来使托管资源代码访问非托管的COM服务器。Projects选项卡允许当前项目引用同一解决方案里其他项目生成的程序集。Recent选项卡允许选择最近为别的项目添加的程序集。
要让你自己的程序集出现在.NET选项卡的列表中,需要在注册表中添加如下的子键:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\AssemblyFolders\MyLibNameMyLibName是我们创建的唯一的名字——Visual Studio不会显示该名称。创建了子键之后,修改其默认的字符串的值,使其指向一个包含程序集文件的目录路径(如C:\Program Files\MyLibPath)。使用HKEY_LOCAL_MACHINE可以为机器上的所有用户添加该程序集,使用HKEY_CURRENT_USER为特定用户添加程序集。
使用Assembly Linker
除了C#编译器,你还会使用Assembly Linker工具(AL.exe)来创建程序集。如果你希望创建由不同编译器(不支持类似于C#中/addmodule开关的功能)生成的模块组成的程序集,或者在生成模块时不知道程序的打包需求,这时Assembly Linker是十分有用的。你还可以使用AL.exe创建只包含资源的程序集——卫星(satellite)程序集,用于解决本地化问题。
AL.exe工具可以生成一个仅包含清单不包含其他模块的EXE或DLL PE文件。要了解AL.exe是如何工作的,可以更改一下JeffTypes.dll程序集的生成方式:
csc /t:module RUT.cscsc /t:module FUT.cs
al /out:JeffTypes.dll /t:library FUT.netmodule RUT.netmodule
结果如下:
在本例中,创建了两个单独的模块RUT.netmodule和FUT.netmodule。这两个模块都不是程序集,因为它们都不包含清单元数据表。接着创建了第三个文件:JeffTypes.dll,它是一个小的DLL PE文件(/t[arget]:library开关),不包含IL代码但包含清单元数据表,指明RUT.netmodule和FUT.netmodule是程序集的一部分。最终得到的程序集由三个文件组成:JeffTypes.dll、RUT.netmodule和FUT.netmodule。Assembly Linker无法将多个文件合并为一个。
AL.exe工具可以使用/t:exe或/t:winexe命令行开关生成CUI和GUI PE文件。但这很少使用,因为这意味着EXE PE文件必须包含足够的IL代码来调用其他模块中的方法。在调用AL.exe时,可以使用/main命令行开关来指定其他模块中的哪个方法将作为程序的入口点。下面的例子演示了如何使用/main命令行开关调用Assembly Linker。
csc /t:module /r:JeffTypes.dll Program.csal /out:Program.exe /t:exe /main:Program.Main Program.netmodule
第一行将Program.cs文件生成Program.netmodule文件。第二行生成一个包含清单元数据表的Program.exe PE文件。此外,由于使用了/main:Program.Main命令行开关,AL.exe还生成了一个小的全局函数,__EntryPoint。它的代码如下:
.method privatescope static void __EntryPoint$PST06000001() cil managed{
.entrypoint
// Code size 8 (0x8)
.maxstack 8
IL_0000: tail.
IL_0002: call void [.module 'Program.netmodule']Program::Main()
IL_0007: ret
} // end of method 'Global Functions'::__EntryPoint
如你所见,该代码简单调用了定义在Program.netmodule文件中的Program类型中的Main方法。AL.exe的/main开关并不常用,因为我们不会为应用程序创建一个(清单元数据表所在的PE文件不包含入口点的)程序集。
在随书代码中,作者创建了Ch02-3-BuildMoltiFileLibrary.bat文件,封装了创建多文件程序集所需的所有步骤。Ch02-4-AppUsingMultiFileLibrary项目将该batch文件作为预编译的命令行步骤进行调用。你可以查看该项目,来了解如何在Visual Studio中继承生成和引用多文件程序集。
在程序集中添加资源文件
使用AL.exe创建程序集时,可以使用/embed[resource]开关将资源文件添加到程序集中。该开关接受一个文件(任何文件),然后将该文件的内容嵌入到生成的PE文件中。清单中的ManifestResourceDef表将被修改以反映该资源的存在。
AL.exe同样支持/link[resource]开关,也接受一个包含资源的文件。但/link开关修改清单中的ManifestResourceDef表和FileDef表,来反映资源的存在,并标识程序集中的哪个文件包含该资源文件。资源文件不嵌入到程序集PE文件中,它仍然是独立的,必须随其他程序集文件一起打包和部署。
和AL.exe类似,CSC.exe也允许将资源组合到C#编译器生成的程序集中。C#编译器的/resource开关把指定资源文件嵌入到产生的程序集PE文件中,修改ManifestResourceDef表。编译器/linkresource开关将在ManifestResourceDef和FileDef清单表中添加条目,指向一个独立的资源文件。
最后要注意的是,我们可以将标准Win32资源嵌入到程序集中。只需在使用AL.exe或CSC.exe时使用/win32res开关来指明.res文件的路径名。此外,你可以在使用AL.exe或CSC.exe时使用/win32icon开关,并指明.ico文件的路径,将一个Win32图标(icon)资源快速便捷地嵌入到程序集中。使用Visual Studio来为程序集添加资源,在项目属性的Application选项卡中。将图标嵌入到程序集中,这样Windows Explorer可以将一个托管的可执行文件显示为一个图标。
注意,托管的程序集文件同样包含Win32清单资源信息。默认情况下,C#编译器自动生成该清单信息,可以使用/nowin32manifest开关关闭该功能。C#编译器生成的默认清单如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity version="1.0.0.0" name="MyApplication.app" /> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"> <security> <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"> <requestedExecutionLevel level="asInvoker" uiAccess="false"/> </requestedPrivileges> </security> </trustInfo> </assembly>
CLR via C# 2.4 将模块组合为程序集(下)