Scripting GUI toolkits

Yesterday I was thinking about Qt and how it needs MOC to provide type safety in the GUI code when I started to think about how it the build process would be simpler if it was possible to just script the GUI and generate the GUI layout code and type definitions at runtime. Are there any GUI toolkits designed like game engines, where you code the bulk of the logic in a scripting language and only call out to C/++ to do the heavy lifting?
Qt needs the use of the moc (Meta-Object Compiler) when compiling classes that use Qt's C++ extensions. For compiling the actual UI, the uic (User Interface Compiler) is used. But ahead-of-time compilation with uic is not the only way to build your UI! The QUiLoader class can be used to load .ui files dynamically at runtime:
https://doc.qt.io/archives/qt-4.8/quiloader.html
https://doc.qt.io/qt-5/designer-using-a-ui-file.html

Furthermore, I think Qt is moving more towards Qt Quick (QML), which uses JSON and JavaScript syntax:
https://doc.qt.io/qt-5/qtquick-index.html

_____________________

If Java is an option for you, there is JavaFX, which uses FXML, an "XML-based language for building a user interface separate from the application logic". FXML can embed scripts written in JavaScript or Groovy:
https://docs.oracle.com/javafx/2/get_started/fxml_tutorial.htm#CHDIBFJD
Last edited on
You're right about the distinction between UIC and MOC. I always forget it, because I just lump all of it with code generation. But my point is that a script generation step at run time would eliminate the need for both.

The QUiLoader class can be used to load .ui files dynamically at runtime
Sure, but then you can't access the widgets with type safety. You need calls to findChild() and casts to the correct types (which you must know).

Imagine for a moment C++ was an interpreted language. You'd start with a bootstrapping sequence that would call the code generators, which would be part of the library rather than separate programs, and that would generate the appropriate classes. The bootstrapper would then link together these classes with your custom UI code and execute them, after doing the usual checks C++ does at compile time.
It'd be nice to have a framework that did this. Not necessarily with C++ (you don't want to run libclang at startup), but with some language that had similar static assurances.

Furthermore, I think Qt is moving more towards Qt Quick (QML), which uses JSON and JavaScript syntax:
Yuck! I'd rather call findChild() and do dynamic_cast everywhere than use JS.

If Java is an option for you, there is JavaFX which uses FXML
I've done similar stuff in the past with C#, WinForms, and P/Invoke. Passing parameters and results between the CLR and the native parts is always a major pain. JNI in my experience is even worse ergonomically than P/Invoke.
Also what I don't like so much is that the native parts become a library for the C#/Java parts, which are the entry point, whereas I much prefer the native parts to take control of the entry point and call into the GUI implementation.
Perhaps the canonical way to script a GUI is with the Tk toolkit library, part of Tcl/Tk. You don’t really need Tcl to script with it, but it helps a lot. Bindings exist for quite a few languages, including C and C++: https://wiki.tcl-lang.org/page/Languages+with+a+Tk+binding
Sure, but then you can't access the widgets with type safety. You need calls to findChild() and casts to the correct types (which you must know).

I don't think so. First of all, you can simply use the widget as a QWidget*, which allows for all the "standard" widget operations to be called. Secondly, if you really need the concrete sub-class, you can use the template-version of QObject::findChild() to get a pointer of the specified type - or null, if no such widget exist.

In other words: dynamic_cast<>() is not usually needed.

But, most important: Qt is heavily based on the "Signals & Slots" concept. So, the standard way of "connecting" a loaded widget (from the .ui file) to your back-end code would be the QObject::connect() method. This works with any generic QObject* pointers; the concrete widget type is not required. Still, it should be perfectly type-safe, as connect() would simply fail, if the parameter types of the specified signal and slot don't match.

Yeah, you still need to know beforehand which Signal and/or Slot of the loaded widget you want to connect to. But, I don't see how this could be avoided. I think the Signals/Slots are part of the "interface" that the loaded widget is required to implement. The only real alternative would be embedding all the callback code into the .ui file, which is exactly what QML and FXML allow (they choose JavaScript as scripting language here).

Yuck! I'd rather call findChild() and do dynamic_cast everywhere than use JS.

