作者: Jim Wang 公众号: 巴博萨船长

摘要:Inno Setup 生成的安装包是如何在安装的过程中注册COM组件DLL文件的,.NET的DLL为什么需要注册?如何将.NET程序集安装到GAC中,这样做的目的又是什么?在这项任务的一开始,我也有类似的疑问,查找归纳后就有了这篇文章。

Abstract: How does the installation package generated by Inno Setup register COM component DLL files during installation? What is the difference between .NET DLL and general DLL registration?

作者: Jim Wang 公众号: 巴博萨船长

Inno Setup 注册 Windows 中的COM程序集DLL

Inno Setup在设计程序安装包的时候,如果所需安装的文件中有DLL文件,而这些件又需要注册,一般情况下最容易想到且便于实施的,就是在**[Run]**段使用诸如下列的代码:

1
2
3
4
Filename: regsvr32; Parameters: "/s "
""<FilePath>\demo1.dll"""; WorkingDir: {commonappdata}; StatusMsg: Registering DLLs

Filename: {#DotNetRegistToolPath}}\regasm.exe; Parameters: " ""<FilePath>\demo2.dll"" /codebase /verbose"; WorkingDir: "{commonappdata}"; StatusMsg: Registering DLLs; Flags: 64bit runhidden waituntilterminated;

代码中regsvr32regasm.exe都是用来在Windows系统下注册DLL文件的,需要知道的是,不是所有的DLL都需要在Windwos系统中注册的,需要注册的只有COM(Compoment Object Modal,组件对象模型)组件。所谓的注册就是向注册表的相应位置写入一些数据,该数据一般包含GUID(Global Unique Identity)与DLL文件的绝对路径,且两者间一一对应。这里的GUID可以理解为COM对象的统一入口,应用在使用该COM组件的时候,就是在使用这个GUID,而不需要关注该DLL文件具体在哪。

注册COM组件是为了什么呢?COM的最大特点就是:复用。注册的核心目的就是为了代码复用,在Windows中,我们可以把DLL文件存放在一些公共位置(例如C:Windows\System32文件夹中)这样也可以实现相同的目的,但是这写公共位置的DLL可能由于加载机制的问题被先使用,就容易出问题,判断加载的是自己想要的DLL也比较困难,在此列出Windwos系统查找并加载DLL的顺序,如下:

系统标准DLL查找顺序(非安全模式):

  1. 应用程序所在目录;
  2. 当前目录。GetCurrentDirectory返回的目录;
  3. 系统目录。GetSystemDirectory返回的目录,通常是系统盘\Windows\System32;
  4. 16位系统目录。该项只是为了向前兼容的处理,可以不考虑;
  5. Windows目录。GetWindowsDirectory返回的目录,通常是系统盘\Windows;
  6. 环境变量PATH中所有目录。

Regsvr32 是用于注册COM组件的,具体的使用参数和注册机制和DLL内部的与注册相关的函数不在这里赘述。

Regasm是.NET下注册COM组件的工具。因为Regsvr32不适用于注册.NET组件。可以理解为专为.NET设计的Regsvr32。

全局程序集缓存GAC

GAC的全称是Global Assembly Cache,主要目的是存放一些公共的程序集。这样程序就可以从GAC中取得并使用程序集,而不需要将这些拷贝到应用程序的执行目录下面。换句话说,如果没有GAC的话,每个.NET的WinForm程序的目录下都有一份C:\WINDOWS\Microsoft.NET\Framework\\System.Windows.Forms.dll的拷贝,这样既浪费存储空间也不利于升级和应用程序的版本控制。

在程序开发和测试阶段,可以使用.NET自带的工具gacutil.exe把程序集添加到GAC中和从GAC中删除出去,命令如下。在版本2.0的.NET中有一个名为Microsoft .NET Framework 2.0 Configuration的图形化界面GAC管理工具,也可以实现DLL在GAC中的安装与卸载,但该工具在之后的.NET版本中就不再存在。

1
2
gacutil /i <path>\sample.dll (参数 /i 是安装的意思 )
gacutil /u sample.dll (参数 /u 是移除,不需要路径,仅DLL文件名即可 )

需要注意的是,GAC中保存的都是强命名的程序集(Strong-named assemblies)。强命名的目的在于保持程序集的在系统中的唯一性。因为可能不同的公司会开发出具有相同名字的程序集,如果这样的程序集被复制到同一个目录内,最后一次安装的程序集会代替前面安装的,这就是著名的Windows “DLL Hell”问题。所以说,单单使用文件名,是不足以来区分程序集的,所以CLR(Common Language Runtime)就引入了一种新的程序集唯一标识机制,这就是所谓的强命名程序集。这样的程序集里会包含四个唯一标识:文件名(不带扩展名),版本号,语言文化信息(如果存在)和公有密钥,例如:

1
“MyType, Version=1.0.0.0, Culture=neutral, PublicKeyToken=af5779ac332fc045”

可Windows中可以在Powershell中输入如下命令来看产DLL的上述四个标识:

1
([system.reflection.assembly]::loadfile("MyType.dll")).FullName

至于如何创建强命名的.NET程序集,在这儿就不再详细介绍了。想提示的是,.NET附带的有一名为sn.exe的工具。该工具可以对程序集进行签名,也可以查看已签名的程序集的公有密钥。

Inno Setup 安装强命名.NET程序集到GAC中

Inno Setup 在程序安装过程中,可以使用类似在本文第一章节中介绍的添加一个**[Run]**字段的条目,通过使用gacutils工具,在程序安装完成后,在安装程序显示最终向导页面前,通过执行这个条目来实现强命名.NET程序集安装到GAC中。虽然这样可行,但在前面也说过,gacutils该工具在开发环境用起来还可以,如果到了生产环境,由于无法保证该工具在目标主机里也存在,所以这个方法失败风险很大。

另一个方法是使用名为gacinstall的标记(Flags),这样的标记常用在**[Files]**段里,需要注意的是Inno Setup在5.3.0-beta (2009-04-22)版和其以后的版本中才支持该标记,所以在使用标记时请检查Inno Setup的版本,以免出错。

使用该标记的时,可以在**[Files]添加如下条目内容。需要注意的是类似ignoreversion**标记的不应该在类似的条目中被使用,因为.NET的DLL应该是版本敏感的,不应该忽略版本的变化。

1
2
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
Source: "Demo.dll"; DestDir: "{app}"; StrongAssemblyName: "Demo, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A, ProcessorArchitecture=MSIL"; Flags: "gacinstall sharedfile uninsnosharedfileprompt"

StrongAssemblyName关键字后面的内容就是上文提到的强命名程序的四个唯一标识。sharefile标记用于更新相关注册表信息,切勿略去。uninsnosharedfileprompt标记是为了保证在卸载该共享文件时,如果其引用计数为零,则自动删除文件,而不需要询问用户。 该标记必须与sharefile标志结合使用才能生效。

补充内容

.NET 3.5 版本及之前版本 GAC 路径在: C:\windows\Assembly

.NET 4.0 版本及之后版本 GAC 路径在:C:\Windows\Microsoft.Net\version\

文章中介绍了很多背景知识,仅最后一部分是才是文章中心内容。这些背景知识是自己在完成该项任务的一开始具有的疑问,进而在茫茫文海中查阅,再收集整理成文的,这利于自己,也方便你我。如果你有问题或者不同的见解,欢迎关注我的微信公众号,然后留言讨论。


版权声明:
文章首发于 Jim Wang's blog , 转载文章请务必以超链接形式标明文章出处,作者信息及本版权声明。