[ACCEPTED]-Using Side-by-Side assemblies to load the x64 or x32 version of a DLL-64-bit
I created a simple solution that is able 44 to load platform-specific assembly from 43 an executable compiled as AnyCPU. The technique 42 used can be summarized as follows:
- Make sure default .NET assembly loading mechanism ("Fusion" engine) can't find either x86 or x64 version of the platform-specific assembly
- Before the main application attempts loading the platform-specific assembly, install a custom assembly resolver in the current AppDomain
- Now when the main application needs the platform-specific assembly, Fusion engine will give up (because of step 1) and call our custom resolver (because of step 2); in the custom resolver we determine current platform and use directory-based lookup to load appropriate DLL.
To demonstrate 41 this technique, I am attaching a short, command-line 40 based tutorial. I tested the resulting binaries 39 on Windows XP x86 and then Vista SP1 x64 38 (by copying the binaries over, just like 37 your deployment).
Note 1: "csc.exe" is a C-sharp 36 compiler. This tutorial assumes it is in 35 your path (my tests were using "C:\WINDOWS\Microsoft.NET\Framework\v3.5\csc.exe")
Note 2: I 34 recommend you create a temporary folder 33 for the tests and run command line (or powershell) whose 32 current working directory is set to this 31 location, e.g.
(cmd.exe)
C:
mkdir \TEMP\CrossPlatformTest
cd \TEMP\CrossPlatformTest
Step 1: The platform-specific assembly 30 is represented by a simple C# class library:
// file 'library.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Library
{
public static class Worker
{
public static void Run()
{
System.Console.WriteLine("Worker is running");
System.Console.WriteLine("(Enter to continue)");
System.Console.ReadLine();
}
}
}
Step 2: We 29 compile platform-specific assemblies using 28 simple command-line commands:
(cmd.exe from Note 2)
mkdir platform\x86
csc /out:platform\x86\library.dll /target:library /platform:x86 library.cs
mkdir platform\amd64
csc /out:platform\amd64\library.dll /target:library /platform:x64 library.cs
Step 3: Main program 27 is split into two parts. "Bootstrapper" contains 26 main entry point for the executable and 25 it registers a custom assembly resolver 24 in current appdomain:
// file 'bootstrapper.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Program
{
public static class Bootstrapper
{
public static void Main()
{
System.AppDomain.CurrentDomain.AssemblyResolve += CustomResolve;
App.Run();
}
private static System.Reflection.Assembly CustomResolve(
object sender,
System.ResolveEventArgs args)
{
if (args.Name.StartsWith("library"))
{
string fileName = System.IO.Path.GetFullPath(
"platform\\"
+ System.Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")
+ "\\library.dll");
System.Console.WriteLine(fileName);
if (System.IO.File.Exists(fileName))
{
return System.Reflection.Assembly.LoadFile(fileName);
}
}
return null;
}
}
}
"Program" is the "real" implementation 23 of the application (note that App.Run was 22 invoked at the end of Bootstrapper.Main):
// file 'program.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Program
{
public static class App
{
public static void Run()
{
Cross.Platform.Library.Worker.Run();
}
}
}
Step 4: Compile 21 the main application on command line:
(cmd.exe from Note 2)
csc /reference:platform\x86\library.dll /out:program.exe program.cs bootstrapper.cs
Step 5: We're 20 now finished. The structure of the directory 19 we created should be as follows:
(C:\TEMP\CrossPlatformTest, root dir)
platform (dir)
amd64 (dir)
library.dll
x86 (dir)
library.dll
program.exe
*.cs (source files)
If you now 18 run program.exe on a 32bit platform, platform\x86\library.dll 17 will be loaded; if you run program.exe on 16 a 64bit platform, platform\amd64\library.dll 15 will be loaded. Note that I added Console.ReadLine() at 14 the end of the Worker.Run method so that 13 you can use task manager/process explorer 12 to investigate loaded DLLs, or you can use 11 Visual Studio/Windows Debugger to attach 10 to the process to see the call stack etc.
When 9 program.exe is run, our custom assembly 8 resolver is attached to current appdomain. As 7 soon as .NET starts loading the Program 6 class, it sees a dependency on 'library' assembly, so 5 it tries loading it. However, no such assembly 4 is found (because we've hidden it in platform/* subdirectories). Luckily, our 3 custom resolver knows our trickery and based 2 on the current platform it tries loading 1 the assembly from appropriate platform/* subdirectory.
My version, similar to @Milan, but with 11 several important changes:
- Works for ALL DLLs that were not found
- Can be turned on and off
AppDomain.CurrentDomain.SetupInformation.ApplicationBase
is used instead 10 ofPath.GetFullPath()
because the current directory might 9 be different, e.g. in hosting scenarios, Excel 8 might load your plugin but the current directory 7 will not be set to your DLL.Environment.Is64BitProcess
is used instead 6 ofPROCESSOR_ARCHITECTURE
, as we should not depend on what the 5 OS is, rather how this process was started 4 - it could have been x86 process on a x64 3 OS. Before .NET 4, useIntPtr.Size == 8
instead.
Call this 2 code in a static constructor of some main 1 class that is loaded before all else.
public static class MultiplatformDllLoader
{
private static bool _isEnabled;
public static bool Enable
{
get { return _isEnabled; }
set
{
lock (typeof (MultiplatformDllLoader))
{
if (_isEnabled != value)
{
if (value)
AppDomain.CurrentDomain.AssemblyResolve += Resolver;
else
AppDomain.CurrentDomain.AssemblyResolve -= Resolver;
_isEnabled = value;
}
}
}
}
/// Will attempt to load missing assembly from either x86 or x64 subdir
private static Assembly Resolver(object sender, ResolveEventArgs args)
{
string assemblyName = args.Name.Split(new[] {','}, 2)[0] + ".dll";
string archSpecificPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
Environment.Is64BitProcess ? "x64" : "x86",
assemblyName);
return File.Exists(archSpecificPath)
? Assembly.LoadFile(archSpecificPath)
: null;
}
}
Have a look at SetDllDirectory. I used it 5 around the dynamically loading of an IBM 4 spss assembly for both x64 and x86. It also 3 solved paths for non assembly support dll's 2 loaded by the assemblies in my case was 1 the case with the spss dll's.
http://msdn.microsoft.com/en-us/library/ms686203%28VS.85%29.aspx
You can use the corflags utility to force an AnyCPU 4 exe to load as an x86 or x64 executable, but 3 that doesn't totally meet the file copy 2 deployment requirement unless you choose 1 which exe to copy based on the target.
This solution can work for non managed assemblies 9 as well. I have created a simple example 8 similar to Milan Gardian's great example. The 7 example I created dynamically loads a Managed 6 C++ dll into a C# dll compiled for the Any 5 CPU platform. The solution makes use of 4 the InjectModuleInitializer nuget package 3 to subscribe to the AssemblyResolve event 2 before the dependencies of the assembly 1 are loaded.
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.