Click here to Skip to main content
15,880,796 members
Articles / Desktop Programming / WPF

Depends4Net - Part 1

Rate me:
Please Sign up or sign in to vote.
4.94/5 (41 votes)
1 Sep 2011CPOL3 min read 71.6K   2.6K   69   33
"Dependency Walker" light for .NET using a separate AppDomain and the reflection-only context

Introduction

I’ve been looking for something like “Dependency Walker” for a while – something that simple to deploy. Currently Depends4Net allows me to view a tree of all dependent assemblies – even those not present on the system, or otherwise inaccessible to the rather simple assembly resolver currently implemented, in a manner similar to “Dependency Walker”.

A .NET 2.0 Windows Forms based version of the utility is available in the second part of this series. Also includes a preview of an Expression Dark like style for the WPF DataGrid.

Depends4NetFound600.png

While far from production quality, it’s still a useful little tool that allows me to figure out which assemblies are missing from a given installation. The code depends on the WPF Toolkit, included with Visual Studio 2010, and does not require any other framework to build. You can easily use this code to create a command line utility requiring nothing but a working .NET 4 installation.

To keep things interesting, I load the assemblies into a separate application domain, and this presents us with a few challenges. As the .NET Assembly class is [Serializable], accessing the assembly directly would actually load the assembly into the current domain, and hence defeat the whole purpose of creating a separate domain, so another type must be used to transfer information about the loaded assemblies back to the primary AppDomain. A bit of testing also seems to indicate that this gives us the correct version the .NET Framework libraries.

The class responsible for loading the assemblies is part of the primary assembly for the application, and as I really want to be able to load assemblies from any location, this requires a little trick.

C#
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;

Notice that the domainSetup.ApplicationBase is set to the BaseDirectory of the current domain – this allows appDomain.CreateInstanceAndUnwrap to locate the currently executing assembly and create an instance of the Loader class. At this point, the new AppDomain does not know where to find the assembly we want to inspect – so we pass the “real” ApplicationBase to loader.AddPath before calling loader.LoadAssembly.

The Loader

The Loader class is derived from MarshalByRefObject, so when we call loader.AddPath and loader.LoadAssembly the code is executed in our new AppDomain and not in the primary AppDomain. So when we implement loader.LoadAssembly

C#
public void LoadAssembly(string filename)
{
    if (File.Exists(filename))
    {
        AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoaded;
        try
        {
            InternalLoadAssemblyFrom(filename);
                    
        }
        finally
        {
            AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoaded;
        }
    }
}

AppDomain.CurrentDomain refers to the new AppDomain. By adding our OnAssemblyLoaded event handler to the AssemblyLoad event, we get notified whenever an assembly gets loaded into the domain. Since we are using Assembly.ReflectionOnlyLoadFrom and Assembly.ReflectionOnlyLoad to load assemblies, the event handler loads referenced assemblies into the AppDomain until all the resolvable assemblies are found, and we also add an AssemblyInfo element for each assembly we are unable to locate – as my primary reason for writing Depends4Net is to collect information about missing assemblies.

C#
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;
  }
 }
}

The XAML

In “App.xaml”, we pull in the “ExpressionDark.xaml” resource dictionary giving us a nice “expression” like user interface – without exactly exerting ourselves. This theme and a few others are available from as part of the WPF Toolkit on CodePlex.

XML
<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>

Building the Hierarchy

A careful look at how I build the hierarchy will reveal that each assembly is only represented by one AssemblyNode instance – even if present at several locations in the hierarchy. AddAssemblyNode is called recursively and takes a dictionary containing all the nodes created so far, this allows me to reuse existing nodes for assemblies that are referenced by multiple assemblies.

C#
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;
}

Concluding Remarks

The most interesting thing here is how I set up the paths for the new AppDomain and that I avoid instantiating the assemblies in the primary domain by inadvertently serializing them from the new AppDomain to the primary AppDomain.

History

  • 29th of August, 2011 – Initial posting

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Sea Surveillance AS
Norway Norway
Chief Architect - Sea Surveillance AS.

Specializing in integrated operations and high performance computing solutions.

I’ve been fooling around with computers since the early eighties, I’ve even done work on CP/M and MP/M.

Wrote my first “real” program on a BBC micro model B based on a series in a magazine at that time. It was fun and I got hooked on this thing called programming ...

A few Highlights:

  • High performance application server development
  • Model Driven Architecture and Code generators
  • Real-Time Distributed Solutions
  • C, C++, C#, Java, TSQL, PL/SQL, Delphi, ActionScript, Perl, Rexx
  • Microsoft SQL Server, Oracle RDBMS, IBM DB2, PostGreSQL
  • AMQP, Apache qpid, RabbitMQ, Microsoft Message Queuing, IBM WebSphereMQ, Oracle TuxidoMQ
  • Oracle WebLogic, IBM WebSphere
  • Corba, COM, DCE, WCF
  • AspenTech InfoPlus.21(IP21), OsiSoft PI


More information about what I do for a living can be found at: harlinn.com or LinkedIn

You can contact me at espen@harlinn.no

Comments and Discussions

 
Generalwaiting for your part 2 Pin
Southmountain25-May-16 9:13
Southmountain25-May-16 9:13 
GeneralRe: waiting for your part 2 Pin
Espen Harlinn25-May-16 9:23
professionalEspen Harlinn25-May-16 9:23 
QuestionAdd assembly platform? Pin
Lord of Scripts28-Jan-13 7:08
Lord of Scripts28-Jan-13 7:08 
AnswerRe: Add assembly platform? Pin
Lord of Scripts29-Jan-13 4:50
Lord of Scripts29-Jan-13 4:50 
AnswerRe: Add assembly platform? Pin
Southmountain25-May-16 9:12
Southmountain25-May-16 9:12 
QuestionVersion column is missing Pin
Lord of Scripts18-Jan-13 10:54
Lord of Scripts18-Jan-13 10:54 
GeneralMy vote of 5 Pin
Sam Gorman28-Sep-12 0:26
Sam Gorman28-Sep-12 0:26 
GeneralRe: My vote of 5 Pin
Espen Harlinn28-Sep-12 0:30
professionalEspen Harlinn28-Sep-12 0:30 
GeneralMy vote of 5 Pin
Filip D'haene6-Sep-11 12:07
Filip D'haene6-Sep-11 12:07 
GeneralRe: My vote of 5 Pin
Espen Harlinn6-Sep-11 12:18
professionalEspen Harlinn6-Sep-11 12:18 
GeneralMy vote of 5 Pin
Abhinav S6-Sep-11 7:08
Abhinav S6-Sep-11 7:08 
GeneralRe: My vote of 5 Pin
Espen Harlinn6-Sep-11 7:47
professionalEspen Harlinn6-Sep-11 7:47 
QuestionGreat stuff Pin
Sacha Barber5-Sep-11 22:30
Sacha Barber5-Sep-11 22:30 
AnswerRe: Great stuff Pin
Espen Harlinn5-Sep-11 23:48
professionalEspen Harlinn5-Sep-11 23:48 
GeneralMy vote of 5 Pin
Wendelius30-Aug-11 9:14
mentorWendelius30-Aug-11 9:14 
GeneralRe: My vote of 5 Pin
Espen Harlinn30-Aug-11 10:22
professionalEspen Harlinn30-Aug-11 10:22 
GeneralMy vote of 5 Pin
fjdiewornncalwe30-Aug-11 9:08
professionalfjdiewornncalwe30-Aug-11 9:08 
GeneralRe: My vote of 5 Pin
Espen Harlinn30-Aug-11 10:20
professionalEspen Harlinn30-Aug-11 10:20 
QuestionSuggestion Pin
WillemSe29-Aug-11 22:05
WillemSe29-Aug-11 22:05 
AnswerRe: Suggestion Pin
Espen Harlinn29-Aug-11 22:37
professionalEspen Harlinn29-Aug-11 22:37 
AnswerRe: Suggestion Pin
Espen Harlinn30-Aug-11 8:37
professionalEspen Harlinn30-Aug-11 8:37 
GeneralRe: Suggestion Pin
WillemSe30-Aug-11 20:47
WillemSe30-Aug-11 20:47 
GeneralRe: Suggestion Pin
Espen Harlinn30-Aug-11 21:47
professionalEspen Harlinn30-Aug-11 21:47 
GeneralGreat job - 5 Pin
Greg Cadmes29-Aug-11 9:59
Greg Cadmes29-Aug-11 9:59 
GeneralRe: Great job - 5 Pin
Espen Harlinn29-Aug-11 11:01
professionalEspen Harlinn29-Aug-11 11:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.