原文:http://www.blogcn.com/User8/flier_lu/index.html?id=4046367
在传统的 Windows 环境中,造成 DLL Hell 灾难的因素,除了不同版本 DLL 可以在不同目录下共存且被载入的优先顺序不可知以外,还有一大问题就是在卸载程序时如何对待程序使用到的公共 DLL。如果卸载程序顺手删掉放在 Windows/System32 目录下的自己用到的公共组件,则可能造成其他以来于此组件的程序无法正常使用;但如果置之不理,长此以往必然会造成 Windows 系统目录的无限制膨胀。对象我这样喜欢乱装软件的人来说,硬盘上 Windows 系统的生命期很大程度上取决于系统目录下垃圾的膨胀速度,呵呵。一般使用半年或者更长时间,Windows 就会膨胀到不可接受的地步,也就意味着痛苦的重装系统又要开始了。:P
好在 CLR 在设计时就重复考虑到了这类问题,为 GAC 提供了基于引用计数的垃圾组件清扫机制。每个强签名组件在安装到 GAC 的时候,都可以指定一个引用数据;而在删除时,也将给出相同的引用数据,否则无法正常删除;同时 GAC 可以根据引用数据的内容判断某个组件是否仍然被使用,在合适的时候清除不被使用的垃圾组件。而这一切都归功于 GAC 的引用计数机制,Junfeng Zhang 在 GAC Assembly Trace Reference 一文中详细介绍了这一机制。
而我在前端时间的一片文章《使用 Fusion API 控制 GAC》里面曾经做了错误的介绍,实在是当时没有很好理解这一概念。:P 再次感谢 Junfeng Zhang 的帮助,呵呵。
在实现上,GAC 的 Fusion API 提供了 CreateInstallReferenceEnum 函数,能够获取针对某个 Assembly 的引用枚举器接口 IInstallReferenceEnum,进而通过其 IInstallReferenceEnum::GetNextInstallReferenceItem 方法获取引用项接口 IInstallReferenceItem,最终调用 IInstallReferenceItem::GetReference 方法获得引用数据 FUSION_INSTALL_REFERENCE 结构。
上述函数和接口定义如下:
使用方法比较简单,唯一需要注意的是 IInstallReferenceItem::GetReference 方法取得的是一个指向 FUSION_INSTALL_REFERENCE 结构的指针,因此需要通过 Marshal.PtrToStructure 强行将被其指向内存转换为托管结构。
定义了引用数据的 FUSION_INSTALL_REFERENCE 结构,是 GAC 引用计数的核心数据所在。
dwFlags 字段表示此引用计数的类型,目前来说只用到一个必设的 ASSEMBLYINFO_FLAG_INSTALLED 标志;
guidScheme 字段内容是几个预定义 GUID 之一,表示此结构内容的意义,后面详细解释;
szIdentifier 字段则是系统级别的应用程序标识符;
szNonCannonicalData 字段则是应用程序级别的组件标识符;
当 guidScheme 内容为 FUSION_REFCOUNT_MSI_GUID 时,表示此 Assembly 是通过 MSI 安装并进行维护的,其他字段内容 szIdentifier = "MSI", szNonCannonicalData = "Windows Installer"。通过 gacutil /lr 命令我们可以发现绝大多数 .NET Framework 自带的 Assembly 都是通过这种方式安装的。不过在后面要提到的手工安装中并不能使用这个为 MSI 内置的 Scheme。
当 guidScheme 内容为 FUSION_REFCOUNT_UNINSTALL_SUBKEY_GUID 时,szIdentifier 内容字符串被认为是系统添加删除程序面板中项目的 ID。如果 GAC 工具没有在注册表键 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall 下发现相同的 ID,则可以认为此 Assembly 的引用者已经不存在。
当 guidScheme 内容为 FUSION_REFCOUNT_FILEPATH_GUID 时,szIdentifier 内容字符串被认为是一个文件名。如果 GAC 工具发现此文件不存在,则可以认为此 Assembly 的引用者已经不存在。
最后当 guidScheme 内容为 FUSION_REFCOUNT_OPAQUE_STRING_GUID 时,Assemlby 的安装和引用者可以任意为 szIdentifier 和 szNonCannonicalData 赋值,自行维护 Assembly 的引用生命周期。
在安装和卸载 Assembly 时,都有一个参数可以显式参数 pRefData 指定引用数据。
使用时可以忽略此参数,也可以使用上述的某种模式来管理引用的生命周期。为了不造成 GAC 的膨胀,推荐所有手工安装都指定一种引用模式。使用方式如下:
完整的实现例子短期内可以从这里下载:http://flier.5i4k.net/GacUtilW.rar
btw: 感谢 Junfeng Zhang 在 Fusion 方面的强力支持
在传统的 Windows 环境中,造成 DLL Hell 灾难的因素,除了不同版本 DLL 可以在不同目录下共存且被载入的优先顺序不可知以外,还有一大问题就是在卸载程序时如何对待程序使用到的公共 DLL。如果卸载程序顺手删掉放在 Windows/System32 目录下的自己用到的公共组件,则可能造成其他以来于此组件的程序无法正常使用;但如果置之不理,长此以往必然会造成 Windows 系统目录的无限制膨胀。对象我这样喜欢乱装软件的人来说,硬盘上 Windows 系统的生命期很大程度上取决于系统目录下垃圾的膨胀速度,呵呵。一般使用半年或者更长时间,Windows 就会膨胀到不可接受的地步,也就意味着痛苦的重装系统又要开始了。:P
好在 CLR 在设计时就重复考虑到了这类问题,为 GAC 提供了基于引用计数的垃圾组件清扫机制。每个强签名组件在安装到 GAC 的时候,都可以指定一个引用数据;而在删除时,也将给出相同的引用数据,否则无法正常删除;同时 GAC 可以根据引用数据的内容判断某个组件是否仍然被使用,在合适的时候清除不被使用的垃圾组件。而这一切都归功于 GAC 的引用计数机制,Junfeng Zhang 在 GAC Assembly Trace Reference 一文中详细介绍了这一机制。
而我在前端时间的一片文章《使用 Fusion API 控制 GAC》里面曾经做了错误的介绍,实在是当时没有很好理解这一概念。:P 再次感谢 Junfeng Zhang 的帮助,呵呵。
在实现上,GAC 的 Fusion API 提供了 CreateInstallReferenceEnum 函数,能够获取针对某个 Assembly 的引用枚举器接口 IInstallReferenceEnum,进而通过其 IInstallReferenceEnum::GetNextInstallReferenceItem 方法获取引用项接口 IInstallReferenceItem,最终调用 IInstallReferenceItem::GetReference 方法获得引用数据 FUSION_INSTALL_REFERENCE 结构。
上述函数和接口定义如下:
|
使用方法比较简单,唯一需要注意的是 IInstallReferenceItem::GetReference 方法取得的是一个指向 FUSION_INSTALL_REFERENCE 结构的指针,因此需要通过 Marshal.PtrToStructure 强行将被其指向内存转换为托管结构。
|
定义了引用数据的 FUSION_INSTALL_REFERENCE 结构,是 GAC 引用计数的核心数据所在。
dwFlags 字段表示此引用计数的类型,目前来说只用到一个必设的 ASSEMBLYINFO_FLAG_INSTALLED 标志;
guidScheme 字段内容是几个预定义 GUID 之一,表示此结构内容的意义,后面详细解释;
szIdentifier 字段则是系统级别的应用程序标识符;
szNonCannonicalData 字段则是应用程序级别的组件标识符;
|
当 guidScheme 内容为 FUSION_REFCOUNT_MSI_GUID 时,表示此 Assembly 是通过 MSI 安装并进行维护的,其他字段内容 szIdentifier = "MSI", szNonCannonicalData = "Windows Installer"。通过 gacutil /lr 命令我们可以发现绝大多数 .NET Framework 自带的 Assembly 都是通过这种方式安装的。不过在后面要提到的手工安装中并不能使用这个为 MSI 内置的 Scheme。
当 guidScheme 内容为 FUSION_REFCOUNT_UNINSTALL_SUBKEY_GUID 时,szIdentifier 内容字符串被认为是系统添加删除程序面板中项目的 ID。如果 GAC 工具没有在注册表键 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall 下发现相同的 ID,则可以认为此 Assembly 的引用者已经不存在。
当 guidScheme 内容为 FUSION_REFCOUNT_FILEPATH_GUID 时,szIdentifier 内容字符串被认为是一个文件名。如果 GAC 工具发现此文件不存在,则可以认为此 Assembly 的引用者已经不存在。
最后当 guidScheme 内容为 FUSION_REFCOUNT_OPAQUE_STRING_GUID 时,Assemlby 的安装和引用者可以任意为 szIdentifier 和 szNonCannonicalData 赋值,自行维护 Assembly 的引用生命周期。
在安装和卸载 Assembly 时,都有一个参数可以显式参数 pRefData 指定引用数据。
|
使用时可以忽略此参数,也可以使用上述的某种模式来管理引用的生命周期。为了不造成 GAC 的膨胀,推荐所有手工安装都指定一种引用模式。使用方式如下:
|
完整的实现例子短期内可以从这里下载:http://flier.5i4k.net/GacUtilW.rar
btw: 感谢 Junfeng Zhang 在 Fusion 方面的强力支持