第90章 マルチスレッド その4


今回は、CreateThread関数でスレッドを作ります。 これは、API関数です。

HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );

LPSECURITY_ATTRIBUTES lpThreadAttributesは、新しい スレッドに対するセキュリティ属性を指定します。 Windows95では関係ありません。

dwStackSizeには新しいスレッドのスタックサイズを指定します。 0にするとプライマリスレッドと同じサイズになります。

LPTHREAD_START_ROUTINE lpStartAddressには、新しいスレッド関数の アドレスを指定します。

lpParameterには、新しいスレッドに与えるパラメーターを指定します。

dwCreationFlagsをCREATE_SUSPENDEDにすると、ResumeThread関数が 呼ばれるまでスレッドは待機します。0を指定すると直ちに スレッドは動き出します。

lpThreadIdには、新しいスレッドのIDが格納されます。Windows95の 場合ID不要ならNULLにできます。

また、この関数で呼ばれるスレッド関数は

DWORD WINAPI function(LPVOID x)

の形でなくてはいけません。

また、CreateThread関数の戻り値(HANDLE)は、スレッドが不要になったら CloseHandleでクローズする必要があります。(スレッドが終了する だけではいけない)

今回は、親ウィンドウが4つの子供ウィンドウを作成して、それぞれの子供が スレッドを作成して子供ウィンドウに図形やら、時刻を表示します。

プログラムそのものを終了する前に、各スレッドの後かたづけを しなくてはいけません。サンプルでは終了する前に「スレッドの終了」 メニューを選択して、各スレッドをきちんと終了させないと プログラム自体が終了できないようにしてあります。


「こども1」には、現在の時刻が表示されます。「子供2」には いろいろな色の点がランダムに描画されます。「子供3」には、 四角形が、「子供4」には楕円がランダムに描画されます。

このプログラムを終了させるにはいきなり「ファイル」「終了」 ではだめで、まず「スレッド終了」メニューを選択して スレッドをきちんと終了させる必要があります。

では、サンプルのプログラムを見てみることにしましょう。

// mult04.rcの一部 // 自前でリソーススクリプトを書く人はwindows.hをインクルードする // 必要があります。また、IDM_END, IDM_THENDを定義したヘッダーファイルも // インクルードする必要があります。 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END MENUITEM "スレッド終了", IDM_THEND END

ま、これはごく普通のメニューのリソース・スクリプトです。

// mult04.cpp // Programmed By Y.Kumei #define STRICT #include <windows.h> #include <stdlib.h> #include "resource.h" #define WM_ENDTHREAD (WM_USER) #define WM_ENDOK (WM_USER + 1) BOOL PROGRAM_END = FALSE; typedef struct{ HWND hwnd; RECT rc; BOOL thread_end; } DATA, *PDATA; DWORD WINAPI Thread1(LPVOID); DWORD WINAPI Thread2(LPVOID); DWORD WINAPI Thread3(LPVOID); DWORD WINAPI Thread4(LPVOID); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK Child1Proc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK Child2Proc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK Child3Proc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK Child4Proc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); HWND CreateMyChild(HWND, WNDPROC, LPSTR, char *); int CreateMyThread(void); char szClassName[] = "mult04"; //ウィンドウクラス 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 = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); }

ここも、いつもと同じですが今回はメニュー付きなので wc.lpszMenuName = "MYMENU";を忘れないでください。

//ウィンドウの生成 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; }

これもいつもと同じです。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id, x1, x2, x3, x4, y1, y2, y3, y4, w, h; RECT rc; static HMENU hMenu, hSubMenu; static HWND hChild1, hChild2, hChild3, hChild4; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0L); break; case IDM_THEND: SendMessage(hChild1, WM_ENDTHREAD, 0, 0L); SendMessage(hChild2, WM_ENDTHREAD, 0, 0L); SendMessage(hChild3, WM_ENDTHREAD, 0, 0L); SendMessage(hChild4, WM_ENDTHREAD, 0, 0L); PROGRAM_END = TRUE; break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } break; case WM_CREATE: hChild1 = CreateMyChild(hWnd, Child1Proc, "mychild1", "子供1"); hChild2 = CreateMyChild(hWnd, Child2Proc, "mychild2", "子供2"); hChild3 = CreateMyChild(hWnd, Child3Proc, "mychild3", "子供3"); hChild4 = CreateMyChild(hWnd, Child4Proc, "mychild4", "子供4"); break; case WM_SIZE: GetClientRect(hWnd, &rc); x1 = 0, y1 = 0; x2 = (rc.right - rc.left) / 2; y2 = 0; x3 = 0; y3 = (rc.bottom - rc.top) / 2; x4 = x2, y4 = y3; w = x2, h = y3; if(IsIconic(hChild1)) OpenIcon(hChild1); if(IsIconic(hChild2)) OpenIcon(hChild2); if(IsIconic(hChild3)) OpenIcon(hChild3); if(IsIconic(hChild4)) OpenIcon(hChild4); MoveWindow(hChild1, x1, y1, w, h, TRUE); MoveWindow(hChild2, x2, y2, w, h, TRUE); MoveWindow(hChild3, x3, y3, w, h, TRUE); MoveWindow(hChild4, x4, y4, w, h, TRUE); break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { if (PROGRAM_END) DestroyWindow(hWnd); else MessageBox(hWnd, "スレッドを終了させてください", "注意!", MB_ICONEXCLAMATION | MB_OK); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }

