WebAssemblyのためのQt

Qt for Webassembly を使うと、Qt アプリケーションを Web 上で実行することができます。

WebAssembly(略称:Wasm)は、Webブラウザなどの仮想マシン上で実行されることを想定したバイナリ命令形式です。

Qt for WebAssembly を使えば、ブラウザのサンドボックスで動作する Web アプリケーションとしてアプリケーションを配布することができます。このアプローチは、ホストデバイス機能へのフルアクセスを必要としない Web 分散アプリケーションに適しています。

注意: Qt for WebAssembly はサポートされているプラットフォームですが、いくつかのモジュールはまだサポートされていないか、技術プレビュー版です。サポートされている Qt モジュールを参照してください。

Qt for WebAssembly を始める

WebAssembly用のQtアプリケーションのビルドは、他のプラットフォーム用のQtのビルドと似ています。SDK(Emscripten)をインストールし、Qtをインストールし(またはソースからQtをビルドし)、最後にアプリケーションをビルドする必要があります。例えば、Qt for WebAssemblyは他のQtビルドよりもサポートするモジュールが少なく、機能も少ないです。

Emscripten のインストール

Emscriptenは WebAssembly にコンパイルするためのツールチェーンです。これにより、ブラウザのプラグインなしで、ネイティブに近い速度で Web 上で Qt を実行することができます。

Emscripten SDK のインストールの詳細については、Emscripten のドキュメントを参照してください。

インストール後、パスに Emscripten コンパイラがあるはずです。次のコマンドで確認してください:

em++ --version

Qt の各マイナーバージョンは、特定の Emscripten バージョンをターゲットとしており、パッチリリースでは変更されません。Qt のバイナリパッケージは、ターゲットとなる Emscripten のバージョンを使用してビルドされます。Emscripten はバージョン間のABI 互換性を保証していないため、アプリケーションは同じバージョンを使用する必要があります。

Emscripten のバージョンは次のとおりです:

  • Qt 6.2: 2.0.14
  • Qt 6.3: 3.0.0
  • Qt 6.4: 3.1.14
  • Qt 6.5: 3.1.25
  • Qt 6.6: 3.1.37
  • Qt 6.7:3.1.50
  • Qt 6.8: 3.1.56

Emscripten の特定のバージョンをインストールするには、emsdk を使用してください。例えば、Qt 6.8をインストールするには、次のように入力します:

  • ./emsdk install 3.1.56
  • ./emsdk activate 3.1.56

Windows では、Emscripten はインストール後のパスにあります。macOS や Linux では、次のようにパスに追加する必要があります:

source /path/to/emsdk/emsdk_env.sh

以下のコマンドで確認してください:

em++ --version

Emscripten のバージョンをより柔軟に選択したい場合は、ソースから Qt をビルドすることができます。この場合、上記のバージョンは最小バージョンです。それ以降のバージョンでも動作することが期待されますが、Qt に変更を加える必要があるような動作の変更を導入する可能性があります。

Qt のインストール

Qt アカウントのダウンロードセクションから Qt をダウンロードしてください。開発プラットフォームとして Linux、macOS、Windows 用のビルドを提供しています。

バイナリビルドは、できるだけ多くのブラウザで動作するように設計されており、シングルスレッド版とマルチスレッド版があります。Wasm SIMDWasm exceptions のような非標準の機能は、バイナリビルドではサポートされていません。

ソースから Qt をビルドする

ソースからビルドすることで、スレッドサポート、OpenGL ES レベル、SIMD サポートなどの Qt 設定オプションを設定することができます。Qt アカウントのダウンロードセクションから Qt ソースをダウンロードしてください。

Qt をwasm-emscripten プラットフォーム用のクロスコンパイルビルドとして設定します。これにより、-static-no-feature-thread-no-make examples の configure オプションが設定されます。-feature-thread の configure オプションでスレッドサポートを有効にできます。共有ライブラリのビルドはサポートされていません。

同じバージョンの Qt のホストビルドが必要で、QT_HOST_PATHCMake 変数または-qt-host-path configure 引数でそのパスを指定します。

./configure -qt-host-path /path/to/Qt -platform wasm-emscripten -prefix $PWD/qtbase

注意: ninja の実行ファイルがある場合、configure は常にNinjaジェネレーターとビルドツールを使用します。Ninjaはクロスプラットフォームで、機能が豊富で、パフォーマンスが高く、すべてのプラットフォームで推奨されています。他のジェネレータを使っても動作するかもしれませんが、公式にはサポートされていません。

Windows では、PATH に Mingw-w64 があることを確認し、以下のように configure してください:

configure -qt-host-path C:\Path\to\Qt -no-warnings-are-errors -platform wasm-emscripten -prefix %CD%\qtbase

その後、必要なモジュールをビルドしてください:

cmake --build . -t qtbase -t qtdeclarative [-t another_module]

コマンドラインでのアプリケーションのビルド

Qt for WebAssemblyは、qmakeとmake、またはCMakeとninja、makeを使ったアプリケーションのビルドをサポートしています。

$ /path/to/qt-wasm/qtbase/bin/qt-cmake .
$ cmake --build .

Note: vanillaCMake (Linux のqt-cmake や Windows のqt-cmake.bat とは異なります) を使用する場合は、他のクロスプラットフォームビルドと同様に、"-DCMAKE_TOOLCHAIN_FILE" でツールチェーンファイルを指定することを忘れないでください。詳細はこちらを参照してください:CMakeを使い始める

アプリケーションをビルドすると、アプリケーションとQtコード(静的にリンクされている)を含む.wasmファイル、ブラウザで開いてアプリケーションを実行できる.htmlファイルなど、いくつかの出力ファイルが生成されます。

注意: Emscripten は "-g" デバッグ・レベルで比較的大きな .wasm ファイルを生成します。デバッグビルド用に "-g2" でリンクすることを検討してください。

アプリケーションの実行

アプリケーションを実行するには、Web サーバーが必要です。ビルド出力ファイルはすべて静的コンテンツなので、どんなウェブサーバーでも大丈夫です。使用例によっては、https証明書の提供や、マルチスレッドサポートを有効にするために必要なhttpヘッダーの設定など、特別なサーバー設定が必要になる場合があります。

エムラン

Emscripten は、アプリケーションをテスト実行するためのemrunユーティリティを提供します。Emrun は Web サーバーを起動し、ブラウザーを起動し、stdout/stderr (通常は JavaScript コンソールに送られます) をキャプチャして転送します。

/path/to/emscripten/emrun --browser=firefox appname.html

Python http.server

開発用のウェブサーバを起動し、ウェブブラウザを別に起動する方法もあります。Pythonのhttp.serverは最もシンプルなオプションの1つだ:

python -m http.server

これは単純なウェブサーバーであり、スレッディングに必要なSharedArrayBufferをサポートしていないことに注意してください。

qtwasmserver

Qt はmkcert を使用して https 証明書を生成する開発者向け Web サーバを提供します。これにより、安全なコンテキストを必要とする Web 機能をテストすることができます。なお、http://localhost での配信も証明書を必要とせず、安全であると考えられます。

また、ウェブサーバはCOOPヘッダとCOEPヘッダを設定し、SharedArrayBufferとマルチスレッドをサポートします。

qtwasmserverスクリプトは、デフォルトでlocalhostにバインドする1つのサーバーを起動します。aコマンドライン引数を使用してアドレスを追加するか、--allを使用して利用可能なすべてのアドレスにバインドすることができます。

python /path/to/qtbase/util/wasm/qtwasmserver/qtwasmserver.py --all

Qt Creatorを使用したアプリケーションのビルド

WebAssembly 用の Qt Creator のセットアップ

アプリケーションを Web にデプロイする

アプリケーションをビルドすると、いくつかのファイルが生成されます(以下の表では、"app "をアプリケーション名に置き換えています)。

生成されるファイル簡単な説明
app.htmlHTMLコンテナ
qtloader.jsQt アプリケーションを読み込むための JavaScript API
app.jsEmscriptenによって生成されたJavaScriptランタイム
app.wasmアプリバイナリ

app.htmlはそのままデプロイすることもできますし、カスタムHTMLファイルに置き換えて破棄することもできます。スプラッシュ画面の画像を Qt のロゴからアプリのロゴに変更するなどの小さな調整も可能です。どちらの場合も、qtloader.jsはアプリケーションをロードするためのJavaScript APIを提供します。

デプロイする前に、gzip またはbrotli を使用して Wasm ファイルを圧縮してください。他のツールよりも圧縮率が高くなります。詳細は「バイナリのサイズを最小化する」を参照してください。

マルチスレッドや SIMD など、特定の機能を有効にすると、その機能をサポートしていないブラウザと互換性のない .wasm バイナリが生成されます。複数の .wasm ファイルをビルドし、JavaScript の機能検出を使って正しいものを選択することで、この制限を回避することは可能ですが、Qt はそのための機能を提供していないことに注意してください。

qtloader を使う

Qt は Qt for WebAssembly アプリケーションをダウンロード、コンパイル、インスタンス化するための JavaScript API を提供します。このローディングAPIは、Emscriptenによって提供されるローディング機能をラップし、Qtベースのアプリケーションに有用な追加機能を提供します。これは qtloader.js ファイルに実装されています。このファイルのコピーは、ビルド時にビルド・ディレクトリに書き込まれます。

典型的な使い方は以下のようになる:

const app_container_element = ...;
const instance = await qtLoad({
    qt: {
        containerElements: [ app_container_element ],
        onLoaded: () => { /* handle application load completed */  },
        onExit: () => {  /* handle application exit */ },
    }
});

このコードは、コンフィギュレーションオブジェクトで qtLoad() ローダー関数を呼び出します。このコンフィギュレーションオブジェクトは、emscriptenのコンフィギュレーションオプションと、特別な "qt "コンフィギュレーションオブジェクトを含むことができます。qt設定オブジェクトは以下のプロパティをサポートしています:

プロパティ簡単な説明
containerElementsHTMLコンテナ要素の配列。アプリケーションはこれらをQScreensとして見る。
onLoadedアプリケーションのロードが完了したときのコールバック。
onExitアプリケーションが終了するときのコールバック。

containerElements配列は、QtとWebページの間の主要なインターフェイスで、この配列内のhtml要素(通常は<div>要素)は、Webページ上のアプリケーションコンテンツの位置を指定します。

