Hexa.NET.ImGui icon indicating copy to clipboard operation
Hexa.NET.ImGui copied to clipboard

[ARM/ARM64] C# interop broken due to HVA ABI

Open JunaMeinhold opened this issue 3 months ago • 24 comments

On ARM/ARM64, the current Hexa.NET.ImGui bindings break because C# cannot handle homogeneous vector aggregates (HVAs) correctly. Functions that take or return ImVec2, ImVec4, or similar types fail at runtime due to ABI mismatches.

Root Cause:

  • ARM64 ABI passes HVAs in SIMD registers.
  • C# P/Invoke does not support HVAs, leading to corrupted arguments/returns.
  • Works on x86/x64 because ABI rules differ.

Proposed Solution: Develop a C++ → C generator for the bindings:

  1. Automatically generate C ABI-safe wrapper functions from the ImGui C++ API.
  2. Replace vector-type parameters/returns with scalar equivalents or plain structs with const ptr.
  3. Expose only C-style functions to C#, ensuring ABI compatibility across ARM/ARM64 and x86/x64.

Benefits:

  • Eliminates manual binding errors.
  • Ensures ARM64 compatibility.
  • Keeps API surface consistent for managed consumers.

Action Items:

  • [ ] HexaEngine/HexaGen#29
  • [ ] Replace existing vector-type interop in Hexa.NET.ImGui.
  • [ ] Validate ARM64 runtime behavior with generated wrappers.

Notes:

  • This approach is preferable to attempting direct P/Invoke of C++ vectors.
  • Could be extended in the future for other C++ libraries with similar issues.

JunaMeinhold avatar Sep 12 '25 10:09 JunaMeinhold

Update: There is a workaround available on windows, by force building for x86 and using the build-in x86 emulation.

JunaMeinhold avatar Oct 17 '25 07:10 JunaMeinhold

Is that related? https://github.com/cimgui/cimgui/issues/298 Would be the last message a solution?

sonoro1234 avatar Oct 28 '25 10:10 sonoro1234

Hi, the last message would be just a workaround but not a solution, a real solution would be to change the c api

from ImVec2 vec to const ImVec2* vec

And yeah the issue is related

JunaMeinhold avatar Oct 28 '25 15:10 JunaMeinhold

I have a Surface Pro with an arm64 CPU, I could help testing if you want.

JunaMeinhold avatar Oct 28 '25 15:10 JunaMeinhold

Hi, the last message would be just a workaround but not a solution, a real solution would be to change the c api

from ImVec2 vec to const ImVec2* vec

And yeah the issue is related

Could you elaborate with a C function example? igGetColorU32_Vec4 ?

I have a Surface Pro with an arm64 CPU, I could help testing if you want.

Sure!!

Did you noticed it only happens with MSVC and no in mingw-w64?

sonoro1234 avatar Oct 28 '25 17:10 sonoro1234

I will do some more tests today and try both clang and gcc for Windows and report back later

JunaMeinhold avatar Oct 29 '25 06:10 JunaMeinhold

For additional context https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170

JunaMeinhold avatar Oct 29 '25 10:10 JunaMeinhold

Okay small update, could you give me the binary from mingw, because i think that the output dll is not arm64 but rather x86_64 which is just the workaround i mentioned, but it comes with an performance impact, because windows emulates (jits) it to arm64 which adds overhead.

JunaMeinhold avatar Oct 29 '25 11:10 JunaMeinhold

I dont have it!! It was checked in the related cimgui issue by the person posting the issue.

sonoro1234 avatar Oct 29 '25 11:10 sonoro1234

Ah okay sorry. I found out that doing this resolves the issue

Image Image Image Image

JunaMeinhold avatar Oct 29 '25 12:10 JunaMeinhold

I am doing a list with all structs potentially not ABI compatible used as returns or args.

sonoro1234 avatar Oct 30 '25 09:10 sonoro1234

This the list of structs with constructor used as args to functions:

