wxWidgetsでのスレッド間通信
発端
スレッド間通信なんてけったいなものは、普通趣味のプログラミングでは使わない(たぶん)
使おうと思ったのは純粋にそれが必要になったからだ。
やりたかったこと
アプリケーションにログを出したかった。
古くはIEのステータスバーに出るログのように、アプリケーションの動作の進捗を伝える機能は珍しくない。
そこでwxTextCtrlというクラスのオーバーロードされた演算子の機能を使ってみた。
まさかのクラッシュ
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(); };