See the question and my original answer on StackOverflow

Here is the solution I've used on many projects:

  • name the 32-bit assembly with a "32-bit oriented name". For example MyAssembly.Native.x86.dll
  • name the 64-bit assembly with a "64-bit oriented name". For example MyAssembly.Native.x64.dll
  • compile the managed assembly as 'Any Cpu'
  • ship everything in the same path

Here is how I declare P/Invoke methods:

[DllImport("MyAssembly.Native.x86.dll", EntryPoint = "MyTest")]
private static extern void MyTest86(MyType myArg);

[DllImport("MyAssembly.Native.x64.dll", EntryPoint = "MyTest")]
private static extern void MyTest64(MyType myArg);

And here is the corresponding 'MyTest' function which is the one I'll always use (the others are here just for correct bitness binding). It has the same signature than the other P/Invoke ones:

public static void MyTest(MyType myArg)
    if (IntPtr.Size == 8)


The advantages are:

  • you can ship all binaries (DLLs, EXEs, ...) in the same path
  • you support 32-bit and 64-bit processes and OSes with the same file layout
  • you don't have to resort to Win32 apis for changing dll load path

The inconveniences are:

  • you'll have 3 method declarations for 1 'real' method
  • you'll loose some CPU cycles because of the bitness test
  • depending on your context, sometimes you can't change the native DLLs names, so you just can't do this