Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Load x64 or a x86 DLL depending upon the platform? [duplicate]

Tags:

c#

.net

I have an application built as 'Any CPU' and have two third party Dlls of the same library targeted to x86 and x64. I would like to include one of these libraries at runtime depending upon the platform it would run on the client machine. What would be the best way to go about it ?.

like image 733
Ammark Avatar asked May 15 '13 15:05

Ammark


3 Answers

If we are talking about unmanaged DLLs, declare the p/invokes like this:

[DllImport("DllName.dll")]
static extern foo();

Note that we are not specifying a path to the DLL, just its name, which I presume is the same for both 32 and 64 bit versions.

Then, before you call any of your p/invokes, load the library into your process. Do that by p/invoking to the LoadLibrary API function. At this point you will determine whether your process is 32 or 64 bit and build the full path to the DLL accordingly. That full path is what you pass to LoadLibrary.

Now, when you call your p/invokes for the library, they will be resolved by the module that you have just loaded.

For managed assemblies then you can use Assembly.LoadFile to specify the path of the assembly. This can be a little tricky to orchestrate, but this excellent article shows you how: Automatically Choose 32 or 64 Bit Mixed Mode DLLs. There are a lot of details relating to mixed mode and the native DLL dependencies that are probably not relevant to you. The key is the AppDomain.CurrentDomain.AssemblyResolve event handler.

like image 175
David Heffernan Avatar answered Oct 13 '22 21:10

David Heffernan


I'm actually kind of experienced about this topic, so I thought I would post the answer according to the ways I used in Pencil.Gaming. Firstly, you have to "DllImport" two functions, one from the 32-bit dll, and one from the 64-bit dll (or so, or dylib, whatever your platform uses).

static class Foo32 {
    [DllImport("32bitdll.dll")]
    internal static extern void Foo();
}
static class Foo64 {
    [DllImport("64bitdll.dll")]
    internal static extern void Foo();
}

Then you need an intermediate class containing delegates, and importing them from the 32 or 64-bit interop according to the size of an IntPtr (I don't use Environment.Is64BitProcess, since that's a .NET 4 function):

internal delegate void FooDelegate();
static class FooDelegates {
    internal static FooDelegate Foo;

    static FooDelegates() {
        Type interop = (IntPtr.Size == 8) ? typeof(Foo64) : typeof(Foo32);
        FieldInfo[] fields = typeof(FooDelegates).GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
        foreach (FieldInfo fi in fields) {
            MethodInfo mi = glfwInterop.GetMethod(fi.Name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            Delegate function = Delegate.CreateDelegate(fi.FieldType, mi);
            fi.SetValue(null, function);
        }
    }
}

And then I generally use a "real" class, containing the function you imported (though this is not technically required):

public static class FooApi {
    public static void Foo() {
        FooDelegates.Foo();
    }
}

This is a real pain if you only need one or two functions, but the way of importing delegates is really efficient for larger scale libraries/applications. You might want to check out Pencil.Gaming on github, as it uses this method quite extensively (here is an example of it being used a lot).

Another benefit of this method is that it's 100% cross-platform, and doesn't rely on any WinAPI functions.

like image 39
antonijn Avatar answered Oct 13 '22 20:10

antonijn


My complete solution to my problem was by using the second link provided by David Heffernan. What I did was 1. Referenced a dummy dll in the project. 2. Specified two pre-build events

xcopy /y "$(SolutionDir)\Assemblies\Lib\x86\(Assembly name)*" "$(TargetDir)"
xcopy /y "$(SolutionDir)\Assemblies\Lib\x64\(Assemble name)*" "$(TargetDir)"

3. and on startup of the app in the assembly resolve event changed the corresponding assembly depending upon the platform.

        var currentDomain = AppDomain.CurrentDomain;
        var location = Assembly.GetExecutingAssembly().Location;
        var assemblyDir = Path.GetDirectoryName(location);

        if (assemblyDir != null && (File.Exists(Path.Combine(assemblyDir, "(Assembly name).proxy.dll"))
                                    || !File.Exists(Path.Combine(assemblyDir, "(Assembly name).x86.dll"))
                                    || !File.Exists(Path.Combine(assemblyDir, "(Assembly name).x64.dll"))))
        {
            throw new InvalidOperationException("Found (Assembly name).proxy.dll which cannot exist. "
                + "Must instead have (Assembly name).x86.dll and (Assembly name).x64.dll. Check your build settings.");
        }

        currentDomain.AssemblyResolve += (sender, arg) =>
        {
            if (arg.Name.StartsWith("(Assembly name),", StringComparison.OrdinalIgnoreCase))
            {
                string fileName = Path.Combine(assemblyDir,
                    string.Format("(Assembly).{0}.dll", (IntPtr.Size == 4) ? "x86" : "x64"));
                return Assembly.LoadFile(fileName);
            }
            return null;
        };
like image 2
Ammark Avatar answered Oct 13 '22 21:10

Ammark