EternalWindows
ウインドウ管理 / タイマ

Windowsアプリケーションは、基本的にメッセージが送られてきたときに動作します。 WM_LBUTTONDOWN等のマウスイベント、さらにキーボードイベントなどは、 全てユーザーの操作によって生成されるメッセージであり、 アプリケーションはこれらを捕らえて適切な処理を実行します。 今回紹介するタイマと呼ばれる機能は、ユーザーの操作によって生じるものではなく、 一定間隔毎にメッセージキューにWM_TIMERというメッセージがポストされるという仕組みですが、 これにはどのような利用方法があるのでしょうか。

比較的よく見られるのは、アニメーションなどの描画処理です。 画像をアニメーションさせるには、自動で一定間隔毎に描画しなければならず、 ユーザー入力を待っているだけでは実現することはできません。 また、データの監視処理にもタイマは用いられます。 これは、システムの現在のパフォーマンスを一定間隔で取得したい場合や、 特定のデータに変化が発生していないかを確認したい場合に便利な存在となります。 タイマを作成するには、SetTimerを呼び出します。

UINT_PTR SetTimer(
    HWND hWnd,
    UINT_PTR nIDEvent,
    UINT uElapse,
    TIMERPROC lpTimerFunc
);

hWndは、タイマと関連付けたいウインドウのハンドルを指定します。 nIDEventは、タイマの識別子です。 uElapseは、タイムアウト値をミリ秒単位で指定します。 この時間を経過後、hWndにWM_TIMERが送られます。 lpTimerFuncは、タイマプロシージャのアドレスです。 WM_TIMERでタイマ処理を行う場合は、この引数はNULLを指定します。

作成したタイマは、不要になった時点でKillTimerで破棄しなければなりません。

BOOL KillTimer(
    HWND hWnd,
    UINT_PTR uIDEvent
);

hWndは、SetTimerで指定したウインドウのハンドルを指定します。 uIDEventは、SetTimerで指定したタイマの識別子を指定します。 一般には、SetTimerをWM_CREATEで呼び出し、 KillTimerをWM_DESTROYで呼び出すことになるでしょう。

SetTimerでタイマに識別子を付けることから想像できるように、 タイマは複数作成できます。 このようなとき、送られてきたWM_TIMERがどのタイマのものかを調べるために、 wParamとタイマに識別子を比較することになります。

case WM_TIMER:
	if (wParam == 1) { // 識別子が1であるタイマの処理
	}
	else if if (wParam == 2) { // 識別子が2であるタイマの処理
	}
	else
		;
	return 0;

ここでは、2つのタイマの識別子を1と2としていますが、値は自由に決定できます。 実際の開発では、タイマを複数作成する際は識別子用の変数を用意して、 変数名からどのタイマであるかを推測できるようにしたほうがよいでしょう。

今回のプログラムは、WM_TIMERでFindWindowを呼び出してウインドウの監視処理を行います。 検索対象ウインドウのクラス名をNotepadとすることにより、メモ帳が起動されているかどうかを監視します。

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static BOOL bFindNotepad = FALSE;

	switch (uMsg) {

	case WM_CREATE:
		SetTimer(hwnd, 1, 40, NULL);
		return 0;
	
	case WM_TIMER:
		if (!bFindNotepad) {
			if (FindWindow(TEXT("Notepad"), NULL) != NULL) {
				bFindNotepad = TRUE;
				MessageBox(NULL, TEXT("メモ帳の起動を検出しました。"), TEXT("OK"), MB_OK);
			}
		}
		return 0;

	case WM_DESTROY:
		KillTimer(hwnd, 1);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

SetTimerの呼び出しは、WM_CREATEで行われています。

case WM_CREATE:
	SetTimer(hwnd, 1, 40, NULL);
	return 0;

引数から分かるようにタイマの識別子は1に、WM_TIMERがポストされる間隔は 40ミリ秒後となります。ここで指定したタイマ識別子と同じ値は、 タイマが不要になったときにKillTimerに指定することになります。 次に、WM_TIMERの処理を見てみます。

case WM_TIMER:
	if (!bFindNotepad) {
		if (FindWindow(TEXT("Notepad"), NULL) != NULL) {
			bFindNotepad = TRUE;
			MessageBox(NULL, TEXT("メモ帳の起動を検出しました。"), TEXT("OK"), MB_OK);
		}
	}
	return 0;

WM_TIMERでは、FindWindowを呼び出してNotepadというクラス名を持つウインドウを見つけるようにします。 見つけた場合は、これ以上監視処理を行う必要がないため、bFindNotepadをTRUEにします。 これ以降WM_TIMERが不要になることから、ここでKillTimerを呼び出すようにしてもよいでしょう。 なお、今回のプログラムが監視するのはあくまでNotepadというクラス名を持つウインドウであり、 必ずしもそれがメモ帳プロセスであるとは限らないことに注意してください。 完全にはないにしても、より正確さを求めるなら、取得したウインドウハンドルに対して GetWindowModuleFileNameを呼び出し、モジュールのパスを調べてみるという方法があります。

タイマプロシージャについて

SetTimerで作成したタイマの処理は、WM_TIMERの他にタイマプロシージャで行うことも可能です。 タイマプロシージャを利用するには、コールバック関数のアドレスを第4引数に指定します。

SetTimer(hwnd, 1, 40, TimerProc);

これにより、約40ミリ秒後にTimerProcが呼ばれることになります。 TimerProcは、次のようなプロトタイプを持たなければなりません。

VOID CALLBACK TimerProc(
    HWND hwnd,
    UINT uMsg,
    UINT_PTR idEvent,
    DWORD dwTime
);

hwndは、SetTimerで指定したウインドウハンドルです。 uMsgは、WM_TIMERの値が格納されます。 idEventは、SetTimerで指定したタイマの識別子です。 dwTimeは、システム起動後の経過時間です。

WM_TIMERとの実質上の違いといえば、タイマ処理をWindowProcで行うか、 専用の関数のTimerProcで行うかという程度のことですが、 TimerProcを利用する限りでは、WindowProcで静的に宣言して変数は利用できません。 ただし、TimerProcがウインドウハンドルを受け取っていることに注目し、 WindowProcの静的なデータをSetWindowLongPtrでウインドウに関連付ければその限りではありません。 この場合、TimerProcでGetWindowLongPtrを呼び出せば、 データを取得できるようになります。



戻る