dotnet clr:
My team has been struggling with an issue involving assemblies
dynamically loaded into separate app domains in the context of an .Net
extension running in a third party executable via COM Interop. After
studying what's available online and reading thoroughly through most of
Steven Pratschner's very good book "Customizing the Microsoft .Net
Framework CLR", I believe we now understand what's going on, but we
still don't know how to solve the problem. To describe what exactly
the issue is I will need to give quite a bit of detail, so brace
yourself for a lenghty post. I would certainly appreciate any
suggestions.
So here's what we set out to do. We have a third-party unmanaged
application that allows us to create extensions. These extensions are
easiest accessible via COM, but it would be possible to write simple
C++ code (say to custom host a CLR). What we need to do is create a
managed .Net extension that allows for a bidirectional communication
between the underlying application and our extension. The easies
solution appeared to be simply exposing our .Net extension as a COM
server, and that's what we did, but we ran into an issue.
To make things somewhat more complicated, our extension must itself
support plug-ins that need to be dynamically loaded. These plugins
would be .Net assemblies that expose a known set of predifined
interfaces, and get dynamically loaded using Assembly.Load or similar.
Moreover, since we want to support seamless updates to these plugins,
we need to place them in separate app domains, so that they can be
unloaded when a new version is available.
This rather complex setup leads to an unexpected problem which has to
do with assembly loading contexts. The CLRs default assembly loading
mechanism (which also takes place when using Assembly.Load) searches
for the references assemblies only in GAC (if the assembly is signed)
and in the directory of the main executable (and potentially its
subdirectories). Assemblies found there are loaded into the default
loading context. Unfortunately, when running under COM Interop the
hosting executable resides in an altogether different directory than
our extension and all of its plugins. As a result, the CLR loads our
extension's main assembly and all of its early-bound referenced
assemblies into the other, "LoadFrom", context. If our extension then
goes about creating app domains for its plugins (specifying the
appropriate app base path) and loads the plugin assemblies into these
app domains, the plugin assemblies get loaded using the usual loading
mechanism and end up in the default loading context. The plugin
assemblies in turn contain early-bound references to at least one
shared assembly that defines the interfaces used to communicate between
the extension and its plugins. These shared assemblies get loaded into
the default context of the plugin app domain, and if our extension
attempts to access any of the plugin via one of the shared interfaces,
we get he ominous InvalidCastException, because the two types are
incompatible since they are defined in different loading contexts.
Interestingly enough, if our extension's assembly and all shared
assemblies are copied into the hosting executable's directory, they get
loaded into the default loading context and everything works just fine.
Unfortunatly, since in our case the executable is a third party
application, we do not have the option of sticking our dlls into the
exeuctable's directory.
I used the Fusion Log Viewer to examine exactly what happens, and I
believe my description above is accurate, but please correct me if I'm
wrong. I still have a few open questions, and naturally I do NOT have
a good solution at this point, so I would appreciate any help. Here
are the questions.
1. Nothing that I found online or in Steven's book explains what
exactly the COM Interop layer does when creating the CLR to load a the
first .Net assembly. Specifically, I have no clue how the CLR knows to
look for the assembly in the specific directory that actually contains
it. Naturally, such information would be available from Windows
Registry, but I can't seem to find anything that would instruct the CLR
to load such an assembly. Does the COM Interop layer actually
explicitly load the assembly into the CLR by passing the full path and
effectively executing LoadFrom?
2. If, in fact, a LoadFrom is executed, does the COM Interop attempt to
load the assembly by the usual CLR means of looking under the main
executable's directory FIRST? This would explain why copying the dlls
into the executable's directory actually loads them into the default
loading context.
3. What happens if an assembly loaded into the LoadFrom context
early-bound references another assembly? Does the referenced assembly
automatically land in the LoadFrom context?
4. Is there any way to intercept the process by which COM Interop
creates the CLR and establishes the default app domain? If so, can one
then change the app domain's base path?
5. Is there any other way to solve this problem by doing some magic of
custom hosting the CLR?
Thanks for any suggestions.
Andrew