none
MFCダイアログベースでのメッセージ待ちループの実装方法 RRS feed

  • 質問

  • ::TranslateMessage(&msg)や、::DispatchMessage(&msg)の使い方が理解できていないようです。
    visual studio 2008 MFC ダイアログベースプログラミングを使っています。


    class TopLevelDlg : public CDialog(以下、ダイアログ1)の中に実装したコマンドボタン(以下、コマンドボタン1)をクリックすると、作業の進捗を表示するための「経過ダイアログ」(以下、ダイアログ2)が表示されるように書いています。ダイアログ2にあるOnInitDialog()の中でマルチスレッドを起動して時間のかかるるる処理をする関数を呼び出しています。ここで呼び出される関数(時間のかかる処理をするための関数)はダイアログ1のクラス内に定義されております。最終的には、ダイアログ2に実装された強制終了のためのコマンドボタンをクリックしたときに、時間のかかる処理を強制終了する機能も盛り込みたいと考えています。

    ダイアログ2には、1本のプログレスバーと、3個のコマンドボタンがついています。コマンドボタンは、
     1.強制終了用に自分で追加したコマンドボタン
     2.ウィザードでダイアログを作った時にデフォルトで表示される、OKボタン
     3.ウィザードでダイアログを作った時にデフォルトで表示される、キャンセルボタン
    の3つです。


     上記のようにマルチスレッドを起動したのち、クラス:TopLevelDlgクラス内にメッセージ待ち関数の中で、下記のようなメッセージ待ちループを作って、作業終了を意味する自作のメッセージを待っています。


            while(1)
            {
                    if( ::PeekMessage( &msg, this->GetSafeHwnd(), 0, 0, PM_NOREMOVE )  )
                    {
                            if (msg.message == WM_QUIT ) break;

                            if( RetGetMsg = ::GetMessage( &msg, this->GetSafeHwnd(), 0, 0 ) != 0 )
                            {
                                    if( RetGetMsg == -1 )
                                    {
                                            break;
                                    }
                                    else{
                                            ::TranslateMessage(&msg);         ここの記述を生かすか、消す(コメントアウト)するか?
                                            ::DispatchMessage(&msg);        ここの記述を生かすか、消す(コメントアウト)するか?
                                    }
                                   
                                    if( msg.message == MYMSG_CFM_WRITE_FINISH)
                                    {
                                            break;        //時間のかかる作業終了を意味するメッセージを受信した。
                                    };
                            };
                    }
            };


     下記は呼び出し履歴の一部抜粋です。メッセージを受けたとき、なぜかOnBnClickedCancel()が呼ばれています。この関数は開発環境にあるウィザードを使ってダイアログを使ったときに最初から実装されている"Okボタン"、"Cancelボタン"のうちのCancelボタンのイベントハンドラです。何らかの画面操作でボタンをクリックした覚えはありませんし、ソースコード上でOnBnClickedCancel()を呼ぶ記述もありません。また、::TranslateMessage(&msg)や、::DispatchMessage(&msg)をソースコード上で生かすか、殺すかで状況が変わりました。これらの使い方に何か問題があるのでしょうか?

     また、今回のように時間のかかるループ処理実行中にダイアログ間でのイベントを通知する方法(つまりダイアログ2にあるコマンドボタンのクリックイベントをダイアログ1へ伝える方法)は、
     http://msdn.microsoft.com/ja-jp/library/efk30beh(v=vs.90).aspx
    あたりを使うのが一般出来ですか?もし、もっと簡単な方法があればお教えください。

    宜しくお願いします。

    ************************************* 
     以下、呼び出し履歴の引用です
    *************************************

    -----------------------------------------------------------------------------------------------------------
            ::TranslateMessage(&msg); と、::DispatchMessage(&msg); をソースコード上から削除した場合
            ここにある、OnBnClickedButton8が、コマンドボタン1です。
    -----------------------------------------------------------------------------------------------------------
    >        TestDlgAppli.exe!CTestDlgAppliDlg::Run_WorkJugemuJugemu()  行 2372        C++
             TestDlgAppli.exe!CTestDlgAppliDlg::OnBnClickedButton8()  行 2200        C++
             mfc90ud.dll!_AfxDispatchCmdMsg(CCmdTarget * pTarget=0x0012f24c, unsigned int nID=1015, int nCode=0, void (void)* pfn=0x0041500f, void * pExtra=0x00000000, unsigned int nSig=57, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000)  行 82        C++
             mfc90ud.dll!CCmdTarget::OnCmdMsg(unsigned int nID=1015, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000)  行 381 + 0x27 バイト        C++
             mfc90ud.dll!CDialog::OnCmdMsg(unsigned int nID=1015, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000)  行 85 + 0x18 バイト        C++
             mfc90ud.dll!CWnd::OnCommand(unsigned int wParam=1015, long lParam=2428232)  行 2364        C++
             mfc90ud.dll!CWnd::OnWndMsg(unsigned int message=273, unsigned int wParam=1015, long lParam=2428232, long * pResult=0x0012ec3c)  行 1769 + 0x1e バイト        C++
             mfc90ud.dll!CWnd::WindowProc(unsigned int message=273, unsigned int wParam=1015, long lParam=2428232)  行 1755 + 0x20 バイト        C++
             mfc90ud.dll!AfxCallWndProc(CWnd * pWnd=0x0012f24c, HWND__ * hWnd=0x00290bf2, unsigned int nMsg=273, unsigned int wParam=1015, long lParam=2428232)  行 240 + 0x1c バイト        C++
             mfc90ud.dll!AfxWndProc(HWND__ * hWnd=0x00290bf2, unsigned int nMsg=273, unsigned int wParam=1015, long lParam=2428232)  行 403        C++
             mfc90ud.dll!AfxWndProcBase(HWND__ * hWnd=0x00290bf2, unsigned int nMsg=273, unsigned int wParam=1015, long lParam=2428232)  行 441 + 0x15 バイト        C++


    --------------------------------------------------------------------------------------------------------------------
            ::TranslateMessage(&msg); と、::DispatchMessage(&msg); を実行するよう記述したときの呼び出し履歴
            同じ操作を行ったにも関わらず、コマンドボタン1(OnBnClickedButton8())や、そのあ呼ばれるはずの
            Run_WorkJugemuJugemuが、履歴には出てきていない。(のは、単に履歴を保存しておくバッファの量の問題かもしれません)
    --------------------------------------------------------------------------------------------------------------------
    >        TestDlgAppli.exe!CTestDlgAppliDlg::OnBnClickedCancel()  行 2032        C++
             mfc90ud.dll!_AfxDispatchCmdMsg(CCmdTarget * pTarget=0x0012f24c, unsigned int nID=2, int nCode=0, void (void)* pfn=0x00415ce4, void * pExtra=0x00000000, unsigned int nSig=57, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000)  行 82        C++
             mfc90ud.dll!CCmdTarget::OnCmdMsg(unsigned int nID=2, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000)  行 381 + 0x27 バイト        C++
             mfc90ud.dll!CDialog::OnCmdMsg(unsigned int nID=2, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000)  行 85 + 0x18 バイト        C++
             mfc90ud.dll!CWnd::OnCommand(unsigned int wParam=2, long lParam=2231502)  行 2364        C++
             mfc90ud.dll!CWnd::OnWndMsg(unsigned int message=273, unsigned int wParam=2, long lParam=2231502, long * pResult=0x0012e10c)  行 1769 + 0x1e バイト        C++
             mfc90ud.dll!CWnd::WindowProc(unsigned int message=273, unsigned int wParam=2, long lParam=2231502)  行 1755 + 0x20 バイト        C++
             mfc90ud.dll!AfxCallWndProc(CWnd * pWnd=0x0012f24c, HWND__ * hWnd=0x002a0bf2, unsigned int nMsg=273, unsigned int wParam=2, long lParam=2231502)  行 240 + 0x1c バイト        C++
             mfc90ud.dll!AfxWndProc(HWND__ * hWnd=0x002a0bf2, unsigned int nMsg=273, unsigned int wParam=2, long lParam=2231502)  行 403        C++
             mfc90ud.dll!AfxWndProcBase(HWND__ * hWnd=0x002a0bf2, unsigned int nMsg=273, unsigned int wParam=2, long lParam=2231502)  行 441 + 0x15 バイト        C++
             user32.dll!77cf8734()                                                              

    2012年4月3日 10:55