メニューで「スレッド終了」(IDM_THEND)が選択されたら 各子供ウィンドウにWM_ENDTHREAD(ユーザー定義メッセージ)を送っています。 そして、これを送ったらPROGRAM_ENDをTRUEにしています。 本当はスレッドの後始末を確認後TRUEにする必要がありますが 手抜きでこのようにしています。

あとは、特に説明は不要でしょう。

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; }

子供ウィンドウを作るための関数です。

LRESULT CALLBACK Child1Proc(HWND hChild1, UINT msg, WPARAM wp, LPARAM lp) { static DATA data1; static HANDLE hThread1; DWORD thId1; switch (msg) { case WM_CREATE: data1.hwnd = hChild1; data1.thread_end = FALSE; hThread1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Thread1, (LPVOID)&data1, 0, &thId1); break; case WM_ENDTHREAD: data1.thread_end = TRUE; break; case WM_ENDOK: if(CloseHandle(hThread1) != 0) MessageBox(hChild1, "hThread1クローズ成功", "成功", MB_OK); break; default: return (DefWindowProc(hChild1, msg, wp, lp)); } return 0L; }

「子供1」ウィンドウのプロシージャです。ウィンドウができたらすぐに スレッドを作って開始します。この時自分のウィンドウハンドルを スレッドに渡しています。

また、親からWM_ENDTHREADメッセージが来たらdata1.thread_endを TRUEにしてスレッド関数中のループを終了させます。 スレッドの方ではループが終了したらWM_ENDOKメッセージをここに 送ります。するとスレッドハンドルをクローズします。 スレッドハンドルのクローズが無事に成功したらその旨を ユーザーに知らせるためにメッセージボックスを出します。

LRESULT CALLBACK Child2Proc(HWND hChild2, UINT msg, WPARAM wp, LPARAM lp) { static HANDLE hThread2; static DWORD thId2; static DATA data2; switch (msg) { case WM_CREATE: data2.hwnd = hChild2; data2.thread_end = FALSE; hThread2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Thread2, (LPVOID)&data2, 0, &thId2); break; case WM_ENDTHREAD: data2.thread_end = TRUE; break; case WM_ENDOK: if(CloseHandle(hThread2) != 0) MessageBox(hChild2, "hThread2クローズ成功", "成功", MB_OK); break; default: return (DefWindowProc(hChild2, msg, wp, lp)); } return 0L; } LRESULT CALLBACK Child3Proc(HWND hChild3, UINT msg, WPARAM wp, LPARAM lp) { static HANDLE hThread3; static DWORD thId3; static DATA data3; switch (msg) { case WM_CREATE: data3.hwnd = hChild3; data3.thread_end = FALSE; hThread3 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Thread3, (LPVOID)&data3, 0, &thId3); break; case WM_ENDTHREAD: data3.thread_end = TRUE; break; case WM_ENDOK: if(CloseHandle(hThread3) != 0) MessageBox(hChild3, "hThread3クローズ成功", "成功", MB_OK); break; default: return (DefWindowProc(hChild3, msg, wp, lp)); } return 0L; } LRESULT CALLBACK Child4Proc(HWND hChild4, UINT msg, WPARAM wp, LPARAM lp) { static DATA data4; static HANDLE hThread4; static DWORD thId4; switch (msg) { case WM_CREATE: data4.hwnd = hChild4; data4.thread_end = FALSE; hThread4 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Thread4, (LPVOID)&data4, 0, &thId4); break; case WM_ENDTHREAD: data4.thread_end = TRUE; break; case WM_ENDOK: if(CloseHandle(hThread4) != 0) MessageBox(hChild4, "hThread4クローズ成功", "成功", MB_OK); break; default: return (DefWindowProc(hChild4, msg, wp, lp)); } return 0L; }

2,3,4の子供ウィンドウプロシージャの構造は1の子供と全く同じです。

