なんとな~くしあわせ?の日記

ClojureとかAWSの設定とかをメモする技術ブログ

C++ (fork) Advent Calendar 2013 9日目



Advent Calendar 初参加です。あまり言語仕様的なところはわからないのですが、なんかC++GUI作りたいって人のためにwxWidgetsのことを書きます。

概要

  1. 1.1 wxWidgetsアーキテクチャと位置づけ
  2. 1.2 他のライブラリとの比較
  3. 2.各プラットフォームでのwxWidgetsインストールとビルド
  4. 3.wxWidgets固有の仕組み、コーディングの注意
  5. 4.まとめ

1.1 wxWidgetsアーキテクチャと位置づけ

 そもそもwxWidgetsとは何かと言いますと、ウィジェット・ツールキット - Wikipediaです。OS固有のボタンとかツールバーを描画するためのAPIを叩くライブラリをまとめたものです。なおかつ、wxWidgetsクロスプラットフォームウィジェットツールキットであります。つまり、同じソースコードからWindows/Linux/Mac OS X/*BSD等々のGUI描画が可能となるという事です。

 ですので、wxWidgetsや他のクロスなウィジェットツールキットを使い、一度C++で書いたソースコードさえ用意しとけばいろいろなプラットフォームで動くアプリケーションが作れるのです。

 さてさて、「JavaならSwingやSWTを使えば簡単にクロスプラットフォームなアプリ作れるんじゃない?」という意見があると思います。そこに関しては、効率を重視するならJavaを使うべきだと思います。しかし、やはりC++は高速です。無茶な処理させても割と早いと思います。それに楽しいです。あと、C++で作っとけばC++独自の面白げな文法と組み合わせてアプリ作れたり、ゲーム作るための勉強になったり、次につながりやすいと思います。

1.2 他のライブラリとの比較

 画面にウィンドウやボタンを配置する方法は2種類ある。1つは、OSが用意しているウィンドウ描画のAPIを叩くもの。2つ目はOSが用意している図形描画のAPIを叩いてウィンドウを描画するもの。前者はウィンドウ描画のAPIをラップする仕様になるため、構造的にレイヤーが厚くなるができあがるウィンドウは綺麗だ。後者はウィンドウ描画の制御のコーディングが大変だが、描画は高速、ただしできあがるウィンドウはあまり綺麗ではなかったりする。

 前者をAPI使う組、後者を直接描画組とすると、有名なツールキットはだいたいこんな分類になる(…と思う)

  1. API使う組    wxWidgets, SWT(Java)
  2. 直接描画組    Qt, Tk, FLTK, Swing(Java)

2014/01/07 訂正、Qtはウィンドウやボタンを独自に全て描画している

2.各プラットフォームでのwxWidgetsインストールとビルド

 つい最近wxWidgets-3.0がリリースされたので、さっそく使ってみましょう。また、C++11の機能を使用したいので、ビルドには-std=c++11をつけましょ。

  1. Windows

環境構築についてはこちら
MinGW64環境の構築手順2 - なんとな~くしあわせ?の日記

ちょっと以前に作成した環境は以下から
MinGW64環境の構築手順とwxのビルド - なんとな~くしあわせ?の日記

  1. Linux

DebianにはまだパッケージとしてwxWIdgets-3.0は入っていません、配布のことを考えると、まだ2.8を使うのがいいと思います。

 # apt-get install libwxgtk2.8-dev libwxbase2.8-dev wx-common wx2.8-examples wx2.8-doc wx2.8-headers
  1. Mac OS X

最近portsにwxWidgets-3.0が入ったようなので、それを使うのが最も簡単なインストール方法です
インストール始めるとかなり長い間ガリガリいってると思うので、インストールしてる間に寝とくといいと思います

 // 普通にportsでインストール
 # port install wxWidgets-3.0 @3.0.0_4

ビルドしたい場合は以下を参考にしてください
MacでwxWidgets - なんとな~くしあわせ?の日記

アイコンやMacでのMakefileの作り方は以下で
wxWidgetsにおけるプラットフォームごとのアイコン読み込み方法 - なんとな~くしあわせ?の日記

3.wxWidgets固有の仕組み、コーディングの注意

サンプルを見よう

wxWidgetsを開発版でインストールするか、ソースを落としてきてビルドすると、内部にsamplesというディレクトリが存在します。そこでだいたいのできることがわかります。

インスタンスの自動deleteに注意

ここに書かれているように、wxWidgets: wxWindow Class Reference

Please note that all children of the window will be deleted automatically by the destructor 
before the window itself is deleted which means that you don't have to worry about deleting them manually.

wxWidgetsで使用されるクラスは大概wxWindowクラスからの継承である。そしてwxWindowクラスは自身のインスタンスを自動でdeleteする仕組みをもっている。自分で作成したクラスのインスタンスは自分で管理しなければいけないが、wxWindow型からの継承クラスは特に管理は考えなくて良い。

ダメな例:

/**
 * wxFrameクラスのコンストラクタ
 */