アプリケーションは、各コンテナ要素をQScreen インスタンスとして認識し、通常通りアプリケーション・ウィンドウをスクリーン・インスタンスに配置することができます。Qt::WindowFullScreen 」ステートが設定されたウィンドウはスクリーン領域全体を使用し、「フルスクリーン」でないウィンドウはウィンドウの装飾を取得します。

qtLoad() 関数はプロミスを返し、このプロミスは待ち受け時に Emscripten インスタンスを生成します。このインスタンスは Embind のエクスポート関数へのアクセスを提供します。Qt はそのような関数をいくつかエクスポートしており、これらの関数がインスタンス API を構成しています。

Qt インスタンス API の使用

Qt はいくつかのインスタンス関数を提供しています。現在のところ、これらは実行時のコンテナ要素の追加と削除をサポートしています。

プロパティ簡単な説明
qtAddContainerElementコンテナ要素を追加します。要素を追加すると、新しいQScreen が追加されます。
qtRemoveContainerElement コンテナ要素を削除します。コンテナ要素とそれに対応するスクリーンを削除します。
qtSetContainerElementsすべてのコンテナ要素を設定する。
qtResizeContainerElement (コンテナ要素のサイズ変更)コンテナ要素のサイズの変更を Qt に拾わせる

Qt 6.6 qtloader への移植

Qt 6.6 では、実装が簡素化され、スコープが小さくなった新しい qtloader が含まれています。これには API の変更が含まれており、アプリケーションの JavaScript コードの移植が必要になるかもしれません。Qt は互換 API を提供し、移行を容易にします。ユースケースによって、いくつかの方法があります:

  • 生成されたapp.html ファイルを直接使用している場合、ビルド時にこのファイルも更新されます。何もする必要はありません。
  • 基本的な qtloader 機能セットを使用している場合は、Qt 6.6 に含まれる互換性 API を一時的に使用することができます。この API は将来のリリースで削除される予定ですので、新しい qtloader を使用するためのアップデートを計画する必要があります。以下のステップ1の移植が必要です。
  • 高度な機能(実行時にコンテナ要素を追加するなど)を使用する場合は、新しいローダーまたはインスタンス API への移植が必要です。以下のステップ1と2の移植が必要です。

移植手順

  1. ローディングhtmlファイルからapp.js (Emscriptenによって生成されたJavaScriptランタイム)をインクルードします。
    <script src="app.js"></script>

    Qt 6.6以前では、qtloaderはこのJavaScriptファイルを読み込んで評価していました。これはもはや行われず、<script>タグを使ってファイルをインクルードする必要があります。

  2. 新しいJavaScriptとインスタンスAPIを使うように移植してください。

上記のドキュメントセクションを参照してください。

対応ブラウザ

デスクトップ

Qt for WebAssembly は以下のブラウザで開発、テストされています:

  • Chrome
  • Firefox
  • サファリ
  • エッジ

ブラウザがWebAssemblyをサポートしていれば、Qtは動作するはずです。アプリケーション自体がハードウェアアクセラレーショングラフィックスを使用しない場合でも、Qt は WebGL の要件を固定しています。WebAssemblyをサポートしているブラウザは、多くの場合WebGLをサポートしていますが、古いGPUやサポートされていないGPUをブラックリストに入れているブラウザもあります。

Qtはオペレーティングシステムの機能を直接利用しないため、例えばFireFoxがWindowsで動作していてもmacOSで動作していても違いはありません。Qtは、例えばmacOSでのctrl/cmdキー操作のように、いくつかのオペレーティングシステムの適応を使用します。

モバイル

Qt for WebAssembly アプリケーションはモバイル Safari や Android Chrome などのモバイルブラウザで動作します。

サポートされる Qt モジュール

Qt for WebAssemblyはQtモジュールと機能のサブセットをサポートしています。テスト済みのモジュールは以下の通りですが、その他のモジュールは動作する場合としない場合があります。

すべての場合において、モジュールのサポートは完全ではないかもしれませんし、ブラウザのサンドボックスやQtプラットフォームの移植の不完全さによる追加的な制限があるかもしれません。詳しくはQt for WebAssemblyを参照してください。

Qt for WebAssembly Technology Preview のモジュールと機能。これらの機能は Qt の再設定やビルドが必要な場合があります。ブラウザや Emscripten ではまだ実験的な機能が含まれている可能性があります。

Qt for WebAssembly による開発

CMakeでのビルド

CMakeでEmscripten固有の設定が必要な場合は、以下のコードを利用できます:

if(EMSCRIPTEN)
    # WebAssembly specific code
else()
    # other platforms
endif()

このコードにより、他のプラットフォームとの互換性を確保しながら、Emscripten 固有の設定を行うことができます。

OpenGL と WebGL

Qt は、OpenGL を直接使用しないアプリケーションでも WebGL を必要とします。関連するすべてのブラウザは WebGL をサポートしていますが、一部のブラウザは特定の古い GPU をブラックリストに入れていることに注意してください。Qt ローダーはこれを検出し、エラーメッセージを表示します。

QtはWebGLをOpenGL ESとして検出します:

