Depends4Net - 第一部分






4.94/5 (41投票s)
用于 .NET 的“依赖项查看器”精简版,使用独立的 AppDomain 和只反射上下文
引言
我一直在寻找类似“依赖项查看器”的东西 – 部署起来很简单。目前,Depends4Net 允许我以类似于“依赖项查看器”的方式查看所有依赖程序集的树形结构 – 即使是系统中不存在的程序集,或无法访问的程序集(对于目前实现的相当简单的程序集解析器来说)。
该实用程序的 .NET 2.0 Windows Forms 版本可在本系列的第二部分中找到。还包括了 WPF DataGrid
的 Expression Dark 风格的预览。

虽然离生产质量还差得很远,但它仍然是一个有用的工具,可以让我找出给定安装中缺少的程序集。代码依赖于 WPF Toolkit,包含在 Visual Studio 2010 中,不需要任何其他框架即可构建。您可以轻松地使用此代码创建只需要一个正常运行的 .NET 4 安装的命令行实用程序。
为了保持趣味性,我将程序集加载到单独的应用程序域中,这给我们带来了一些挑战。由于 .NET Assembly 类是 [Serializable]
的,直接访问程序集实际上会将程序集加载到当前域中,从而违背了创建单独域的全部目的,因此必须使用另一种类型来将有关已加载程序集的信息传回主 AppDomain
。一些测试似乎也表明这为我们提供了 .NET Framework 库的正确版本。
负责加载程序集的类是应用程序主程序集的一部分,并且由于我真的希望能够从任何位置加载程序集,因此这需要一个小技巧。
AppDomainSetup domainSetup = new AppDomainSetup();
domainSetup.ApplicationName = fileInfo.Name;
domainSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
appDomain = AppDomain.CreateDomain("ReflectionLoader", null, domainSetup);
Loader loader = (Loader)appDomain.CreateInstanceAndUnwrap
(Assembly.GetExecutingAssembly().FullName,
"Harlinn.Depends4Net.Utils.Assemblies.Loader");
loader.AddPath(fileInfo.DirectoryName);
loader.LoadAssembly(filename);
assemblies = loader.Assemblies;
rootAssembly = loader.RootAssembly;
请注意,domainSetup.ApplicationBase
设置为当前域的 BaseDirectory
– 这允许 appDomain.CreateInstanceAndUnwrap
找到当前正在执行的程序集并创建 Loader
类的实例。此时,新的 AppDomain
不知道在哪里可以找到我们想要检查的程序集 – 因此我们在调用 loader.LoadAssembly
之前将“真实”的 ApplicationBase
传递给 loader.AddPath
。
加载器
Loader
类派生自 MarshalByRefObject
,因此当我们调用 loader.AddPath
和 loader.LoadAssembly
时,代码将在我们的新 AppDomain
中执行,而不是在主 AppDomain
中执行。因此,当我们实现 loader.LoadAssembly
时
public void LoadAssembly(string filename)
{
if (File.Exists(filename))
{
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoaded;
try
{
InternalLoadAssemblyFrom(filename);
}
finally
{
AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoaded;
}
}
}
AppDomain.CurrentDomain
指的是新的 AppDomain
。通过将我们的 OnAssemblyLoaded
事件处理程序添加到 AssemblyLoad
事件,我们会在程序集加载到域中时收到通知。由于我们使用 Assembly.ReflectionOnlyLoadFrom
和 Assembly.ReflectionOnlyLoad
加载程序集,因此事件处理程序会将引用的程序集加载到 AppDomain
中,直到找到所有可解析的程序集,并且我们还为我们无法找到的每个程序集添加一个 AssemblyInfo
元素 – 因为我编写 Depends4Net 的主要原因是为了收集有关缺失程序集的信息。
void OnAssemblyLoaded(object sender, AssemblyLoadEventArgs args)
{
Assembly loadedAssembly = args.LoadedAssembly;
AssemblyName assemblyName = loadedAssembly.GetName();
AssemblyInfo assemblyInfo = new AssemblyInfo();
assemblyInfo.AssemblyName = assemblyName;
assemblyInfo.ManifestModuleName = loadedAssembly.ManifestModule.Name;
assemblyInfo.LoadedFromGlobalAssemblyCache = loadedAssembly.GlobalAssemblyCache;
assemblyInfo.Location = loadedAssembly.Location;
assemblyInfo.Found = true;
assemblies.Add(loadedAssembly.FullName, assemblyInfo);
if (currentReferencingAssembly != null)
{
currentReferencingAssembly.
ReferencedAssemblies.Add(loadedAssembly.FullName, assemblyInfo);
assemblyInfo.FirstLoadedBy = currentReferencingAssembly;
}
else
{
rootAssembly = assemblyInfo;
}
AssemblyName[] referencedAssemblies = args.LoadedAssembly.GetReferencedAssemblies();
if (referencedAssemblies.Length > 0)
{
AssemblyInfo previouslyReferencingAssembly = currentReferencingAssembly;
currentReferencingAssembly = assemblyInfo;
try
{
foreach (AssemblyName referencedAssemblyName in referencedAssemblies)
{
if (assemblies.ContainsKey(referencedAssemblyName.FullName) == false)
{
string fileName = resolver.Resolve(referencedAssemblyName);
if (InternalLoadAssemblyFrom(fileName) == false)
{
if (InternalLoadAssembly(referencedAssemblyName) == false)
{
AssemblyInfo referencedAssemblyInfo = new AssemblyInfo();
referencedAssemblyInfo.AssemblyName = referencedAssemblyName;
assemblies.Add(referencedAssemblyName.FullName, referencedAssemblyInfo);
currentReferencingAssembly.
ReferencedAssemblies.Add(referencedAssemblyName.FullName,
referencedAssemblyInfo);
}
}
}
else
{
AssemblyInfo referencedAssemblyInfo =
assemblies[referencedAssemblyName.FullName];
currentReferencingAssembly.
ReferencedAssemblies.Add(referencedAssemblyName.FullName,
referencedAssemblyInfo);
}
}
}
finally
{
currentReferencingAssembly = previouslyReferencingAssembly;
}
}
}
XAML
在“App.xaml”中,我们引入了“ExpressionDark.xaml”资源字典,为我们提供了一个不错的“expression”风格的用户界面 – 并且没有过度消耗。该主题和其他几个主题可从CodePlex 上的 WPF Toolkit中获得。
<Application x:Class="Harlinn.Depends4Net.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:types="clr-namespace:Harlinn.Depends4Net.Types"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary >
<ObjectDataProvider x:Key="DependsDataSource"
ObjectType="{x:Type types:Depends}">
<ObjectDataProvider.ConstructorParameters />
</ObjectDataProvider>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ExpressionDark.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
构建层次结构
仔细看看我是如何构建层次结构的,会发现每个程序集只由一个 AssemblyNode
实例表示 – 即使它存在于层次结构中的多个位置也是如此。 AddAssemblyNode
被递归调用,并接受一个包含到目前为止创建的所有节点的字典,这允许我重用现有节点以用于被多个程序集引用的程序集。
private AssemblyNode AddAssemblyNode(AssemblyNode parent,
Dictionary<string, AssemblyNode> assemblyNodes, AssemblyInfo assemblyInfo)
{
AssemblyNode result = null;
if (assemblyNodes.ContainsKey(assemblyInfo.AssemblyName.FullName))
{
result = assemblyNodes[assemblyInfo.AssemblyName.FullName];
if (parent != null)
{
parent.ReferencedAssemblies.Add(result);
}
}
else
{
result = new AssemblyNode();
result.AssignFromAssemblyInfo(assemblyInfo);
assemblyNodes.Add(assemblyInfo.AssemblyName.FullName, result);
if (parent != null)
{
parent.ReferencedAssemblies.Add(result);
}
Dictionary<string, AssemblyInfo>.ValueCollection referencedAssemblyCollection =
assemblyInfo.ReferencedAssemblies.Values;
List<AssemblyInfo> referencedAssemblies = new List<AssemblyInfo>();
referencedAssemblies.AddRange(referencedAssemblyCollection);
referencedAssemblies.Sort(new AssemblyInfoComparer());
foreach (AssemblyInfo referencedAssemblyInfo in referencedAssemblies)
{
AddAssemblyNode(result, assemblyNodes, referencedAssemblyInfo);
}
}
return result;
}
结论
这里最有趣的事情是我如何为新的 AppDomain
设置路径,以及我如何避免在主域中实例化程序集,无意中将它们从新的 AppDomain
序列化到主 AppDomain
。
历史
- 2011 年 8 月 29 日 – 初次发布