Qtを使って、デスクトップアプリ開発とWebAssemblyアプリ開発を同時に学ぼう

# Qt for WebAssembly を使おう

QtはGUIアプリケーションを開発するためのフレームワークとして知られています。最近では Qt 5.13 でWebAssemblyに正式対応したことで、デスクトップで動いていたGUIアプリが、そのままウェブブラウザ上でも動かせるようになりました。デスクトップ向けOS上でしか動かせなかったアプリがウェブブラウザで動いているのを見ると、結構感動できます。

後でがっかりしないように、始めにできないことを押さえておきましょう。

- 日本語を表示するのに一工夫いる
- 日本語を入力するのがかなり難しい
- モバイル端末で文字入力できない
- セキュリティの都合により、ローカルリソース（ファイルなど）へのアクセスが制限される
- セキュリティの都合により、ネットワークのクロスオリジン（別ドメイン）へのアクセスが制限される
- モバイル端末（スマホなど）では実行できないか、または、ロードにとても時間がかかる

## できないこと

#### 日本語を表示するのに一工夫いる

日本語の表示は可能ですが、一工夫いります。単純な Qt for WebAssembly アプリは、内部的にはUnicode(UTF-16)で動作しているものの、画面には基本的なASCII文字しか表示できません。なぜなら、WebAssembly環境からは、クライアントにインストールされているフォントを利用することができないためです。そこで、アプリ内に、リソースとして日本語TrueTypeフォントファイルを埋め込んでおき、起動時にカスタムフォント登録する処理を行えば日本語表示が可能になります。本稿では触れませんが、そのような記事をいずれ書きたいと思います。

#### 日本語を入力するのがかなり難しい

いまのところ、Qt for WebAssemblyはIMEをサポートしていません。そもそもQt for WebAssemblyの仕組み的にIMEのサポートが可能なのか分かりません。仕方ないので、Qt for WebAssemblyアプリケーションがIME機能を丸抱えする必要があります。つまり、アプリケーション内にIMEを自前実装する必要があるので、Qt for WebAssembly で日本語入力することを考えると、開発難易度が実現困難なレベルまで急上昇します。現在筆者（私）は「日本語IME for Qt for WebAssembly」を開発中です。これは日本語変換エンジンに、Google日本語変換クラウドAPIを利用し、それをアプリケーションから利用するためのUIを提供するもので、こちらもいずれ公開したいと思っています。本当に完成できるか分かりませんが、緩くご期待ください。

#### モバイル端末で文字入力できない

スマホやタブレットで文字入力するには、AndroidなりiOSなりが提供するソフトウェアキーボードが必要ですが、Qt for WebAssemblyではこれが利用できません。もっとも、日本語入力IMEが使えないのも根源的には同じだと思います。ソフトウェアキーボードを自前実装するのも現実的ではありませんし、Qt for WebAssembly側で対応されるのを待ちたいところですが、WebAssemblyの仕組みを考えると、それは難しいのではという気もしてきます。グラフィックだけのアプリなら文字入力は不要ですが、多くのアプリでは文字入力できないのは致命的ですね。

#### セキュリティの都合により、ローカルリソース（ファイルなど）へのアクセスが制限される

Qt for WebAssembly のアプリはウェブブラウザ上で動作します。ウェブブラウザで動作するアプリはシステムリソースにアクセスできません。このような制限は、古くはJavaアプレットなどでも同様でした。もし、ウェブアプリがローカルリソースを自由にアクセスできたら、自由にシステムを破壊したり、情報を盗み取ったりできてしまいますので、そういうことはできないように制限されています。ユーザーに明示的にファイルを選択させるダイアログボックスを利用して、ファイルを読み込むことはできます。

#### セキュリティの都合により、ネットワークのクロスオリジン（別ドメイン）へのアクセスが制限される

アクセス中のドメイン以外のドメインに対するネットワーク接続（httpアクセスなど）は制限されます。ウェブブラウザ上で動作するアプリが際限なくどこにでもアクセスできたら、他のサイトを攻撃したり、不正なリクエストを実行してりできてしまいますので、そういうことはできないように制限されています。クロスオリジンを許可する設定がされているウェブAPIなどは利用できます。

#### モバイル端末（スマホなど）では実行できないか、または、ロードにとても時間がかかる

端末側のブラウザがWebAssemblyに対応している必要がありますし、Qt自体がかなり大きいため、実行するためにはそれなりの性能のCPUと通信回線が必要となります。

## Qtのおさらい

### C++

