Flier's Sky

天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西

导航

使用 Fusion API 控制 GAC

Posted on 2004-09-11 23:32  Flier Lu  阅读(2651)  评论(3编辑  收藏  举报

原文:http://www.blogcn.com/User8/flier_lu/index.html?id=3765092


    与传统 Windows 中目录存储定位,以及 COM 中注册表定位等方式不同,CLR 中使用了几级组件定位策略,以试图彻底解决 DLL Hell 的问题。其中 GAC 是 CLR 中代码共享的最重要的中心存储库,所有的强签名 (Strong Named) 配件 (Assembly) 都应该通过 GAC 来进行共享和版本管理。但遗憾的是 BCL 中并没有直接提供对 GAC 的控制和管理功能,而是通过一个名为 gacutil.exe 的命令行工具进行管理。麻烦的是这个界面并不友好的工具是不随 .NET Framework 发布版本分发的,必须在安装程序中自带安装。
    好在 CLR 提供了一个虽然没有写入正式文档,但通过 KB317540 半公开的 Fusion API 用以控制 GAC 等代码存储机制。

    这些接口和函数的定义文件在 .NET FX 1.1 中可在 SDK 的 Include/Fusion.h(.idl) 文件中找到,而在 FX 2.0 beta 中则只有 Include/Fusion.h,大概是因为 Fusion 以及完全合并到 mscorwks.dll 里面去了的原因吧。

    这套 API 主要分为三部分功能:Assembly 列表及信息的获取;Assembly 的安装、删除和信息查询;Assembly 安装引用信息的获取与设置。

    Assembly 列表及信息的获取功能是我们获得详细信息的最重要接口。

    首先,枚举 ASM_CACHE_FLAGS 中定义了缓存所在的位置:

typedef enum
{
    ASM_CACHE_ZAP            
= 0x1,
    ASM_CACHE_GAC            
= 0x2,
    ASM_CACHE_DOWNLOAD       
= 0x4
}
 ASM_CACHE_FLAGS;



    ZAP 是 ngen 生成本地代码的缓存;GAC 是全局强名字组件的缓冲;DOWNLOAD 则是 Internet 下载组件的缓存。可以使用 GetCachePath 函数获得这三种缓存的物理目录。
public class Fusion
  
{
    [Flags] 
public enum ASM_CACHE_FLAGS
    
{
      ASM_CACHE_ZAP            
= 0x1,
      ASM_CACHE_GAC            
= 0x2,
      ASM_CACHE_DOWNLOAD       
= 0x4
    }


    [DllImportAttribute(
"fusion.dll"[img]/images/wink.gif[/img]]
    
public static extern int GetCachePath(
      ASM_CACHE_FLAGS dwCacheFlags,
      [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwzCachePath,
      
ref uint pcchPath);
  }


  
public class FusionUtil : Fusion, IEnumerable
  
{
    
public static string GetCachePath(ASM_CACHE_FLAGS cache)
    
{
      
uint len = 0;

      
int hr = Fusion.GetCachePath(cache, nullref len);

      
if(hr == ComUtil.ERROR_INSUFFICIENT_BUFFER && len > 0)
      
{
        StringBuilder sb 
= new StringBuilder((int)len);

        ComUtil.ComCheck(Fusion.GetCachePath(cache, sb, 
ref len));

        
return sb.ToString();
      }

      
else
      
{
        
return "";
      }

    }

  }


    对每种缓存,都可以通过 CreateAssemblyCache 函数获取一个 Assembly 枚举器 IAssemblyEnum 接口实例,然后通过 IAssemblyEnum::GetNextAssembly 函数遍历所有容器中的 Assembly。
public class Fusion
  
{
    [ComImport, Guid(
"21b8916c-f28e-11d2-a473-00c04f8ef448"[img]/images/wink.gif[/img],
      InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
      
public interface IAssemblyEnum
    
{
      [PreserveSig]
      
int GetNextAssembly(
        
out IApplicationContext ppAppCtx, // Must be null.
        out IAssemblyName ppName,         // Pointer to a memory location that is to receive the interface pointer to the assembly name of the next assembly that is enumerated.
        uint dwFlags);                    // Must be zero

      [PreserveSig]
      
int Reset();

      [PreserveSig]
      
int Clone(out IAssemblyEnum ppEnum);
    }


    [DllImportAttribute(
"fusion.dll"[img]/images/wink.gif[/img]]
    
public static extern int CreateAssemblyEnum(
      
out IAssemblyEnum ppEnum,     // Pointer to a memory location that contains the IAssemblyEnum pointer
      IApplicationContext pAppCtx,  // Must be null. ???
      IAssemblyName pName,          // An assembly name that is used to filter the enumeration. Can be null to enumerate all assemblies in the GAC.
      ASM_CACHE_FLAGS dwFlags,      // Exactly one bit from the ASM_CACHE_FLAGS enumeration.
      int pvReserved);              // Must be NULL

  }


  
public class FusionUtil : Fusion, IEnumerable
  
{
    
private ArrayList _asms = new ArrayList(256);
    
private ASM_CACHE_FLAGS _cache = ASM_CACHE_FLAGS.ASM_CACHE_GAC;

    
public void Refresh(ASM_CACHE_FLAGS cache)
    
{
      IAssemblyEnum asmEnum;

      
this._cache = cache;

      ComUtil.ComCheck(CreateAssemblyEnum(
out asmEnum, nullnull, cache, 0));

      IApplicationContext appCtxt;
      IAssemblyName asmName;

      _asms.Clear();

      
while(ComUtil.SUCCEEDED(asmEnum.GetNextAssembly(out appCtxt, out asmName, 0)) && asmName != null)
      
{
        AssemblyName name 
= new AssemblyName(asmName, appCtxt);

        _asms.Add(name);
      }

    }


    
public void Refresh()
    
{
      Refresh(ASM_CACHE_FLAGS.ASM_CACHE_GAC);
    }

  }


    对每一个 Assembly 都可以通过 IAssemblyEnum::GetNextAssembly 函数获得表现其名称信息的 IAssemblyName,通过此接口可以进一步获取 Assembly 的细节信息,如名称、版本号、Culture、Public Key 等等。可以通过一个 AssemblyName 类将之封装起来。
public class AssemblyName
{
  
private IAssemblyName _name;
  
private IApplicationContext _ctxt;

  
public AssemblyName(IAssemblyName name, IApplicationContext ctxt)
  
{
    
this._name = name;
    
this._ctxt = ctxt;

    Properties 
= new PropertyCollection(this);
  }

  
public readonly PropertyCollection Properties;
}


    IAssemblyName 接口本身提供了一些常用信息的直接的获取方法,如 Assembly 名字和版本号信息:
public class AssemblyName
{
  
public Version Version
  
{
    
get
    
{
      
uint hi, low;
      ComUtil.ComCheck(_name.GetVersion(
out hi, out low));
      
return new System.Version((int)(hi >> 16), (int)(hi & 0xFFFF), (int)(low >> 16), (int)(low & 0xFFFF));
    }

  }

}


    不过更加常用的是通过 GetDisplayName 方法获取类似 "System.Configuration.IgnoreSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 这样格式的显式名称:
public class AssemblyName
{
  
protected string getDisplayName(ASM_DISPLAY_FLAGS flags)
  
{
    
uint len = 0;

    
int hr = _name.GetDisplayName(nullref len, flags);

    
if(hr == ComUtil.ERROR_INSUFFICIENT_BUFFER && len > 0)
    
{
      StringBuilder displayName 
= new StringBuilder((int)len);

      ComUtil.ComCheck(_name.GetDisplayName(displayName, 
ref len, flags));

      
return displayName.ToString();
    }

    
else
    
{
      
return "";
    }

  }

}


    标志 ASM_DISPLAY_FLAGS 定义了希望获得由哪些部分组成的显式名称
 [Flags] public enum ASM_DISPLAY_FLAGS
    
{
      ASM_DISPLAYF_VERSION                
= 0x1,  // Includes the version number as part of the display name.
      ASM_DISPLAYF_CULTURE                = 0x2,  // Includes the culture.
      ASM_DISPLAYF_PUBLIC_KEY_TOKEN       = 0x4,  // Includes the public key token.
      ASM_DISPLAYF_PUBLIC_KEY             = 0x8,  // Includes the public key.
      ASM_DISPLAYF_CUSTOM                 = 0x10// Includes the custom part of the assembly name.
      ASM_DISPLAYF_PROCESSORARCHITECTURE  = 0x20// Includes the processor architecture.
      ASM_DISPLAYF_LANGUAGEID             = 0x40// Includes the language ID.
      ASM_DISPLAYF_DEFAULT                = ASM_DISPLAYF_VERSION | ASM_DISPLAYF_CULTURE | ASM_DISPLAYF_PUBLIC_KEY_TOKEN
    }


    一般来说使用由名称、版本号、Culture和Public Key Token 组成的显式名称。

    如果希望获取或设置更细致的信息,则可以使用 GetProperty/SetProperty 方法。
public class Fusion
{
  ASM_NAME_PUBLIC_KEY,
  ASM_NAME_PUBLIC_KEY_TOKEN,
  ASM_NAME_HASH_VALUE,
  ASM_NAME_NAME,
  ASM_NAME_MAJOR_VERSION,
  ASM_NAME_MINOR_VERSION,
  ASM_NAME_BUILD_NUMBER,
  ASM_NAME_REVISION_NUMBER,
  ASM_NAME_CULTURE,
  ASM_NAME_PROCESSOR_ID_ARRAY,
  ASM_NAME_OSINFO_ARRAY,
  ASM_NAME_HASH_ALGID,
  ASM_NAME_ALIAS,
  ASM_NAME_CODEBASE_URL,
  ASM_NAME_CODEBASE_LASTMOD,
  ASM_NAME_NULL_PUBLIC_KEY,
  ASM_NAME_NULL_PUBLIC_KEY_TOKEN,
  ASM_NAME_CUSTOM,
  ASM_NAME_NULL_CUSTOM,
  ASM_NAME_MVID,
  ASM_NAME_MAX_PARAMS
}


public class AssemblyName
{
  
protected string getProperty(ASM_NAME id)
  
{
    
uint len = 0;

    
int hr = _name.GetProperty(id, IntPtr.Zero, ref len);

    
if(hr == ComUtil.ERROR_INSUFFICIENT_BUFFER && len > 0)
    
{
      IntPtr buf 
= Marshal.AllocCoTaskMem((int)len);

      ComUtil.ComCheck(_name.GetProperty(id, buf, 
ref len));

      
string str = Marshal.PtrToStringUni(buf);

      Marshal.FreeCoTaskMem(buf);

      
return str;
    }

    
else
    
{
      
return "";
    }

  }


  
protected byte[] getPropertyData(ASM_NAME id)
  
{
    
uint len = 0;

    
int hr = _name.GetProperty(id, IntPtr.Zero, ref len);

    
if(hr == ComUtil.ERROR_INSUFFICIENT_BUFFER && len > 0)
    
{
      IntPtr buf 
= Marshal.AllocCoTaskMem((int)len);

      ComUtil.ComCheck(_name.GetProperty(id, buf, 
ref len));

      
byte[] data = new byte[len];

      Marshal.Copy(buf, data, 
0, (int)len);

      Marshal.FreeCoTaskMem(buf);

      
return data;
    }

    
else
      
return new byte[0];
  }


  
public string Name
  
{
    
get
    
{
      
return getProperty(ASM_NAME.ASM_NAME_NAME);
    }

  }


  
public string Culture
  
{
    
get
    
{
      
return getProperty(ASM_NAME.ASM_NAME_CULTURE);
    }

  }


  
public Data PublicKey
  
{
    
get
    
{
      
return new Data(getPropertyData(ASM_NAME.ASM_NAME_PUBLIC_KEY));
    }

  }

}


    枚举值 ASM_NAME 用于指定要获取哪个数据,可以通过一个 Data 类和 PropertyCollection 类进一步包装直接对数据进行的访问。
public class AssemblyName
{
  
public class Data
  
{
    
private byte[] _data;

    
public Data(byte[] data)
    
{
      _data 
= data;
    }


    
public override string ToString()
    
{
      
return IntToHex(_data);
    }


    
public static string IntToHex(byte[] data)
    
{
      StringBuilder sb 
= new StringBuilder(data.Length * 2);

      
for(int i=0; i<data.Length; i++)
        sb.Append(data[i].ToString(
"X2"[img]/images/wink.gif[/img]);

      
return sb.ToString();
    }

  }


  
public class PropertyCollection
  
{
    
readonly AssemblyName _name;

    
internal PropertyCollection(AssemblyName name)
    
{
      _name 
= name;
    }


    
public Data this[AssemblyProperty id]
    
{
      
get
      
{
        
if((int)id >= (int)Fusion.ASM_NAME.ASM_NAME_MAX_PARAMS)
          
throw new IndexOutOfRangeException();

        
return new Data(_name.getPropertyData((ASM_NAME)id));
      }

    }


    
public int Count
    
{
      
get
      
{
        
return (int)ASM_NAME.ASM_NAME_MAX_PARAMS;
      }

    }

  }


  
public readonly PropertyCollection Properties;
}


    这样一来使用时只需要通过 Properties 属性的索引进行访问即可,如

asmName.Properties[AssemblyProperty.PublicKeyToken].ToString()
asmName.Properties[AssemblyProperty.Custom].ToString()

    除了不同缓存的 Assembly 列表获取以外,对 Assembly 的安装、卸载和信息查询功能也经常用到。可以通过 CreateAssemblyCache 函数获取一个 IAssemblyCache 接口实例,进而对缓存进行操作。
public class Fusion
{
  [ComImport, Guid(
"e707dcde-d1cd-11d2-bab9-00c04f8eceae"[img]/images/wink.gif[/img],
    InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    
public interface IAssemblyCache
  
{
    
//
  }


  [DllImportAttribute(
"fusion.dll"[img]/images/wink.gif[/img]]
  
public static extern int CreateAssemblyCache(
    
out IAssemblyCache ppAsmCache,
    
uint dwReserved);
}



    在安装 Assembly 时,需要通过 pszManifestFilePath 参数指定 Assembly 的 manifest 文件所在路径。对大多数 Assembly 来说,只有一个独立的 dll 包括了所有的信息和数据;但某些情况下,一个 Assembly 可能被编译为一个包含 manifest 信息的主 Module 辅以多个延迟获取的网络分发的辅助 Module。例如不同语言的资源文件 Module 可能独立存在,直到需要的时候才从网络上获取。
    此外还可用通过 dwFlags 参数指定安装的策略,如 IASSEMBLYCACHE_INSTALL_FLAG_REFRESH 表示如果安装文件较新则替换缓存中原有文件;而 IASSEMBLYCACHE_INSTALL_FLAG_FORCE_REFRESH 标记则不考虑版本号强制进行替换,对应于 gacutil 的 /f 参数。
    最后还可以使用 FUSION_INSTALL_REFERENCE 结构指定 GAC 中此 Assembly 所依赖的外部信息,但这种方式降低了 GAC 的重用性,不被推荐也很少用到,这儿暂且忽略。
public class Fusion
{
  [StructLayout(LayoutKind.Sequential, CharSet
=CharSet.Unicode)]
  
public struct FUSION_INSTALL_REFERENCE
  
{
    
public uint cbSize;
    
public uint dwFlags;
    
public Guid guidScheme; // contains one of the pre-defined guids.
    public string szIdentifier;  // unique identifier for app installing this  assembly.
    public string szNonCannonicalData;  // data is description; relevent to the guid above
  }


  
public enum ASM_INSTALL_FLAG
  
{
    IASSEMBLYCACHE_INSTALL_FLAG_REFRESH       
= 1,
    IASSEMBLYCACHE_INSTALL_FLAG_FORCE_REFRESH 
= 2
  }


  
public interface IAssemblyCache
  
{
    [PreserveSig]
    
int InstallAssembly(
      ASM_INSTALL_FLAG dwFlags,
      [MarshalAs(UnmanagedType.LPWStr)] 
string pszManifestFilePath,
      IntPtr pvReserved);
  }

}


public class FusionUtil : Fusion, IEnumerable
{
  
public void Install(string fileName)
  
{
    IAssemblyCache cache;

    ComUtil.ComCheck(Fusion.CreateAssemblyCache(
out cache, 0));

    ComUtil.ComCheck(cache.InstallAssembly(Fusion.ASM_INSTALL_FLAG.IASSEMBLYCACHE_INSTALL_FLAG_REFRESH, fileName, IntPtr.Zero));
  }

}


    卸载时则只需要给出 Assembly 的完整名称即可,注意是包括版本号等信息的名称。如果安装时指定了外部依赖信息,则必须给出同样信息。IAssemblyCache::UninstallAssembly 方法会返回一个状态信息 ASM_UNINSTALL_DISPOSITION,表示卸载成功与否。
public class Fusion
{
  
public enum ASM_UNINSTALL_DISPOSITION
  
{
    IASSEMBLYCACHE_UNINSTALL_DISPOSITION_UNINSTALLED            
= 1,
    IASSEMBLYCACHE_UNINSTALL_DISPOSITION_STILL_IN_USE           
= 2,
    IASSEMBLYCACHE_UNINSTALL_DISPOSITION_ALREADY_UNINSTALLED    
= 3,
    IASSEMBLYCACHE_UNINSTALL_DISPOSITION_DELETE_PENDING         
= 4,
    IASSEMBLYCACHE_UNINSTALL_DISPOSITION_HAS_INSTALL_REFERENCES 
= 5,
    IASSEMBLYCACHE_UNINSTALL_DISPOSITION_REFERENCE_NOT_FOUND    
= 6
  }


  
public interface IAssemblyCache
  
{
    [PreserveSig]
    
int UninstallAssembly(
      
uint dwFlags,
      [MarshalAs(UnmanagedType.LPWStr)] 
string pszAssemblyName,
      IntPtr pvReserved,
      
out ASM_UNINSTALL_DISPOSITION pulDisposition);
  }

}


public class FusionUtil : Fusion, IEnumerable
{
  
public ASM_UNINSTALL_DISPOSITION Uninstall(string assemblyName)
  
{
    IAssemblyCache cache;

    ComUtil.ComCheck(Fusion.CreateAssemblyCache(
out cache, 0));

    ASM_UNINSTALL_DISPOSITION disposition;

    ComUtil.ComCheck(cache.UninstallAssembly(
0, assemblyName, IntPtr.Zero, out disposition));

    
return disposition;
  }

}


    此外还可以通过 IAssemblyCache::QueryAssemblyInfo 方法查询某个 Assembly 是否被安装、缓存中的文件名、以及缓存中文件的大小等等信息。
public class Fusion
{
  
public enum QUERYASMINFO_FLAG
  
{
    QUERYASMINFO_FLAG_VALIDATE  
= 1,
    QUERYASMINFO_FLAG_GETSIZE   
= 2
  }


  [StructLayout(LayoutKind.Sequential)]
  
public struct ASSEMBLY_INFO
  
{
    
public uint cbAssemblyInfo; // size of this structure for future expansion
    public ASSEMBLYINFO_FLAG dwAssemblyFlags;
    
public ulong uliAssemblySizeInKB;
    
public IntPtr pszCurrentAssemblyPathBuf;
    
public uint   cchBuf; // size of path buf.
  }


  
public interface IAssemblyCache
  
{
    [PreserveSig]
    
int QueryAssemblyInfo(
      QUERYASMINFO_FLAG dwFlags,
      [MarshalAs(UnmanagedType.LPWStr)] 
string pszAssemblyName,
      
out ASSEMBLY_INFO pAsmInfo);
  }

}


public class FusionUtil : Fusion, IEnumerable
{
  
public bool Check(string assemblyName, out string msg)
  
{
    IAssemblyCache cache;

    ComUtil.ComCheck(Fusion.CreateAssemblyCache(
out cache, 0));

    ASSEMBLY_INFO info 
= new ASSEMBLY_INFO();

    info.cbAssemblyInfo 
= (uint)Marshal.SizeOf(info);
    info.cchBuf 
= 0;
    info.pszCurrentAssemblyPathBuf 
= IntPtr.Zero;

    
int hr = cache.QueryAssemblyInfo(QUERYASMINFO_FLAG.QUERYASMINFO_FLAG_VALIDATE, assemblyName, out info);

    
if(hr == ComUtil.ERROR_INSUFFICIENT_BUFFER && info.cchBuf > 0)
    
{
      info.pszCurrentAssemblyPathBuf 
= Marshal.AllocCoTaskMem((int)info.cchBuf);

      ComUtil.ComCheck(cache.QueryAssemblyInfo(
        QUERYASMINFO_FLAG.QUERYASMINFO_FLAG_VALIDATE 
| QUERYASMINFO_FLAG.QUERYASMINFO_FLAG_GETSIZE,
        assemblyName, 
out info));

      
string path = Marshal.PtrToStringUni(info.pszCurrentAssemblyPathBuf);

      Marshal.FreeCoTaskMem(info.pszCurrentAssemblyPathBuf);

      msg 
= string.Format("Assembly {0} ({1} KB) installed @ {2}",
        assemblyName, info.uliAssemblySizeInKB, path);

      
return true;
    }

    
else
    
{
      
try { Marshal.ThrowExceptionForHR(hr); msg = ""; } catch(Exception e) { msg = e.Message; }

      
return false;
    }

  }

}


    如果指定 QUERYASMINFO_FLAG_VALIDATE 参数则 fusion 会对缓存中文件进行验证;如果指定 QUERYASMINFO_FLAG_GETSIZE 则通过 uliAssemblySizeInKB 返回 Assembly 的大小。

    最后还可以通过 CreateInstallReferenceEnum 函数和 IInstallReferenceEnum 接口,浏览安装 Assembly 时给出的外部依赖信息;或者通过 IAssemblyCache::CreateAssemblyCacheItem 方法和 IAssemblyCacheItem 接口,控制缓存项目内容。不过这些高级功能一般也用不上,这儿就不再罗嗦了。

    在 .NET Framework 2.0 beta 中,Fusion API 做了不大的调整:对缓存类型增加了 ASM_CACHE_ROOT 标记,表示在对某个缓存类型进行操作时的影响范围;还增加了 PEKIND 枚举类型提供对多平台支持;Assembly 属性 ASM_NAME 也做了相应扩展,增加了对文件版本号、平台架构等等信息的支持:
public enum PEKIND
{
  peNone 
= 0,
peMSIL 
= 1,
peI386 
= 2,
peIA64 
= 3,
peAMD64 
= 4,
peInvalid 
= -1
}


public enum ASM_NAME
{
  
ASM_NAME_FILE_MAJOR_VERSION 
= ASM_NAME_MVID + 1,
ASM_NAME_FILE_MINOR_VERSION 
= ASM_NAME_FILE_MAJOR_VERSION + 1,
ASM_NAME_FILE_BUILD_NUMBER 
= ASM_NAME_FILE_MINOR_VERSION + 1,
ASM_NAME_FILE_REVISION_NUMBER 
= ASM_NAME_FILE_BUILD_NUMBER + 1,
ASM_NAME_RETARGET 
= ASM_NAME_FILE_REVISION_NUMBER + 1,
ASM_NAME_SIGNATURE_BLOB 
= ASM_NAME_RETARGET + 1,
ASM_NAME_CONFIG_MASK 
= ASM_NAME_SIGNATURE_BLOB + 1,
ASM_NAME_ARCHITECTURE 
= ASM_NAME_CONFIG_MASK + 1,
ASM_NAME_MAX_PARAMS 
= ASM_NAME_ARCHITECTURE + 1
}


    一个新增的有趣特性是支持对两个 Assembly 的比较,能够检查两个 Assembly 的相似性。
typedef
enum _tagAssemblyComparisonResult
{ ACR_Unknown = 0,
ACR_EquivalentFullMatch = ACR_Unknown + 1,
ACR_EquivalentWeakNamed = ACR_EquivalentFullMatch + 1,
ACR_EquivalentFXUnified = ACR_EquivalentWeakNamed + 1,
ACR_EquivalentUnified = ACR_EquivalentFXUnified + 1,
ACR_NonEquivalentVersion = ACR_EquivalentUnified + 1,
ACR_NonEquivalent = ACR_NonEquivalentVersion + 1,
ACR_EquivalentPartialMatch = ACR_NonEquivalent + 1,
ACR_EquivalentPartialWeakNamed = ACR_EquivalentPartialMatch + 1,
ACR_EquivalentPartialUnified = ACR_EquivalentPartialWeakNamed + 1,
ACR_EquivalentPartialFXUnified = ACR_EquivalentPartialUnified + 1,
ACR_NonEquivalentPartialVersion = ACR_EquivalentPartialFXUnified + 1
}  AssemblyComparisonResult;

STDAPI CompareAssemblyIdentity(LPCWSTR pwzAssemblyIdentity1, BOOL fUnified1, LPCWSTR pwzAssemblyIdentity2, BOOL fUnified2, BOOL *pfEquivalent, AssemblyComparisonResult *pResult);



完整的实现例子短期内可以从这里下载:::URL::http://flier.5i4k.net/GacUtilW.rar

btw: 感谢 Junfeng Zhang 在 Fusion 方面的强力支持