[1]="ImColor*"
        [2]="ImDrawCmd*"
        [3]="ImDrawData*"
        [4]="ImDrawDataBuilder*"
        [5]="ImDrawList*"
        [6]="ImDrawListSharedData*"
        [7]="ImDrawListSplitter*"
        [8]="ImFont*"
        [9]="ImFontAtlas*"
        [10]="ImFontAtlasBuilder*"
        [11]="ImFontAtlasRect*"
        [12]="ImFontBaked*"
        [13]="ImFontConfig*"
        [14]="ImFontGlyph*"
        [15]="ImFontGlyphRangesBuilder*"
        [16]="ImFontLoader*"
        [17]="ImGuiBoxSelectState*"
        [18]="ImGuiComboPreviewData*"
        [19]="ImGuiContext*"
        [20]="ImGuiContextHook*"
        [21]="ImGuiDebugAllocInfo*"
        [22]="ImGuiDockContext*"
        [23]="ImGuiDockNode*"
        [24]="ImGuiErrorRecoveryState*"
        [25]="ImGuiIDStackTool*"
        [26]="ImGuiIO*"
        [27]="ImGuiInputEvent*"
        [28]="ImGuiInputTextCallbackData*"
        [29]="ImGuiInputTextDeactivatedState*"
        [30]="ImGuiInputTextState*"
        [31]="ImGuiKeyOwnerData*"
        [32]="ImGuiKeyRoutingData*"
        [33]="ImGuiKeyRoutingTable*"
        [34]="ImGuiLastItemData*"
        [35]="ImGuiListClipper*"
        [36]="ImGuiListClipperData*"
        [37]="ImGuiMenuColumns*"
        [38]="ImGuiMultiSelectState*"
        [39]="ImGuiMultiSelectTempData*"
        [40]="ImGuiNavItemData*"
        [41]="ImGuiNextItemData*"
        [42]="ImGuiNextWindowData*"
        [43]="ImGuiOldColumnData*"
        [44]="ImGuiOldColumns*"
        [45]="ImGuiOnceUponAFrame*"
        [46]="ImGuiPayload*"
        [47]="ImGuiPlatformIO*"
        [48]="ImGuiPlatformImeData*"
        [49]="ImGuiPlatformMonitor*"
        [50]="ImGuiPopupData*"
        [51]="ImGuiPtrOrIndex*"
        [52]="ImGuiSelectionBasicStorage*"
        [53]="ImGuiSelectionExternalStorage*"
        [54]="ImGuiSettingsHandler*"
        [55]="ImGuiStackLevelInfo*"
        [56]="ImGuiStoragePair*"
        [57]="ImGuiStyle*"
        [58]="ImGuiStyleMod*"
        [59]="ImGuiTabBar*"
        [60]="ImGuiTabItem*"
        [61]="ImGuiTable*"
        [62]="ImGuiTableColumn*"
        [63]="ImGuiTableColumnSettings*"
        [64]="ImGuiTableColumnSortSpecs*"
        [65]="ImGuiTableInstanceData*"
        [66]="ImGuiTableSettings*"
        [67]="ImGuiTableSortSpecs*"
        [68]="ImGuiTableTempData*"
        [69]="ImGuiTextBuffer*"
        [70]="ImGuiTextFilter*"
        [71]="ImGuiTextRange*"
        [72]="ImGuiTypingSelectState*"
        [73]="ImGuiViewport*"
        [74]="ImGuiViewportP*"
        [75]="ImGuiWindow*"
        [76]="ImGuiWindowClass*"
        [77]="ImGuiWindowSettings*"
        [78]="ImRect*"
        [79]="ImTextureData*"
        [80]="ImTextureRef"
        [81]="ImTextureRef*"
        [82]="ImVec1*"
        [83]="ImVec2"
        [84]="ImVec2*"
        [85]="ImVec2i*"
        [86]="ImVec2ih*"
        [87]="ImVec4*"
        [88]="const ImFontConfig*"
        [89]="const ImFontLoader*"
        [90]="const ImRect"
        [91]="const ImVec2"
        [92]="const ImVec2*"
        [93]="const ImVec4"
        [94]="const ImVec4*"

Only five are not pointers but if for example we use ImVec2* and is pointer to cimgui::ImVec2 the layout would be not OK also

so in dear_bindings there is:

CIMGUI_API const char*  cimgui::ImFont_GetDebugName(const cimgui::ImFont* self)
{
    return reinterpret_cast<const ::ImFont*>(self)->GetDebugName();
}

but reinterpret_cast is not magic and does not conversion so it seems no useful?

sonoro1234 avatar Oct 30 '25 16:10 sonoro1234

I would imagine that passing pointers is totally fine, because when i used ScaleAllSizes(ImStyle*) no errors occurred, and they just redefine cpp classes as c structs and that's probably why they use reinterpret_cast.

like i did here

Image Image

JunaMeinhold avatar Oct 30 '25 19:10 JunaMeinhold

like i did here

Wrong code? You mean that ImStyle* used in an ARM64 MSVC compiled works correctly? ImVec2* works also?

