-
Notifications
You must be signed in to change notification settings - Fork 19
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
Reuse the C ABI bindings to generate Qt bindings for other non-Go languages #21
Comments
Played around a bit with this idea in arnetheduck#1 - works fine - miqt codebase makes it very easy to adapt/copypaste around, great stuff ;) Here's a laundry list of questions and random thoughts that came to my mind while doing the POC, in no particular order:
Regarding non-go languages, one candidate is
As a side note, the above is more or less how the existing Nim QML bindings are structured as well, via DOtherSide where a single C binding is used to support multiple languages - it has fallen out of maintenance however, so here we are. |
This is necessary for Qt's ecosystem, namely event handling. Without the virtual methods, customization is closed off. I don't know if this is true for QML since I'm not a huge fan of it.
I haven't really publicized it anywhere too large but I've been working on releasing bindings for C and Zig. I have both in a working state for Qt 6.4 but I need to get some things sorted for Qt 6.7 along with more examples and some other things before I'm comfortable saying more publicly or releasing them to the masses.
On my end, I was comfortable leaving things in Go so as to try to keep with "upstream" (being Go bindings) so that future improvements would be simpler to implement. Unfortunately, I've made quite a few structural changes so it's not as easy as rebasing but I highly value the idea of something as close to a shared codebase for such a large and complex undertaking where the goal is consumption rather than maintenance. (I'm also hoping to be able to backport some of the changes/decisions to benefit the Go bindings as well.) Still, I've mostly ended up with two hard forks. Of course, you are free to do what you feel is best. I look forward to seeing what you come up with! |
Ah sorry, I meant the inheritance itself, not the methods, ie the
Nice! Looking forward to seeing them come out. Nim is somewhat closer to zig when it comes to wrapping things (vs cgo / go that brings more opinion to the table) so this might be a better starting point for nim as well.
Indeed, and interesting. I'll see if I can extract some minor things from the nim poc as well - with luck, we'd be going in similar directions and then we can see how far off we end up. One thing I wanted to reach for while messing with the poc was some basic template engine to make the generated code pop out more vs the go infrastructure of various quotes and plusses, but again not a critical concern for hopefully write-once stuff like this. |
I've thought about this quite a bit. Whether there will be a larger audience for consuming these bindings/wrappers or if there will be a larger audience for porting them. Languages like Nim, D, C3, etc. have crossed my mind for where interested folks might find value as long as they don't mind a little bit of Go.
While trying to document some of this for some others, I've realized the large intersection of knowledge required to execute on this: C++, Qt, Go, C ABI, and then whatever target language. That is a complex set and Qt is a moving target with every passing version. This doesn't include the auxiliary tooling like Clang. Just thinking about it leads to burnout. 😅 But as for write-once, I don't know if that is possible. Given what has just changed with Clang (in the issue you recently opened) and upstream Qt versions able to change/introduce/deprecate significant portions of the toolkit, maintenance will be required. Still, as I've told @mappu, these bindings strike me as the best I've seen for Qt to hopefully minimize the long-term maintenance burden. The bindings for Qt 6.4 mostly work from the standpoint of being able to be dynamically bound on systems with Qt 6.7 but that is likely to remain true for the core components as long as they do not change too much. The less-traveled components may not be so stable. It remains to be seen over time how this will play out but I am optimistic. The main thing I've done to further try to simplify things is only focus on Qt 6 since Qt 5 will be deprecated by the middle of 2025 and these are novel bindings for the respective lanuages. I believe the Go bindings will maintain support for Qt 5. |
Pursuant to these, I think I'm going to alter my plans and get the Qt 6.4 bindings/wrappers ready for release instead. I don't like that it will have a short-term limitation of contributors to the library of requiring Qt 6.4 but the resulting bindings/wrappers will be mostly usable for Qt 6.4+ and will allow for unblocking other interested parties who want to use a base closer to C ABI. If it means more collaborators, I think it will be worth it. |
Agree here - this is where my loose thought of having this in C/C++ came from, to reduce the surface area of "getting started" with contributions - targeting C in particular ensures that the core bindings are "functional enough" for all downstream languages without requiring knowledge in any one that you don't already have to know (assuming you know c++) - I maintain an llvm-based compiler for Nim, where the bindings are done more or less like this, ie a single C++->C binding set and each language (zig as well btw!) builds on top of that though the sheer scale of Qt makes it a different beast somewhat. Anyway, this was mostly a food-for-thought comment that I thought might be relevant to bring up in an issue like this.
The Nim bindings would eventually be in this boat too I suspect as they would be used in part to make the qt5/6 transition for some projects, should the PoC move ahead - but there's a few ifs and buts to clear before that happens ;) |
Miqt has grown some (small) handwritten packages; other language bindings will need to reimplement qt{,6}/mainthread. To allow mainthread/ to be automatically generated, the bindings layer would have to support whatever is missing for QMetaObject::invokeMethod. I think this is the same issue as #122, but haven't looked into it yet. |
As much as I want to semi-rush this, I'm thinking of a mid-February release date to follow on the release of Zig 0.14.0 (edit: delayed until March). I don't have anything even pushed online yet but I do want to share with both of you if I have something ready beforehand since I think it might address some of the recently opened issues. |
Two things mainly:
|
I've mostly been extracting small patches and issues from the nim poc that seemed like overall improvements no matter if the poc goes ahead or not - lmk if any of them run counter to your work - they're all entirely discardable from my point of view since it's just prototype code - I think the most interesting thing that would make things a lot easier is actually the override change described here: #133 (comment) - that would also simplify calling the base class method because the bindings can expose a "default" implementation with the base class implementations as the "default" pointers - code wanting call super then has as an easy option to do so but above all, I think this would simplify memory management / lifetimes of callback closures. |
Oh, I passed the "difficult rebase" threshold a long time ago. 😅 At this point, I would need to hand-patch anything that can't be applied but that's on me. Similar to you, I started small and then... I kept most of the Go name mangling and I really tried to keep the codebases as similar as possible so that code could hopefully be shared between all of the projects.
I actually took an entirely different approach here. I'm using the object's pointer for all of the inherited methods since the vtable alignment works as long as it is upcasting (which is all the bindings/wrappers make available by default). Maybe I'm wrong here. 😅 I'd already removed I initially started with just implementing the signals/slots in C ABI instead of the Go callbacks, removing the Go headers (similar as you did), etc. etc. It spiraled a lot more than I wanted but it is also reasonably simple to grok the code and also maintain (🤞🏼). Assuming that I haven't categorically screwed this up, I'm hopeful that some of these changes are well-received. I will admit that it is a little bit of a relief to see that you've identified some of the same things that I have. Hopefully, I haven't completely wrecked this. 😅 At the moment, I'm primarily working on getting examples done, documentation, bug fixes, packaging, and keeping an eye on changes upstream. With this additional time before a release, I'm thinking I'll implement some form of the TLDR: I started with a soft fork that eventually became a hard fork but that shouldn't stop us all from being able to collaborate well. |
This is probably inevitable for any binding work, ie that there's a home for hand-written stuff to deal with the exceptions and corner cases so might as well make them "regular" in the generator somehow.
As it happens "initial capital" is used for types instead of "public" in nim, so that cause a little bit of mess - easier to start from the Qt source names than "undo" what the go bindings need, also for reasons of Nim can offer almost 1:1 naming with respect to upstream Qt because it supports overloading, so longer term any "conflict renaming" would also have to live in the "language-specific" section for things to work well. The point of the naming PR was more that this hopefully is "not unreasonable" change for the go bindings (it seems pretty clean code-wise at least) that at the same time has the potential to make future patchwork easier.
In playgrounds like this, I try maintain my changes as a set of commits on top of the upstream so that I can upstream features individually and then rebase and reorder - this is still viable in the case of nim-miqt, but ymmv of course. There's a few more commits in that branch (which keeps getting rebased) that might be of general interest, now or eventually. |
I actually utilize mostly the same workflow... typically. I had a rough rebase in around November though so I broke things off at that point. Millions of lines of code at this point and all striving for mostly the same goals but in even slightly different ways. Even C and Zig are their own hard forks after I've split them up. The Venn diagram at least remains intact though. |
one more: access to |
I looked into this as a possible option when implementing the qt/mainthread packages. However, the moc tool produces a header that is tied to a specific semver-minor version of Qt. The same moc output (allegedly) cannot be used for both Qt 6.4 and Qt 6.8. That makes it difficult to support multiple Qt 6 versions from one codebase. |
It's abi-backwards-compatible within all minor releases - ie the data generated by moc 6.4 can be used with qt 6.8 but not the other way around. If you only generate 6.0-level data, you're fine for all 6.x releases - this is aided by a Also, the changes are decently small, even between the latest major releases - ie I looked at 5.15 vs 6.x and while they're different, it's mostly detail, the overall structure remains the same. |
First, a little success story: the miqt-based nim poc now has reached the quality level that it can replace (and extend) DOtherSide (the "manual" C binding), verifying this with a port of an actual app: status-im/status-desktop#17270 Separately, a second POC of the nim bindings is now available which further expand on treating the C bindings as a "stand-alone" binding and then importing those into a language projection. There are two major / notable differences here to consider:
|
Hi guys, they're finally public!
There's a lot to catch up on and a number of things to discuss. I'm not sure where to start but I have been reading through the issues. @arnetheduck, I think you'll see that the memory allocations for more manual cleanups match what you're working on and hopefully they can be instructive for you there (assuming I haven't screwed anything up). I don't think all the issues you've identified have been addressed. I ended up separating out the virtual classes to separate files because I'd experimented with virtual inheritance among the virtual classes. I got it working but ended up removing it because it didn't appear to add any value (and I was worried about the long-term maintenance of likely future diamond inheritance problems). I saw the work being done on Overall, I got very greedy with the approach to the libraries, trying to get as much of the bindable surface areas as possible. This resulted in a lot of ugly hacks and brute force solutions. Apologies. The cleanup work is in flight to get the code back to a more readable state. If you happen to run the bindings generator, you'll also notice that the runtime is extended. It was originally six seconds when I started and right now, the C bindings take about a minute while Zig is about half of that. The timing will vary based on your hardware but the point is that these are taking much longer than the upstream. I'm also planning on some optimizations there but it wasn't urgent for the release so it's been left that way for now. I'll try to dig into the Clang changes again and hopefully have something closer to a resolution. I have some ideas I want to plug through based on the last look I did. Hopefully we can put off dealing with Clang 20 for a little while (or it's hopefully a less troublesome upgrade from 19.x). My goal was to spend more time consuming than maintaining but right now, I am deep in the ledger in the wrong direction. While that won't likely change in the short-term, I can't wait to get to a point where I can really put all of these libraries through their paces. The only other note is that I think Debian 13 will ship with Qt 6.8 if things go well. That will probably be the next version that I target and then most likely I'll wait for Debian 14, patching what might need to be patched along the way until then. Qt 6.9 is still in beta and will likely be released before Debian 13 but hopefully it'll be okay. Up next is Nim! |
Funny coincidence - I pushed https://github.com/seaqt/seaqt-gen just yesterday (with links to C/Nim repos for Qt5/6), along with a few Nim projects and bindings - made it an org so that anyone with a qt binding itch to scratch can be part of it. Happy to sift through your changes to see where we align, but I only see the generated code in those repos rather than the gen itself. Will the gen changes you've made be pushed as well? Re Qt versions, one thing Qt does well is ABI stability - ie the only reason to use a later version is if you really need the new features but there are advantages to using the lowest possible version as well (6.2 for example, which is LTS) since binaries will then be compatible with all reasonable qt6 versions. At some point I imagine the easiest thing to do might actually be to create a custom docker image with each minor qt version built from source on some random fixed distro so that it's not tied to any particular debian release schedule, though that's far down the prio list for me at least. That would fix clang at a specific version, much like currently. The major "gap" right now is ownership tracking - ie when you call something like The other thing is exposing member data - this is necessary for meta-object support, ie Anyway, congrats! |
Everything should be under the |
While they are certainly more stable, I have noticed breaking changes in the ABI for Qt. Enum values changing, function headers, etc. etc. I've seen some segfaults on distros with Qt 6.8 and I haven't dug deep yet, but I'm assuming it's something along those lines leading to the different outcomes. The stable/mature paths are good though. It at least meant that most of the examples compiled easily. 😅
I was only hoping to release updates every few versions unless there was a drastic need. Debian's release schedule and stability line up pretty nicely for that. Users on faster-moving distros might feel differently. We'll see where the road takes us!
I saw this and wasn't sure what to make of it. I still have a lot to learn when it comes to Qt. The one thing I know would be helpful that I didn't get done (the hack is currently commented out) are the converter and mutable view functions for QMetaType. I think they'll be easier to bind in newer Qt but I could be wrong. I started down the path of function pointers in general so that it wouldn't need to be a hack but didn't get that done yet either. There's so many fascinating things about working on these bindings. I've learned a lot and while I wish it was less painful, I'm still looking forward to doing more. I really want to get through the backlog of apps I want to write too. |
https://woboq.com/blog/how-qt-signals-slots-work.html is a great resource for metaobject internals! They're needed if you want the users of your bindings to declare their own signals and slots and have these accessible from QML interfaces for example. |
Ah. I saw your QML work. I don't know if that's a target yet. |
I keep thinking if the vtable approach is where things should go despite the complications that it adds. I opted for a simpler approach because I want maintenance to be easy so that I have time to develop some applications but if there's core functionality that is nerfed because of that, it might not be worth it. We'll see where this takes us... The directory structure is interesting. I can see the appeal. |
@rcalixte @arnetheduck Congratulations to both of you, on the releases! You've both been incredibly helpful and it's been a pleasure working together. |
the https://github.com/seaqt/seaqt-gen/tree/seaqt-c branch now contains a version of genbindings that produces only C code (ie no go/nim/etc) - on that branch, there is a bunch of commits that could be backported to other languages potentially, though they obviously require adaptations to the language projection as well. |
No description provided.
The text was updated successfully, but these errors were encountered: