◆マルチスレッド - VC++ 2005 MFC 〜 ユーザーインターフェイススレッド その1
[ ホーム ] [ 上へ ] [ マルチスレッド VC++ 2005 MFC 〜 ユーザインターフェイススレッド その2 ]
・スレッド作成の実際(ユーザインターフェイススレッド)- WindowsXP + VC++ 2005
ワーカースレッドではセカンダリスレッド内でウィンドウは作成されないので、 見た目に複数のスレッドが起動していることがわかりづらいです (もちろん動作をみれば確認できると思います)。今回は簡易な ストップウォッチをつくってユーザインターフェイスの動作を確認してみます。
ダイアログベースのプロジェクトを作成するので、 プライマリスレッド側はCDialogの派生クラスがメインウィンドウになり、こちらでストップウォッチの スタート/ストップを担当します。セカンダリスレッドではウィンドウを表示して 時間の経過を表示します。 調整はできますが、10ミリ秒単位でタイマを実行して経過時間を表示します。10msのタイマの精度に 関してはちょっと疑問がありますが本題とは直接関係ないので無視します。短い時間間隔でSetTimerの コールバック関数が呼ばれ続けるので、別スレッドで動作させる意味は十分あります。では作ってみます。
メニューのファイル(F)→新規作成(N)→プロジェクト(P)でアプリケーションウィザードから
プロジェクトの種類(P) : Visual C++ テンプレート : MFCアプリケーション プロジェクト名 : StopWatch(任意) ソリューション名 : UIThread(任意) →OK →次へ アプリケーションの種類 : ダイアログベース(D) (他はデフォルト) →次へ メインフレームのスタイル : システムメニュー(S)のみチェック →次へ 高度な機能 : (デフォルト) →次へ →完了
次にダイアログリソースを作成します。ツールバーの 表示(V)→その他のウィンドウ(E)→リソースビュー(R)でリソースビューを表示してダイアログを 以下のように作成してください。
各コントロールを右クリックしてプロパティ(R)を実行して プロパティシートを表示後、以下のようにプロパティ・管理イベントを設定してください。
コントロールタイプ | プロパティ・イベント | 値 | |
1 | ボタン | IDプロパティ | IDC_BTN_START |
Captionプロパティ | Start | ||
BN_CLICKEDイベント | <追加> OnBnClickedBtnStart | ||
2 | ボタン | IDプロパティ | IDC_BTN_STOP |
Captionプロパティ | Stop | ||
BN_CLICKEDイベント | <追加> OnBnClickedBtnStop | ||
3 | ボタン | IDプロパティ | IDC_BTN_CLEAR |
Captionプロパティ | Clear | ||
BN_CLICKEDイベント | <追加> OnBnClickedBtnClear |
上記の各ボタンのクリック時の実装をするのですが、ストップウォッチの経過時間の 取得・表示機能はセカンダリスレッドが担当するので、先にセカンダリスレッド (CWinThreadの派生クラス)と、セカンダリスレッドで稼動する表示ウィンドウ(CWndの派生クラス)を 実装します。それぞれの名前をCSecThread、CDspWndとします。 まず、CDspWndクラスの作成&実装から。
<セカンダリスレッドで表示するウィンドウ実装 - CDspWnd(CWnd派生クラス)>
ツールバーのプロジェクト(P)→クラスの追加(C)で「クラスの追加」 ダイアログで以下のように設定してください。
カテゴリ(C) :MFC テンプレート(T) :MFCクラス →追加(A) クラスウィザードで次のように設定してください。 クラス名(L) :CDspWnd 基本クラス(B):CWnd (デフォルト) →完了
DspWnd.hとDspWnd.cppにCDspWndクラスのヘッダファイル・実装ファイルのスケルトンが できているので、次のようにコードを記述してください。
(DspWnd.h)
//DspWnd.h : ヘッダファイル // #pragma once //---------------------------------------------------------------------------------- #define WM_TERMINATEAPP (WM_USER + 10) //アプリケーション終了メッセージ #define TIMER_SPAN 10 //ストップウォッチ間隔(ミリ秒単位) //---------------------------------------------------------------------------------- // CDspWnd - 経過時間表示ウィンドウ class CDspWnd : public CWnd { DECLARE_DYNAMIC(CDspWnd) public: CDspWnd(); //コンストラクタ virtual ~CDspWnd(); //デストラクタ //メンバ変数//////////////////////////////////////////////////////////////////////// protected: CPoint m_point; //クライアント領域中心位置 DWORD m_dwStart; //スタート時間(ミリ秒単位) DWORD m_dwCurrent; //現在時間(ミリ秒) BOOL m_bBusy; //ストップウォッチ開始フラグ //メンバ関数//////////////////////////////////////////////////////////////////////// protected: virtual void PostNcDestroy(); //C++オブジェクト自動削除 public: BOOL Create(); //Window構築 afx_msg void OnDestroy(); //Window破壊時 afx_msg void OnTimer(UINT_PTR nIDEvent); //タイマ afx_msg void OnPaint(); //クライアント領域描画 BOOL Start(UINT nElapse = TIMER_SPAN); //ストップウォッチ開始 BOOL Stop(); //ストップウォッチ停止 BOOL Clear(); //ストップウォッチデータクリア protected: DECLARE_MESSAGE_MAP() }; |
(DspWnd.cpp)
// DspWnd.cpp : 実装ファイル // #include "stdafx.h" #include "StopWatch.h" #include "DspWnd.h" #define TIMER_ID 1 //タイマID // CDspWnd IMPLEMENT_DYNAMIC(CDspWnd, CWnd) //コンストラクタ CDspWnd::CDspWnd() : m_point(0, 0) , m_dwStart(0) , m_dwCurrent(0) , m_bBusy(FALSE) { } //デストラクタ CDspWnd::~CDspWnd() { } BEGIN_MESSAGE_MAP(CDspWnd, CWnd) ON_WM_DESTROY() ON_WM_TIMER() ON_WM_PAINT() END_MESSAGE_MAP() // CDspWnd メッセージハンドラ //Window構築 BOOL CDspWnd::Create() { CRect rect; //CDspWndの表示位置取得 theApp.m_pMainWnd->GetWindowRect(&rect); rect.OffsetRect(0, -1 * rect.Height()); //Window構築 if (! CWnd::CreateEx(0, AfxRegisterWndClass(CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW, 0, reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1), 0), _T("Stop Watch"), WS_OVERLAPPED, rect, NULL, 0) ) return FALSE; //クライアント領域更新 UpdateWindow(); //Window表示 ShowWindow(SW_SHOW); //秒数表示位置取得 GetClientRect(&rect); m_point = rect.CenterPoint() - CPoint(10, 5); return TRUE; } void CDspWnd::OnDestroy() { CWnd::OnDestroy(); //StopWatchDlgにアプリケーション終了を通知。 theApp.m_pMainWnd->PostMessage(WM_TERMINATEAPP, 0, 0); } //C++オブジェクト自動破壊 void CDspWnd::PostNcDestroy() { delete this; } //タイマ void CDspWnd::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent == TIMER_ID) { m_dwCurrent = ::GetTickCount(); Invalidate(); } } //クライアント領域描画 void CDspWnd::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: ここにメッセージハンドラコードを追加します。 // 描画メッセージでCWnd::OnPaint() を呼び出さないでください。 CString strMsg; strMsg.Format(_T("%d.%02d"), (m_dwCurrent - m_dwStart) / 1000, (m_dwCurrent - m_dwStart) % 1000 / 10); dc.TextOutW(m_point.x, m_point.y, strMsg); } //ストップウォッチ開始 BOOL CDspWnd::Start(UINT nElapse /*=TIMER_SPAN*/) { BOOL bRet = FALSE; if (! m_bBusy && ! m_dwStart && ! m_dwCurrent) { m_dwStart = ::GetTickCount(); bRet = SetTimer(TIMER_ID, nElapse, NULL) ? TRUE : FALSE; m_bBusy = TRUE; } return bRet; } //ストップウォッチ停止 BOOL CDspWnd::Stop() { BOOL bRet = KillTimer(TIMER_ID); m_bBusy = FALSE; return bRet; } //ストップウォッチデータクリア BOOL CDspWnd::Clear() { BOOL bRet = FALSE; if (! m_bBusy) { m_dwStart = 0; m_dwCurrent = 0; Invalidate(); bRet = TRUE; } return bRet; } |
このウインドウは、このあと作成するセカンダリスレッドの メインウィンドウに設定することになります。各メンバの機能は次の通りです。
m_point | ストップウォッチの経過時間を表示する位置を設定するためのCPoint型変数。Create関数内で位置の設定をして、OnPaint関数内で使用しています。 |
m_dwStart | ストップウォッチ開始時刻。Start関数で現在時刻に設定され、OnTimerコールバック関数でm_dwCurrentに取得される時刻との差を経過時間として表示しています。 |
m_dwCurrent | ストップウォッチ開始後(タイマ開始後)、OnTimer関数で現在の時刻を取得してStart関数で設定したm_dwStartとの差を表示します。 |
m_bBusy | ストップウォッチの実行中フラグ。Start関数でTRUEに設定されて、Stop関数でFALSEに設定される |
PostNcDestroy関数 | CDspWndウィンドウが閉じられて最後に呼ばれる関数で、この後に記述する、セカンダリスレッドであるCSecThreadのInitInstanceで作成されたC++オブジェクトを自動的に破壊します。 |
Create関数 | CWnd::CreateExを呼んでWindowを構築し、CStopWatchDlgダイアログの上部に表示します。加えて、ストップウォッチの表示位置を計算しています。 |
OnDestroy関数 | ユーザがCDspWndを閉じるときに、APIのDestroyWindow関数が呼ばれたのちにフレームワークに呼ばれる関数。この関数内で、ユーザ定義のWM_TERMINATEAPPメッセージをプリマリスレッドのCStopWatchDlgに送り、このウィンドウが破壊され、セカンダリスレッドが終了するのを待った後、メインのダイアログが終了します。 |
OnTimer関数 | Start関数でSetTimerを呼び出してタイマを実行した後に呼ばれるコールバック関数。現在の時刻をm_dwCurrentに取得してクライアント領域を再描画(OnPaint)します。 |
OnPaint関数 | OnTimerでクライアント領域の再描画要求されると、この関数が呼ばれストップウォッチを開始した時刻(m_dwStart)とタイマ時に取得した時刻(m_dwCurrent)の差を計算して経過時間を表示します。 |
Start関数 | ストップウォッチが停止していること、以前のデータがクリアされていることを確認してストップウォッチを開始します。 |
Stop関数 | タイマを停止してストップウォッチを止め、ストップウォッチ稼動フラグ(m_bBusy)をおろします。 |
Clear関数 | ストップウォッチが停止していることを確認したうえで、過去の表示データ(m_dwCurrentとm_dwStart)をクリアして再描画します。 |
<セカンダリスレッド実装>
続いてAfxBeginThreadに渡すCWinTheadの派生クラスを作成&実装します。
ツールバーのプロジェクト(P)→クラスの追加(C)で 「クラスの追加」ダイアログで以下のように設定してください。
カテゴリ(C) :MFC テンプレート(T) :MFCクラス →追加(A) クラスウィザードで次のように設定してください。 クラス名(L) :CSecThread 基本クラス(B) :CWinThread →完了
SecThread.hとSecThread.cppにCSecThreadクラスのヘッダファイル・実装ファイルの スケルトンができているので、次のようにコードを記述してください。
(SecThread.h)
//SecThread.h :ヘッダファイル #pragma once // CSecThread class CSecThread : public CWinThread { DECLARE_DYNCREATE(CSecThread) protected: CSecThread(); // 動的生成で使用されるprotected コンストラクタ public: virtual ~CSecThread(); public: virtual BOOL InitInstance(); virtual int ExitInstance(); protected: DECLARE_MESSAGE_MAP() }; |
(SecThread.cpp)
AfxBeginThreadでスレッド作成後、 _AfxThreadEntry関数が呼ばれて上記の CWinThread::InitInstanceでCDspWndウィンドウが構築 & 表示されます。
(A)でセカンダリスレッドのメインウィンドウをCDspWndに設定しています。
スレッドのメインウィンドウ(m_pMainWnd)に設定されたウインドウが閉じられると、
APIのPostQuitMessageが呼ばれてWM_QUITがメッセージキューに送られ
CWinThread::RunでGetMessageがFALSEが返して
CWinThread::ExitInstanceが呼ばれた後、無限ループから抜けます。その後、
_AfxThreadEntryでAfxEndThreadで
スレッドが終了します。
つまり、CWinThread::m_pMainWndに設定されたウインドウ
(CDspWnd)が閉じられると、自動的にそのスレッドが
閉じられるということです。現段階ではこの点だけを覚えておいてください。
以上がセカンダリスレッド部分の実装です。