[2022.11] Qt5/Qt6 両対応.
Qt Creator を Qt5/Qt6 両対応にする
[2022.11] Fedora 37
Qt の開発環境は Qt Creator か, GUI フォームだけ Qt Designer を使う。通常, 前者.
Fedora 37 の Qt Creator は, 初期設定では Qt5 のみ使うようになっている。Qt5/Qt6 両対応にする.
Fedora でパッケージされているのは Qt Creator v8.0.1. これは Qt5 でビルドされている。依存するのは qt5-qtbase-devel パッケージ。これと別に, qt6-qtbase-devel-6.3.1-4.fc37.x86_64 をインストールする。
Qt Creator の画面で, Edit > Preferences で設定画面を開く. 左側の Kits を選ぶと, Kits タブに Desktop (default) が表示され, Qt version: に利用可能な Qt が選択できる。ここで, 右の [Manage...] ボタンをクリック.
Qt Versions タブに移る. Qt 5.15 のみが表示されているはず。[Add...] ボタンで Qt6 を追加する。
これだけでいけるはず。
ほかには, https://www.qt.io/download-open-source から Qt Online Installer をダウンロードする方法もある。Qt5.15 LTS, Qt6.2 LTS, Qt6.4, Qt Creator 8.0, Qt Design Studio v3.8 をインストールできる。Qt Design Studio は, Qt Designer とは別の, 2D/3D のUIの見た目を定義するソフトウェアのようだ。QML と C++ コードを生成。Fedora ではパッケージされていない。
試してみたが, 上手く Fedora ディストリビューションの環境に馴染ませることができなかった。Fedora パッケージを使おう。
何を作るか
前ページと同じものを Qt5/Qt6 で作る。
Qt5, Qt6 は, ソースコードレベルで互換性が高い。非互換な部分も, workaround で両対応にできる。バイナリレベルでの互換性はない。CMakeLists.txt
ファイルで、どちらでビルドするか決める.
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets)
以下のサンプルは、どちらでもビルドできる。
スレッドをつくる
DNS lookup, read()
がブロックするので、スレッドで並列化する。ソケットは blocking モードのままとする。非同期 I/O (asynchronous I/O) だからといって、non-blocking モードにしなければならないわけではない。
簡単に, QThread
クラスから派生させる。スレッドを起動すると run()
がコールバックされる.
connect_thread.h
C++
-
-
-
-
-
-
- class ConnectThread: public QThread
- {
- Q_OBJECT
-
- public:
- ConnectThread(QObject* parent = nullptr);
- ~ConnectThread();
-
- void requestNewFortune(const std::string& hostName, int port);
-
- protected:
-
- virtual void run() override;
-
- signals:
- void fortuneGot(const QString& fortune);
- void error(int socketError, const QString& message);
-
- private:
- std::string hostName;
- int port;
- };
起動する
メインスレッド側から, スレッドクラスのメソッドを呼び出す.
mainwindow.cpp
C++
-
- void MainWindow::on_pushButton_clicked()
- {
- if (m_thread)
- return;
-
- int port = ui->portLineEdit->text().toInt();
- if (port <= 0 || port > 65535) {
- QMessageBox msgBox(this);
- msgBox.setText("ポート番号は 1..65535");
- msgBox.setStandardButtons(QMessageBox::Ok);
- msgBox.setIcon(QMessageBox::Critical);
- msgBox.exec();
- return;
- }
-
- ui->pushButton->setEnabled(false);
-
- QApplication::setOverrideCursor(Qt::WaitCursor);
-
- m_thread = new ConnectThread();
- connect(m_thread, &ConnectThread::fortuneGot, this, &MainWindow::showFortune);
- connect(m_thread, &ConnectThread::error, this, &MainWindow::displayError);
-
- m_thread->requestNewFortune( ui->hostLineEdit->text().toStdString(),
- port );
- }
スレッドクラスで start()
を呼び出して、子スレッドを起動する。
Blocking モードのソケットで、順番に処理するだけ。
子スレッドからシグナルを emit
すれば、あとは Qt がメインスレッドへの伝達、スレッド切り替えをよしなにしてくれる。すごい楽。
connect_thread.cpp
C++
- ConnectThread::ConnectThread(QObject* parent):
- QThread(parent)
- {
- }
-
- ConnectThread::~ConnectThread()
- {
- }
-
- void ConnectThread::requestNewFortune(const string& hostName, int port)
- {
-
- this->hostName = hostName;
- this->port = port;
-
- start();
- }
-
-
- void ConnectThread::run()
- {
- SOCKET sockfd = connect_to_server(hostName.c_str(), port);
- if ( sockfd == INVALID_SOCKET ) {
- emit error(errno, tr("client error") );
- return;
- }
-
- char fortune_buf[1000];
- char* p = fortune_buf;
- while (true) {
- int r = recv(sockfd, p,
- sizeof(fortune_buf) - (p - fortune_buf) - 1, 0);
- if (r < 0) {
- if (errno == EINTR)
- continue;
- emit error(errno, tr("read() error") );
- closesocket(sockfd);
- return;
- }
- p[r] = '\0';
- if ( !r )
- break;
- p += r;
- }
-
- closesocket(sockfd);
-
-
- emit fortuneGot(fortune_buf);
- }
グルッと回って、シグナルを受け取って、表示するだけ。
mainwindow.cpp
C++
-
- void MainWindow::showFortune(const QString& fortune)
- {
- ui->textBrowser->setText(fortune);
-
- delete_thread();
- QApplication::restoreOverrideCursor();
- enableGetFortuneButton();
- }
-
-
- void MainWindow::displayError(int socketError, const QString& message)
- {
- ui->textBrowser->setText(message);
-
- delete_thread();
- QApplication::restoreOverrideCursor();
- enableGetFortuneButton();
- }
これだけ! カンタンすぎる。