回答

  • これのループはメインスレッドですよね?
    なぜ、自分でメッセージループを回す必要があるのでしょうか?

    本来、BEGIN_MESSAGE_MAP ~ END_MESSAGE_MAP の間に ON_MESSAGE で割り当てれば、独自のメッセージ受信とそれに応じた処理を割り当てることができるわけです。
    それなのに、こういった変なことをしているので、よくわからない状況に陥っているように見えます。

    「使い方に何か問題があるのか」と問われれば、「そもそも使う必要がない」という結果になりますよ、これでは。

    ちなみに、MYMSG_CFM_WRITE_FINISH って 273 だったりするんですか?wParam に 2 を渡しているということはあるんですか?(ちなみに、それは WM_COMMAND で IDCANCEL を送っているのと同じメッセージだと思う)
    そうだとすると、独自メッセージの定義の仕方の問題ですね。MFC で使うなら、WM_APP 以降の方がよいかもしれませんが、未検討。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。

    2012年4月4日 13:57
    モデレータ
  • Azulean さんの補足になります。
    一般にMFCを使用したウインドウの場合は、いわゆる
    「メッセージポンプ」を自前で実装してはいけません。

    では、メッセージポンプ内で、特定のメッセージを
    捕まえたい場合はどうしたら良いのか・・・。

    この場合は当該クラスの継承元のPreTranslateMessage()を
    オーバーライドします。
    この関数は、継承元のメッセージポンプ内で、GetMessage()に
    成功した場合、 TranslateMessage() と DispatchMessage()に
    渡される前にコールされます。
    プログラマは、取得できるメッセージの内、対象とするもの
    (今回はMYMSG_CFM_WRITE_FINISH)の場合だけ処理を行えばよいですね。

    BOOL MyApp::PreTranslateMessage(MSG* msg)
    {
       if( msg->message != MYMSG_CFM_WRITE_FINISH){
        return 派生元::PreTranslateMessage( msg);
       }
       // きたぞ!
       return TRUE;//もうTranslateMessage() と DispatchMessage()しないでね。
    // return FALSE;//TranslateMessage() と DispatchMessage()してください。
    }

    もちろんメッセージは正しく定義しましょう WM_APPより下は非推奨ですね。
    #define MYMSG_CFM_WRITE_FINISH  (WM_APP + 0x00000100) // とか、

    当然ですが、それ以前の問題として、スレッドとそのコントロールには
    イベントやミューテックス等を使うのが安全で便利で確実ですね。

    2012年4月5日 8:08
  • > 今回定義したMYMSG_CFM_WRITE_FINISHは、0x10という値になっていました。

    winuser.h  #define WM_CLOSE   0x0010

    2012年4月6日 14:33

