10

Is there a way to have the particular DLL referenced by a P/Invoke (DllImport) signature depend on the CPU architecture?

I'm working on an application that loads a large number of method signatures from a native dll from a third party vendor, in this case the user-space interface DLL to a piece of hardware. That vendor has now started supplying both x86 and x64 versions of the DLL now, and I think my app would benefit from running as a 64bit process. Except for this one DLL, everything is .NET code, so building as "Any CPU" would work.

All of the method signatures in the native DLL are the same on 64bit, however name of the DLL is different (Foo.dll vs. Foo_x64.dll). Is there any way through either the P/Invoke signatures or app.config entries I can get it to pick which DLL to load based on the running CPU architecture?

If instead of different DLL names it was the same name in different folders, does that open any other options?

NB: Because it is essential that the version of this user-space DLL match the installed kernel driver for the hardware, the DLL is not bundled with our app, but instead we depend on the vendor installer to place it in a directory in the %PATH%.

Cheetah
  • 1,141
  • 11
  • 21
  • Possible duplicate of http://stackoverflow.com/questions/23215518/target-32-bit-or-64-bit-native-dll-depending-on-environment – Simon Mourier May 25 '14 at 06:35
  • 3
    I think the duplication is the other way around, given this question is four years older than that one :) – Cheetah May 28 '14 at 14:21

3 Answers3

14

"If instead of different DLL names it was the same name in different folders, does that open any other options?"

Maybe this would work for you:

public static class NativeMethods
{
  // here we just use "Foo" and at runtime we load "Foo.dll" dynamically
  // from any path on disk depending on the logic you want to implement
  [DllImport("Foo", EntryPoint = "bar")]
  private void bar();

  [DllImport("kernel32")]
  private unsafe static extern void* LoadLibrary(string dllname);

  [DllImport("kernel32")]
  private unsafe static extern void FreeLibrary(void* handle);

  private sealed unsafe class LibraryUnloader
  {
    internal LibraryUnloader(void* handle)
    {
      this.handle = handle;
    }

    ~LibraryUnloader()
    {
      if (handle != null)
        FreeLibrary(handle);
    }

    private void* handle;

  } // LibraryUnloader

  private static readonly LibraryUnloader unloader;

  static NativeMethods()
  {
    string path;

    if (IntPtr.Size == 4)
      path = "path/to/the/32/bit/Foo.dll";
    else
      path = "path/to/the/64/bit/Foo.dll";

    unsafe
    {
      void* handle = LoadLibrary(path);

      if (handle == null)
        throw new DllNotFoundException("unable to find the native Foo library: " + path);

      unloader = new LibraryUnloader(handle);
    }
  }
}

It consists in explicitly loading the native library with its full path before P/Invoke itself tries to load it.

What do you think?

Gregory Pakosz
  • 69,011
  • 20
  • 139
  • 164
  • It would be work with Window/MS.NET but what about Mono? – AndreyAkinshin May 23 '14 at 18:06
  • no idea about Mono, please tell us what you find out – Gregory Pakosz Jun 02 '14 at 11:49
  • You don't have to use `unsafe` keyword, you can use `IntPtr` type and forgo `void*`. For other platforms there are similar APIs that can load link libraries at runtime, I suspect the implementation would be similar on those systems. Just different API calls than `LoadLibrary`. btw, `FreeLibrary` isn't needed unless you really need to unload the library from memory, which only ever is the case if you actually have a life time of the DLL that is diffrent from the life time of your application. – John Leidegren Feb 28 '18 at 11:46
  • @tig The size of pointer tells you if the current architecture is 32bit or 64bit – Gregory Pakosz Feb 03 '20 at 21:55
4

There is no way to have a single PInvoke signature and get the behavior you want. The attribute is burned into metadata and must have constant values. One hack you could do though is to have multiple methods.

public static class NativeMethods32 {
  [DllImport("Foo.dll")]
  public static extern int SomeMethod();
}

public static class NativeMethods64 {
  [DllImport("Foo_x864.dll")]
  public static extern int SomeMethod();
}

public static class NativeMethods {
  public static bool Is32Bit { return 4 == IntPtr.Size; }
  public static SomeMethod() {
    return Is32Bit ? 
      NativeMethods32.SomeMethod(); 
      NativeMethods64.SomeMethod();
  }
}

However this is not the preferred approach. An easier approach would be to make the DLL have the same name on multiple platforms and create a platform agnostic PInvoke signature. This is the approach most / all windows libraries take.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • The same-name thing doesn't work for my case because 1) This is a third party vendor DLL 2) The DLL(s) are installed to a folder in the system PATH so that apps can find them automatically (thankfully the vendor doesn't install to %SystemRoot%\system32 any more) 3) On a 64bit OS, both the 32bit and 64bit DLLs need to be available #1 means I can't fiddle and #2 conflicts with #3. I ended up using a solution similar to what you suggested. I defined an interface with all the methods, and used LinFu to create a proxy object at runtime that forwards to the correct static methods. – Cheetah Jun 30 '10 at 16:03
1

I developed a special library for the target: InteropDotNet. It introduces new RuntimeDllImport attribute with dynamical library path resolving (on the fly). In the default way, you can write

[RuntimeDllImport("NativeLib", 
   CallingConvention = CallingConvention.Cdecl, EntryPoint = "sum")]
int Sum(int a, int b);

And the library will be resolved depends on environment. For example, paths for Win/Linux, x86/x64:

x86/NativeLib.dll
x86/libNativeLib.so
x64/NativeLib.dll
x64/libNativeLib.so
AndreyAkinshin
  • 18,603
  • 29
  • 96
  • 155