8000 Add zero-argument function for variadic functions by tuket · Pull Request #297 · cimgui/cimgui · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add zero-argument function for variadic functions #297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

tuket
Copy link
@tuket tuket commented May 7, 2025

Hello!
I'm helping maintain ImGui.Net C# binds.
We are having trouble invoking variadic functions such as:

CIMGUI_API void igText(const char* fmt, ...)

These functions allow passing an arbitrary number of arguments.

The problem is that C# doesn't have good support for calling such native functions.
In Windows it kind of works with some tweaks in the C# side. However, that's the only platform supported.
In WebAssembly, for example, it crashes.
This is issue has been reported to .Net years ago but there is no solution so far.

Also we can't call the va_list version of these functions, as the format of that struct is not standardized.

The solution we found is to generate a new version of these functions which has zero variadic arguments (e.g igText0).
Fortunately, all the functions that have variadic arguments are for string formatting purposes, so it's not an issue not being able to specify those arguments as C# already has string formatting built it the language.

This will allow for better compatibility with C#
@sonoro1234 sonoro1234 self-assigned this May 7, 2025
@sonoro1234
Copy link
Contributor
sonoro1234 commented May 7, 2025

Why not just use igText("already formated string") and avoid extra arguments.
I think that it is the way used by https://github.com/HexaEngine/Hexa.NET.ImGui
Also: https://github.com/ImGuiNET/ImGui.NET/blob/8e26803be78b344fd68834817905405b3cdffb94/src/ImGui.NET/Generated/ImGuiNative.gen.cs#L742

@tuket
Copy link
Author
tuket commented May 8, 2025

Hello, thanks for the fast reply!

Indeed! That's what we are doing already in our bindings:

https://github.com/EvergineTeam/ImGui.Net/blob/0b0c73d250192a4b2556e983c3abcddeb92987b0/Generator/Evergine.Bindings.Imgui/Generated/Funtions.cs#L1860

And that works in Windows. But it crashes in WebAssembly.
Let me explain:

In the C side, the function is defined as:

CIMGUI_API void igText(const char* fmt, ...)

In the C# side the binding is defined as:

[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
public static extern void igText([MarshalAs(UnmanagedType.LPUTF8Str)] string fmt);

The way variadic arguments are handled is platform dependent. But it has to, somehow, pass the number of arguments. In Windows, the number of arguments would be implicit from the format string. When you call this function in the C# side as igText("Hello");, it passes only a pointer to the string. But, in Wasm, it should also be passing the number of arguments (i.e 0). Wasm does signature checking at runtime, that's why it catches the error. I don't know if other platforms would manifest other issues.

The way C# provides for passing variadic args is using the __arglist keyword:

[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
public static extern void igText([MarshalAs(UnmanagedType.LPUTF8Str)] string fmt, __arglist);

Unfortunately, as I mentioned earlier, this is only supported in Windows, and the issue has been opened for years :(

@sonoro1234
Copy link
Contributor

As I am not a C# user, I would like to hear @JunaMeinhold and @zaafar opinions in this issue: Is there any other way to solve it, etc?

@JunaMeinhold
Copy link
JunaMeinhold commented May 9, 2025

Hi, i had the same issue and tried to workaround it with "hacks" (trying to build the signature on runtime) and that didn't work out so i made a small library to achieve something similar but in pure c# and in the end i came up with this: (copy pasted from my best practices with Hexa.NET.ImGui)

  1. Minimize GC Pressure by Avoiding String Interpolations and Conversions

Don’t:

int number = 123;
ImGui.Text($"Hello {123} world!");  // Creates unnecessary string allocations

String interpolation and ToString calls can lead to increased garbage collection pressure, as they generate new string objects on the heap.

Do:

byte* buffer = stackalloc byte[128];               // Use a stack-allocated buffer
StrBuilder builder = new(buffer, 128);            // Initialize a memory-safe builder
builder.Reset();                                  // Clear any previous content
builder.Append("Hello "u8);                       // Append UTF-8 literal
builder.Append(number);                           // Append number directly
builder.Append(" world!"u8);                      // Append more text
builder.End();                                    // Finalize the buffer
ImGui.Text(builder);                              // Render the content

Using stack-allocated buffers and the StrBuilder struct ensures: (Can be found in the nuget package Hexa.NET.Utilities)

Reduced heap allocations.
Efficient UTF-8 handling.
Memory safety while building and displaying complex text.

https://github.com/HexaEngine/Hexa.NET.Utilities
https://github.com/HexaEngine/Hexa.NET.Utilities/blob/master/Hexa.NET.Utilities%2FText%2FStrBuilder.cs
https://github.com/HexaEngine/Hexa.NET.Utilities/blob/master/Hexa.NET.Utilities%2FText%2FUtf8Formatter.cs

And i tried to implement a printf in c# but never finished it.

@sonoro1234
Copy link
Contributor

Great thanks for sharing @JunaMeinhold

Basically, if I understood correctly, you are using ig.Text first string argument an skiped variadic arguments completely. I still have doubts about:

But, in Wasm, it should also be passing the number of arguments (i.e 0). Wasm does signature checking at runtime, that's why it catches the error. I don't know if other platforms would manifest other issues.

and so the crashing in WebAssembly?

@JunaMeinhold
Copy link
JunaMeinhold commented May 9, 2025

I don't use variadic arguments at all, i simply pass a byte* (c equivalent of char*), i simply format the string from c# side using raw pointers with stack allocated memory and then pass it.

And __arglist is a undocumented c# feature the way variadic args are handled always varies from compiler to compiler means that there is probably no stable way of making __arglist work since it's closely tied to MSVC and windows.

And i also call them differently, i basically load all exported functions up-front and populate a void** table and then cast it to a delegate* which is basically a c function pointer, idk if that works with WASM because it bypasses some C# safety mechanisms.

	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	internal static void TextNative(byte* fmt)
	{
		#if NET5_0_OR_GREATER
		((delegate* unmanaged[Cdecl]<byte*, void>)funcTable[133])(fmt);
		#else
		((delegate* unmanaged[Cdecl]<nint, void>)funcTable[133])((nint)fmt);
		#endif
	}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0