<プリマリスレッド ダイアログ実装>
前頁でセカンダリスレッド・そのメインウィンドウの実装をしたので、次に
プライマリスレッド側の実装をします。といってもこちらはすでにスケルトンができている上に、
コードを記述するのはCStopWatchDlgだけです。
StopWatchDlg.h、StopWatchDlg.cppに以下のように記述してください。
(StopWatchDlg.h)
// StopWatchDlg.h : ヘッダーファイル // #pragma once //前方宣言-------------------------- class CSecThread; //---------------------------------- // CStopWatchDlg ダイアログ class CStopWatchDlg : public CDialog { // コンストラクション public: CStopWatchDlg(CWnd* pParent = NULL); // 標準コンストラクタ // ダイアログデータ enum { IDD = IDD_STOPWATCH_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV サポート // 実装 protected: HICON m_hIcon; // 生成された、メッセージ割り当て関数 virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); //メンバ変数////////////////////////////////////////////////////////////////// protected: CSecThread *m_pSecThread; //セカンダリスレッド用ポインタ //メンバ関数////////////////////////////////////////////////////////////////// protected: void TerminateSecThread(); //セカンダリスレッド終了 afx_msg LRESULT OnTerminateApp(WPARAM wParam, //アプリケーション終了ハンドラ LPARAM lParam); //(セカンダリスレッドからの終了通知) virtual void OnOK(); //CDialog::OnOKオーバーライド virtual void OnCancel(); //CDialog::OnCancelオーバーライド public: afx_msg void OnBnClickedBtnStart(); //Startボタンクリック afx_msg void OnBnClickedBtnStop(); //Stopボタンクリック afx_msg void OnBnClickedBtnClear(); //Clearボタンクリック protected: DECLARE_MESSAGE_MAP() }; |
(StopWatchDlg.cpp)
以上ですべての実装はできたのでビルドして実行してください。
<プログラム解説>
マルチスレッドがテーマなのでストップウォッチに関する機能のことは書きません。 ユーザインターフェイススレッドでは、スレッドの起動・破棄だけでも結構ややこしいので、 同期に関することは盛り込んでないです。
(起動)
プログラムを実行するとCStopWatchAppの唯一のインスタンス
theAppのInitInstanceが呼ばれてオーバーライドされたCStopWatchApp::InitInstance()で
CStopWatchDlgダイアログが構築されてCStopWatchDlg::OnInitDialog
がフレームワークから呼ばれます。ここまではプライマリスレッド内の話です。
ここで第一引数にCSecThread型のRuntimeクラス情報を渡して、
AfxBeginThreadヘルパー関数を呼び出すと
CRuntimeClass::CreateObjectを使って動的にCSecThreadインスタンスが作られます。そして、
AfxBeginThreadはCWinThread::CreateThreadを呼びますが、第四引数にCREATE_SUSPENDを指定している
のでスレッドを作りますが、保留状態になります。
CWinThread::m_bAutoDeleteはスレッドが終了するとき、つまり、CWinThread::Runの
ループが抜けて制御関数_AfxThreadEntryが
終了するときに自動的にCWinThread(CSecThread)インスタンスを削除するかどうかのフラグです。
その仕組みは_AfxThreadEntryの最後に
AfxEndThreadが呼ばれてスレッドを終了していますが、その最後のほうで
pThread->Delete();
が実行されています。pThreadはCWinThreadへのポインタでDelete()の内容が
if (m_bAutoDelete) { delete this};
となっています。ということでこのメンバがTRUEの時はスレッドの終了とともに
CWinThreadインスタンス(今回の場合はCSecThread)も終了します。
この後、CWinThread::ResumeThread()が呼ばれて保留になっていたセカンダリスレッドが 再開されます。InitInstanceで先ほど実装した CDspWndを構築&表示してメッセージポンプを起動しています。一方プライマリスレッドも、 AfxBeginThreadを呼んでセカンダリスレッドであるCSecThreadへのポインタを取得したら、 OnInitDialogを終了してダイアログを表示します。
(終了)
起動の場合は割りと素直にできますが、問題は終了の場合です。
例えば、表示ウィンドウが表示された状態で、ダイアログが閉じられたらどうでしょう?
セカンダリスレッドのウィンドウの終了できないし、スレッドの終了処理もないままに
アプリケーションが終了していまいメモリリーク等重大な問題が発生します。
起動時はプライマリスレッド→セカンダリスレッドの順で作られているので、表示ウィンドウ
(CDspWnd)、操作ウィンドウ(CStopWatchDlg)のどちらが閉じられようとしても、
基本的に、CDspWnd(セカンダリのメインウィンドウ)→CSecThread(セカンダリスレッド)
→CStopWatchDlg(プライマリのメインウィンドウ)→CStopWatchApp(プライマリスレッド)の順に
終了しないといけません。今回はアプリケーションとしてプライマリとセカンダリの
各ウィンドウは一体となっていて、どちらか一方が終了したら意味がないのでこういうことを
いうのですが、アプリによっては作業が終了したらセカンダリだけ終わらせるということもあります。
いずれにしても、セカンダリだけが残っている状況はまずいということです。
この辺のオブジェクトの生存確認は、
セカンダリ側は
CDspWnd::OnDestroy
プライマリ側は
CStopWatch::TerminateSecThread
CStopWatch::OnTerminateApp
CStopWatch::OnOK
CStopWatch::OnCancel
が担当しています。
最初にCDspWndが閉じられる場合です。WM_DESTROYによってOnDestroyが呼ばれます。
ここで、「CDspWndが閉じられるので、セカンダリスレッドの終了を待ってCStopWatchDlgも
終了してください」という意味で、プライマリのCStopWatchDlgにユーザ定義の
WM_TERMINATEAPPメッセージを送ります。WM_TERMINATEAPPを受け取ったCStopWatchDlgは
ハンドラのOnTerminateAppでTerminiteSecThreadを呼び出してセカンダリスレッドCSecThreadが
終了するのを待ちます(WaitForSingleObject)。
ここで、CDspWndを閉じようとしているだけなのに、なぜセカンダリスレッド(CSecThread)まで
終了するのがわかるのかという疑問を持たれるかもしれません。
それはセカンダリスレッド側のメインウィンドウ
(CDspWnd)が閉じられると自動的にスレッドが終了するようにCSecThread::m_pMainWndにCDspWndが
設定したことを思い出すとCDspWndの終了=CSecThreadの終了が成立することがわかると思います。
セカンダリスレッドの終了を確認したら、CDialog::OnOKを呼んでプライマリスレッドのダイアログを
閉じてアプリケーションを終了します。
問題はプライマリスレッドのCStopWatchDlgを先に閉じようとした場合です。 CDialogの派生クラスの既定ではフレームワークからOnOKまたはOnCancelが呼ばれてその中で EndDialogがコールされ閉じられます。それでは、セカンダリスレッド側が残ってしまいまずいので、 この2つの関数をオーバーライドして既定の動作を変更して、先にセカンダリのCDspWndウィンドウを 閉じるように変更します。といっても簡単でCDspWndにWM_CLOSEメッセージをポストするだけです。 あとはCDspWndが先に閉じられる場合と同じことです。
実際は、スレッド間で共有データを使うことがほとんどだと思うのですが、 ユーザインターフェイススレッドの場合はかなり複雑なので、共有データをつかった同期については 一切触れてません。しかし、マルチスレッドで本当に大変なのはこの同期についてだと思うので プロセス間通信やワーカースレッド でもすこし書いているので確認してみてください。