My applications and tests depend on a .NET vendor library and on System.Data.SQLite. The vendor library also depends on System.Data.SQLite, but a different version. My applications consist of:
- C# WinForm applications
- C++ applications which use C++/CLI to interface with vendor library
- C# unit tests that are run either within Visual Studio or by a custom executable (which is highly configurable about running them repeatedly, changing test settings, etc.)
Modifying the app.config with a dependentAssembly
for the C# WinForm applications is easy enough. We put the older SQLite DLL in a subdirectory and redirect to it with:
<dependentAssembly>
<assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" />
<codeBase version="1.0.98.0" href="SQLite_v1.0.98\System.Data.SQLite.dll" />
</dependentAssembly>
But I'd rather avoid .config files for the C++ applications. And trying to get .config files to work for the custom test runner executable has been a real pain. So I tried to replace the app.config redirect with a preload step which gets called before the vendor library does:
public ref class AssemblyResolver
{
public:
static Reflection::Assembly^ ResolveEventHandler(Object^ sender, ResolveEventArgs^ args)
{
if (args->Name != "System.Data.SQLite, Version=1.0.98.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139")
return nullptr;
auto sb = gcnew Text::StringBuilder();
sb->Append(GetAssemblyDirectory());
sb->Append("\\SQLite_v1.0.98\\System.Data.SQLite.DLL");
return Reflection::Assembly::LoadFrom(sb->ToString());
}
//Returns the containing directory of the assembly that contains the function.
static String^ GetAssemblyDirectory()
{
auto codeBase = Reflection::Assembly::GetExecutingAssembly()->CodeBase;
auto uri = gcnew UriBuilder(codeBase);
auto path = Uri::UnescapeDataString(uri->Path);
return System::IO::Path::GetDirectoryName(path);
}
};
void preload()
{
AppDomain::CurrentDomain->AssemblyResolve += gcnew ResolveEventHandler(AssemblyResolver::ResolveEventHandler);
}
This works except in the case where an application uses SQLite 1.0.105 before preload()
gets called. I'd rather not have to enforce that all applications call preload()
as the first thing they do when starting an application, so I'm asking if there's a better way.
For example, is there an easy way to put the vendor library (actually it's a set of DLLs) in a subdirectory with its older SQLite.dll, so that the vendor DLLs will use that older SQLite, but all the other 1st party code (DLLs and EXEs) will use the newer SQLite? Again, preferably without needing .config files for the C++ applications and unit test runner.
Update:
Both versions of the SQLite DLL have the same name. The official System.Data.SQLite.dll
. I have another vendor dependency which I've updated to use the same SQLite version we use (105). Based on @MickyD's comment I tried to change our SQLite file to a different name: System.Data.SQLite.105.dll
. But then I got an error:
Could not load file or assembly 'System.Data.SQLite, Version=1.0.105.2, Culture=neutral, PublicKeyToken=db937bc2d44ff139' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference.
Since this other vendor dependency is not strongly named, I can decompile/recompile it (in fact that's what I already did to update it to 105). So I tried to update the name to add the .105 suffix:
.assembly extern System.Data.SQLite.105
{
.publickeytoken = (DB 93 7B C2 D4 4F F1 39 ) // ..{..O.9
.ver 1:0:105:2
}
Which gives me an error recompiling:
Assembling 'B.il' to DLL --> 'B.dll'
Source file is ANSI
B.il(57) : error : syntax error at token '.105' in: .assembly extern System.Data.SQLite.105
***** FAILURE *****
Foiled at every turn!