OpenGLWebGL
OpenGL ES 2WebGL 1
OpenGL ES 3WebGL 2

OpenGL ES 2とOpenGL ES 3はデフォルトで有効になっており、QSurfaceFormat::setMajorVersion ()関数で選択できます。

WebとデスクトップのOpenGLの違いは、WebGLとOpenGLの違いで文書化されています。WebGL 1.0とWebGL 2.0の間にはさらに違いがあり、WebGL 2.0 Specificationで文書化されています。

デフォルトでは、ES2(およびES3)のWebGLに適したサブセットが使用されます。バウンドバッファなしでglDrawArraysglDrawElements を使用する必要がある場合は、ES2 の完全なサポートを有効にするために

target_link_options(<your target> PRIVATE -s FULL_ES2=1)

target_link_options(<your target> PRIVATE -s FULL_ES3=1)

をプロジェクトのCMakeLists.txt

OpenGLコンテキストの共有

WebGLでは、1つのネイティブサーフェスにつき1つのコンテキストしか使用できず、OpenGLコンテキストの共有はサポートされていません。Qtは、複数のOpenGLContextを使用する場合、ネイティブコンテキストを再利用することで、これを回避しています。

例えば、QOpenGLWidget の代わりにQOpenGLWindow を使うなどして、OpenGLコンテキストの使用を、ネイティブ・サーフェスごとに1つのコンテキストに制限することをお勧めします。QOpenGLWidget は、アプリケーション・コードが、QOpenGLWidget::paintGL() 呼び出しを行うときに、関連するすべてのOpenGLステートをリストアし、QOpenGLWidget::initializeGL() とQOpenGLWidget::paintGL() 呼び出しの間のステートの永続化に依存しない限り、動作する可能性があります。

マルチスレッド

Qt for WebAssembly は、Emscripten のPthreads サポートを使用したマルチスレッドをサポートしています。Qt Maintenance Tool から "WebAssembly (multi-threaded)" コンポーネントをインストールするか、Qt をソースからビルドして "-feature-thread" フラグを configure に渡すことでマルチスレッドを有効にできます。

既存のスレッドコードは一般的に再利用できますが、pthread 実装の仕様に対応するために変更が必要な場合があります。いくつかの Emscripten と Qt の機能はサポートされていません。これにはスレッドプロキシ機能と Qt Quick のスレッドレンダーループが含まれます。

Qt for WebAssembly では、メインスレッドをブロックしないことが特に重要です。例えば、Qtのすべてのタイマーはメインスレッドでスケジュールされ、メインスレッドがブロックされると起動しません。別の例として、新しい Web Worker を作成する(スレッドに対して)のはメインスレッドからのみ可能です。

Emscripten はこれに対していくつかの緩和策を提供します。ミューテックスロックの取得のような短時間の待機は、ビジーウェイトと、ロックを待っている間のイベント処理によってサポートされます。メインスレッドでの長い待ち時間は避けるべきです。特に、QThread::wait() や pthread_join() を呼び出してセカンダリスレッドを待機させるという一般的な方法は、アプリケーションがそのスレッド(およびウェブワーカー)がすでに開始されており、wait() や join() が呼び出された時点でメインスレッドの支援なしに完了できることを保証できない限り、機能しません。

マルチスレッド機能には、SharedArrayBufferAPI のブラウザサポートが必要です。(通常、Emscripten はヒープを ArrayBuffer オブジェクトに格納します。マルチスレッドのためには、ヒープを Web Worker と共有する必要があり、SharedArrayBuffer が必要です)この API は一般的にすべてのモダンブラウザで利用可能ですが、特定のセキュリティ要件が満たされない場合は無効になる可能性があります。スレッドサポートを有効にした WebAssembly バイナリは、バイナリが実際にスレッドを開始しない場合にも実行に失敗します。

