C++ (fork) Advent Calendar 2013 9日目
Advent Calendar 初参加です。あまり言語仕様的なところはわからないのですが、なんかC++でGUI作りたいって人のためにwxWidgetsのことを書きます。
概要
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使う組、後者を直接描画組とすると、有名なツールキットはだいたいこんな分類になる(…と思う)
2014/01/07 訂正、Qtはウィンドウやボタンを独自に全て描画している
2.各プラットフォームでのwxWidgetsインストールとビルド
つい最近wxWidgets-3.0がリリースされたので、さっそく使ってみましょう。また、C++11の機能を使用したいので、ビルドには-std=c++11をつけましょ。
- Windows
環境構築についてはこちら
MinGW64環境の構築手順2 - なんとな~くしあわせ?の日記
ちょっと以前に作成した環境は以下から
MinGW64環境の構築手順とwxのビルド - なんとな~くしあわせ?の日記
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
最近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しなければならない。
ちょっと危険な気もするけど、まあ多少はね?
Mac特有の問題
wxWidgetsをMacで使用する場合越え難い壁が3つほどあります
- 1.Application Bundle
Macにインストールされたバイナリの構造を見て欲しい、たぶん「Application.app/Contents/MacOS/"バイナリ"」という構造になっているはずだ。これをMakefileでやろうとするとキツイ。
- 2.install_name_tool
Mac OS Xで使用されている動的リンクライブラリはdylibとかいう変な名前がついており、どこにインストールされたかとかどのライブラリを参照しているかという情報をもっている。バイナリを配布する際はinstall_name_toolを使用してこの参照の向きを変更してやらなければならない。(MacOSXの動的リンクライブラリの設定変更 - なんとな~くしあわせ?の日記)正直これはかなり面倒だった。
- 3.マルチスレッドによるGUI更新が不可能
以前、wxWidgetsでのスレッド間通信 - なんとな~くしあわせ?の日記で書いたのですが
wxWidgetsはMacOSXでのマルチスレッドGUI更新をサポートしていません。
マルチスレッドでのGUI更新とは、親ウィンドウが呼び出した子ウィンドウのインスタンス内での画面更新ができないということです。解決策としては、子ウィンドウで画面を更新する際には、親ウィンドウを呼び出して親ウィンドウから子ウィンドウを更新するというおかしなコーディングが必要になる(…正直欠陥じゃないのかこれ(゜∀。)ワヒャヒャヒャヒャヒャヒャ)。