次にイベントオブジェクトをシグナル状態にするにはSetEvent関数を、非シグナル状態 にするにはResetEvent関数を使います。HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, // セキュリティ属性WINDOWS95では無視 BOOL bManualReset, // FALSEに設定すると WaitForSingleObjectが制御を戻したときに //自動的に非シグナル状態になる BOOL bInitialState, // 初期状態 TRUE:シグナル状態 FALSE:非シグナル状態 LPCTSTR lpName // オブジェクトの名前 );
成功したときはTRUE、失敗したときはFALSEを返します。BOOL SetEvent( HANDLE hEvent // イベントオブジェクトのハンドル );
成功したらTRUE、失敗したときはFALSEを返します。BOOL ResetEvent( HANDLE hEvent // イベントオブジェクトのハンドル );
オブジェクトがシグナル状態になるか、タイムアウトになるまで 待機します。dwMillisecondsにINFINITEを指定するとタイムアウトはなくなります。 (オブジェクトがシグナル状態になるまでずっと待機)DWORD WaitForSingleObject( HANDLE hHandle, // オブジェクトのハンドル DWORD dwMilliseconds // タイムアウト時間(ミリセカンド) );
では、サンプルを見てみましょう。前章で作った プログラムをイベントオブジェクトをつかって、スレッドを制御します。 結構複雑になってしまったので、そのあらましを表にします。
ウィンドウ | プロシージャ | 動作 |
---|---|---|
hWnd | WndProc | |
hChild1 | Child1Proc | WM_LBUTTONDOWNでHitスレッドを動かす |
- | - | Hitスレッドが終了する直前にhChild1にWM_COUNTOKメッセージを送る |
hChild1 | Child1Proc | WM_CONUNTOKメッセージが来たらChild1スレッドを動かす |
- | - | Child1スレッドはhChildに描画が終わったらhWndにWM_COUNTOKメッセージを送る |
hWnd | WndProc | WM_COUNTOKメッセージが来たらhChild2にWM_COUNTOKメッセージを送る |
hChild2 | Child2Proc | WM_COUNTOKメッセージが来たらChild2スレッドを動かす |
WM_COUNTOKメッセージは筆者が勝手に作ったメッセージです。 Child1, Child2スレッドは WaintForSingleObject関数で待機しています。変数NextOKが1の時、1番の子供ウィンドウがクリックされると Hitスレッドが作られて待機します。すぐにシグナル状態になってHitスレッドではcountが1増えます。 そのあと、NextOK=0にして一連の動作が終わるまで新たな左クリックを無視させます。 countを1増やすとhChild1にWM_COUNTOKメッセージを送ってスレッドを終了します。
Child1ProcではWM_COUNTOKメッセージが来たら、Child1スレッドに対してオブジェクトを シグナル状態にしてこれを動かします。
Child1スレッドではhChild1に対してcountの値を描画します。描画し終わったら 親ウィンドウにWM_COUNTOKメッセージを送ります。なぜ直接hChild2に送らないかというと このハンドルがわからないからです。hChild2をグローバル変数にすればよいのですが なるべくグローバル変数を使いたくないのでこのような面倒なことを行っています。
親のプロシージャではWM_COUNTOKメッセージが来たら、hChild2にWM_COUNTOKメッセージを 送ります。
Child2Procでは、WM_COUNTOKメッセージが来たらChild2スレッドを動かします。 Child2スレッドはcountの値をhChild2に描画します。そしてNextOK=1にします。
各スレッドは勝手に動けないのでNextOKに同時にアクセスが起こることは まずないと思われますが、念のためこれにアクセスするときは クリティカルセクションに入っています。
前章とほとんど同じですがDATA型に新たなメンバhEventが入っています。 また、自作のメッセージを作るときはその値をWM_USERから作っていきます。// mult03.cpp // Programmed by Y.Kumei #define STRICT #include <windows.h> #include <process.h> #define WM_COUNTOK WM_USER int NextOK = 1; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK ChildProc1(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK ChildProc2(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void Child1(void *); void Child2(void *); void Hit(void *); HWND CreateMyChild(HWND hWnd, WNDPROC ChildProc, LPSTR szChildName, char *); char szClassName[] = "mult03"; //ウィンドウクラス int count; typedef struct { HANDLE hEvent; HWND th_wnd; int th_end; }DATA, *PDATA; CRITICAL_SECTION cs; int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
という感じになります。#define WM_MYMESSAGE WM_USER #define WM_YOURMESSAGE (WM_USER + 1) #define WM_HERMESSAGE (WM_USER + 2)
ウィンドウのタイトルが変わっただけです。//ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるイベント",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
Child1スレッドからWM_COUNTOKメッセージが来たら、 WM_COUNTOKメッセージをhChild2に送っています。//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static HWND hChild1, hChild2; LPSTR szChild1 = "child1", szChild2 = "child2"; RECT rc; switch (msg) { case WM_COUNTOK://Child1スレッドから SendMessage(hChild2, WM_COUNTOK, 0, 0); break; case WM_CREATE: InitializeCriticalSection(&cs); hChild1 = CreateMyChild(hWnd, ChildProc1, szChild1, "子供その1"); hChild2 = CreateMyChild(hWnd, ChildProc2, szChild2, "子供その2"); break; case WM_SIZE: if (IsIconic(hChild1)) OpenIcon(hChild1); if (IsIconic(hChild2)) OpenIcon(hChild2); GetClientRect(hWnd, &rc); MoveWindow(hChild1, 0, 0, (rc.right - rc.left) / 2, rc.bottom - rc.top, TRUE); MoveWindow(hChild2, (rc.right - rc.left) / 2, 0, (rc.right - rc.left) / 2, rc.bottom - rc.top, TRUE); break; case WM_CLOSE: id = MessageBox(hWnd, (LPCSTR)"終了してもよいですか", (LPCSTR)"終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: DeleteCriticalSection(&cs); PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
親が作られたらすぐに、InitializeCriticalSection(&cs);で クリティカルセクションを初期化して、親が終了するときに DeleteCriticalSection(&cs);でクリティカルセクションを削除しています。
子供ウィンドウを作る関数で前回と全く同じです。HWND CreateMyChild(HWND hWnd, WNDPROC ChildProc, LPSTR szChildName, char *title) { HWND hChild; WNDCLASSEX wc; HINSTANCE hInst; hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = ChildProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; //メニュー名 wc.lpszClassName = (LPCSTR)szChildName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); RegisterClassEx(&wc); hChild = CreateWindow(szChildName, title, WS_CHILD | WS_VISIBLE | WS_THICKFRAME | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPSIBLINGS, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hWnd, (HMENU)100, hInst, NULL); return hChild; }
hChild2が作られたらすぐにhEvent1, hEvent_Hitを作ります。 そして、DATA型変数にhEvent1tをセットしてChild1スレッドに渡します。LRESULT CALLBACK ChildProc1(HWND hChild1, UINT msg, WPARAM wp, LPARAM lp) { static DATA data, data_hit; static HANDLE hEvent1, hEvent_Hit; switch (msg) { case WM_CREATE: hEvent1 = CreateEvent(NULL, FALSE, FALSE, NULL); hEvent_Hit = CreateEvent(NULL, FALSE, FALSE, NULL); data.hEvent = hEvent1; data.th_end = 0; data.th_wnd = hChild1; _beginthread(Child1, 0, &data); break; case WM_LBUTTONDOWN: EnterCriticalSection(&cs); if (NextOK == 0) { LeaveCriticalSection(&cs); MessageBeep(0xFFFFFFFF); return 0; } LeaveCriticalSection(&cs); data_hit.hEvent = hEvent_Hit; data_hit.th_wnd = hChild1; data_hit.th_end = 0; _beginthread(Hit, 0, &data_hit); EnterCriticalSection(&cs); NextOK = 0; LeaveCriticalSection(&cs); SetEvent(hEvent_Hit); //Hitスレッドを動かす break; case WM_COUNTOK: //Hitスレッドから来る SetEvent(hEvent1); //Child1スレッドを動かす break; case WM_DESTROY: data.th_end = 1; break; default: return (DefWindowProc(hChild1, msg, wp, lp)); } return 0; }
左ボタンが押されたら、Hitスレッドを作り、動作させます。Hitスレッドは countを1増やすと終了するので、イベントで制御する必要はないのですが あえてこのようにしています。また、NextOK=0の時はHitスレッドを 作らずにビープをならしています。
uTypeに0xFFFFFFFFを指定すると、スタンダードビープになります。BOOL MessageBeep( UINT uType // 音のタイプ );
HitスレッドからWM_COUNTOKメッセージが来たら、Child1スレッドを 動かします。
Child1スレッドです。わざとに無駄な時間つぶしをしています。 描画が終わったら親にWM_COUNTOKメッセージを送っています。void Child1(void *data) { PDATA pEnd_data; char *str_org = "Count = %d"; char str[256]; HDC hdc; int j, k; long m = 1; static HWND hParent; pEnd_data = (PDATA)data; hParent = GetParent(pEnd_data->th_wnd); while (pEnd_data->th_end == 0) { WaitForSingleObject(pEnd_data->hEvent, INFINITE); for (j = 0; j <= 10000; j++) { for (k = 0; k <= 5000; k++) { m = m + j + k; } } wsprintf(str, str_org, count); hdc = GetDC(pEnd_data->th_wnd); TextOut(hdc, 10, 10, str, strlen(str)); DeleteDC(hdc); SendMessage(hParent, WM_COUNTOK, 0, 0);//親ウィンドウへ } _endthread(); return; }
Hitスレッドではcountを1増やしたらhChild1にWM_COUNTOKメッセージを送って スレッドを終了します。void Hit(void *data) { PDATA pData; pData = (PDATA)data; WaitForSingleObject(pData->hEvent, INFINITE); count++; SendMessage(pData->th_wnd, WM_COUNTOK, 0, 0); //Child1ウィンドウへ _endthread(); return; }
hChild2が作られたらすぐにhEvent2を作ってChild2スレッドを 待機させておきます。親からWM_COUNTメッセージが来たら Child2スレッドを動かします。LRESULT CALLBACK ChildProc2(HWND hChild2, UINT msg, WPARAM wp, LPARAM lp) { static DATA ch2_data; static HANDLE hEvent2; switch (msg) { case WM_CREATE: hEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL); ch2_data.hEvent = hEvent2; ch2_data.th_end = 0; ch2_data.th_wnd = hChild2; _beginthread(Child2, 0, &ch2_data); break; case WM_COUNTOK: //親から SetEvent(hEvent2); //Child2スレッドを動かす break; case WM_DESTROY: ch2_data.th_end = 1; break; default: return (DefWindowProc(hChild2, msg, wp, lp)); } return 0; }
Child2スレッドではcountの値をhChild2に描画してNextOK=1とします。 これでhChild2が左クリックされたときにHitスレッドを作ることが できるようになります。void Child2(void *data) { static PDATA pdata; HDC hdc; char str[256]; char * str_org = "Count = %d"; pdata = (PDATA)data; while(pdata->th_end == 0) { WaitForSingleObject(pdata->hEvent, INFINITE); wsprintf(str, str_org, count); hdc = GetDC(pdata->th_wnd); TextOut(hdc, 10, 10, str, strlen(str)); DeleteDC(hdc); EnterCriticalSection(&cs); NextOK = 1; LeaveCriticalSection(&cs); } _endthread(); return; }
今回作ったプログラムを動かしてみてください。1番の子供ウィンドウの
クライアント領域を素早く連続してクリックしても1番、2番のウィンドウに
表示される数値に差ができません。また、番号が飛ぶということもなくなりました。
あまり速くクリックするとビープ音が鳴って無視されます。
しかし、今回作ったプログラムにもいろいろ問題があります。
そもそも_beginthread関数はWindowsのAPI関数ではなくCのランタイムです。
次回はもっと違う方法でスレッドを作ります。
Update Nov/23/1997 By Y.Kumei