views:

422

answers:

2

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

+1  A: 

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
The same-name thing doesn't work for my case because1) This is a third party vendor DLL2) 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
+3  A: 

"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 {

  [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