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 はウェブサーバーを起動し、ブラウザーを起動し、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 機能をテストすることができます。なお、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での開発

CMakeでビルドする

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

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

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

OpenGL と WebGL

Qt for WebAssembly はhttps://developer.mozilla.org/en-US/docs/Web/API/WebGL_APIWebGL を使用したハードウェアアクセラレーションレンダリングをサポートします。

WebGLはOpenGL ESに密接に準拠しており、バージョンマッピングは以下のようになっています:

OpenGLWebGL
OpenGL ES 2WebGL 1
OpenGL ES 3WebGL 2

Qt は利用可能な最も高い WebGL バージョンを使用します。これは今日のブラウザでは通常 WebGL 2 ですが、ハードウェアによって制限されている場合は WebGL 1 になるかもしれません。Qt for WebAssemblyでは、WebGL 2をサポートするデバイスをターゲットにすることを推奨します。

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

デフォルトでは、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 サポートを使用したマルチスレッドをサポートしています。WebAssembly (multi-threaded)」コンポーネントをQt Maintenance Tool からインストールするか、Qt をソースからビルドして configure に「-feature-thread」フラグを渡すことで、マルチスレッドを有効にできます。

既存のスレッドコードは一般的に再利用できますが、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 WidgetsQt Quick の両方がサポートされています。

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

  • VoiceOverとSafari(macOS
  • 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().

リンカー・オプションに"-sASYNCIFY -Os "フラグを追加して、非同期化を有効にする:

CMake:

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

qmake:

QMAKE_LFLAGS += -sASYNCIFY -Os

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

Asyncify JSPI (JavaScript Promise Integration)

JSPIはコード変換の代わりにネイティブブラウザサポートを使用して実装された新しい asyncify バックエンドです。Emscripten asyncify と比較して、アプリケーションのリンク時間を改善し、コードサイズを増加させません。

JSPI WebAssembly機能は現在 "実装 "段階にあり、まだ標準化されていません。

リンカー・オプションに "-sJSPI" フラグを追加して、JSPI を有効にしてください:

CMakeを使用する:

target_link_options(<your target> PUBLIC -sJSPI)

qmake:

QMAKE_LFLAGS += -sJSPI

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

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.52 を使用し、patch#1およびpatch#2 のパッチを適用してください。
  • マルチスレッドには対応していません。
  • Asyncify には対応していません。

クイック スタート

ビルドとデプロイの手順は、静的な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 ルックアップ、 、 は動作せず、Web サンドボックスのためサポートされていません。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。
-特徴-wasm-simd128WebAssembly SIMD サポートを有効にします。
-feature-wasm-exceptionsWebAssembly の例外サポートを有効にします。
-特徴-opengles3デフォルトの opengles2 に加えて opengles3 を使用します。
-デバイス・オプション QT_EMSCRIPTEN_ASYNCIFY=1asyncifyサポートを有効にする。
-デバイス・オプション QT_EMSCRIPTEN_ASYNCIFY=2asyncify (JSPI) サポートを有効にする。

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 ライセンスをご覧ください。

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.