Search code examples
c#.netdll.net-assemblyspire.doc

Reference multiple versions of the same DLL


I have a project which needs to indirectly use three different versions of a third-party library. These versions are incompatible with each other, so I can't use a binding redirect - it has to be the exact .dll file. (The libraries are Spire.Doc, Spire.XLS & Spire.PDF; the Spire.PDF DLL is referenced by all three)

I have separated the three components into individual wrapper projects, and created classes which wrap direct references to anything in the libraries. However, this doesn't solve my issue: the 'consuming' project still has to copy all of the libraries to the bin folder in order to run. The build process doesn't know which version to copy, and so just copies the latest one. This gives me runtime exceptions due to the wrong DLL being present.

What I've considered/tried:

  • Adding a binding redirect to a specific version (runtime exception because the exact version of the library is not found)
  • Using a post-build step to merge the wrapper projects (again a runtime exception complaining about the absence of the library DLL)
  • Creating separate console applications for each part of the application, then invoking them in a separate - this is a complicated last resort that I'd really rather not do!

I have read that extern alias might be able to help - but as far as I can tell, you can only distinguish between assemblies with different names. The Spire.PDF library has the same name in each project (and the same signed public token).

How can I use these three separate versions of the library independently in the same solution?

Edit:

This issue is slightly different to the suggested duplicate because I don't have the ability to change any code in the dependent libraries. Spire.Doc relies on a different version of Spire.PDF to Spire.XLS


Solution

  • In your consuming project (Project A), create a common interface (ISpiroPdfAlex) that encompasses all the functionality that the 3 versions of your external assembly provides (and you use). You cannot reference anything in Project A from these wrappers in any way, otherwise you'd create a dependency, which is what you're trying to avoid.

    Have all 3 wrapper projects import Project A and implement ISpiroPdfAlex. This will give you the ability to call each of the 3 different versions through the same API.

    After this, create a subfolder under Project A for each of the versions (so 3 subfolders total) - since Project A has no reference to any of the external assemblies, it cannot load them by itself - you'll have to manually load them when you need the right version. Since your external DLLs may have dependencies with the same name, they cannot all be in the same folder (as you wrote), this is why you need the subfolders.

    At run-time when you need one of these versions, you can call Assembly.LoadFile to load a specific version of your assembly from the specified folder and then you can either use Activator.CreateInstance or dependency injection to create an instance of a class that implements your interface. Once you have the instance, you're free to call any of the functions and you'll get version-dependent behavior.

    Edit:

    OP mentioned in a comment that it's not his code that has the dependency on different versions of the PDF library but the other 3rd-party Spire libraries that his code depends on.

    In this case, the 3rd-party code cannot be modified to support dynamic loading of assemblies and they already have a binary dependency. It's not possible to load different versions of the "same" assembly into the same process, especially that you mentioned that these versions are not even backward-compatible with each other.

    The only solution I can think of in this situation is to break out all dependent functionality into separate console applications (one for each different version) and call those separate .exe-s through the command-line.

    To pass information, you can either pass data directly on the command-line or through stdin. Alternatively, you can just pass the name of a temporary file that has all data necessary to do some processing. To get return data back from the console process, you can either read its stdout or use the same / different file.

    This way your main process never loads any of these assemblies and has no dependency on them - each console application has a dependency on just one version so there's no collision.