• ベストアンサー

メッセージ機構とマルチスレッド

windowsのメッセージは、元のスレッドと関係なしに勝手に実行されますよね? たとえばSetTimerを呼びだした状態でウィンドウプロシージャ内で case WM_TIMER:    MessageBox(NULL,"","",MB_OK);    break; とすれば、メッセージボックスを閉じようが、閉じまいが一定時間ごとに、新たにメッセージボックスが生成されます。 しかし、Win32の本などを見ると、プロシージャからただのグローバル変数にアクセスするなど、通常のマルチスレッドではやってはいけないとされる事を平気でやっています。 この点で、マルチスレッドとメッセージ機構はどのように違うのか、ご存知の方、教授お願いします。 ナンセンスな疑問でしたらすいません。

  • qOat
  • お礼率80% (42/52)

質問者が選んだベストアンサー

  • ベストアンサー
  • ninigi
  • ベストアンサー率43% (10/23)
回答No.2

  ウィンドウメッセージは全て、そのウィンドウを作成したスレッドで実行されます。 このメッセージ機構はマルチタスクの再入ではなく、シングルタスクの再帰呼び出しをイメージした方が良いでしょう。   スレッドが作成した全てのウィンドウに宛てられたメッセージは、そのスレッドのメッセージキューに一緒くたに格納されます。 ご質問のケースの場合、MessageBox( )はメッセージボックスを表示した後OKボタンのクリックを検出するためのGetMessage( )/DispatchMessage( )のメッセージループを関数内に持っています。このメッセージループでメインウィンドウに宛てられたWM_TIMERが取り出されてメインウィンドウのプロシージャが呼び出され2つめのメッセージボックスが表示されます。つまりMessageBox( )呼び出しの中から再度ウィンドウプロシージャが呼び出されている再帰呼び出しの構造になっているのです。   MessageBox( )以外でもUpdateWindow( )やMoveWindow( )、SetScrollInfo( )など多くのウィンドウ操作系のAPIで、関数内部でメッセージが発行されてウィンドウプロシージャが再帰呼び出しされています。  

qOat
質問者

お礼

丁寧な回答ありがとうございます。 ずっと疑問に思っていたことが、氷解しました。 ありがとうございます。

その他の回答 (1)

回答No.1

Windowsのメッセージ機構は,特定のスレッド上で動きます。 マルチスレッド的には動きません。 ユーザーがメッセージループを明示的に書かないとメッセージはウィンドウプロシージャまで届きません。 ウィンドウメッセージはウィンドウを作成したスレッドへ届くためです。 MessageBox APIやDialogBox APIなどは,関数内部にメッセージループを持っているため, 呼び出し元のスレッドへのメッセージが問題なく呼び出されます。 http://msdn2.microsoft.com/en-us/library/ms644994.aspx#modal_boxes いろいろなところでGetCurrentThreadId APIを呼び出してみると,処理しているスレッドが変化していないことがわかるかと。

qOat
質問者

お礼

迅速な回答ありがとうございます。 そういうことでしたか。 疑問が晴れました。