最近ではGoやPythonをはじめとした、モダンな言語に押され気味で、C++人口が減ってきており、筆者は寂しく思っています。余談ですが、筆者の勤務先でもC++人材の確保に苦労していますし、某社で聞いた話では、C++で募集しても人が集まらないので、C#やウェブ系に移行せざるを得ないみたいな状況だそうです。それでも筆者はC++が大好きです。生産性の高さと開発やデバッグのしやすさでは一日の長があります（と筆者は信じています）。C++はおっさん向け言語ではないと言いたいです。皆さんC++をやりましょう！

### Qt for WebAssembly

というわけで、C++とQtでアプリケーションを作ります。そのままビルドすればデスクトップアプリ、ビルド方法を少し変えるだけでWebAssemblyアプリが出来上がります。実際、開発中は、デスクトップアプリとして作って、デバッグして、WebAssemblyアプリをビルドして、ウェブサーバーにデプロイするといった開発手順になると思います。

## 開発環境の構築

Ubuntu上で作業を行う想定です。次のものをインストールします。

- Ubuntu 18.04
- Qt 5.13
- Emscripten
- Apache 2

### Ubuntu

筆者のメイン開発環境は未だに16.04を使用していますが、今から新規構築するなら18.04が良いでしょう。どちらでも大差はありません。

とりあえず、開発ツールをインストールします。あと、Emscriptenのインストールのために git も必要となります。QtアプリのビルドにOpenGLの開発パッケージが必要になります。 Python はEmscriptenによって使用されます。

`sudo apt-get install build-essential git libgl1-mesa-dev python`

### Apache 2

Qtは後回しにして、先にウェブサーバをインストールします。お好みで Apache の代わりに nginx などを使用しても構いません。

`sudo apt-get install apache2`

ローカルPCでウェブブラウザを起動して、 http://localhost/ にアクセスし、Apacheの初期ページが表示されることを確認します。

後ほどWebAssemblyアプリをデプロイする場所を作っておきます。

`sudo mkdir /var/www/html/wasmtest`
`sudo chmod 777 /var/www/html/wasmtest`

/var/www/html/wasmtest の中に、適当な内容で index.html を作成し、ブラウザから http://localhost/wasmtest/ が表示されることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/0dd29567-b0da-f376-fa59-86b9853eed93.png)

### Qt

Qtインストーラーをダウンロードします。

https://www.qt.io/download-qt-installer

実行属性を付与します。

`chmod +x qt-unified-linux-x64-3.1.1-online.run`

インストール先を /opt/Qt にしますので、rootアカウントでインストーラーを実行します。

`sudo ./qt-unified-linux-x64-3.1.1-online.run`

インストーラー画面を進めていくと、コンポーネントの選択画面になります。Qt 5.13 は Latest releases に含まれていますので、チェックボックスをオンにして Refresh ボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/a948eb2f-4a8a-292f-7cdf-7b196fa91b24.png)

Qt 5.13 の「Desktop gcc 64-bit」と「WebAssembly」をチェックして、インストールを続行します。

### Emscripten

Emscriptenのダウンロードページは https://emscripten.org/docs/getting_started/downloads.html です。インストール方法はそこに書かれているとおりです。

`git clone https://github.com/emscripten-core/emsdk.git`

インストール先は、ユーザーディレクトリのトップが便利です。 /home/*(username)*/emsdk の様なディレクトリ構成になる想定です。

Qt for WebAssembly の公式サイトに記載されている手順のとおりにインストールします。

https://doc.qt.io/qt-5/wasm.html

上記サイトに書かれている内容で注意すべき点は、emsdkのバージョンを間違えないことです。通常はQt5.13の64ビット環境だと思いますので、emsdkのインストールでは、次のようなコマンドを入力します。

`cd ~/emsdk`
`./emsdk install sdk-1.38.30-64bit`
`./emsdk activate --embedded sdk-1.38.30-64bit`

バージョン番号は基本的に上記の通りですが、Emscriptenのバージョンが古すぎても新しすぎても動作しませんので、公式の Qt for WebAssembly サイトを確認して、ご利用のQtのバージョンと合っていることを確認してください。

後にも出てきますが、emsdkを利用可能にするには、環境変数の設定が必要です。次のように実行します。（後でいいです）

`source ~/emsdk/emsdk_env.sh`

## Qtデスクトップアプリケーション

WebAssemblyに関係なく、デスクトップで動作するアプリを作っておいて、後ほどWebAssembly用にビルドするような開発環境を構築します。何よりまず、デスクトップアプリのビルドができるようにする必要があります。

Qt Creatorを起動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/a9bf5a16-d7ff-19e4-e34d-437328d9035e.png)

[プロジェクト]→[新しいプロジェクト]ボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/d1aa0180-2e1f-ec5b-d1c5-0a89793f7343.png)

[Qt Widgets Application]を選択して[選択]を押します。

プロジェクト名を決めます。お試し用なら適当な名前で構いませんが、とりあえず「Hello」とします。名前を決めたら[次へ]を押します。