sonoro1234 avatar Oct 30 '25 19:10 sonoro1234

ah no sorry for the confusion i just wanted to give an example on why they might use reinterpret_cast, and yeah the ImStyle* method worked fine, but i didn't test the ImVec2* overloads yet, but i think they should work because the HVA ABI thing is only applied on "by value copy" parameters and not on references/pointers, i will do some tests

JunaMeinhold avatar Oct 30 '25 20:10 JunaMeinhold

can confirm, passing ImVec2 as pointer works too. (both const and non const)

Image Image

JunaMeinhold avatar Oct 30 '25 20:10 JunaMeinhold

So, I should add conversions to cpp for

{
        [1]="ImTextureRef"
        [2]="ImVec2"
        [3]="const ImRect"
        [4]="const ImVec2"
        [5]="const ImVec4"}

and from cpp for:

{
        [1]="ImColor"
        [2]="ImRect"
        [3]="ImTextureRef"
        [4]="ImVec2"
        [5]="ImVec2i"
        [6]="ImVec4"}

sonoro1234 avatar Nov 03 '25 10:11 sonoro1234

Could you check branch conv2? https://github.com/cimgui/cimgui/tree/conv2

There is some mixing of ImVec2 and ImVec2_c. This other branch could be better: Also added call_args_old as it is used by LuaJIT-ImGui in generation instead of new call_args https://github.com/cimgui/cimgui/tree/conv3

Changes only done in cimgui and not in other repos until cimgui is ready.

Given that it works in non ARM architectures (althought I dont know in ARM) I think it will be harmless doing merge (at least it wont be worse than previous binding generator)

sonoro1234 avatar Nov 06 '25 08:11 sonoro1234

well there are some issues when i tried building (conv2) https://github.com/HexaEngine/Hexa.NET.ImGui/actions/runs/19357073101/job/55380329872 (conv3) https://github.com/HexaEngine/Hexa.NET.ImGui/actions/runs/19357235665/job/55380793092

JunaMeinhold avatar Nov 14 '25 07:11 JunaMeinhold

I dont have any problem to build conv3 with cmake and also backend test. Cpp and C building.

Also with Actions build.

I dont understand your build. Did you perform any C# generation?

sonoro1234 avatar Nov 14 '25 07:11 sonoro1234

No this is pure cmake building, here the workflow source: https://github.com/JunaMeinhold/cmake-actions/blob/main/.github/workflows/cmake.yml (its a universal cmake build pipeline reusable workflow)

JunaMeinhold avatar Nov 14 '25 08:11 JunaMeinhold

idk if it matters, but i build with freetype support and with wchar32

JunaMeinhold avatar Nov 14 '25 08:11 JunaMeinhold

and it specifically says

MSBuild version 17.14.23+b0019275e for .NET Framework
  1>Checking Build System
  Building Custom Rule D:/a/Hexa.NET.ImGui/Hexa.NET.ImGui/src/CMakeLists.txt
  cimgui.cpp
  imgui.cpp
  imgui_demo.cpp
  imgui_draw.cpp
  imgui_tables.cpp
  imgui_widgets.cpp
  imgui_freetype.cpp
D:\a\Hexa.NET.ImGui\Hexa.NET.ImGui\src\cimgui.cpp(683,12): error C2664: 'void igTextColored(const ImVec4_c,const char *,...)': cannot convert argument 1 from 'ImVec4' to 'const ImVec4_c' [D:\a\Hexa.NET.ImGui\Hexa.NET.ImGui\src\build\cimgui.vcxproj]
      D:\a\Hexa.NET.ImGui\Hexa.NET.ImGui\src\cimgui.cpp(683,45):
      No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
      D:\a\Hexa.NET.ImGui\Hexa.NET.ImGui\src\cimgui.cpp(673,17):
      see declaration of 'igTextColored'
      D:\a\Hexa.NET.ImGui\Hexa.NET.ImGui\src\cimgui.cpp(683,12):
      while trying to match the argument list '(ImVec4, const char *)'
  
  Generating Code...

JunaMeinhold avatar Nov 14 '25 08:11 JunaMeinhold

I see. You are using CIMGUI_VARGS0. I have to check. Should work now.

There is https://github.com/cimgui/cimgui/pull/311 and https://github.com/cimgui/cimgui/issues/309 to follow up.

@JunaMeinhold tag 1.92.4dock_conv and 1.92.5dock work with new generator. cimgui extensions are adapted also. Does everything work for you?

sonoro1234 avatar Nov 14 '25 08:11 sonoro1234