65.9K
CodeProject 正在变化。 阅读更多。
Home

Depends4Net - 第一部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (41投票s)

2011 年 8 月 28 日

CPOL

3分钟阅读

viewsIcon

75765

downloadIcon

2652

用于 .NET 的“依赖项查看器”精简版,使用独立的 AppDomain 和只反射上下文

引言

我一直在寻找类似“依赖项查看器”的东西 – 部署起来很简单。目前,Depends4Net 允许我以类似于“依赖项查看器”的方式查看所有依赖程序集的树形结构 – 即使是系统中不存在的程序集,或无法访问的程序集(对于目前实现的相当简单的程序集解析器来说)。

该实用程序的 .NET 2.0 Windows Forms 版本可在本系列的第二部分中找到。还包括了 WPF DataGrid 的 Expression Dark 风格的预览。

Depends4NetFound600.png

虽然离生产质量还差得很远,但它仍然是一个有用的工具,可以让我找出给定安装中缺少的程序集。代码依赖于 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.AddPathloader.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.ReflectionOnlyLoadFromAssembly.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 日 – 初次发布
© . All rights reserved.