関連するQ&A

  • 結果待ちの関数を強制終了

    結果待ちで処理が止まっている場合、その結果待ちの部分を 結果はどうでもいいから、もう結果を待たないというふうに させる方法を教えてください。 例えば、MessageBox関数を使った場合、5秒以内に クリックさせなかったら、MessageBoxの返り値を適当に決める 方法を教えてください。 たぶんそれをすると、メッセAとメッセBは別プロシージャとか 別関数に分かれて書くことになると思うけど、それでもいいんです。 例えば、まず最初に、case ONE;が実行させる場合です。 case ONE;  SetTimer(hWnd, 1234, 5000, NULL);  PostMessage(hWnd, TWO, 0, 0);  break; case TWO;  MessageBox(hWnd, "クリックしてください", "メッセA", MB_OK);  //クリックされるまでここで処理が止まる。  MessageBox(hWnd, "結果は~でした。", "メッセB", MB_OK);  break; case WM_TIMER:  KillTimer(hWnd, 1234);  //メッセAを取り消すような処理をしてメッセBを表示させたい。  break;

  • エディットボックス内でキー押されたときのメッセージ

    VC++6.0です。 メインウインドウ内のエディットボックス(一行)で、特定のキー入力を検知したいのですが、どのようにすればよいでしょうか。 mfcは使ってません。 //ウインドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {  switch( message )  {   case WM_CREATE:    //エディットボックスを作成    CreateWindowEx( WS_EX_CLIENTEDGE, "EDIT","",          WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,          0,0,2000, 20, hWnd, (HMENU)IDC_EDIT, g_hInst, NULL );    break;   case WM_COMMAND:    //エディットボックス内の変更はEN_CHANGEで受け取れるが、    //キーコードは送られてこない    break;  } }

  • ビットマップ表示とSetTimer関数を同居させる方法

    ビットマップ表示とSetTimer関数を同居させる方法 現在、vc++2005を使用して、簡易的なGUIアプリケーションを作成しようとしているのですが、自分では解決ができない問題が発生してしまったので、質問させていただきます。 それはビットマップ表示とSetTimer関数を同居させる方法についてです。 ビットマップを読み込んで表示させる機能を追加してから、SetTimer関数が反応しなくなってしまい困っています。 ちなみにビットマップ表示の機能を追加する前まではSetTimer関数が正常に機能していました。 ウィンドウのハンドルhWndが何か関係しているのかと思ったのですが、解決方法がわからず・・・・・・ ご存知の方がいらっしゃましたら御教授いただけると幸いです。 ↓ ソースの一部です case WM_CREATE: //ビットマップファイル読み込み + 表示の準備 static HBITMAP hbitmap,prebitmap; static HDC hDC, hcomDC;      hbitmap = (HBITMAP)LoadImage(NULL,_T("kouen.bmp"),IMAGE_BITMAP,0,0, LR_LOADFROMFILE); if( hbitmap == NULL ) { MessageBox(hWnd, _T("ビットマップのロードに失敗しました"), _T("エラー"),MB_OK | MB_ICONWARNING); return 0; } hDC =GetDC(hWnd); hcomDC =CreateCompatibleDC(hDC); prebitmap= (HBITMAP)SelectObject(hcomDC,hbitmap);            (中略) break;                     case WM_LBUTTONDOWN: //2連続のシングルクリック防止 EnableWindow(hWnd,FALSE); SetTimer(hWnd, ID_TIMER1, 500, NULL); ← これが機能していない          (中略) break; case WM_PAINT: BitBlt( hDC, 0, 0, 1024, 690, hcomDC, 0, 0, SRCCOPY ); break; case WM_TIMER://機能しなくなってしまった部分 if(wParam==ID_TIMER1){ KillTimer(hWnd,ID_TIMER1); EnableWindow(hWnd,TRUE); }

  • SetTimer関数について。

    プログラム初心者です。質問させてください! 現在私はWin32APIを用いて、簡単なプログラムを作成しています。 そこで例えばイベントAが起きた10秒後にあるアクション(1)をさせたくて、 WM_イベントAの時: SetTimer(hWnd,ID_MYTIMER1, 10000, NULL); というようにプログラムを書きました。 そのSetTimer関数が実行されることより、 WM_TIMER:イベントが発生するので、WM_TIMER:イベントのところにあるアクション(1)をさせるプログラムを書きました。 しかし、イベントAが起きた100秒後には、あるアクション(1)とは違うあるアクション(2)をさせたいのですが、 この場合、WM_イベントAが起きたときに、 SetTimer(hWnd,ID_MYTIMER1, 10000, NULL); SetTimer(hWnd,ID_MYTIMER2, 100000, NULL); のようにプログラムをかいてしまうと、 二つともWM_TIMERイベントがおこるので、 あるアクション(1)も(2)も100秒後には実行されてしまいます。 100秒後にはアクション(1)はおこらず、アクション(2)だけおこる。 10秒後にはアクション(2)はおこらず、アクション(1)だけおこる。 このようにするには、どのようにすればいいのでしょうか? アドバイスよろしくお願いいたします。

  • メッセージループについて

    while(true) { if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { if(msg.message==WM_QUIT) break; DispatchMessage(&msg); if(!GetMessage(&msg,NULL,0,0)) { } } else { } これはpeekMessageがメッセージを取り出してメッセージがあったら if(msg.message==WM_QUIT) break; DispatchMessage(&msg); if(!GetMessage(&msg,NULL,0,0)) を実行して、GetMessage()で待機してもし、メッセージがWM_QUITならGetMessageにWM_QUITのメッセージを渡してメッセージが消えてPeekMessageはメッセージキューがないため、0を返し永遠にelseを繰り返すという認識でよろしいのですか? あと、while(true) { if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { if(!GetMessage(&msg,NULL,0,0)) { } if(msg.message==WM_QUIT) break; DispatchMessage(&msg); } else { } if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))がWM_QUITのメッセージで そしてメッセージが消えてGetMessageで待機状態になるということでよろしいのでしょうか?ご教授お願いします。

  • マルチスレッドについて

    私はいまマルチスレッドの勉強をしているのですが、ビルドが通るのに実行結果がおかしい状況に陥っています。 ロード画面の処理なのですが、プライマリスレッドでロード画面を描画し、セカンダリスレッドでロード処理を行おうとしています。 問題は、ロードが途中で止まることとロード画面を描画できません。 多分下記の関数が悪いとは思うのですがどうか、ご助力おねがいします。 HRESULT GameMain::LoadScreen() { // スレッドの生成 static bool onlyonce_createthread = FALSE; if(onlyonce_createthread ==FALSE) { hTh = (HANDLE)_beginthreadex( NULL, 0, &loadthread, this, 0, (unsigned*)&thID ); onlyonce_createthread =TRUE; } // ローディング画面の描画 static bool loopflg = TRUE; while(loopflg) { int threadCondition = CheckThread( hTh ); switch(threadCondition) { case THREAD_RUNNING: if(graphloaded_flg ==TRUE) { EnterCriticalSection( &m_criticalSection ); float keep_item = (float)(load_item/MAX_LOAD_ITEM); LeaveCriticalSection( &m_criticalSection ); d2d_control->GaugeDraw(0, 0, keep_item); } break; case THREAD_EXIT: loopflg =FALSE; break; case THREAD_ERROR: return E_FAIL; break; } } float keep_item = (float)(load_item/MAX_LOAD_ITEM); d2d_control->GaugeDraw(0, 0, keep_item); return S_OK; }

  • マルチスレッドについて

    現在”猫でもできる”の87、88章を学んでおります。 まず87章でマルチスレッドの根本的なやり方を学びましたが、いきなり疑問が浮かびました。 _beginthread関数によりスレッドをスタートさせ、この関数で登録した関数内で_endthread関数を実行し終了させていることは分かります。 しかし_beginthread関数で登録した関数に引数を渡す処理がどの部分で行われているのかわかりません。 登録する関数はvoid型で引数はvoid*型でなければいけないことは分かったのですが、プログラムのどこを見てもこの登録した関数に引数を渡す処理が行われていません。 その辺の動作の説明を分かる方でいいのでよろしくお願いします。 そして88章では排他制御のマルチスレッドを行うプログラムの製作を行っているのですが、ちょっとした疑問が浮かびました。 EnterCriticalSection関数、LeaveCriticalSection関数ではさまれたプログラムは排他制御され他からアクセスされない。 この関数はこんな理解で良いんですかね? この理解で行くと、88章で説明していきますが、子ウィンドウを2つ作成しそれぞれのプロシージャ内で排他制御された関数をスレッドとしてスタートしています。 この2つのスレッドの動作についてですが、互いに排他制御関数が記述されているため、動作としてはまず左の子ウィンドウのスレッドが処理されている場合、右の子ウィンドウのスレッドは停止している。そして左の子ウィンドウのスレッドの排他制御が解放されたときに、右の子ウィンドウのスレッドが開始する。 そしてあるとき左の子ウィンドウのクライアントウィンドウ内で右クリックされた場合、その時点で排他制御されたスレッドが終了するのを待ち、終了したらcountを+1する。 こんな動作が行なわれていると理解してよいのでしょうか?

  • マルチスレッドについて・・・

    先日マルチスレッドについて質問させていただいたものですが、助言のもと動かしてうまくいったように見えたのですが、ロードしていない部分がありました。 今回はスレッドの中身もかいておきます。 ご助力お願いいたします。 unsigned int WINAPI GameMain::loadthread(void *lpx) { GameMain* gm = (GameMain*)lpx; gm->GInit();   //ロード return 0; } HRESULT GameMain::LoadScreen() { // スレッドの生成 static bool onlyonce_createthread = FALSE; if(onlyonce_createthread ==FALSE) { hTh = (HANDLE)_beginthreadex( NULL, 0, &loadthread, this, 0, (unsigned*)&thID ); onlyonce_createthread =TRUE; } // ローディング画面の描画 static bool loopflg = TRUE; while(loopflg) { int threadCondition = CheckThread( hTh ); switch(threadCondition) { case THREAD_RUNNING: if(graphloaded_flg ==TRUE) //2D画像のロードが終わったら { EnterCriticalSection( &m_criticalSection ); float keep_item = (float)(load_item/MAX_LOAD_ITEM); LeaveCriticalSection( &m_criticalSection ); d2d_control->GaugeDraw(0, 0, keep_item); //画像の描画関数 } break; case THREAD_EXIT: loopflg =FALSE; break; case THREAD_ERROR: return E_FAIL; break; } } float keep_item = (float)(load_item/MAX_LOAD_ITEM); d2d_control->GaugeDraw(0, 0, keep_item);  //画像の描画関数 return S_OK; }

  • マルチスレッドでの画像描画

    マルチスレッドを使ってロード画面を作ろうとしているのですが、 上手く画像が描画更新してくれません。 スレッドの中身は下記の通りです。よろしくお願いします。 HRESULT GameMain::LoadScreen() { // スレッドの生成 static bool onlyonce_createthread = FALSE; if(onlyonce_createthread ==FALSE) { hTh = (HANDLE)_beginthreadex( NULL, // SECURITY_ATTRIBUTES 構造体へのポインタ 0, // 新規スレッドのスタックサイズ &loadthread, // スレッドの実行開始アドレス this, // 新規スレッドに渡される引数リスト 0, // 新規スレッドの初期状態 (unsigned*)&thID ); // スレッドのIDを格納するためのDWORD型変数へのポインタ onlyonce_createthread =TRUE; } // ローディング画面の描画 static bool loopflg = TRUE; while(loopflg) { int threadCondition = CheckThread( hTh ); switch(threadCondition) { case THREAD_RUNNING: if(graphloaded_flg ==TRUE) { EnterCriticalSection( &m_criticalSection ); load_item = (float)(load_item/MAX_LOAD_ITEM); LeaveCriticalSection( &m_criticalSection ); d2d_control->GaugeDraw(0, 0, load_item); Sleep(100); } break; case THREAD_EXIT: loopflg =FALSE; break; case THREAD_ERROR: return E_FAIL; break; } } return S_OK; }

  • ウィンドウハンドルがメッセージ処理ループの後おかしくなる(?)

    下のコードをコンパイルしても、 WinMain KillTimer hr=ERROR_INVALID_WINDOW_HANDLE (0x00000578) というエラーメッセージが出てうまく通りません。 エラーを無視する意味でKillTimerをせずにやってしまえば、特に問題ないのですが、後々問題が出てきても怖いので、直したいです。 メッセージ処理ループの手前でKillTimerを行うと、エラーは出ませんし、 メッセージ処理ループのすぐ後でSetTimerを行うと、SetTimerでエラーが出るので、 ループでおかしくなるのかな、と思いました。 プログラムをシェイプアップしてもエラーがとれませんし、 もしかして根本的な間違いがあるのでしょうか・・・? #include <windows.h> #include <dxerr9.h> #define kWCLASS_NAME "WndClass" // ウィンドウクラス名 #define kWINDOW_NAME "Wnd" // ウィンドウ名 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow ) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInst; wcex.hIcon = NULL; wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName =NULL; wcex.lpszClassName = kWCLASS_NAME; wcex.hIconSm = NULL; RegisterClassEx(&wcex); /* --- メイン・ウィンドウの作成 --- */ HWND hWnd; hWnd = CreateWindow(kWCLASS_NAME, kWINDOW_NAME, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); /* --- ウィンドウの表示 --- */ ShowWindow( hWnd, SW_SHOWDEFAULT ); UpdateWindow( hWnd ); /* --- タイマのセット --- */ if ( SetTimer( hWnd, 1, 1000, NULL ) == 0 ) // WM_TIMERはWndProcで処理 { DXTRACE_ERR( "WinMain SetTimer", GetLastError() ); return -1; } /* --- メッセージ処理ループ --- */ MSG msg; ZeroMemory( &msg, sizeof( msg ) ); while ( msg.message != WM_QUIT ) // PostQuitMessage()が呼ばれたら終了 { if ( !GetMessage( &msg, NULL, 0, 0 ) ) { msg.message = WM_QUIT; } else { TranslateMessage( &msg ); DispatchMessage( &msg ); } } /* --- タイマの破棄 --- */ if ( KillTimer( hWnd, 1 ) == 0 ) { DXTRACE_ERR( "WinMain KillTimer", GetLastError() ); return -1; } /* --- 終了処理 --- */ // ウィンドウクラスの登録解除 if ( UnregisterClass( kWCLASS_NAME, hInst ) == 0 ) { DXTRACE_ERR( "WinMain UnregisterClass", GetLastError() ); return -1; } return 0; }