Reputation: 1335
A .NET assembly that is registered for COM Interop combined with some reflection code that shall load types in assemblies results in some strange behavior. I have analyzed what is going on in the debugger and I have searched the net to find a solution. I have found many articles with help, but nothing that have made me solve the problem completely.
Overview of the problem
A have an exe-file that is not .NET (in my case a VB6 application). It resides in folder A.
I have some .NET dlls in folder B. One of them is a COM dll.
The exe-files instantiates a COM instance of a .NET object of the COM .NET assembly.
Then the main path of the AppDomain is folder A, but I would like it to be folder B. Since it is folder A, some reflection type-loading inside my .NET code fails.
Here are the details:
I have a VB6 application. The exe file resides in folder A. Inside it I have a VB6 statement
Set DotNetE2 = CreateObject("MyDotNet.E2")
This creates an instance of my .NET class that is registred for COM interop. The header of the .NET class looks like this:
namespace MyDotNet.E2.COM
{
[ComVisible(true)]
[Guid("776FF4EA-2F40-4E61-8EF3-08250CB3712B")]
[ProgId("MyDotNet.E2")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class E2
{
My .NET assembly 'MyDotNet.E2.COM.dll" resides in folder B. This assembly has references to two other .NET assemblies called E3 and E4 that resides in the same folder. E3 does not have a reference to E4. I am able to execute code in these assemblies as expected, so the references are OK. So far so good. Now, I have some code in E3 that tries to do some reflection on the types in E4. This fails.
Here is the code:
string dotnetPath = Path.GetDirectoryName(
Assembly.GetExecutingAssembly().Location);
string mainDir = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
string otherDirs = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
Assembly assembly = Assembly.LoadFrom(Path.Combine(dotnetPath, "E4.dll"));
Type mytype = assembly.GetType("MyDotnet.E4.MyForm");
Observations
dotnetPath is different from mainDir.
mytype is null. Expected result is a type-instance.
If I move the exe file to folder B, along with the .NET assemblies, it works. Then dotnetPath and mainDir is the same.
If I execute the reflection-code in E2 instead of E4, it works, even though dotnetPath != mainDir.
But in exactly the scenario I have outlines it does not work.
I have found some tips on adding other folders to the AppDomain in the PrivateBinPath by specifying those in the config file. But I have not succeeded on this. I have tried to add a config file to my COM .NET file and my VB6 exe-file but nothing happened to my PrivateBinPath property. Here is the config file I tried to add:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="..\..\FolderB"/>
</assemblyBinding>
</runtime>
</configuration>
Don't ask me to restructure my assemblies and types. The project is rather complicated and this is the best architecture.
Upvotes: 1
Views: 999
Reputation: 1335
I managed to solve this myself. The key was the AssemblyResolve-event that is fired if the system fails to resolve an assembly. It is explained here: http://msdn.microsoft.com/library/system.appdomain.assemblyresolve
It seems like a bug in the .NET framework that I have to use this event, but this workaround turned out to be fairly nice.
string dotnetPath = Path.GetDirectoryName(
Assembly.GetExecutingAssembly().Location);
string mainDir = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
if (!mainDir.Equals(dotnetPath, StringComparison.CurrentCultureIgnoreCase))
{
// This will happen if .NET process is fired
// from a COM call from another folder.
// Solution: an event is fired if assembly-resolving fails.
AppDomain.CurrentDomain.AssemblyResolve +=
new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
The event handler is fairly simple:
Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.FullName == args.Name) return assembly;
}
return null;
}
Upvotes: 1