I'm not a fan of JavaScript at all. But what else should be used? If you want to embed executable code into a dynamically-loaded UI definition file, then that code must be written in a scripting language that the GUI Framework can interpret at runtime. For JavaScript we have various engines (V8, SpiderMonkey, GraalVM, etc.) that can easily be embedded into a GUI Framework, as a library, to choose from. If you wanted to do the same thing with C/C++ code, you'd have to ship a complete C/C++ compiler alongside with your application. Another advantaged of JavaScript is that it can easily be "sandboxed", which does not apply to C/C++. This means that allowing C/C++ code in the dynamically-loaded UI definition file would be a new attack vector...
Last edited on
Secondly, if you really need the concrete sub-class, you can use the template-version of QObject::findChild() to get a pointer of the specified type - or null, if no such widget exist.
The template version simply static_casts to the specified type, so you need to specify at the point of usage the correct type. That means you need to get it right every time you use that particular widget. If you get it wrong, the compiler can't help you. Compare with the code generated by UIC, which statifies the types and therefore allows the compiler to check the types.

But, most important: Qt is heavily based on the "Signals & Slots" concept. So, the standard way of "connecting" a loaded widget (from the .ui file) to your back-end code would be the QObject::connect() method. This works with any QObject* pointers; the concrete widget type is not required. Still, it should be perfectly type-safe, as connect() would simply fail, if the parameter types of the specified signal and slot don't match.
Doesn't connect depend on the MOC? The signals and slots are specified as character strings, which the library matches to functions via MOCed reflection code.

But what else should be used? If you want to embed executable code into a dynamically-loaded UI definition file, then that code must be written in a scripting language that the GUI Framework can interpret at runtime.
JavaScript is far from the only existing scripting language. It's just the most popular one right now.
TypeScript is very similar to JavaScript, but with the added benefit of static type checks. It can even be used with the same VMs as JS, since it compiles to it.

If you wanted to do the same thing with C/C++ code
Note that I specifically said that was a bad idea.

Another advantaged of JavaScript is that it can easily be "sandboxed", which can't easily be done with C/C++.
That's not actually true. It's true that you can't easily sandbox compiled C/++, because native code is just too general, but C/++ can both be fairly easily sandboxed at the compilation stage by not providing them risky library functions and rejecting programs that use certain constructions[1], and at run time by virtualizing library functions and inserting aggressive checks everywhere. An example of the former could be restricting fopen() to opening a row in an SQLite table, and of the latter could be inserting bounds checking on all array accesses.


[1] Off the top of my head I can think of a few good candidates to forbid: casting between data pointers and function pointers, pointer arithmetic (including pointer[offset]), unions, implicit T (&)[N] -> T * casts... It's not too difficult to come up with a verifiably memory-safe subset of C++.
Last edited on
Doesn't connect depend on the MOC? The signals and slots are specified as character strings, which the library matches to functions via MOCed reflection code.

The scenario I had in mind is that, on the one hand, you have your "backend" classes that are compiled ahead-of-time and, on the other hand, you have the UI which is loaded dynamically from an .ui file at runtime.

Your "backend" classes would have to use the moc, if they contain any Signals or Slots, yes. But that's not really a problem, as they are compiled ahead-of-time. The actual UI, which is loaded dynamically from an .ui file at runtime, does not require the moc. You still will be able to connect() your "backend" to the loaded UI.

Connections between those widgets that are defined inside the .ui file can be set up directly in the .ui file.

That's not actually true. It's true that you can't easily sandbox compiled C/++, because native code is just too general, but C/++ can both be fairly easily sandboxed at the compilation stage by not providing them risky library functions and rejecting programs that use certain constructions[1], and at run time by virtualizing library functions and inserting aggressive checks everywhere. An example of the former could be restricting fopen() to opening a row in an SQLite table, and of the latter could be inserting bounds checking on all array accesses.

Still sounds like a pretty complex endeavor to me. As the developer of a GUI framework you probably want to embed a ready-to-use scripting engine, instead of having to invent something that "complex" yourself.

(BTW: Besides JavaScript/TypeScript other options would be Python, Lua or Guile)
Last edited on
Topic archived. No new replies allowed.