第93章 マルチスレッド その7


今回は、オーナー付きオーバーラップウィンドウ(後述)を2つ作ります。 オーバーラップウィンドウ1では 赤い球が左から右に進みます。右端に行ったらウィンドウからだんだん はみ出してついには見えなくなってしまいます。すると、オーバーラップ ウィンドウ2の左端から赤い球が見えてきて右に移動します。 右端に着いたら、方向転換して左に進みます。再び左端に到着すると ウィンドウから消えてゆき、今度はオーバーラップウィンドウ1の右端から 赤い球が見えてきます。あたかも2つのウィンドウを赤い球が 行ったり来たりしているように見えます。 どうです。おもしろそうでしょうこのようなプログラムは シングルスレッドでももちろん作れます。しかし、それぞれのウィンドウの 描画を別々のスレッドで実行させてこれを実現させるには どうすればよいのでしょうか。

ミューテックスオブジェクトを作って各スレッドで排他的に所有させる!

前回の経験から、2つのスレッドが交互に動いてうまくいきそうな気がします。 しかし、実際にプログラムを作るとうまくいきません。一方のスレッドのみが 動いて、他方は休みっぱなしです。ReleseMutexを実行したからといって 次に今まで休んでいたスレッドにミューテックスが所有されるという保証はありません。 むしろもっと簡単にイベントを手動で使います。

では、早速サンプルを見てみましょう。

// multi07.cpp #define STRICT #include <windows.h> #define CHILD_WX 400 #define CHILD_WY 100 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyChild1Proc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyChild2Proc(HWND, UINT, WPARAM, LPARAM); DWORD WINAPI Thread1(PVOID); DWORD WINAPI Thread2(PVOID); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); HWND CreateMyChild(HWND, WNDPROC, LPCTSTR, LPCTSTR); DWORD WINAPI Thread(LPVOID); typedef struct { HWND hwnd; BOOL th_end; }DATA, *PDATA; char szClassName[] = "multi07"; //ウィンドウクラス HANDLE hEvent1, hEvent2; 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; }

いつもと同じです。

//ウィンドウ・クラスの登録 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, //ウィンドウの種類 0, //X座標 0, //Y座標 300, //幅 50, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

いつもとたいして変わりませんが、親ウィンドウの位置、大きさを具体的に指定しています。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static HWND hChild1, hChild2; HWND hDesktop; RECT rc; int dx, dx_max, x; switch (msg) { case WM_CREATE: hDesktop = GetDesktopWindow(); GetWindowRect(hDesktop, &rc); dx_max = rc.right - rc.left; hChild1 = CreateMyChild(hWnd, MyChild1Proc, "mychild1", "子供1"); GetWindowRect(hChild1, &rc); dx = rc.right - rc.left; x = (dx_max - dx*2) / 3; MoveWindow(hChild1, x, 100, rc.right - rc.left, rc.bottom - rc.top, TRUE); hChild2 = CreateMyChild(hWnd, MyChild2Proc, "mychild2", "子供2"); MoveWindow(hChild2, x * 2 + dx, 100, dx, rc.bottom - rc.top, TRUE); break; case WM_CLOSE: id = MessageBox(hWnd, (LPCSTR)"終了してもよいですか", (LPCSTR)"終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { SendMessage(hChild1, WM_CLOSE, 0, 0L); SendMessage(hChild2, WM_CLOSE, 0, 0L); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }

親ができたらすぐにオーナー付きオーバーラップウィンドウ(後述)を を2つ作って横に並べて表示します。

GetDesktopWindow関数についてはすでに第15章で 出てきています。

親ウィンドウが終了すると子供のたぐいも自動的に終了しますが ここでは、スレッドをきちんと終了させるために少し小細工をしています。 WM_CLOSEメッセージが来たら、明示的にオーバーラップウィンドウにも WM_CLOSEメッセージを送っています。(これがないとスレッドの終了や、 スレッドハンドルのクローズを確認できない)

HWND CreateMyChild(HWND hWnd, WNDPROC ChildProc, LPCTSTR szChildName, LPCTSTR title) { HWND hChild; WNDCLASSEX wc; HINSTANCE hInst; RECT rc = {0, 0, CHILD_WX, CHILD_WY}; AdjustWindowRect(&rc, WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_THICKFRAME, FALSE); hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_VREDRAW | CS_HREDRAW; 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); if(RegisterClassEx(&wc) == 0) { MessageBox(NULL, "RegisterClassEx失敗", "失敗", MB_OK); return NULL; } hChild = CreateWindow(szChildName, title, WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_THICKFRAME, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hWnd, NULL, hInst, NULL); return hChild; }

ここでは、オーナー付きのオーバーラップウィンドウを作ります。オーナー付き オーバーラップウィンドウとは特殊なオーバーラップウィンドウで、常にオーナーより 手前にあります。また、オーナーが破棄されると自動的に破棄されます。子供ウィンドウ と違い、親の外にも出られます。(取り扱いは子供と同じ)

BOOL AdjustWindowRect( LPRECT lpRect, DWORD dwStyle, BOOL bMenu );

これは、希望の大きさのクライアント領域を持つウィンドウの大きさを 決める関数です。希望の大きさ(というか左上右下の座標)をRECT型の変数に格納してそのアドレスを lpRectに指定します。dwStyleにウィンドウスタイルを指定します。bMenu には、メニューを持つ(TRUE)か、持たない(FALSE)かをしています。 関数が成功するとlpRectにウィンドウの大きさが返ってきます。この関数を 使いたくない人は第73章で面倒くさいやり方を 紹介しています。

LRESULT CALLBACK MyChild1Proc(HWND hChild, UINT msg, WPARAM wp, LPARAM lp) { static HANDLE hThread1; static DATA data; DWORD threadID1; switch (msg) { case WM_CREATE: hEvent1 = CreateEvent(NULL, TRUE, TRUE, NULL); data.hwnd = hChild; data.th_end = FALSE; hThread1 = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)Thread1, (PVOID)&data, 0, &threadID1); break; case WM_CLOSE: data.th_end = TRUE; SetEvent(hEvent1); WaitForSingleObject(hThread1, INFINITE); MessageBox(hChild, "スレッド1終了", "終了", MB_OK); if (CloseHandle(hThread1) != 0) MessageBox(hChild, "hThread1クローズ成功", "成功", MB_OK); break; default: return (DefWindowProc(hChild, msg, wp, lp)); } return 0L; }

