dearimgui / dear_bindings

C header (and language binding metadata) generator for Dear ImGui
MIT License
221 stars 12 forks source link

No way to call variadic functions / functions with va_list from some languages #45

Closed ZimM-LostPolygon closed 8 months ago

ZimM-LostPolygon commented 9 months ago

I've recently started writing a new C# wrapper for Dear ImGui, and things were going great until I've started working on function declarations.

Variadic functions / va_list are both quite C/C++ focused concepts, and in a lot of languages, there's simply no way to call into those. In the case of Dear ImGui, I'm talking about functions like:

CIMGUI_API void cimgui::ImGui_Text(const char* fmt, ...)
CIMGUI_API void cimgui::ImGui_TextV(const char* fmt, va_list args)

Those can't be called from C#, at all. What ImGui.NET has done is simply ignored the problem by... Not allowing any args to be passed:

public static extern unsafe void igText(byte* fmt);

I'm not sure how cimgui is handling it in the first place, but if you ask me, that's a pretty hair raising "solution" in the end, since it makes it extremely prone to memory violations, it's super easy to forget to escape the % etc etc.

Unfortunately, there is also no "good" solution to this. The only thing that can be done is in addition to variadic functions/va_list, there would also be N functions like

CIMGUI_API void cimgui::ImGui_Text(const char* fmt, ImGuiFunctionArg arg1)
CIMGUI_API void cimgui::ImGui_Text(const char* fmt, ImGuiFunctionArg arg1, ImGuiFunctionArg arg2)
CIMGUI_API void cimgui::ImGui_Text(const char* fmt, ImGuiFunctionArg arg1, ImGuiFunctionArg arg2, ..., ImGuiFunctionArg argN)

Where N is some arbitrary "sane" number like 10, and ImGuiFunctionArg is a union like:

union ImGuiFunctionArg_t {
  int     i;
  float   f;
  char    c;
  char   *s;
} ImGuiFunctionArg;
ocornut commented 9 months ago

My intuition is that in those situations you’d be encouraged to perform the formatting in C# side. You would only refer to printf-style format string in functions like SliderFloat where you can override the fomat.

ZimM-LostPolygon commented 9 months ago

My intuition is that in those situations you’d be encouraged to perform the formatting in C# side. You would only refer to printf-style format string in functions like SliderFloat where you can override the fomat.

Yes, but for that to really work, every function that currently accepts a format + varargs would need to have a variant that doesn't do formatting, and seems like currently very few have that. For example, there is a Text/TextUnformatted pair, but TextColored doesn't have a corresponding TextColoredUnformatted variant.

There's also a slight performance benefit to doing the formatting on the ImGui side, since while you can do the formatting without heap allocation in C#, it's way more cumbersome. Although the wrapper can also take care of that by always passing %s as the format and then doing the actual formatting in whatever way, but then again, some kind of way to call varargs functions is needed.

I'll take a stab at adding the support in the manner described in the issue, enabled with a command-line flag.

ShironekoBen commented 9 months ago

Adding -argument variants that take a union is technically possible, probably, but I worry it would clutter the API a lot and unless I'm missing a clever trick for doing it also require some fairly unpleasant code inside the wrapper to decode the format string, pull the parameters out of the unions and then push them back into varargs in a manner that the C++ code can then decode again.

It doesn't feel like it would be too difficult to auto-generate "non-formatted" variants if required, based on the presence of the IM_FMTARGS/IM_FMTLIST macros, though. That sounds like it might well be worth doing.

In terms of where to do the formatting, I suspect any possible performance gain from avoiding heap allocations would be cancelled out both by the extra parse+repack step mentioned about, and also by the usability implications of having to manually use ToString()/etc to get everything C#-side into a primitive format that C-style printf() understands, though.

ocornut commented 9 months ago

All the format string supporting function also have a fast path for when the format is %s or %.*s, which skips the formatting.

ZimM-LostPolygon commented 9 months ago

All the format string supporting function also have a fast path for when the format is %s or %.*s, which skips the formatting.

That still implies having to pass both the format string and the actual string, but since the actual string is part of the varargs, and functions with varargs can't be called from C#, we circle back to needing some kind of function variant without the varargs.