August 02, 2011

Dynamic use of P/Invoke on 32- and 64-bit systems

I’m now back from a nice, long weekend celebrating the Swiss National Day (August 1st) and our 10th wedding anniversary (which isn’t until November, but who wants to hold a party then? ;-).

So, getting back into the saddle, here’s a question that came in recently by email:

I'm using P/Invoke to call a function which is different on x86 and x64. How to code such entry point addresses dynamically, so one does not have to compile two separate versions, one for x86, and one for x64?

While the answer can be found a previous post, it seemed worth calling it out in a post of its own.

The process is quite simple. We’ll start – using the example from the above post – by adding DllImport (or Declare in VB) to define our P/Invokable functions for 32- and 64-bit versions of our code (which can be determined using one of the approaches in theseposts):

[DllImport("acad.exe",

CallingConvention = CallingConvention.Cdecl,

EntryPoint = "?acedGetCurrentColors@@YAHPAUAcColorSettings@@@Z"

)]

staticexternbool acedGetCurrentColors32(

outAcColorSettings colorSettings

);

[DllImport("acad.exe",

CallingConvention = CallingConvention.Cdecl,

EntryPoint = "?acedGetCurrentColors@@YAHPEAUAcColorSettings@@@Z"

)]

staticexternbool acedGetCurrentColors64(

outAcColorSettings colorSettings

);

We now have our distinctly-named functions for each platform, which will only be resolved when called (or at least they don’t cause a dependency error on platforms on which they don’t exist). Now we need to find a way of calling the correct version on each platform.

To make this easy, we can wrap the decision and the call into a single function to be called on both platforms:

staticbool acedGetCurrentColors(outAcColorSettings colorSettings)

{

if (IntPtr.Size > 4)

return acedGetCurrentColors64(out colorSettings);

else

return acedGetCurrentColors32(out colorSettings);

}

The interesting part of this is the test to see whether we’re on a > 32-bit platform: a pointer (or reference to a memory location) on 32-bit Windows is stored in 4 bytes (4 x 8 = 32 bits) while on 64-bit Windows it’s stored in 8 bytes. This is a much better technique than hard-coding test for the platform via (for instance) environment variables, and it reminds me of the kind of “feature detection” you need to do during web development to support different browsers: best practice is to test for the existence of browser features exposed via Javascript, rather than testing for the specific browser/version you’re in.

One minor caveat: the above code is not entirely future-proof. 128-bit Operating Systems will also cause the 64-bit function version to be called, but I’m guessing we’re a ways off having to worry about that. :-)

Then all that’s left is to call the acedGetCurrentColors() function from your code, as needed.