オーバーラップウィンドウ1(ここでは簡単のためにプロシージャ名や、ウィンドウハンドル の名前にchildを使っています(適当な名前が思いつかなかった))ができたらすぐに スレッド1を作っています。また、イベントを作るときの各引数に注意してください。

このウィンドウが破棄されるときにスレッドの終了を待ちます。 うまくいったらメッセージボックスで知らせます。

DWORD WINAPI Thread1(PVOID data) { PDATA pData; pData = (PDATA)data; HDC hdc; int x, dx, old_x; static int i = 0; static int hoko = 0; // 0:right 1:left RECT rc; HBRUSH hBrush; HPEN hPen; while (!pData->th_end) { WaitForSingleObject(hEvent1, INFINITE); hdc = GetDC(pData->hwnd); GetClientRect(pData->hwnd, &rc); dx = rc.right - rc.left; if (hoko == 0) { x = dx - i * 10; old_x = dx - (i - 1) * 10; i++; if (x <= 0) { hoko = 1; i = 0; } } else { x = i * 10; old_x = (i - 1) * 10; i++; if (x >= dx) { hoko = 0; i = 0; ResetEvent(hEvent1); SetEvent(hEvent2); } } SelectObject(hdc, GetStockObject(WHITE_BRUSH)); SelectObject(hdc, GetStockObject(WHITE_PEN)); Ellipse(hdc, old_x, 0, old_x + 100, 100); hBrush = CreateSolidBrush(RGB(255, 0, 0)); hPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0)); SelectObject(hdc, hBrush); SelectObject(hdc, hPen); Ellipse(hdc, x, 0, x + 100, 100); ReleaseDC(pData->hwnd, hdc); DeleteObject(hBrush); DeleteObject(hPen); Sleep(100); } return 0L; }

赤い球の移動方法はiを10ずつ動かしてxの値をずらしていっています。 それだけでは、古い赤玉が残ってしまいますので、バックグラウンド と同じ色で古い赤玉を消しています。

LRESULT CALLBACK MyChild2Proc(HWND hChild, UINT msg, WPARAM wp, LPARAM lp) { static HANDLE hThread2; static DATA data; DWORD threadID2; switch (msg) { case WM_CREATE: hEvent2 = CreateEvent(NULL, TRUE, FALSE, NULL); data.hwnd = hChild; data.th_end = FALSE; hThread2 = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)Thread2, (PVOID)&data, 0, &threadID2); break; case WM_CLOSE: data.th_end = TRUE; SetEvent(hEvent2); WaitForSingleObject(hThread2, INFINITE); MessageBox(hChild, "スレッド2終了", "終了", MB_OK); if (CloseHandle(hThread2) != 0) MessageBox(hChild, "hThread2クローズ成功", "成功", MB_OK); break; default: return (DefWindowProc(hChild, msg, wp, lp)); } return 0L; }

2番目のオーバーラップウィンドウのプロシージャです。イベントを作るときの 引数の違いを確認してください。(最初に待機)

DWORD WINAPI Thread2(PVOID data) { PDATA pData; pData = (PDATA)data; HDC hdc; int x, dx, old_x; static int i = 0; static int hoko = 0; // 0:right 1:left RECT rc; HBRUSH hBrush; HPEN hPen; while (!pData->th_end) { WaitForSingleObject(hEvent2, INFINITE); hdc = GetDC(pData->hwnd); GetClientRect(pData->hwnd, &rc); dx = rc.right - rc.left; if (hoko == 0) { x = -100 + i*10; old_x = -100 + (i - 1) * 10; i++; if (x >= dx - 100) { hoko = 1; i = 0; } } else { x = (dx-100) - (i * 10); old_x = (dx-100) - ((i - 1) * 10); i++; if (x <= -100) { hoko = 0; i = 0; ResetEvent(hEvent2); SetEvent(hEvent1); } } SelectObject(hdc, GetStockObject(WHITE_BRUSH)); SelectObject(hdc, GetStockObject(WHITE_PEN)); Ellipse(hdc, old_x, 0, old_x + 100, 100); hBrush = CreateSolidBrush(RGB(255, 0, 0)); hPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0)); SelectObject(hdc, hBrush); SelectObject(hdc, hPen); Ellipse(hdc, x, 0, x + 100, 100); ReleaseDC(pData->hwnd, hdc); DeleteObject(hBrush); DeleteObject(hPen); Sleep(100); } return 0L; }

スレッド1とほぼ同じですがxの変化のさせ方が少し違います。 このへんは、自分で試行錯誤でプログラムを書いてみるとよくわかります。

今回のプログラムは、実際作ってみて動かしてみるとなかなかおもしろいです。 シングルスレッドで同じ動作をするものを作ってみてください。


[SDK Index] [総合Index] [Previous Chapter] [Next Chapter]

Update Dec/07/1997 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。