I am developing on software that uses the Matrox Imaging Library (MIL).
This software used the version 9 of the MIL in the past, now we moved to v10. Due to backwards compatibility we must continue supporting v9.
There are some difficulties when working with MIL and its DLLs:
- MIL 9 and MIL 10 cannot be installed at the same time. It wouldn't make any sense either.
- The C# DLLs of MIL 9 and MIL 10 are both named
Matrox.MatroxImagingLibrary.dll
. - The namespaces in both DLLs are identical.
While this is quite useful for exchangeability (despite the fact that some functions have changed), it is a big issue for parallel use.
I could not reference both DLLs in the same assembly due to the identical file name and namespace, so I created one assembly for each,
imaging-system_mil9
and
imaging-system_mil10
.
This is necessary, but quite useless so far. What I needed was a base class in a common assembly so that I could use inheritance, therefore I created the assembly
imaging-system
.
Inside this assembly I added my own wrappers for the MIL commands.
This seemed to be a pretty nice solution and worked really well when I initially developed and tested on my development computer where MIL 9 is installed. When I moved to another computer and developed and tested with MIL 10, I found that some commands in my wrapper need to be adapted because they had changed in the MIL 10 C# DLL. So far so good.
Today I moved back to my MIL 9 computer and wanted to test more things, but my test program failed to start, saying MissingMethodException
. After some search I found that I completely forgot to find a solution for one point: The identical file names:
My test program references imaging-system
, imaging-system_mil9
, and imaging-system_mil10
. The last two both reference a file Matrox.MatroxImagingLibrary.dll
, thus the output in the bin
folder looked like:
test.exe
imaging-system.dll
imaging-system_mil9.dll
imaging-system_mil10.dll
Matrox.MatroxImagingLibrary.dll (the one from MIL 9)
Matrox.MatroxImagingLibrary.dll (the one from MIL 10)
As you can see, the last two files have the same name, so it basically is like lottery which one is overwritten by the other one.
The first idea that I had to solve this problem was renaming the files into
Matrox.MatroxImagingLibrary9.dll
and
Matrox.MatroxImagingLibrary10.dll
.
This works on the first compilation level when the
imaging-system_mil9.dll
and
imaging-system_mil10.dll
are compiled because they directly reference the respective files. The output in one of the bin
folders:
imaging-system_mil10.dll
Matrox.MatroxImagingLibrary10.dll
But it fails on the next level when the assembly is compiled that does not reference the Matrox DLLs directly. The compiler just skips the renamed files, most likely because the assembly name does not match the file name any more. The bin
folder here:
test.exe
imaging-system.dll
imaging-system_mil9.dll
imaging-system_mil10.dll
missing: Matrox.MatroxImagingLibrary9.dll, Matrox.MatroxImagingLibrary10.dll
Furthermore, copying the renamed files manually into the EXE's output folder does not help either, because the EXE does not "see" them. This makes sense: imagine that there are 1000 DLLs and none is named like the assembly that the program is looking for. How should it find it? It can't load all 1000 DLLs... Thus the file name must match the assembly name.
The next idea that I have is setting CopyLocal = false
for the Matrox DLLs and copying them separately by a post-build event into a dll\mil9
resp. dll\mil10
subfolder.
Every assembly will run a pre-build or post-build PowerShell script that copies all content from all dll
subfolders of all referenced DLLs.
Every EXE will get an adapted app.config
file as described in How to save DLLs in a different folder when compiling in Visual Studio?.
Problem: I have not done this before because there was no need to. Thus I am currently facing several questions:
1) Will the EXE find the correct Matrox DLL because it sees both of them when searching the subfolders? The DLLs have same name, same culture and same publicKeyToken, but different version numbers, so they can be distinguished from each other.
2) How can I get a list of referenced DLL paths during build to feed into my PowerShell script that looks for dll
subfolders and copies files? The only way that comes to my mind is reading the csproj
file.
What I have tested so far on #1:
I have already done several tests with a test solution containing a console EXE and 2 DLLs, which replicate the situation.
I used "CopyLocal=false" and "SpecificVersion=true", I tried <probing>
and codebase>
in the app.config
file, but it only works with one of the DLLs:
Test folder structure:
test.exe
dll\testDLL9.DLL
dll\testDLL10.DLL
mil9-x64\mil.net\Matrox.MatroxImagingLibrary.dll
mil10-x64\mil.net\Matrox.MatroxImagingLibrary.dll
The test EXE:
private static void Main ()
{
Mil10 (); // when stepping into this, dll\testDLL10.dll is loaded
Mil9 (); // when stepping into this, dll\testDLL9.dll is loaded
}
private static void Mil10 () // when arriving here, dll\testDLL10.dll has been loaded
{
testDLL10.CDLL10.Work (); // when stepping into this, mil10-x64\Mil.net\Matrox.MatroxImagingLibrary.dll is loaded
}
private static void Mil9 () // when arriving here, dll\testDLL9.dll has been loaded
{
testDLL9.CDLL9.Work (); // when stepping into this, MissingMethodException is thrown, which is correct, because the EXE uses the already loaded DLL, which is the wrong one.
}
Now, when Mil9()
is called first, it also loads the
mil10-x64\Mil.net\Matrox.MatroxImagingLibrary.dll
when testDLL9.CDLL9.Work()
is called, which obviously is completely wrong. Why does this happen?
It only works when I remove the reference to testDLL10
and comment out the related functions.
app.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="dll" />
<dependentAssembly>
<assemblyIdentity name="Matrox.MatroxImagingLibrary"
publicKeyToken="5a83d419d44a9d98"
culture="neutral" />
<codeBase version="9.2.1109.1" href="mil9-x64\Mil.net\Matrox.MatroxImagingLibrary.dll" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Matrox.MatroxImagingLibrary"
publicKeyToken="5a83d419d44a9d98"
culture="neutral" />
<codeBase version="10.30.595.0" href="mil10-x64\Mil.net\Matrox.MatroxImagingLibrary.dll" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Final notes:
- The DLLs don't have to be loaded at the same time, as this is just impossible, because MIL 9 and MIL 10 cannot be installed in parallel, see above.
- I have read Referencing DLL's with same name, but up to now I don't want to load the DLLs manually as suggested in step 3 of the answer. I would prefer if the CLR loaded the correct DLL for me.
- I have read How to reference different assemblies with the same name?. I cannot use the GAC because I need to be able to switch between different versions of the software just by changing folders. As little connection to the operating system as possible.