DWORD WINAPI Thread1(LPVOID data) { PDATA pData; pData = (PDATA)data; SYSTEMTIME st; HDC hdc; char *str_org = "現在%2d時%2d分%2d秒"; char str[256]; char *space = " "; while (!pData->thread_end) { Sleep(500); hdc = GetDC(pData->hwnd); GetLocalTime(&st); wsprintf(str, str_org, st.wHour, st.wMinute, st.wSecond); TextOut(hdc, 0, 0, space, strlen(space)); TextOut(hdc, 0, 0, str, strlen(str)); ReleaseDC(pData->hwnd, hdc); } SendMessage(pData->hwnd, WM_ENDOK, 0, 0L); MessageBox(pData->hwnd, "Thread1終了します", "終了", MB_OK); return (DWORD)0; }

「子供1」ウィンドウによって作られたスレッドです。 クライアント領域に時刻を表示します。

SYSTEMTIME構造体とかGetLocalTime関数についてはすでに 第34章で解説してあるので、忘れた人は見直してください。

プロシージャがthread_endをTRUEにしたら、whileループが 終了します。そして、子供1ウィンドウにWM_ENDOKメッセージを送ります。 まもなく、スレッドは終了しますがそのままではわからないので メッセージボックスでユーザーに終了する旨連絡します。

DWORD WINAPI Thread2(LPVOID data) { PDATA pData2; pData2 = (PDATA)data; HDC hdc; RECT rc; int x, y, r, g, b, dx, dy; while (!pData2->thread_end) { hdc = GetDC(pData2->hwnd); GetClientRect(pData2->hwnd, &rc); dx = rc.right - rc.left; if (dx <= 0) dx = 1; dy = rc.bottom - rc.top; if (dy <= 0) dy = 1; x = rand() % dx; y = rand() % dy; r = rand() % 256; g = rand() % 256; b = rand() % 256; if (x < 0) x = 0; if (y < 0) y = 0; SetPixel(hdc, x, y, RGB(r, g, b)); ReleaseDC(pData2->hwnd, hdc); } SendMessage(pData2->hwnd, WM_ENDOK, 0, 0L); MessageBox(pData2->hwnd, "Thread2終了します", "終了", MB_OK); return 0; }

ランダムに点を描画するものです。

COLORREF SetPixel( HDC hdc, int X, int Y, COLORREF crColor );

ま、初めて出てきた関数ですが説明はあんまりいりませんね。 ループの終わり方や、その後の処理はThread1と同じです。

DWORD WINAPI Thread3(LPVOID data) { static PDATA pData3; static HDC hdc; static RECT rc; HBRUSH hBrush; int x1, x2, y1, y2, dx, dy, r, g, b; pData3 = (PDATA)data; while (!pData3->thread_end) { hdc = GetDC(pData3->hwnd); GetClientRect(pData3->hwnd, &rc); dx = rc.right - rc.left; dy = rc.bottom - rc.top; if (dx <= 0) dx = 1; if (dy <= 0) dy = 1; x1 = rand() % dx; x2 = rand() % dx; y1 = rand() % dy; y2 = rand() % dy; r = rand() % 256; g = rand() % 256; b = rand() % 256; hBrush = CreateSolidBrush(RGB(r, g, b)); SelectObject(hdc, (HGDIOBJ)hBrush); Rectangle(hdc, x1, y1, x2, y2); ReleaseDC(pData3->hwnd, hdc); DeleteObject(hBrush); } SendMessage(pData3->hwnd, WM_ENDOK, 0, 0L); MessageBox(pData3->hwnd, "Thread3終了します", "終了", MB_OK); return 0; }

ReleseDCとDeleteObjectの順番を逆にしないでください。 非常にわかりにくいバグになります。(実験するとわかります)

DWORD WINAPI Thread4(LPVOID data) { PDATA pData4; HDC hdc; HBRUSH hBrush; int x1, y1, x2, y2, dx, dy, r, g, b; RECT rc; pData4 = (PDATA)data; while (!pData4->thread_end) { GetClientRect(pData4->hwnd, &rc); dx = rc.right - rc.left; dy = rc.bottom - rc.top; if (dx <= 0) dx = 1; if (dy <= 0) dy = 1; x1 = rand() % dx; x2 = rand() % dx; y1 = rand() % dy; y2 = rand() % dy; r = rand() % 256; g = rand() % 256; b = rand() % 256; hdc = GetDC(pData4->hwnd); hBrush = CreateSolidBrush(RGB(r, g, b)); SelectObject(hdc, hBrush); Ellipse(hdc, x1, y1, x2, y2); ReleaseDC(pData4->hwnd, hdc); DeleteObject(hBrush); } SendMessage(pData4->hwnd, WM_ENDOK, 0, 0L); MessageBox(pData4->hwnd, "Thread4終了します", "終了", MB_OK); return 0; }

結局このプログラムで「スレッド終了」メニューを選択すると 合計8つのメッセージボックスが出てきます。 スレッドは、作るよりもうまく終了させる方がずっと難しいことがわかります。
[SDK Index] [総合Index] [Previous Chapter] [Next Chapter]

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