HelloWorld::HelloWorld(const wxString& title) :
wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(640, 480)) {

 wxMenu *fileMenu = new wxMenu;
 delete fileMenu; // ← ダメ!ゼッタイ

上記はおそらくコンパイルは通るが、どのプラットフォームでもメモリの二重開放で死ぬ。

wxWindow型内部のオブジェクト検索関数を使おう

ウィンドウは動的に追加されたり削除される可能性がある。
ウィンドウ内部に必要な情報を保持させている場合、後からそれを取得・更新しなければならない場合がある。

wxWindowクラスには以下のような関数が用意されている

wxWindow::FindWindow
http://docs.wxwidgets.org/2.8/wx_wxwindow.html#wxwindowfindwindow
wxWindow::FindWindowById
http://docs.wxwidgets.org/2.8/wx_wxwindow.html#wxwindowfindwindowbyid
wxWindow::FindWindowByLabel
http://docs.wxwidgets.org/2.8/wx_wxwindow.html#wxwindowfindwindowbylabel
wxWindow::FindWindowByName
http://docs.wxwidgets.org/2.8/wx_wxwindow.html#wxwindowfindwindowbyname

☆使い方:例1☆

// ウィンドウの情報が欲しいぞ!
void MyFrame::FindSomeWindowInfo (wxFooEvent& event) {

    // よし、ならばこのウィンドウの内部で宣言したウィンドウをIDから引いてくるぜ
    wxWindow* window = this->FindWindowById(ID_I_WANT_TO_FIND_WINDOW, this);

    // 元のクラスの型はwxListctrlだったからダイナミックキャストするぜ
    wxListCtrl* list = dynamic_cast<wxListCtrl*>(window);

    list->SomeProcess();  // 何かしらの処理を行う
}

※しかしこの関数を使うと、規定クラスから継承クラスを呼び出すために大概dynamic_castしなければならない。
ちょっと危険な気もするけど、まあ多少はね?

STLとの連携

wxWidgetsには、一応独自のコンテナが用意されているが、特にそれを絶対使用しなければならないというわけでもなく、普通にvectorやmapなどを使っていくべきだと思います。

Mac特有の問題

wxWidgetsをMacで使用する場合越え難い壁が3つほどあります

  1. 1.Application Bundle

Macにインストールされたバイナリの構造を見て欲しい、たぶん「Application.app/Contents/MacOS/"バイナリ"」という構造になっているはずだ。これをMakefileでやろうとするとキツイ。

  1. 2.install_name_tool

Mac OS Xで使用されている動的リンクライブラリはdylibとかいう変な名前がついており、どこにインストールされたかとかどのライブラリを参照しているかという情報をもっている。バイナリを配布する際はinstall_name_toolを使用してこの参照の向きを変更してやらなければならない。(MacOSXの動的リンクライブラリの設定変更 - なんとな~くしあわせ?の日記)正直これはかなり面倒だった。

  1. 3.マルチスレッドによるGUI更新が不可能

以前、wxWidgetsでのスレッド間通信 - なんとな~くしあわせ?の日記で書いたのですが
wxWidgetsMacOSXでのマルチスレッドGUI更新をサポートしていません。
マルチスレッドでのGUI更新とは、親ウィンドウが呼び出した子ウィンドウのインスタンス内での画面更新ができないということです。解決策としては、子ウィンドウで画面を更新する際には、親ウィンドウを呼び出して親ウィンドウから子ウィンドウを更新するというおかしなコーディングが必要になる(…正直欠陥じゃないのかこれ(゜∀。)ワヒャヒャヒャヒャヒャヒャ)。

4.まとめ

あまり新しいことは書けませんでした。
振り返ってみると、wxWidgetsはWindows環境ではMinGW環境を作るのが面倒。
OSXではイベントがうまく使えないという欠点がある。(← これは結構致命的なのでなんとかしてほしいところ)

単にGUIアプリを軽く作りたいという方はwxPythonなどの言語バインディングを試してみてはいかがだろうか。