From fe6e9ce30b9e8ed92574b5a7ebca9d39729eeb7f Mon Sep 17 00:00:00 2001 From: mappu Date: Mon, 18 Nov 2024 19:23:30 +1300 Subject: [PATCH 1/3] genbindings/qapplication: use LockOSThread() to bind Qt main thread --- cmd/genbindings/emitgo.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/genbindings/emitgo.go b/cmd/genbindings/emitgo.go index 37861fc8..f5c72b34 100644 --- a/cmd/genbindings/emitgo.go +++ b/cmd/genbindings/emitgo.go @@ -272,6 +272,12 @@ func (gfs *goFileState) emitParametersGo2CABIForwarding(m CppMethod) (preamble s tmp = append(tmp, "argc, &argv[0]") + // Additional quirk for QApplication constructor: bind to OS thread + gfs.imports["runtime"] = struct{}{} + preamble += "\n" + preamble += "runtime.LockOSThread() // Prevent Go from migrating the main Qt thread\n" + preamble += "\n" + } else if skipNext { // Skip this parameter, already handled skipNext = false From 5955fdb6efbd873bb6a12a41a265e1964f9e0738 Mon Sep 17 00:00:00 2001 From: mappu Date: Mon, 18 Nov 2024 19:34:48 +1300 Subject: [PATCH 2/3] qt: rebuild (call runtime.LockOSThread in QApplication ctors) --- qt/gen_qapplication.go | 6 ++++++ qt/gen_qcoreapplication.go | 6 ++++++ qt/gen_qguiapplication.go | 6 ++++++ qt6/gen_qapplication.go | 6 ++++++ qt6/gen_qcoreapplication.go | 6 ++++++ qt6/gen_qguiapplication.go | 6 ++++++ 6 files changed, 36 insertions(+) diff --git a/qt/gen_qapplication.go b/qt/gen_qapplication.go index 7b10b2fd..8f7b95ee 100644 --- a/qt/gen_qapplication.go +++ b/qt/gen_qapplication.go @@ -61,6 +61,9 @@ func NewQApplication(args []string) *QApplication { for i := range args { argv[i] = C.CString(args[i]) } + + runtime.LockOSThread() // Prevent Go from migrating the main Qt thread + ret := C.QApplication_new(argc, &argv[0]) return newQApplication(ret) } @@ -74,6 +77,9 @@ func NewQApplication2(args []string, param3 int) *QApplication { for i := range args { argv[i] = C.CString(args[i]) } + + runtime.LockOSThread() // Prevent Go from migrating the main Qt thread + ret := C.QApplication_new2(argc, &argv[0], (C.int)(param3)) return newQApplication(ret) } diff --git a/qt/gen_qcoreapplication.go b/qt/gen_qcoreapplication.go index 79323e74..f9957ee0 100644 --- a/qt/gen_qcoreapplication.go +++ b/qt/gen_qcoreapplication.go @@ -59,6 +59,9 @@ func NewQCoreApplication(args []string) *QCoreApplication { for i := range args { argv[i] = C.CString(args[i]) } + + runtime.LockOSThread() // Prevent Go from migrating the main Qt thread + ret := C.QCoreApplication_new(argc, &argv[0]) return newQCoreApplication(ret) } @@ -72,6 +75,9 @@ func NewQCoreApplication2(args []string, param3 int) *QCoreApplication { for i := range args { argv[i] = C.CString(args[i]) } + + runtime.LockOSThread() // Prevent Go from migrating the main Qt thread + ret := C.QCoreApplication_new2(argc, &argv[0], (C.int)(param3)) return newQCoreApplication(ret) } diff --git a/qt/gen_qguiapplication.go b/qt/gen_qguiapplication.go index cf486621..68b0714c 100644 --- a/qt/gen_qguiapplication.go +++ b/qt/gen_qguiapplication.go @@ -53,6 +53,9 @@ func NewQGuiApplication(args []string) *QGuiApplication { for i := range args { argv[i] = C.CString(args[i]) } + + runtime.LockOSThread() // Prevent Go from migrating the main Qt thread + ret := C.QGuiApplication_new(argc, &argv[0]) return newQGuiApplication(ret) } @@ -66,6 +69,9 @@ func NewQGuiApplication2(args []string, param3 int) *QGuiApplication { for i := range args { argv[i] = C.CString(args[i]) } + + runtime.LockOSThread() // Prevent Go from migrating the main Qt thread + ret := C.QGuiApplication_new2(argc, &argv[0], (C.int)(param3)) return newQGuiApplication(ret) } diff --git a/qt6/gen_qapplication.go b/qt6/gen_qapplication.go index 47f435a2..345a7bba 100644 --- a/qt6/gen_qapplication.go +++ b/qt6/gen_qapplication.go @@ -53,6 +53,9 @@ func NewQApplication(args []string) *QApplication { for i := range args { argv[i] = C.CString(args[i]) } + + runtime.LockOSThread() // Prevent Go from migrating the main Qt thread + ret := C.QApplication_new(argc, &argv[0]) return newQApplication(ret) } @@ -66,6 +69,9 @@ func NewQApplication2(args []string, param3 int) *QApplication { for i := range args { argv[i] = C.CString(args[i]) } + + runtime.LockOSThread() // Prevent Go from migrating the main Qt thread + ret := C.QApplication_new2(argc, &argv[0], (C.int)(param3)) return newQApplication(ret) } diff --git a/qt6/gen_qcoreapplication.go b/qt6/gen_qcoreapplication.go index d66cfea2..833f6b54 100644 --- a/qt6/gen_qcoreapplication.go +++ b/qt6/gen_qcoreapplication.go @@ -59,6 +59,9 @@ func NewQCoreApplication(args []string) *QCoreApplication { for i := range args { argv[i] = C.CString(args[i]) } + + runtime.LockOSThread() // Prevent Go from migrating the main Qt thread + ret := C.QCoreApplication_new(argc, &argv[0]) return newQCoreApplication(ret) } @@ -72,6 +75,9 @@ func NewQCoreApplication2(args []string, param3 int) *QCoreApplication { for i := range args { argv[i] = C.CString(args[i]) } + + runtime.LockOSThread() // Prevent Go from migrating the main Qt thread + ret := C.QCoreApplication_new2(argc, &argv[0], (C.int)(param3)) return newQCoreApplication(ret) } diff --git a/qt6/gen_qguiapplication.go b/qt6/gen_qguiapplication.go index 737cf43d..23fea611 100644 --- a/qt6/gen_qguiapplication.go +++ b/qt6/gen_qguiapplication.go @@ -53,6 +53,9 @@ func NewQGuiApplication(args []string) *QGuiApplication { for i := range args { argv[i] = C.CString(args[i]) } + + runtime.LockOSThread() // Prevent Go from migrating the main Qt thread + ret := C.QGuiApplication_new(argc, &argv[0]) return newQGuiApplication(ret) } @@ -66,6 +69,9 @@ func NewQGuiApplication2(args []string, param3 int) *QGuiApplication { for i := range args { argv[i] = C.CString(args[i]) } + + runtime.LockOSThread() // Prevent Go from migrating the main Qt thread + ret := C.QGuiApplication_new2(argc, &argv[0], (C.int)(param3)) return newQGuiApplication(ret) } From 281b1a832ce87868d62a21b5199a3d4e62f4b136 Mon Sep 17 00:00:00 2001 From: mappu Date: Mon, 18 Nov 2024 19:35:02 +1300 Subject: [PATCH 3/3] doc/README: add note about LockOSThread affinity --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 83932ae5..8f0a534e 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,8 @@ Qt class inherited types are projected as a Go embedded struct. For example, to - When a Qt subclass adds a method overload (e.g. `QMenu::addAction(QString)` vs `QWidget::addAction(QAction*)`), the base class version is shadowed and can only be called via `myQMenu.QWidget.AddAction(QAction*)`. +The Go runtime migrates goroutines between OS threads, but Qt expects fixed OS threads to be used for each QObject. When you first call `qt.NewQApplication` in MIQT, that will be considered the [Qt main thread](https://doc.qt.io/qt-6/thread-basics.html#gui-thread-and-worker-thread) and will automatically signal the Go runtime to bind to a fixed OS thread using `runtime.LockOSThread()`. + Some C++ idioms that were difficult to project were omitted from the binding. But, this can be improved in the future. ### Q6. Can I use Qt Designer and the Qt Resource system?