すべての返信

  • 以下の記事が参考になりませんか?

    時間のかかる処理で「処理中」を表現する(前編)(1/4):CodeZine
    http://codezine.jp/article/detail/5332

    2012年4月4日 0:28
  • な、なんか見たことある記事が。。。w
    マルチスレッドでやるなら後編もご覧ください。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    2012年4月4日 1:43
  • TANO_ASOさん、ありがとうございます。

    聞き方が悪かったのかもしれません。済みません。ちょっと私の知りたいこととは違っているようです。しかも、環境が私の(VS2008  MFC )ものとも違うようで、どこまで自分の環境に流用できるのか分からないので、、、、

    私が教わりたかったのは、以下のところです。

    TestDlgAppli.exe!CTestDlgAppliDlg::OnBnClickedCancel()  行 2032        C++
             mfc90ud.dll!_AfxDispatchCmdMsg(CCmdTarget * pTarget=0x0012f24c, unsigned int nID=2, int nCode=0, void (void)* pfn=0x00415ce4, void * pExtra=0x00000000, unsigned int nSig=57, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000)  行 82

    Dispatchのあと、なんでOnBnClickedCancelが勝手に呼ばれているのかということです。そういう意味で、何らかのメッセージ処理をしているらしい、::TranslateMessage(&msg)や、::DispatchMessage(&msg)の使い方に何か問題があるのかなと思いました。  

    2012年4月4日 11:14
  • これのループはメインスレッドですよね?
    なぜ、自分でメッセージループを回す必要があるのでしょうか?

    本来、BEGIN_MESSAGE_MAP ~ END_MESSAGE_MAP の間に ON_MESSAGE で割り当てれば、独自のメッセージ受信とそれに応じた処理を割り当てることができるわけです。
    それなのに、こういった変なことをしているので、よくわからない状況に陥っているように見えます。

    「使い方に何か問題があるのか」と問われれば、「そもそも使う必要がない」という結果になりますよ、これでは。

    ちなみに、MYMSG_CFM_WRITE_FINISH って 273 だったりするんですか?wParam に 2 を渡しているということはあるんですか?(ちなみに、それは WM_COMMAND で IDCANCEL を送っているのと同じメッセージだと思う)
    そうだとすると、独自メッセージの定義の仕方の問題ですね。MFC で使うなら、WM_APP 以降の方がよいかもしれませんが、未検討。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。

    2012年4月4日 13:57
    モデレータ
  • Azulean さんの補足になります。
    一般にMFCを使用したウインドウの場合は、いわゆる
    「メッセージポンプ」を自前で実装してはいけません。

    では、メッセージポンプ内で、特定のメッセージを
    捕まえたい場合はどうしたら良いのか・・・。

    この場合は当該クラスの継承元のPreTranslateMessage()を
    オーバーライドします。
    この関数は、継承元のメッセージポンプ内で、GetMessage()に
    成功した場合、 TranslateMessage() と DispatchMessage()に
    渡される前にコールされます。
    プログラマは、取得できるメッセージの内、対象とするもの
    (今回はMYMSG_CFM_WRITE_FINISH)の場合だけ処理を行えばよいですね。

    BOOL MyApp::PreTranslateMessage(MSG* msg)
    {
       if( msg->message != MYMSG_CFM_WRITE_FINISH){
        return 派生元::PreTranslateMessage( msg);
       }
       // きたぞ!
       return TRUE;//もうTranslateMessage() と DispatchMessage()しないでね。
    // return FALSE;//TranslateMessage() と DispatchMessage()してください。
    }

    もちろんメッセージは正しく定義しましょう WM_APPより下は非推奨ですね。
    #define MYMSG_CFM_WRITE_FINISH  (WM_APP + 0x00000100) // とか、

    当然ですが、それ以前の問題として、スレッドとそのコントロールには
    イベントやミューテックス等を使うのが安全で便利で確実ですね。

    2012年4月5日 8:08
  • どこかでWM_CLOSEかWM_SYSCOMMAND(SC_CLOSE)を発行していませんか。

    これらは、WM_COMMAND(IDCANCEL)をPostします。

    2012年4月5日 10:32
  • Azulreanさん、ありがとうございます。

    本来、BEGIN_MESSAGE_MAP ~ END_MESSAGE_MAP の間に ON_MESSAGE で割り当てれば

    独自のメッセージ受信とそれに応じた処理を割り当てることができるわけです。それなのに、こういった変なことをしているので、

    >よくわからない状況に陥っているように見えます。

    そうでしたか。知りませんでした。今回、メッセージを送る側がPostMessageを使っているため、ネット上で拾ってきた情報(ただ、MFCという情報がうまく見つけられなかったので、2012年4月4日 11:14の投稿では、環境云々という話をしました)を頼りに、GetMessageループを使って受け取るもんだと思っていました。Postでも(=Sendではなくても)、 ON_MESSAGEのところに記述すれば受けられるのですね。

    WM_COMMAND で IDCANCEL を送っているのと同じメッセージ

    調べてみたら、それは大丈夫でした。ただ、「WM_APP以降」になるよう、定義しなおしたらなぜか今回ご現象は出なくなりました。


    2012年4月5日 13:26
  • 仲澤さん、有難うございます。

    一般にMFCを使用したウインドウの場合は、いわゆる
    >「メッセージポンプ」を自前で実装してはいけません。

    これを知りませんでした。実は、これもどこかのHPにあった情報だったのですが、メッセージポンプを自前で実装しなければならないというような記事を見かけまして、

    afx<スペル忘れました>()->MessagePump

    というような(記憶力が悪くて済みません)サンプルが載っていたので使ったらうまくいかず、

    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);

    を使う記述に変えたのが、今回投稿させていただいたソースでした。そもそも必要なかったのですね。

    PreTranslateMessageは、まったく知らなかったので、ちょっと時間使って読んでみます。


    もちろんメッセージは正しく定義しましょう WM_APPより下は非推奨ですね。

    こちらもAzuleanさんの投稿を読んだ後、対策しました。

    2012年4月5日 13:40
  • sanoさん、ありがとうございます。

    今のところ、そういうところが見つけられていません。原因は別のところにあるかもしれません。

    2012年4月5日 13:41
  • 調べてみたら、それは大丈夫でした。ただ、「WM_APP以降」になるよう、定義しなおしたらなぜか今回ご現象は出なくなりました。

    ということは、何らかのメッセージとバッティングしていたのでしょうね。
    それが Windows で定義済みのメッセージ ID なのか、MFC クラスが定義している(請け負っている)メッセージ ID なのかはわかりませんが。

    その結果として、WM_COMMAND/IDCANCEL 扱いのメッセージが送付される状況を引き起こしていたのでしょう。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。

    2012年4月5日 14:01
    モデレータ
  • Azulean さんの補足になります。
    一般にMFCを使用したウインドウの場合は、いわゆる
    「メッセージポンプ」を自前で実装してはいけません。

    では、メッセージポンプ内で、特定のメッセージを
    捕まえたい場合はどうしたら良いのか・・・。

    この場合は当該クラスの継承元のPreTranslateMessage()

    中澤さん、ご丁寧にありがとうございます。

    まだあまり理解できていないのですが、

    http://msdn.microsoft.com/ja-jp/library/9tdesxec(v=vs.90).aspx

    上記のページには、

        「GetMessage または PeekMessage Windows 関数を呼び出すと、メッセージキュー内

         のメッセージが取得できます。Windows の PostMessage 関数を使用すると、ほかのアプ

         リケーションにアクセスできます。」

    と書いてあって、「PreTranslateMessage」に関わる話が出て来ていないようなのですが、これはどう考えたら良いですか?どうも、例によって混乱しています。(今回の質問に関するソースコードに限っては、PostMessageを使っていて、SendMessageは使っていません)

    [余談]

    今回定義したMYMSG_CFM_WRITE_FINISHは、0x10という値になっていました。

    2012年4月6日 13:45
  • > 今回定義したMYMSG_CFM_WRITE_FINISHは、0x10という値になっていました。

    winuser.h  #define WM_CLOSE   0x0010

    2012年4月6日 14:33
  • sanoさん、ありがとうございます。

    >winuser.h  #define WM_CLOSE   0x0010

    そういうことだったのですね。知りませんでした。

    2012年4月7日 13:40
  • むぅ。参照しているところがやや的外れです(vv;)。
    本件の問題解決には役に立たない知識であると断言しておいて
    少し説明しておきます。
    本質的には、スレッドの利用方法について学んだほうが近道だと考えられます。

    Windows用のExeはWin32APIで実装した場合
     1.WinMain()の開始
     2.メッセージポンプの無限ループを、WM_QUITを受け取るまで実行
     3.終了
    となっていますが、MFCの場合は上記の仕組み全体がCWinAppに、
    PreTranslateMessage()はCThread, CWndなどの各クラスに実装済みです。
    また、PreTranslateMessage()は上記クラスでvirtualなメンバとなっていて、
    2.の中から呼ばれますので、必要なら自クラスでオーバーライドしてください。
    という意味ですね。
    この説明で意味不明な場合はC++言語の入門書を読み直す必要が
    あるかもしれません。

    ・TranslateMessage()は「メッセージの変換」
    ・DispatchMessage()は「メッセージの分配=コールバックの実行」ですから、
    PreTranslateMessage()は「メッセージの変換前処理」と考えられますね。
    MFCは、任意のメッセージを変換前にいじってもいいですよと言っているわけですね。

    2012年4月9日 1:49
  • 仲澤さんありがとうございます。

    MFCの場合は上記の仕組み全体がCWinAppに、
    >PreTranslateMessage()はCThread, CWndなどの各クラスに実装済みです。

    これを知りませんでした。

    2012年4月10日 14:59