托管 Fusion






4.96/5 (20投票s)
Managed Fusion - 用于查看和操作全局程序集缓存的托管 API
引言
在本文中,我将介绍一个提供与gacutil类似功能的API,它利用了Fusion。
在我开发一个名为AppStract的个人项目时,我遇到了需要从我的代码中操作全局程序集缓存(简称GAC)的需求,并且不需要使用安装程序。在研究如何做到这一点的方法时,我得出的结论是,关于这个主题的信息非常少。关于GAC的作用有非常详细的文档,但关于如何操作它的文档却很少。本文旨在改变这种情况,并提供一个API,使在.NET Framework中操作GAC更加方便。
但请注意,MSDN关于Fusion有以下说明:
警告:请勿在您的应用程序中使用这些API来执行程序集绑定或测试程序集的存在性,或进行其他运行时、开发或设计时操作。只有管理工具和安装程序必须使用这些API。如果您使用GAC,这会直接使您的应用程序面临程序集绑定脆弱性,或者可能导致您的应用程序在未来版本的.NET Framework上无法正常工作。
虽然第一行所描述的功能基本上就是这个API提供的功能,但您不应轻率地使用它。自己操作GAC很少有正当理由,并可能在您的应用程序中引入安全风险。
要求
托管Fusion具有以下需要满足的要求:
- .NET Framework版本 3.5
- 进程需要足够的权限(大多数情况下是管理员权限)
- 只有强签名程序集才能安装到GAC中
Using the Code
该API位于System.Reflection.GAC
命名空间中,并公开了以下类型及其public
方法:
AssemblyCache
- 这是API的主要入口类型。在创建此类型的实例时,需要一个InstallerDescription
。InstallerDescription
- 这个类使API能够在操作GAC时使用引用计数,就像MSI使用引用计数一样。这个类型也可以看作是FUSION_INSTALL_REFERENCE
的托管对应物。InstallerType
-InstallerDescription
的一个属性,定义了安装应用程序的类型。InstallBehaviour
- 定义了程序集在GAC中安装的三个可能规则。UninstallDisposition
- 表示尝试从全局程序集缓存中卸载程序集的结果。
如前所述,AssemblyCache
是此API的主要类型;它公开了与GAC交互的所有可能方式。它实现了IEnumerable<AssemblyName>
接口,并公开了四个public
方法:
AssemblyCache.InstallAssembly(AssemblyName, InstallBehaviour)
AssemblyCache.UninstallAssembly(AssemblyName)
AssemblyCache.IsInstalled(AssemblyName)
AssemblyCache.GetReferences(AssemblyName)
这些方法的名称应该不言自明。尽管对于InstallAssembly
,需要做一个重要的附注:AssemblyName
参数需要为其CodeBase
属性指定一个有效值;本质上,这意味着该属性需要指向一个强签名程序集,并且该程序集已保存在文件系统中。
对于InstallerDescription
,API定义了一些public
属性,以及三个static
方法,而不是一个public
构造函数:
InstallerDescription.CreateForInstaller()
- 为安装程序创建一个描述符;此安装程序应始终是“添加/删除程序”列表中出现的应用程序。InstallerDescription.CreateForFile()
- 为由文件系统中的文件表示的应用程序创建一个描述符。InstallerDescription.CreateForOpaqueString()
- 为仅由不透明string
表示的应用程序创建一个描述符。
使用这些方法中的任何一种是获取InstallerDescription
实例的唯一方法。所有方法都接受两个string
参数:第一个基本上是一个描述,而第二个string
用作应用程序的标识符。您可能已经注意到,InstallerType enum
定义了五个值,而上述方法只使用了三种类型。这是因为WindowsInstaller
和OperatingSystem
值是MSI和Windows操作系统保留的,因此任何其他应用程序都永远不应使用它们。
查看GAC
AssemblyCache
使您能够枚举全局程序集缓存中安装的所有程序集,枚举程序集的所有引用,并验证程序集是否已安装在GAC中。所有这些操作都不需要管理员权限。
以下是一个基本示例,演示如何枚举GAC并枚举已安装程序集的所有引用,同时将结果打印到控制台窗口:
using System;
using System.Reflection.GAC;
namespace FusionTestApplication
{
class Program
{
private const string _rndm = "someRandomString";
static void Main()
{
Console.WriteLine("This program will enumerate all " +
"assemblies in the GAC, and their references.");
Console.WriteLine("For displaying purposes it is recommended " +
"to widen your console window.");
Console.WriteLine("Press enter to continue...");
Console.ReadLine();
InstallerDescription description =
InstallerDescription.CreateForOpaqueString("Enumerating the GAC", _rndm);
AssemblyCache cache = new AssemblyCache(description);
// Enumerate all assemblies in the GAC.
foreach (var assembly in cache)
{
Console.WriteLine(assembly.FullName);
foreach (var rfr in cache.GetReferences(assembly))
Console.WriteLine("\t" + rfr);
}
Console.ReadLine();
}
}
}
以下是如何验证程序集是否已安装到GAC的示例:
using System;
using System.Reflection.GAC;
namespace FusionTestApplication
{
class Program
{
private const string _rndm = "someRandomString";
static void Main()
{
InstallerDescription description =
InstallerDescription.CreateForOpaqueString(
"Verifying the existence of assemblies", _rndm);
AssemblyCache cache = new AssemblyCache(description);
while (true)
{
Console.WriteLine("Specify an assembly name:");
string value = Console.ReadLine();
bool isInstalled = cache.IsInstalled(new AssemblyName(value));
Console.WriteLine("IsInstalled: " + isInstalled + Environment.NewLine);
}
}
}
}
操作GAC
请记住,操作GAC始终需要管理员权限。
以下是如何将名为“someTestAssembly.dll”的程序集安装到全局程序集缓存中的示例:
// First create an InstallerDescription. For this example
// we'll use the executable of the current process as a reference
InstallerDescription installer = InstallerDescription.CreateForFile(
"Test application for Managed Fusion",
Process.GetCurrentProcess().MainModule.FileName);
// Then we can create an AssemblyCache
AssemblyCache gac = new AssemblyCache(installer);
// Now load the assembly, verify it's strong named,
// and get its full AssemblyName
Assembly assembly = Assembly.ReflectionOnlyLoadFrom(file.FileName);
if (assembly.GetName().GetPublicKey().Length != 0)
{
// Calling Assembly.GetName() automatically assigns AssemblyName.CodeBase
AssemblyName assemblyName = assembly.GetName();
gac.InstallAssembly(assembly, InstallBehaviour.Default);
}
以下是如何从全局程序集缓存中卸载名为“someTestAssembly.dll”的程序集的示例:
// Create the same InstallerDescription
// as the one used when installing the assembly.
InstallerDescription installer = InstallerDescription.CreateForFile(
"Test application for Managed Fusion",
Process.GetCurrentProcess().MainModule.FileName);
// Initialize an AssemblyCache
AssemblyCache gac = new AssemblyCache(installer);
// Construct an AssemblyName. Remember: the more specific the better.
AssemblyName assemblyName = new AssemblyName("someTestAssembly.dll, Version=1.0.0.0");
UninstallDisposition result = gac.UninstallAssembly(assembly);
关注点
Fusion和程序集名称
出于某种原因,Fusion无法接受完全指定的程序集名称,例如:
myAssembly, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0038abc9deabfle5
经过一些测试,似乎问题是由PublicKeyToken
属性引起的。作为该问题的初步解决方法,已向AssemblyName
添加了一个扩展方法,该方法返回一个不包含PublicKeyToken
的完全指定的程序集名称。
public static string GetFusionCompatibleFullName(this AssemblyName assemblyName)
{
return assemblyName.Name
+ (assemblyName.Version == null? "" : ", Version=" + assemblyName.Version)
+ (assemblyName.CultureInfo == null ||
string.IsNullOrEmpty(assemblyName.CultureInfo.Name) ? "" : ",
Culture=" + assemblyName.CultureInfo.Name);
}
Marshal.SizeOf()
一个花费了我大量时间的问题是Marshal.SizeOf()
为FusionInstallReference
结构返回了不正确的值。当该方法在struct
内部被调用时,它返回32字节的大小。设置FusionInstallReference._size
变量需要此大小,而_size
是为将struct
封送到Fusion API所必需的变量。经过一些测试,我得出结论,当Marshal.SizeOf()
从另一个类或struct
调用时,它返回正确的值,即40字节。现在,此值已硬编码在FusionInstallReference
的构造函数中。
寻求帮助
请报告您遇到的任何问题(错误、未记录的异常等),以及您可能找到的任何可能的增强或解决方案。
参考文献
历史
- 2009/11/14 - 初始发布
- 2009/11/17 - 更新了源代码