18

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 ?.

Ammark
  • 381
  • 1
  • 6
  • 17

3 Answers3

21

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Very cool. I did not know this. – Ani May 15 '13 at 15:51
  • Thanks David, Would I need to create a wrapper around the existing dlls, as the methods I need to access are not static ?. – Ammark May 15 '13 at 15:57
  • Do you use `DllImport` right now when you call your code when you compile just as x86 or just x64? – Scott Chamberlain May 15 '13 at 15:59
  • @ScottChamberlain No, I don't actually. – Ammark May 15 '13 at 16:01
  • Then this is not the way to fix your problem (well, it may still be but the instructions will be slightly different), please edit and clarify your original question that you are linking to a **managaged** DLL, not a native DLL. **EDIT**:Never-mind, I just saw David's Edit. You want the bottom paragraph David put in. – Scott Chamberlain May 15 '13 at 16:22
2

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.

antonijn
  • 5,702
  • 2
  • 26
  • 33
  • 1
    You don't need any delegates. You can do the whole thing with plain old `DllImport` for all imported functions. – David Heffernan May 15 '13 at 17:50
  • @DavidHeffernan But then you can't use "Any CPU". You will then need to redistribute separate versions of your application for 32 bit and 64 bit systems. – antonijn May 15 '13 at 18:59
  • Sure you can use AnyCPU. My answer explains how. You do need the DLLs to have the same name, but be in different folders mind you. But that's normal for DLLs with 32/64 bitness. – David Heffernan May 15 '13 at 19:03
  • @DavidHeffernan I'm sorry, I'm not even considering your solution. P/Invoking WinAPI functions is evil by definition IMO. – antonijn May 15 '13 at 19:04
  • So how do you get the delegates. You call `GetProcAddress` somewhere? – David Heffernan May 15 '13 at 19:22
  • @DavidHeffernan No. If you read the answer, you would find out. I import them using DllImport. – antonijn May 16 '13 at 11:15
  • "If you read the answer". No need to be so rude. I still don't quite follow your answer. Anyway, despite declaring p/invoke to be evil, you are happily using it. Yours in confusion! – David Heffernan May 16 '13 at 11:21
  • @DavidHeffernan I said P/Invoking **WinAPI** functions. I don't see any WinAPI functions here. – antonijn May 16 '13 at 15:04
  • You were the first person to mention WinAPI functions. Why did you bring those into the discussion? – David Heffernan May 16 '13 at 15:07
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/30076/discussion-between-antonijn-and-david-heffernan) – antonijn May 16 '13 at 15:08
2

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;
        };
Ammark
  • 381
  • 1
  • 6
  • 17