[ACCEPTED]-Using Side-by-Side assemblies to load the x64 or x32 version of a DLL-64-bit

Accepted answer
Score: 66

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:

  1. Make sure default .NET assembly loading mechanism ("Fusion" engine) can't find either x86 or x64 version of the platform-specific assembly
  2. Before the main application attempts loading the platform-specific assembly, install a custom assembly resolver in the current AppDomain
  3. 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.

Score: 24

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 of Path.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 of PROCESSOR_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, use IntPtr.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;
    }
}
Score: 4

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

Score: 2

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.

Score: 1

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.

https://github.com/kevin-marshall/Managed.AnyCPU.git

More Related questions