How can you make a C++/CLI DLL resolve a dependency on managed assemblies (DLLs), without using the GAC?

776 Views Asked by At

We currently build an ODBC driver as 4 DLLs:

  1. A DLL implementing the ODBC (C) API, mostly implemented in C++, with some 'glue' code written in C++/CLI, used to interact with #2, #3, & #4

  2. A DLL containing a managed assembly (written in C#) which defines the 'base' interfaces used for #1 & #4 to talk to each other.

  3. Another DLL containing a managed assembly that depends on #2, and defines some extensions to the classes in #2
  4. Yet another DLL containing a managed assembly, which contains the 'business logic' for the driver, which depends on #2 & #3

To deploy the driver, we configure a DSN to point to #1, and put #2, #3 & #4 into the GAC.

We have a customer who wants to avoid the GAC entirely. I know that putting #2, #3, and #4 into the same directory as the application which loads #1 'works', but that's not a good solution, because many different applications might use the driver.

How can we set it up so that the dependencies can be resolved without the GAC? I've tried creating manifest files (based on https://learn.microsoft.com/en-us/windows/win32/sbscs/assembly-manifests), but that didn't seem to work (EEFileLoadException exception gets thrown because it can't find the managed assemblies, same thing that happens as soon as I remove the dependencies from the GAC). I put the manifest files, and all of the .DLL's into the same directory.

I couldn't find any good documentation/examples for this case with some (perhaps not enough) googling.

2

There are 2 best solutions below

2
Aryéh Radlé On

I'm not sure that I entirely understand your question, but I'll try to answer:

While trying to solve this, we should remember the following:

  1. Fusion doesn't kick in (and thus dependency resolution), until you are in a stack frame, where the first appearance of the unresolved type is located.
  2. We can assume that the CLR is already loaded in the host process, and we have at least one Appdomain. (Otherwise, you have to think about both scenarios - especially being consumed by a process, which doesn't load CLR by default. What do you want to do in such case? Load CLR yourself? Implicitly? Explicitly? If so, how do you configure your Appdomain? Where is your App Root? What are your probing paths?)

Given these two points, you can subscribe to AppDomain.AssemblyResolve Event. But you should refactor your code in such a manner, so fusion doesn't try to resolve your assemblies before you've managed to subscribe. In the handler, handle only the events in which YOUR assemblies are being resolved, and load them from any location you want - for example, from the same path, in which your native library resides.

Additional reading:

6
user11953043 On

Updated based on comments...

I found an interesting point in Microsoft's documentation here: learn.microsoft.com/en-us/windows/win32/dlls/… -- "If a DLL has dependencies, the system searches for the dependent DLLs as if they were loaded with just their module names. This is true even if the first DLL was loaded by specifying a full path." This means if those dlls were loaded by any other process the version in memory with that name is what will be used.

Essentially what microsoft's doc is saying is:

You have nothing to worry about unless there are other dlls on the computer with the same module names as #2-#4. Regardless of whether you put those dlls into the GAC. #1 will be loaded by the application using whatever path you specify, #2-#4 will be loaded by module name only.

Your only problem is if your modules #2-#4 do not have good distinctive names and there could exist others with the same name and different definitions. This is because they will be loaded by name only.

As far as order of execution...#1 is your entry point, if it has dependencies on #2 it will be loaded before any execution, and so on down the line. Unless you did something explicit with your build directives, but you would have run into that already with your current implementations as well.