[ARM/ARM64] C# interop broken due to HVA ABI
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:
- Automatically generate C ABI-safe wrapper functions from the ImGui C++ API.
- Replace vector-type parameters/returns with scalar equivalents or plain structs with const ptr.
- 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.
Update: There is a workaround available on windows, by force building for x86 and using the build-in x86 emulation.
Is that related? https://github.com/cimgui/cimgui/issues/298 Would be the last message a solution?
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
I have a Surface Pro with an arm64 CPU, I could help testing if you want.
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?
I will do some more tests today and try both clang and gcc for Windows and report back later
For additional context https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170
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.
I dont have it!! It was checked in the related cimgui issue by the person posting the issue.
Ah okay sorry. I found out that doing this resolves the issue
I am doing a list with all structs potentially not ABI compatible used as returns or args.
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?
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
like i did here
Wrong code? You mean that ImStyle* used in an ARM64 MSVC compiled works correctly? ImVec2* works also?
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
can confirm, passing ImVec2 as pointer works too. (both const and non const)
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"}
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)
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
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?
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)
idk if it matters, but i build with freetype support and with wchar32
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...
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?