SharedArrayBuffer を有効にするには、安全なブラウジング・コンテキスト(ページが https:// または http://localhost で提供される)と、ページがクロスオリジン分離モードであることが必要です。後者は、いわゆるCOOPヘッダーとCOEPヘッダーをウェブサーバーに設定することで可能です:

  • クロスオリジンオープナーポリシー: same-origin
  • クロスオリジン埋め込みポリシー:require-corp

SIMD

Emscripten はWebAssembly SIMD をサポートしており、WebAssembly 用の 128-bit SIMD 型と演算を提供します。

Qt をソースからビルドし、-feature-wasm-simd128 フラグを有効に設定してください。ただし、wasm-simd を有効にすることで、コンパイラによる SIMD 命令の自動ベクトル化が有効になります。

GCC/Clang SIMD Vector Extensions または WASM SIMD128 intrinsics のいずれかを使って、WebAssembly SIMD を直接ターゲットにすることができます。詳細は Emscripten SIMD documentation を参照してください。

さらに、Emscripten は x86 SSE 命令を Wasm SIMD 命令にエミュレート/変換することもサポートしています。ネイティブの Wasm SIMD 命令と同等のものがない SSE SIMD 命令を使用するとパフォーマンスが低下する可能性があるため、Qt ではこのエミュレーションを使用しません。

SIMD 対応のバイナリは、WebAssembly SIMD をサポートしていないブラウザと互換性がないことに注意してください。SIMDのサポートは、'about:config'や'chrome:flags'などのブラウザの詳細設定で有効にする必要があるかもしれません。

ネットワーキング

Qt はネットワーキングを限定的にサポートしています。一般的に、Web 上で既に使用されているネットワークプロトコルは Qt からも使用できますが、その他のプロトコルは Web サンドボックスのため直接使用できません。

以下のプロトコルがサポートされています:

  • QNetworkAccessManager Web ページオリジンサーバーへの http リクエスト、または CORS をサポートするサーバーへのリクエスト。これには QML からの も含まれます。XMLHttpRequest
  • QWebSocket 任意のホストへの接続。なお、安全な https プロトコルで提供されるウェブページでは、安全な wss プロトコルのみでのウェブソケット接続が可能です。
  • Emscripten が提供する機能を使用した、WebSocket 上のエミュレートされた POSIX TCP ソケット。これには、ソケット変換を処理する転送サーバーを実行する必要があることに注意してください。

その他のネットワークプロトコルはサポートされていません。

ローカルファイルへのアクセス

ファイルシステムへのアクセスはウェブ上でサンドボックス化されます。ウェブ・プラットフォームは、ユーザー制御の下でローカル・ファイル・システムにアクセスするための API と、永続ストレージにアクセスするための API を提供します。Emscripten と Qt はこれらの機能をラップし、C++ や Qt ベースのアプリケーションから使いやすい API を提供します。

ウェブ・プラットフォームは、ローカル・ファイルや永続ストレージにアクセスするための機能を提供する:

  • <input type="file">は、ネイティブのファイルを開くダイアログを表示し、ユーザーがファイルを選ぶことができる。
  • IndexedDBは、永続的なローカルストレージを提供します(ブラウザの外部からはアクセスできません)。

Emscripten は POSIX ライクな API でいくつかのファイルシステムを提供します。これらには以下が含まれます:

  • MEMFS エフェメラルファイルシステム。
  • IndexedDB を使用してファイルを格納する IDBFS 永続ファイルシステム

Emscripten は、アプリの起動時に一時的な MEMFS ファイルシステムを "/" にマウントします。つまり、QFile を使用することができ、デフォルトでメモリにファイルを読み書きします。Qt は他の API も提供しています:

クリップボードへのアクセス

Qt はシステムクリップボードへのコピー&ペーストをサポートしています。一般的にクリップボードへのアクセスにはユーザの許可が必要で、これは入力イベント(CTRL+cなど)を処理するか、クリップボードAPIを使用することで取得できます。

クリップボードAPIをサポートしているブラウザが望ましい。このAPIを使用するためには、Webページが安全な接続(httpsなど)で提供される必要があり、ブラウザによっては設定フラグを変更する必要があることに注意してください。

  • Chromeバージョン66とSafariバージョン13.1はクリップボードAPIをサポートしています。
  • Firefoxバージョン90では、about:configで以下のフラグを有効にすることで、クリップボードAPIをサポートしています:
    dom.events.asyncClipboard.read
    dom.events.asyncClipboard.clipboardItem

フォント

Qt WASM モジュールには 3 つの埋め込みフォントが含まれています:「Bitstream Vera Sans」(フォールバックフォント)、「DejaVu Sans」、「DejaVu Sans Mono」です。

これらのフォントが提供する文字セットは限られています。Qtには、フォントを追加するためのオプションがいくつか用意されています:

1つはQMLのFontLoader 。URLからフォントを取得するか、Qt Resource System(通常のデスクトップアプリと同じ方法)を使用します。

もう一つの方法は、QFontDatabase::addApplicationFontFromData を使ってフォントを追加する方法です。

アクセシビリティとスクリーンリーダー

Qt for WebAssemblyは、スクリーンリーダーの基本的なサポートを提供します。ボタンやチェックボックスのような単純な UI 要素は動作しますが、テーブルビューやツリービューのような複雑な UI 要素はサポートされていません。Qt Widgets と Qt Quick の両方がサポートされています。

以下のスクリーンリーダー/ブラウザの設定はテスト済みで、機能することが確認されています。他のブラウザやスクリーンリーダーでも動作する可能性があります。

  • macOSのSafariでのVoiceOver
  • VoiceOverとChrome(macOS版

アクセシビリティの実装は、Qt UI 要素のアクセシビリティ情報を提供する "shadow" html 要素を作成することで動作します。この機能はデフォルトでは無効になっています。エンドユーザーは、スクリーン・リーダーを使って "activate screen reader "ボタンを選択することで、この機能を有効にすることができます。アクティベートされると、ウェブページにアクセシビリティ要素が入力されます。

アプリケーションの起動とイベント・ループ

Qt for WebAssemblyは、アプリケーションがQApplication オブジェクトを作成し、exec関数を呼び出す、標準的なQtの起動方法をサポートしています:

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    QWindow appWindow;

    return app.exec();
}

上記の exec() 呼び出しは通常、アプリケーションのシャットダウンまでイベントをブロックして処理します。残念ながら、これはメインスレッドをブロックすることができないWebプラットフォームでは不可能です。代わりに、各イベントを処理した後、ブラウザのイベントループに制御を戻さなければなりません。

Qtはexec()がメインスレッドの制御をブラウザに返すようにすることで、スタックを保持しながらこの問題を回避しています。アプリケーションコードから見ると、exec()関数が入力され、イベント処理は通常通り行われます。しかし、exec() 呼び出しは決して戻りませんし、アプリケーションの終了時にも戻りません。

ブラウザはアプリケーションのシャットダウン時にアプリケーション・メモリを解放するので、この動作は通常許容されます。アプリケーション・オブジェクトがリークされ、そのデストラクタが実行されないので、シャットダウン・コードが実行されないことを意味します。

これは、main() が戻るときに Emscripten がランタイムを終了しないので可能です。アプリケーションコードは exec() 呼び出しを省略し、トップレベルのウィンドウとアプリケーションオブジェクトを削除することで、Qt をきれいにシャットダウンすることができます。

QApplication *g_app = nullptr;
AppWindow *g_appWindow = nullptr;

int main(int argc, char **argv)
{
    g_app = new QApplication(argc, argv);
    g_appWindow = new AppWindow();
    return 0;
}

非同期化

Qt for WebAssembly のデフォルトのビルドでは、Web プラットフォームの制限により、QEventLoop::exec() やQDialog::exec() を呼び出すなどして、イベントループに再び入ることをサポートしていません。

Emscriptenのasyncify機能は、同期呼び出し(QEventLoop::exec ()やQDialog::exec ()のような)がイベントループに降伏することを可能にすることによって、これらの制限を解除します。ネストされた呼び出しはサポートされておらず、このため asyncify はトップレベルのQApplication::exec() 呼び出しには使用されません。

asyncifyを必要とする機能は以下のとおりである:

  • QDialogs、戻り値を持つQMessageBoxes。
  • ドラッグ・アンド・ドロップ(特にドラッグ)。
  • ネスト/セカンダリ・イベント・ループ exec().

Qt 6.4 では、Asyncify のサポートはバイナリパッケージで有効になっていますが、アプリケーションではリンカオプションに -sASYNCIFY -Os を追加して有効にする必要があります:

CMake:

target_link_options(<your target> PUBLIC -sASYNCIFY -Os)

qmake:

QMAKE_LFLAGS += -sASYNCIFY -Os

asyncifyを有効にすると、バイナリ・サイズの増加やCPU使用量の増加という形でオーバーヘッドが追加されます。オーバーヘッドを最小限に抑えるために最適化を有効にしてビルドしてください。

デバッグとプロファイリング

WasmのデバッグはブラウザのJavaScriptコンソールで行うため、Qt Creator内で直接Wasmのアプリケーションをデバッグすることはできません。

Emscripten のリンカー引数を使用すると、デバッグを支援するためにさらに冗長性を追加できます:

  • -s LIBRARY_DEBUG=1 (ライブラリ呼び出しを出力)
  • -s SYSCALL_DEBUG=1 (sys 呼び出しを出力)
  • -s FS_LOG=1 (ファイルシステム操作を出力)
  • -s SOCKET_DEBUG (ソケット、ネットワークデータ転送を出力)

CMake:

target_link_options(<your target> PRIVATE -s LIBRARY_DEBUG=1)

qmake:

QMAKE_LFLAGS_DEBUG += -s LIBRARY_DEBUG=1

最適化

Qt for WebAssembly はバイナリを生成するために Emscripten ツールチェインを使用します。Emscripten を参照してください:コードの最適化」を参照してください。

通常の C++ アプリケーションと同様に、リンカやコンパイラのフラグを渡すことができます: qmake#tab-qmakeバイナリのサイズを最小化する。

target_compile_options(<your target> PRIVATE -oz -flto)
target_link_options(<your target> PRIVATE -flto)
QMAKE_CXXFLAGS += -oz -flto
QMAKE_LFLAGS += -flto

バイナリのサイズを最小化する

シームレスなユーザー体験を提供するためには、WebAssemblyアプリケーションのダウンロードとロードにかかる時間を短縮することが重要です。アプリケーションのバイナリを小さくすることは、より高速なダウンロードを可能にする重要な側面の1つです。バイナリサイズを小さくするために、以下の代替手段を使用してください:

  • 必ずリリースビルドを配布してください。デバッグビルドにはデバッグシンボルが含まれ、より大きくなります。
  • サーバーで圧縮を有効にする。gzip や Brotli のような最も一般的なアルゴリズムは Wasm バイナリでうまく機能し、サイズを大幅に縮小できます。
  • より小さなバイナリを生成するコンパイラやリンカのフラグを試してみてください(例:'-os'、'-oz')。結果はアプリケーションによって異なります。
  • Qt for WebAssembly をソースからコンパイルする際に、不要な機能を無効にしてください(下記を参照)。
機能のオプトアウト

WebAssembly アプリケーションはデフォルトで Qt ライブラリに静的にリンクします。しかし、Qtは動的な性質を持っているため、コンパイラがそのような最適化を実行できるとは限りません。

Qt for WebAssemblyをソースからビルドする場合、Qtバイナリのサイズを小さくする機能を無効にすることができます。Qt は WebAssembly プラットフォームのためにいくつかの機能をデフォルトで無効にしますが、アプリケーションが使用しない機能を無効にすることもできます。詳しくはdisabled featuresを参照してください。

以下の機能を無効にすることで、バイナリサイズを小さくすることができます(通常 10-15%程度):

設定 引数簡単な説明
-no-機能-cssparserカスケーディング・スタイル・シートのパーサー。
-no-feature-datetimeedit。日付と時刻の編集(datetimeparserに依存)。
-no-feature-datetimeparser日時テキストの解析。
-no-feature-dockwidget(ドックウィジェットウィジェットをQMainWindow 内にドッキングさせたり、デスクトップ上のトップレベルウィンドウとしてフローティングさせる。
-no-feature-ジェスチャージェスチャー用フレームワーク。
-no-feature-mimetype(ノー・フィーチャー・マイメタイプMimetype 処理。
-no-feature-qml-ネットワークネットワークの透過性。
-no-feature-qml-list-model。ListModel QML タイプ。
-no-feature-qml-table-model。TableModel QML 型。
-no-feature-quick-canvas。キャンバス項目。
-no-feature-quick-pathパス要素。
-no-feature-quick-pathview。PathView アイテム。
-no-feature-quick-treeview。TreeView 項目を指定します。
-no-feature-style-スタイルシートCSSで設定可能なウィジェットスタイル。
-no-feature-テーブルビューテーブルビューのデフォルトモデル/ビュー実装。
-no-feature-texthtmlパーサーHTMLのパーサー。
-no-feature-textmarkdownリーダーMarkdown (CommonMark と GitHub) リーダー。
-no-feature-textodfwriterODF ライター。

Wasm の例外

Qt はデフォルトでは例外をサポートせずにビルドされており、例外をスローするとプログラムが中断されます。WebAssembly の例外を有効にするには、ソースからビルドし、Qt configure に -feature-wasm-exceptions フラグを渡します。これにより、コンパイル時とリンク時に -fwasm-exceptions フラグがコンパイラに渡されます。Qt では、以前の JavaScript ベースの例外実装に対する Emscripten のサポートを有効にすることはサポートしていません。

内部実装の詳細のため、例外が有効になっているときにQApplication::exec() を呼び出すことはサポートされていないことに注意してください。その代わりに、アプリケーションの起動とイベントループで説明されているように、main() を早めに戻り、exec() を呼び出さない形式で記述します。

共有ライブラリとダイナミック・リンク Developer Preview

Qt for WebAssembly はデフォルトで静的リンクを使用し、アプリケーションは Qt ライブラリとアプリケーションコードを含む単一の WebAssembly ファイルとしてデプロイされます。動的リンクは、各ライブラリとプラグインが個別に配布されるビルドモードです。

例えば、Qt Quick を使用するアプリケーションは以下のライブラリとプラグインを使用します:

  • <qtpath>/lib/libQt6Core.so
  • <qtpath>/lib/libQt6Gui.so
  • <qtpath>/lib/libQt6Qml.so
  • <qtpath>/lib/libQt6Quick.so
  • <qtpath>/plugins/imageformats/libqjpeg.so
  • <qtpath>/plugins/imageformats/libqjgif.so
  • <qtpath>/qml/QtQuick/Window/libquickwindowplugin.so

ダイナミック・リンクのサポートは現在開発者向けプレビューです。この実装はプロトタイピングや評価には適していますが、本番での使用には適していません。現在の制限事項は以下のとおりです:

  • Emscripten SDK にパッチを適用する必要があります。emsdk 3.1.37 を使用し、このパッチを適用してください。
  • Chrome ブラウザは、4K を超えるサイズの wasm ファイルの同期読み込みをサポートしていないため、サポートされていません。
  • マルチスレッドには対応していません。
  • 非同期化には対応していません。

クイックスタート

ビルドとデプロイの手順は、静的なWASMや共有デスクトップビルドとは少し異なります。完全なアプリケーションのビルドに進む前に、小さな例から始めることを検討してください。

  1. ソースから Qt をビルドし、Qt configure スクリプトに "-shared" オプションを渡します。
  2. ステップ 1 の Qt を使ってアプリケーションをビルドします。
  3. アプリケーションディレクトリ内の "qt "という名前のディレクトリにコピーまたはリンクして、Qtのインストールを展開します。
    • ln -s <qtpath> qt
    • cp -r <qtpath> qt
  4. デプロイスクリプトを実行してプラグインのプリロードリストを作成します。
    • <qtpath>/qtbase/util/wasm/preload/deploy_qt_plugins.py <qtpath
    • <qtpath>/qtbase/util/wasm/preload/deploy_qml_imports.py <qthostpath> <qtpath

共有ライブラリのデプロイの詳細

Qt の共有ライブラリビルドは 2 段階でデプロイされます。第 1 段階では Qt とアプリケーションのビルドを Web サーバーからダウンロードできるようにし、第 2 段階ではアプリケーションの起動時に必要な Qt プラグインと Qt Quick インポートをダウンロードします。

第 1 段階では、Qt のインストールを Web サーバーからダウンロードできるようにします。ウェブサーバのセットアップの仕様によって、これを行う方法は異なります。共通するのは、Qt ローダーが、アプリケーションをロードする html ファイルに相対する、"qt" というディレクトリ名の Qt ライブラリとプラグインを見つけることを期待することです。

デプロイの一部としてアプリケーションをすでにウェブサーバにコピーしている場合、Qt もコピーすることは可能なオプションです。開発フェーズでよくあることですが、ビルドディレクトリから直接アプリケーションを提供する場合、Qt へのシンボリックリンクを作成するとうまくいきます。

プラグインや Qt Quick のインポートなど、Qt コンポーネントのプリロードリストを作成することで、2 番目のステップの準備をします。プリロードすることで、アプリケーションの起動時に必要な Qt コンポーネントがすべて利用できるようになります。コンポーネントを必要に応じてダウンロードするディレイローディングも可能ですが、ここでは取り上げません。

プリロードは Qt JavaScript ローダーによって実装され、Web サーバーから Emscripten が提供するインメモリファイルシステムにファイルをダウンロードします。どのファイルをダウンロードするかは、json 形式のダウンロードリストを使って指定します。Qt では、プリロード リストを生成するための 2 つのスクリプトを提供しています。

既知の問題

  • ネストされたイベントループはサポートされていません。アプリケーションはQDialog::exec() やQEventLoop::exec() のような API を呼び出すべきではありません。実験的な機能である Asyncify を使用できる。
  • 印刷はサポートされていません。
  • QDnsLookup ルックアップ、 、 は動作せず、ウェブ・サンドボックスのためサポートされていません。QTcpSocket QSsl
  • フォント:Wasmサンドボックスはシステムフォントへのアクセスを許可しません。フォントファイルは Qt リソースやダウンロードなど、アプリケーションと一緒に配布する必要があります。Qt for WebAssembly 自体がそのようなフォントを1つ埋め込んでいます。
  • Qt Quick Controls 2 の一部のコンポーネント(チェックボックスなど)で、初期化されていないグラフィックメモリのアーチファクトが発生することがあります。これは HighDPi ディスプレイで見られることがあります。
  • プラットフォームとしての Wasm がその機能を提供していないため、Windows と macOS のネイティブスタイルはサポートされていません。
  • wasm-ld: error: initial memory too small" のようなリンクタイムエラーが発生するため、初期メモリサイズの調整が必要。QT_WASM_INITIAL_MEMORY を使用して、初期サイズを kb 単位で設定します。これは 64KB (65536) の倍数でなければなりません。デフォルトは 50 MB です。CMakeLists.txt内: set_target_properties(<target> PROPERTIES QT_WASM_INITIAL_MEMORY "150MB")
  • CMakeLists.txtのadd_executableは、<target>.htmlを生成したり、qtloader.jsをコピーしたりしません。代わりに qt_add_executable を使用してください。
  • QWebSocket 接続は、メインスレッド上でのみ Emscripten によってサポートされます。
  • QWebSockets for WebAssembly は、Web ページやブラウザで利用可能な API がこの機能を公開していないため、ping や pong フレームの送信をサポートしていません。
  • RangeError. "のようなランタイムエラーが発生します:メモリ不足" のような実行時エラーは、MAXIMUM_MEMORY をデバイスがサポートする値に設定することで回避できます。
    target_link_options(<your target> PRIVATE -s MAXIMUM_MEMORY=1GB)
  • QtWebsockets を使用するには、サブプロトコルを 'mqtt' に設定してQtMqtt を使用する必要があります。QWebSocket を開くときは、QWebSocketHandshakeOptions を使用してください。

その他のトピック

Qt 設定オプションリファレンス

以下の configure オプションは、Qt for WebAssembly をソースからビルドする際に関連します。

configure 引数簡単な説明
-スレッド機能マルチスレッド Wasm。
-feature-wasm-simd128WebAssembly SIMD サポートを有効にします。
-特徴-wasm-exceptionsWebAssembly の例外サポートを有効にします。
-特徴-opengles3デフォルトの opengles2 に加えて opengles3 を使用します。
-デバイス・オプション QT_EMSCRIPTEN_ASYNCIFY=1asyncify を使用します。

Qt はバイナリサイズを小さくするために、WebAssembly プラットフォームではデフォルトでいくつかの機能を無効にします。WebAssembly 用に Qt を設定するときに、明示的に機能を有効にすることができます:

設定引数簡単な説明
-feature-topleveldomainドメインがトップレベルドメインかどうかをチェックするサポートを提供します。

典型的なダウンロードサイズ

予想されるフットプリント(ダウンロードサイズ):コンパイラが生成するWasmモジュールはサイズが大きくなることがありますが、うまく圧縮されます:

gzipブロートリ
helloglwindow (QtCore +QtGui)2.8M2.1M
ウィグリーウィジェット (QtCore +QtGui + QtWidgets)4.3M3.2M
センサータグ (QtCore +QtGui + QtWidgets +QtQuick +QtCharts)8.6M6.3M

圧縮は通常、標準的な圧縮機能を使用して Web サーバー側で処理されます。通常、Wasmファイルを特別に処理する必要はありません。

詳細については、「バイナリサイズの最小化」を参照してください。

外部リソース

ライセンス

Qt for WebAssemblyはThe Qt Companyの商用ライセンスで利用できます。また、GNU General Public License, version 3 の下でも利用可能です。詳細はQt ライセンスをご覧ください。

©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。