原文:
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 结构。
上述函数和接口定义如下:
以下内容为程序代码:
public class Fusion { [ComImport, Guid("582dac66-e678-449f-aba6-6faaec8a9394"[img]/images/wink.gif[/img], InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IInstallReferenceItem { [PreserveSig] int GetReference( out IntPtr ppRefData, uint dwFlags, IntPtr pvReserved); }
[ComImport, Guid("56b1a988-7c0c-4aa2-8639-c3eb5a90226f"[img]/images/wink.gif[/img], InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IInstallReferenceEnum { [PreserveSig] int GetNextInstallReferenceItem( out IInstallReferenceItem ppRefItem, uint dwFlags, IntPtr pvReserved); }
public const string DLL_NAME = "fusion.dll";
[DllImportAttribute(DLL_NAME)] public static extern int CreateInstallReferenceEnum( out IInstallReferenceEnum ppRefEnum, IAssemblyName pName, uint dwFlags, IntPtr pvReserved); }
| |
使用方法比较简单,唯一需要注意的是 IInstallReferenceItem::GetReference 方法取得的是一个指向 FUSION_INSTALL_REFERENCE 结构的指针,因此需要通过 Marshal.PtrToStructure 强行将被其指向内存转换为托管结构。
以下内容为程序代码:
public class ReferenceCollection : IEnumerable { private readonly AssemblyName _name;
private ArrayList _refs;
public void Refresh() { if(_refs == null) _refs = new ArrayList(); else _refs.Clear();
Fusion.IInstallReferenceEnum refEnum;
ComUtil.ComCheck(Fusion.CreateInstallReferenceEnum(out refEnum, _name._name, 0, IntPtr.Zero));
Fusion.IInstallReferenceItem item;
while(ComUtil.SUCCEEDED(refEnum.GetNextInstallReferenceItem(out item, 0, IntPtr.Zero)) && item != null) { IntPtr pRef;
ComUtil.ComCheck(item.GetReference(out pRef, 0, IntPtr.Zero));
Fusion.FUSION_INSTALL_REFERENCE objRef = (Fusion.FUSION_INSTALL_REFERENCE) Marshal.PtrToStructure(pRef, typeof(Fusion.FUSION_INSTALL_REFERENCE));
_refs.Add(new InstallReference(objRef)); } }
| |
定义了引用数据的 FUSION_INSTALL_REFERENCE 结构,是 GAC 引用计数的核心数据所在。
dwFlags 字段表示此引用计数的类型,目前来说只用到一个必设的 ASSEMBLYINFO_FLAG_INSTALLED 标志;
guidScheme 字段内容是几个预定义 GUID 之一,表示此结构内容的意义,后面详细解释;
szIdentifier 字段则是系统级别的应用程序标识符;
szNonCannonicalData 字段则是应用程序级别的组件标识符;
以下内容为程序代码:
public class Fusion { public enum ASSEMBLYINFO_FLAG { ASSEMBLYINFO_FLAG_INSTALLED = 1, ASSEMBLYINFO_FLAG_PAYLOADRESIDENT = 2 }
public static readonly Guid FUSION_REFCOUNT_UNINSTALL_SUBKEY_GUID = new Guid("{8cedc215-ac4b-488b-93c0-a50a49cb2fb8}"[img]/images/wink.gif[/img]; public static readonly Guid FUSION_REFCOUNT_FILEPATH_GUID = new Guid("{b02f9d65-fb77-4f7a-afa5-b391309f11c9}"[img]/images/wink.gif[/img]; references when you remove this. public static readonly Guid FUSION_REFCOUNT_OPAQUE_STRING_GUID = new Guid("{2ec93463-b0c3-45e1-8364-327e96aea856}"[img]/images/wink.gif[/img]; public static readonly Guid FUSION_REFCOUNT_MSI_GUID = new Guid("{25df0fc1-7f97-4070-add7-4b13bbfd7cb8}"[img]/images/wink.gif[/img];
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] public struct FUSION_INSTALL_REFERENCE { public uint cbSize; // The size of the structure in bytes. public uint dwFlags; // Reserved, must be zero. public Guid guidScheme; // contains one of the pre-defined guids. The entity that adds the reference. public string szIdentifier; // unique identifier for app installing this assembly. public string szNonCannonicalData; // data is description; relevent to the guid above. A string that is only understood by the entity that adds the reference. The GAC only stores this string. } }
| |
当 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 指定引用数据。
以下内容为程序代码:
[ComImport, Guid("e707dcde-d1cd-11d2-bab9-00c04f8eceae"[img]/images/wink.gif[/img], InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IAssemblyCache { [PreserveSig] int UninstallAssembly( uint dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, IntPtr pRefData, out ASM_UNINSTALL_DISPOSITION pulDisposition);
//...
[PreserveSig] int InstallAssembly( ASM_INSTALL_FLAG dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszManifestFilePath, IntPtr pRefData); }
| |
使用时可以忽略此参数,也可以使用上述的某种模式来管理引用的生命周期。为了不造成 GAC 的膨胀,推荐所有手工安装都指定一种引用模式。使用方式如下:
以下内容为程序代码:
public class FusionUtil : ... { public void Install(string fileName, IntPtr pRef) { IAssemblyCache cache;
ComUtil.ComCheck(Fusion.CreateAssemblyCache(out cache, 0));
ComUtil.ComCheck(cache.InstallAssembly(Fusion.ASM_INSTALL_FLAG.IASSEMBLYCACHE_INSTALL_FLAG_REFRESH, fileName, pRef)); }
public void Install(string fileName) { Install(fileName, IntPtr.Zero); }
public void Install(string fileName, InstallReference objRef) { IntPtr pRef = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Fusion.FUSION_INSTALL_REFERENCE)));
Marshal.StructureToPtr((Fusion.FUSION_INSTALL_REFERENCE)objRef, pRef, false); try { Install(fileName, pRef); } finally { Marshal.DestroyStructure(pRef, typeof(Fusion.FUSION_INSTALL_REFERENCE));
Marshal.FreeCoTaskMem(pRef); } }
public UninstallDisposition Uninstall(string assemblyName, IntPtr pRef) { IAssemblyCache cache;
ComUtil.ComCheck(Fusion.CreateAssemblyCache(out cache, 0));
ASM_UNINSTALL_DISPOSITION disposition;
ComUtil.ComCheck(cache.UninstallAssembly(0, assemblyName, pRef, out disposition));
return (UninstallDisposition)disposition; }
public UninstallDisposition Uninstall(string assemblyName) { return Uninstall(assemblyName, IntPtr.Zero); }
public UninstallDisposition Uninstall(string assemblyName, InstallReference objRef) { IntPtr pRef = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Fusion.FUSION_INSTALL_REFERENCE)));
Marshal.StructureToPtr((Fusion.FUSION_INSTALL_REFERENCE)objRef, pRef, false); try { return Uninstall(assemblyName, pRef); } finally { Marshal.DestroyStructure(pRef, typeof(Fusion.FUSION_INSTALL_REFERENCE));
Marshal.FreeCoTaskMem(pRef); } } }
| |
完整的实现例子短期内可以从这里下载:
http://flier.5i4k.net/GacUtilW.rar btw: 感谢
Junfeng Zhang 在 Fusion 方面的强力支持
