-1

I generated bindings for a native library but realized that imports differ between 32-bit and 64-bit.

Problem:

Not only the entry point differ, but also the calling convention:

32-bit import:

[DllImport("implot", EntryPoint = "??0ImPlotPoint@@QAE@XZ", CallingConvention = CallingConvention.ThisCall)]

64-bit import:

[DllImport("implot", EntryPoint = "??0ImPlotPoint@@QEAA@XZ", CallingConvention = CallingConvention.Cdecl)]

Since these are compile-time constants, there's no way to have an if (64bit) then ... else ... for these imports.

What I've been thinking about:

Using ordinals instead of decorated names as entry points:

That won't work because they don't necessarily point to the same export.

Defer stuff to two inner classes, e.g. NativeMethods32, NativeMethods64:

One would have to put bit-ness checks everywhere to dispatch to the right class, tedious as well.

Generate two inner managed assemblies, one 32-bit, one 64-bit:

Have the AnyCPU assembly dynamically load the appropriate managed assembly at runtime.

Question:

Is there an effective, proven pattern to tackle such problem?

aybe
  • 15,516
  • 9
  • 57
  • 105
  • Give different names to the two C# import functions, and have a 3rd function that will call one or the other, similar to this (but with same dll name): https://stackoverflow.com/a/23216851/403671 – Simon Mourier Dec 20 '22 at 08:23
  • 1
    The best way to handle this is not to. Stick to 64 bit. – David Heffernan Dec 20 '22 at 08:27
  • @SimonMourier This is easy but that check at every call is just silly in terms of performance. – aybe Dec 20 '22 at 08:28
  • @DavidHeffernan I could, my project is 64-bit ready, but having 32-bit support would be nice too. – aybe Dec 20 '22 at 08:29
  • 1
    No, in release, in most cases the JIT compiler will get rid of the test (it knows it's running x64 or not) and tail call to the corresponding import https://i.imgur.com/HQIlPAY.png – Simon Mourier Dec 20 '22 at 08:52
  • Nobody should recommend pinvoking C++ class member functions. Best to take the approach shown [here](https://github.com/goatcorp/ImGui.NET/tree/930d9067653c3ea6e7992255a69170ecadcde144), relying on a C wrapper for this C++ library. – Hans Passant Dec 20 '22 at 09:20
  • 1
    @SimonMourier That's an interesting piece of information! – aybe Dec 21 '22 at 02:02
  • 1
    @HansPassant While I agree with you on that one, generated stuff with that library is schizophrenic: different vector classes for value types and reference types, additional classes with a `Ptr` suffix, zero XML documentation... My bindings do address all that and are more user friendly, at the expense of what you've mentioned of course. – aybe Dec 21 '22 at 02:19

1 Answers1

0

I solved the problem rewriting the entire stuff with Roslyn to produce an AnyCPU module.

Like so:

public class Whatever
{
    internal partial struct __Internal
    {
        // keep fields
        // remove methods
    }

    internal partial struct __Internal32
    {
        // remove fields
        // insert 32-bit methods
    }
    
    internal partial struct __Internal64
    {
        // remove fields
        // insert 64-bit methods
    }
    
    public static void Test()
    {
        if (IntPtr.Size == 4)
        {
            __Internal32.Test();
        }
        else
        {
            __Internal64.Test();
        }
    }
}

The result works very well, the platform target can be switched as usual and it just works.

aybe
  • 15,516
  • 9
  • 57
  • 105
  • 1
    Using `Environment.Is64BitProcess` is arguably a bit cleaner ([and still JIT-optimizable](https://sharplab.io/#v2:EYLgHgbALANALiAhgZwLYB8ACAmAjAWAChMBmAAhzIGEyBvIsxi8zKMgWQAoBKOhpgQEsAZmU4BRAHYA3QQCcA9pNQBTSXAB0ASWTQAQoLgAFRQGMVyZL3qEBdpplwBOTgAYwpxMJXBEwFQAy3ADc/PYAvowqADbIKny29naOLu4AJiqIaf4qwkGhiRFhZOFE4UA)). – Jeroen Mostert Jan 09 '23 at 22:58
  • Definitely makes sense, I will upgrade my code rewriter to do so, thank you :) – aybe Jan 09 '23 at 23:13