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

「そしてそれゆえ、知識そのものが力である」 (Nam et ipsa scientia potestas est.) 〜 フランシス・ベーコン

wxWidgetsでのスレッド間通信

発端

スレッド間通信なんてけったいなものは、普通趣味のプログラミングでは使わない(たぶん)
使おうと思ったのは純粋にそれが必要になったからだ。

やりたかったこと

アプリケーションにログを出したかった。
古くはIEのステータスバーに出るログのように、アプリケーションの動作の進捗を伝える機能は珍しくない。

そこでwxTextCtrlというクラスのオーバーロードされた演算子の機能を使ってみた。

wxTextCtrl::operator <<
wxTextCtrl *wnd = new wxTextCtrl(my_frame);

  (*wnd) << "Welcome to text control number " << 1 << ".\n";

まあ上記のようにコードを書けば、ウィンドウのテキスト表示部分に「Welcome to text control number」と表示されるわけだ。便利。そしてそのコードをwxWidgetsのイベント処理メソッドや他のクラスのインスタンス内から呼び出して使った。

まさかのクラッシュ

LinuxとWindowsで同じコードが動いたので、Macでも大丈夫だと思ったらダメだった。
クラッシュした時のダンプはこんな感じ。Apppendした後にcocoaの内部コードまで到達して死んでしまう。

#16 0x000000010170e5c3 in wxTextEntryBase::AppendText (this=0x10486a560, text=@0x7fff5fbfe200) at ./src/common/textentrycmn.cpp:222
#17 0x000000010170b626 in wxTextCtrlBase::operator<< (this=0x10486a000, s=@0x7fff5fbfe200) at ./src/common/textcmn.cpp:964

理由は以下に書いてある

Mac OS X only: wx.TextCtrl.AppendText and .WriteText throw exception if executed from a thread
#12225 (Mac OS X only: wx.TextCtrl.AppendText and .WriteText throw exception if executed from a thread) – wxWidgets

We don't support using GUI elements from other threads. 
You need to post a message to the main thread asking it to update it.
(wxWidgetsは別スレッドからのGUI部品の使用はサポートしてません。
それを行いたい場合はメインスレッドにGUIを更新するメッセージを伝える必要がある。)

まあサポート外だったということだ。
しかも、今まで意識していなかったがGUIというのは単一のスレッドで動いているわけではなく常に複数のスレッドを走らせて動いているのだ。

解決法(間違ったアプローチ)

要はイベントによってメインスレッドに情報を伝え、GUIの更新を行えばいいわけだ。
こんなときはStackOverflow主上に答えを聞く。

以下がとても役に立った
What is the SendMessage Equivalent in wxWidgets - Stack Overflow

そしてコードはこんな感じになった

イベント送る側
     // メインのスレッドにログとイベントを送る
     void SendLogging(wxString& message) {
          // イベント用のインスタンスを作る(列挙型のIDをつける)
	  wxCommandEvent* event = new wxCommandEvent(wxEVT_COMMAND_TEXT_UPDATED, ID_Logging);
          // 更新したい情報を設定する
	  event->SetString(message.c_str());

#if wxCHECK_VERSION(2, 9, 0)
	  wxTheApp->GetTopWindow()->GetEventHandler()->QueueEvent(event->Clone());
#else
	  wxTheApp->GetTopWindow()->GetEventHandler()->AddPendingEvent(*event);
#endif
     };
イベント受け取る側
// イベントテーブルにこんな感じの設定をする
BEGIN_EVENT_TABLE(MainWindow, wxFrame)
   EVT_TEXT(ID_Logging, MainWindow::Logging)
END_EVENT_TABLE()

     // ログ出力
     void Logging(wxCommandEvent& event) {
	  *logWindow << event.GetString();
     };

解決法(正攻法)

これを書いている途中でログ専用GUI部品の存在に気づいた。
多分これを使えばwxLogの結果をウィンドウにリダイレクトできるんじゃないかな(白目)

wxLogWindow
wxLog