Mark Gilbert's Blog

Science and technology, served light and fluffy.

Who’s on first? AppDomains, Pre-loaded Assemblies, and CreateInstanceAndUnwrap

One of my home development projects involves loading and unloading .NET assemblies dynamically.  I say “assemblies”, but in reality it’s “sets of assemblies”, or “modules”.  Each module is comprised of the “main” assembly/class that implements my custom interface, and zero, one, or more referenced assemblies.  Each module gets loaded into it’s own App Domain, a fresh one created by the application.

When the application starts up, it looks for a list of subfolders in a path defined in the application’s configuration file.  Each subfolder represents one module, containing all of the DLLs, config files, and so on needed for that module.  The application creates a new app domain for each of these modules, and loads all of the DLLs for that module into that new app domain.  That allows me to later unload the entire app domain, and as a result unload one complete module (.NET doesn’t allow you to unload a single DLL, you have to unload the entire app domain that it is sitting in).

When it comes time to execute code within one of those modules, I need to find the right class in the right .NET assembly to instantiate.  Since a module could contain more than one .NET assemblies, I look for the assembly in the app domain that implements my interface.  Here’s where things got tricky.

My original scheme was to check every type in each assembly in the module subfolder to find the one that implemented my custom interface.  This last piece of magic is accomplished via the following:

If (GetType(IMyInterface).IsAssignableFrom(CurrentType)) Then
End If

Where “CurrentType” is the type from the current assembly that I’m evaluating, and IMyInterface is just that – my custom interface.  I found this gem here:

Now, this If statement was executing in a loop, looking at every assembly, and every type in each assembly.  Every assembly in that folder was loaded into the app domain, but I had a counter going so that once I found the right class I would save off that index for later.  This index would be used to pull out the correct assembly in the app domain’s collection:

Me._TempObject = Me._ModulesAppDomain.GetAssemblies(Me._MainAssemblyIndex + 1).CreateInstance(Me._ModuleClassName)

Where “_MainAssemblyIndex” was the saved index, and “_ModuleClassName” is the name of the class that I found when I was pawing through the DLLs originally.  The _TempObject object is declared as of type IMyInterface, so once I have the instantiated class I can do something like “Me._TempObject.ExecuteCommand(…)”.

You’ll notice I wasn’t using the index of “Me._MainAssemblyIndex”, but rather “Me._MainAssemblyIndex + 1”.  That offset was to accommodate for another assembly that seemed to be loaded into every app domain, ahead of anything that I loaded into it – mscorlib.  In all of my testing I found this was always the 0th element, so my counter would always be off by one.  I was a little worried about just offsetting the index, but it always seemed to work.  I dropped in a TODO to come back to later, and went on my merry way.

Last week I had a run-in with reality: I found my offset was no longer working.

Something I did to the application was now causing yet another DLL to be pre-loaded into the app domains, as the 1st element – Microsoft.VisualStudio.HostingProcess.Utilities.dll.  I tried doing some research into what this was and what I did to get this to load in (I didn’t really turn up much, and looking at this DLL in Reflector didn’t show me anything that rang bells), but the real damage had been done.  My assumption about what gets pre-loaded into app domains was proven wrong.  If .NET could load two assemblies, who’s to say it couldn’t load in 3, 4, or 10?  Basically, I had to find another way.

After some digging I came across this post by Steve Holstad:  He showed how to instantiate the class by name – no more relying on the position of the assembly in the app domain.  The key was AppDomain.CreateInstanceAndUnwrap:

Me._TempObject = Me._ModulesAppDomain.CreateInstanceAndUnwrap("MyAssemblyName", "MyAssemblyName.MyClassName")

The assembly name and fully qualified class name were pulled out when I was walking through the assemblies originally.  Once I found the type that implemented my custom interface, CurrentType, I would save off CurrentType.Namespace and CurrentType.UnderlyingSystemType.FullName to be used in place of “MyAssemblyName” and "MyAssemblyName.MyClassName" in the above example, respectively.

I need to point out that my assemblies have a very simple structure.  There are no custom namespaces or nested class declarations to contend with, so I don’t know how well CurrentType.Namespace and CurrentType.UnderlyingSystemType.FullName will hold up in cases like that.  To find these two, I stepped through my loading routine using the debugger and poked around until I found properties that had the proper values for what I needed.

So, after all of this my uneasy feeling about the +1 offset turned out to be well-founded, and using Steve’s approach I was able to completely get away from it.  Good times.


September 28, 2010 - Posted by | Visual Studio/.NET

Sorry, the comment form is closed at this time.

%d bloggers like this: