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 these posts):

[DllImport("acad.exe",

  CallingConvention = CallingConvention.Cdecl,

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

)]

static extern bool acedGetCurrentColors32(

  out AcColorSettings colorSettings

);

 

[DllImport("acad.exe",

  CallingConvention = CallingConvention.Cdecl,

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

)]

static extern bool acedGetCurrentColors64(

  out AcColorSettings 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:

static bool acedGetCurrentColors(out AcColorSettings 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.

7 responses to “Dynamic use of P/Invoke on 32- and 64-bit systems”

  1. Kean, thanks for the P/Invoke tip. I like this trick I found somewhere on the internet and currently use something similar as a Property in my AppInitialize class.
    /* clever property to detect 32-bit or 64-bit architecture on the current host machine */
    internal static string PC_Architecture
    {
    /* based on the premise: IntPtr.Size = 4 on 32-bit machines and IntPtr.Size = 8 on 64-bit machines */
    get { return string.Format("{0}-bit", (IntPtr.Size * 8).ToString()); }
    }

    I'm running 64-bit @ home & 32-bit @ work. So, it helps. Keep 'em coming!

  2. Kean, I am using VB.NET and have a declaration as follows...
    Private Declare Function acedCmd Lib "acad.exe" Alias "acedCmd" (ByVal vlist As System.IntPtr) As Integer

    What changes might be needed to run properly on a 64 bit system?

  3. Hi Ben,

    I've never encouraged use of acedCmd via P/Invoke - I'd much rather people used sendStringToExecute() or SendCommand().

    That said, the signature as it stands seems OK, at first glance. Is it not working for you?

    Kean

  4. Hi Kean
    Is there any way of synchronously calling an express tools function. I'm trying to run the flatten command, but P/Invoke methods aren't working. I'm assuming this is because they're not defined in acad.exe. SendStringToExecute would work, but I need to do some stuff after the flattening (joining flattened objects to a Polyline) and as the SendStringToExecute will not execute till the rest of the .Net code has finished, this doesn't help much.

  5. Hi Greg,

    You can either use a "continuation passing" style where you split your functionality into different commands and resume by launching the continuation command via the same SendStringToExecute() call, or use COM's SendCommand(), which is synchronous.

    Regards,

    Kean

  6. > SendCommand(), which is synchronous.

    From the doc :

    > This method is generally synchronous. However, if the command sent with this method requires any user interaction (such as picking a point on the screen) then this method will continue as soon as the user input begins. The command will then continue to be processed asynchronously.

    > When this method is called from an event handler it is processed asynchronously.

  7. OK, then. Let's change that to:

    "SendCommand(), which is generally synchronous."

    Kean

Leave a Reply to Kean Walmsley Cancel reply

Your email address will not be published. Required fields are marked *