ほとんどの設定は、既定の値のままで、何画面か先に進むと、「キットの選択」という画面になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/173d0196-4c35-eb74-5da3-9a4b237b407d.png)

ここには、「Qt 5.13.2 WebAssembly」という表示がありますが、あえてこれは選ばず、「Desktop Qt 5.13.2 GCC 64bit」を選択して次へ進みます。最後に[完了]を押すと、アプリケーションの雛形となるファイルがいくつか生成されます。

Qt Creator左下の▶ボタンを押します。何もないウィンドウが表示されたら成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/b09f01d1-6e98-1ec1-e962-6bc89cda170c.png)

Forms/mainwindow.uiをダブルクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/d0e97def-51a6-2f90-0c89-142dd2cb580c.png)

フォームにボタンとラベルを配置します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/6a3ebfbe-aea0-56b4-81e7-98b8c81faa98.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/487fb74e-9972-5a33-e433-8646fcf6ecae.png)

ボタンを右クリックして、[スロットへ移動]を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/fee68805-aa63-91a3-789f-6c358efc818a.png)

[clicked()]を選択してOKを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/ab92f3e4-aaaa-85a4-a69b-1ae587a27fcb.png)

mainwindow.cpp に、ボタンがクリックされたときに実行される関数が生成されますので、ラベルの表示を更新するコードを書きます。

``` .cpp
void MainWindow::on_pushButton_clicked()
{
    static int value = 0;
    value++;
    ui->label->setText(QString::number(value));
}
```

Qt Creator左下の▶ボタンを押します。ボタンを押す度にラベルの表示が変化したら成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/e00bcbbd-34bb-69c2-b395-8d0536c9c3c2.png)

## WebAssembly用にビルド

Qt Creator上でWebAssembly用のビルド環境を作ることは可能ですが、環境変数やパスの設定が面倒なので、コマンドラインで行うことが私のおすすめです。本記事では、コーディングとデスクトップ版アプリのビルドはQt Creator上で行い、WebAssembly版のビルドはコマンドラインで行う、というのを繰り返しながら開発していくスタイルで進めます。

デプロイを簡単にするため、ビルド結果の出力ディレクトリを変更します。プロジェクトファイルを修正します。

``` Hello.pro
DESTDIR = $$PWD/_bin
```

プロジェクトファイルを編集したら、忘れずに Ctrl+S を押して保存しください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/6efa62b2-7666-6fdb-c9c9-48541876bd42.png)

EmscriptenのSDKを利用するための環境変数を定義するためのシェルスクリプトが`emsdk_env.sh`です。これをターミナルから実行します。現在のターミナルに環境変数を取り込むために、`source`コマンドを利用して実行します。

`source ~/emsdk/emsdk_env.sh`

Emscriptenが利用できるようになったか確認します。いろいろごちゃごちゃ表示されると思いますが、バージョン情報らしきものが表示されれば大丈夫です。

`em++ -v`

プロジェクトディレクトリ内にWebAssembly用ビルドディレクトリを作成します。

`cd Hello`
`mkdir build`
`cd build`

qmakeを実行します。WebAssembly専用版のqmakeです。デスクトップアプリ用qmakeとは異なります。

`/opt/Qt/5.13.2/wasm_32/bin/qmake ..`

makeを実行します。

`make`


コンパイル中、 emcc: warning ... の様な表示が出て、しばらく停止しますが、あきらめずに待ちます。Qtのライブラリが大きいので、リンクに時間がかかります。2分くらいかかると思います。

makeが完了すると、buildディレクトリに中間ファイルができます。プロジェクトファイルの`DESTDIR`で指定したディレクトリにビルド済みファイルが作られます。

```
soramimi@ubuntu18x64:~/Hello/build$ ls
Makefile                    main.o              moc_mainwindow.o
hello.js_plugin_import.cpp  mainwindow.o        moc_predefs.h
hello.js_plugin_import.o    moc_mainwindow.cpp  ui_mainwindow.h
soramimi@ubuntu18x64:~/Hello/build$ ls ../_bin
Hello.html  Hello.js  Hello.wasm  qtloader.js  qtlogo.svg
soramimi@ubuntu18x64:~/Hello/build$ 
```
これらのファイルを `/var/www/html/wasmtest` にコピーします。

`cp ../_bin/* /var/www/html/wasmtest/`

ウェブブラウザから`http://localhost/wasmtest/Hello.html`にアクセスすると作成したアプリがウェブブラウザ上で実行されます。

このHTMLファイルをindex.htmlにしておくと、`http://localhost/wasmtest/`でアクセスできます。

`cd /var/www/html/wasmtest/`
`cp Hello.html index.html`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/0ad2619b-3dca-1675-2ea6-adf79f8024de.png)