none
処理待ち時間中にマルチレッドでメッセージダイアログを表示させたい RRS feed

  • 質問

  • お世話になります。
    現在、Visual C++にてアプリケーションを開発しており、行き詰った箇所があったためこちらに
    質問させて頂きました。

    質問させて頂きたい項目は
    何らかの処理待ち中に、マルチスレッドを使用して、「現在処理中です・・・」といったメッセージダイアログ
    を表示させ、処理終了と同時にメッセージダイアログを表示させるにはどのようにすればよいでしょうか?

    現在のソースは
    UINT WAITThread(LPVOID lParam)
    CWaitDlg waitDlg;

    //コンストラクタ
    CMain***Dlg::CMain***Dlg
    {
        waitDlg = new CWaitDlg();
    }

    //処理待ちメッセージダイアログを表示させるマルチスレッド
    AfxBeginThread(WAITThread,this);

    //計算処理関数
    KEISAN();


    //メッセージダイアログ表示マルチスレッド
    UINT WAITThread(LPVOID lParam)
    {
        waitDlg.DoModal();
    }

    //計算処理関数
    void KEISAN(void)
    {
             ・


    //計算終了時に処理待ちダイアログを消去
       waitDlg.EndDialog(IDOK);
    }

    このようにして実行すると、ハンドルエラーが発生してしまいます。

    どなかたご存知の方がいらっしゃいましたら、ご意見を宜しくお願い致します。

    尚、開発環境は
    Windows CE 6.0
    Visual Studio 2005
    です。

    ご回答をお待ちしております。
    2009年6月11日 9:54

回答

  • MFC に限りませんが、UIはメインスレッドで処理して、時間のかかる処理をサブスレッドで処理するようにしないとアプリのみならずOS全体がぎくしゃくします。
    Windows の設計思想に起因する根源的な問題なのでどうにもならない部分もあるのでまぁそういうものだと思うしかないでしょう。

    なので、メインスレッド側(ユーザーのアクションで処理を行う最初のハンドラ)でモーダルダイアログを出して待機するようにします。
    totojo さんはモードレスのほうがやさしい?と言ってますが、モードレスだと自分でメッセージを回さないといけませんが、モーダルなら何もやらずに済みますのでそっちのほうが実装コストは格段に低くなります。

    で、スレッド側には待機中を出すモーダルダイアログのウィンドウハンドルを渡して置き、処理が終わったら専用の処理終了メッセージを発行します。
    待機ダイアログはユーザー操作で終了してしまわないように、OnOK, OnCancel の二つの仮想関数を空で用意しておきます(エンターキー、ESCキーの処理をブロックするための必須機能)。
    そのうえで、専用の処理終了メッセージを受け取ったら、EndDialog(IDOK);など、なにかしら適当な終了コードを入れるようにしておきます。

    あとは、待機ダイアログのWM_INITDIALOG(OnInitDialogで受けるのが望ましい)で、計算スレッドを起動してやれば、ぼさっと待ってるだけで全部段取りが終わります。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 鏑木肆星 2009年6月12日 8:06
    2009年6月12日 2:10
  • 確かに部分的なソースで不明なところが多いですが、このソースが正しいとして気がついたこと。

    スレッド作成時に

    AfxBeginThread(KEISANThread,wait_dlg->GetSafeHwnd());

    としてウィンドウハンドルを渡しているのに、スレッド関数では、

    //計算スレッド
    UINT KEISANThread(LPVOID lParam)
    {
       CWaitDlg* w_dlg = (CWaitDlg*)lParam;

    とハンドルを強制的にCWaitDlgポインタへ変換してますね。これはどう考えてもaccess violationになるでしょう。
    スレッド終了時にメッセージを送りたいなら、

    //計算スレッド
    UINT KEISANThread(LPVOID lParam)
    {
       HWND hWnd = (HWND)lParam;
    // 計算処理

        ::SendMessage(hWnd, WM_MSG,MSG_CLOSE,0);   // <-- 計算終了メッセージ送信
         return 0;
      }

    ってな感じですかね。
    • 回答としてマーク 鏑木肆星 2009年6月12日 8:08
    2009年6月12日 6:05

  • > 初めの説明不足だったのですが、計算処理というのが1つではなく複数あり、さまざまな計算処理の待ち時間に
    > 同じ、計算処理待ちダイアログを表示させたいと思っています。ですので、できれば処理待ちダイアログに固有の計算
    > 処理部分を持たせたくないのですが。。。


    これも考え方次第かと思います。
    同じダイアログを流用したいのであれば、計算部分の関数はグローバル関数にしてしまい、
    呼び出す時に関数のポインタで外から渡してやるとか。

    ダイアログに計算処理を集めてしまって呼び出す時にどの計算処理を呼び出すのか指示をするとか。

    いずれにしても計算処理をワーカースレッド化すると言う事は、計算の元ネタ関連は関数に直接引き渡さないと
    いけないので大丈夫だと思います。ワーカースレッドで起動する関数はstatic関数ですからクラスのメンバーとして
    定義していてもメンバー変数の参照はできませんから。

    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    • 編集済み PATIO 2009年6月12日 7:01
    • 回答としてマーク 鏑木肆星 2009年6月12日 8:10
    2009年6月12日 6:59
  • CWnd のスレッド間受け渡しですが、CWnd を HWND を持つ単なるオブジェクトとして渡すだけなら問題はないですよ。
    ただし、使えるメソッドは、GetSafeHWnd() 程度ですけどね。

    それ以外も使いたいのなら、HWNDを取り出してきて、スレッド側で作成した別のCWnd(派生クラス)にアタッチしてください。
    その際、間違っても元のCWnd からデタッチしてはいけません。
    また、スレッドから抜ける前にどのような状況で抜ける場合でも、必ずデタッチしてください。そうしないとクラッシュします。

    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 鏑木肆星 2009年6月12日 8:04
    2009年6月12日 7:05

すべての返信

  • えーっと、まずやろうとしていることをまとめてみますね。

    「計算中・・」表示のためにCWaitDlgオブジェクトをメインスレッド側で作っておいて、そのCWaitDlgのポインタを、計算を実行する別のスレッドに渡し、その別スレッドからCWaitDlgを表示したり消したりしようとしている、そういう理解で正しいですか?

    だとしたら、これは典型的なMFCの「ハマリ」パターンですね。
    MFCのウィンドウオブジェクトなどは、ポインタを他のスレッドに渡しても正常にアクセスできません。これはMFC内部のオブジェクトマップがスレッド毎に管理されていることに起因します。

    これを回避するには、MFCオブジェクトのポインタを渡すのではなく、ウィンドウハンドルを他のスレッドに渡して、そのハンドルでウィンドウにアクセスするか、
    CWnd* pWnd = CWnd::FromHandle(渡されたウィンドウハンドル);
    みたいな感じで別スレッド側で一時的にCWndオブジェクトに関連付けてCWnd*経由で操作します。

    ってか、こういう場合、CWaitDlgダイアログを制御させるのはメインスレッドだけにしておいて、計算を行う別スレッドが終了したことをWaitForSingleObjectやウィンドウメッセージなどで待つような実装を行うのが一般的方法ではないでしょうかね。

    • 編集済み murano 2009年6月11日 13:02 誤記訂正
    2009年6月11日 10:36
  • こういった場合、モーダル ダイアログでなくてモードレス ダイアログの方が簡単なのですが、
    Windows CE でも同じですよねぇ。
    あと、やはり時間のかかる計算処理はサブ スレッドに逃がす方がいいと思います。
    2009年6月11日 12:16
  • MFC に限りませんが、UIはメインスレッドで処理して、時間のかかる処理をサブスレッドで処理するようにしないとアプリのみならずOS全体がぎくしゃくします。
    Windows の設計思想に起因する根源的な問題なのでどうにもならない部分もあるのでまぁそういうものだと思うしかないでしょう。

    なので、メインスレッド側(ユーザーのアクションで処理を行う最初のハンドラ)でモーダルダイアログを出して待機するようにします。
    totojo さんはモードレスのほうがやさしい?と言ってますが、モードレスだと自分でメッセージを回さないといけませんが、モーダルなら何もやらずに済みますのでそっちのほうが実装コストは格段に低くなります。

    で、スレッド側には待機中を出すモーダルダイアログのウィンドウハンドルを渡して置き、処理が終わったら専用の処理終了メッセージを発行します。
    待機ダイアログはユーザー操作で終了してしまわないように、OnOK, OnCancel の二つの仮想関数を空で用意しておきます(エンターキー、ESCキーの処理をブロックするための必須機能)。
    そのうえで、専用の処理終了メッセージを受け取ったら、EndDialog(IDOK);など、なにかしら適当な終了コードを入れるようにしておきます。

    あとは、待機ダイアログのWM_INITDIALOG(OnInitDialogで受けるのが望ましい)で、計算スレッドを起動してやれば、ぼさっと待ってるだけで全部段取りが終わります。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 鏑木肆星 2009年6月12日 8:06
    2009年6月12日 2:10
  • メイン スレッドで DoModal してしまうと、せっかく時間のかかる処理をサブ スレッドに逃がしても、意味がないように思います。
    特に DoModal を呼ぶのがコントロールのメッセージ ハンドラー(ボタン クリックとか)だと悲しいことになりそうな。

    (モーダル ダイアログしかメッセージを処理せず、呼び出し側のウィンドウ メッセージをキューイングするので、
    例えばモーダル ダイアログ以外をクリックしまくると、ダイアログ終了直後にメッセージを処理しまくるのでは。
    モーダル ダイアログ表示状態でも、アプリケーションの終了ぐらいは処理したいと思うのですが。)

    モードレス ダイアログは確かにやることは増えてしまいますが、この場合は理にかなっているように思えるのですが。

    2009年6月12日 3:20
  • 処理中に仕事させたいのなら、モードレスとかを使うより、ステータスバーとかそういうところで処理する方法を考えたほうがいいと思います。
    ダイアログ出してると、通常のウィンドウズアプリでも邪魔っけですし、CE 6.0(たぶんWindowsMobileですよね?)だと画面狭いから余計。。。

    ちなみに、モーダルダイアログを出してる状況でスレッドが動いていようがいまいが、よけなところのクリックは全部スルーされます。
    メッセージボックス出してる状態で周りをクリックしまくってもボタン押されたりしないでしょ?それと同じです。

    プログラム的にも自前で待機ループを書かなくていい(結論だけでいえばモーダルダイアログがその役割を果たしている)ため、WM_QUITとかOSシャットダウンとかそういう特殊なメッセージ以外は特別な処理はなにも必要ありません。

    私の書いた方法は、処理中は何もさせない(先に進ませない)場合で、なおかつその処理が非常に時間のかかるものの場合にもっとも有効な手段です。
    いわゆる「しばらくお待ちください...」メッセージなわけですが、実装の複雑性が見事なまでに解消されるという点では、メッセージポンプループを作る場合より圧倒的にシンプルになるのでお勧めです。

    もっとも待機しちゃうことに変わりはないので、ほかの処理をユーザーができるようにしつつとはなりませんがね。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    2009年6月12日 3:59
  • ご回答ありがとうございます。

    >CWaitDlgダイアログを制御させるのはメインスレッドだけにしておいて、計算を行う別スレッドが終了したことをWaitForSingleObjectやウィンドウメッセージなどで待つような実装を行うのが一般的方法ではないでしょうかね。

    WaitForSingleObjectで計算スレッドの処理待ちをする場合だと、処理待ちダイアログを表示させることは出来るのでしょうか?
    私のイメージではWaitForSingleObjectはスレッドの終了か、タイムアウトするまでメインスレッドでの処理をとめる役割をするのでは
    と思っているので、これだと処理待ちダイアログを表示させることができません。もう1つスレッドを作って、そこに処理待ちダイアログを表示させる
    ようにするのでしょうか?
    2009年6月12日 4:18
  • totojo様、ご回答ありがとうございます。

    計算処理はメインスレッドではなく、サブスレッドに逃がすほうがいいのですね。
    ワーカースレッドというくらいですから、そうなのかなと思っていたのですが。。

    モードレスダイアログのほうが簡単というのはどういう面ででしょうか?モーダルダイアログ
    だと、処理待ちダイアログが表示された際に、フォーカスが処理待ちダイアログのみになってしまう
    ところが宜しくないということでしょうか?
    2009年6月12日 4:20
  • とっちゃん様、ご回答ありがとうございます。

    >スレッド側には待機中を出すモーダルダイアログのウィンドウハンドルを渡して置き、処理が終わったら専用の処理終了メッセージを発行します。
    待機ダイアログはユーザー操作で終了してしまわないように、OnOK, OnCancel の二つの仮想関数を空で用意しておきます(エンターキー、ESCキーの処理をブロックするための必須機能)。
    そのうえで、専用の処理終了メッセージを受け取ったら、EndDialog(IDOK);など、なにかしら適当な終了コードを入れるようにしておきます

    とっちゃん様のおっしゃることを踏まえて、以下のようなプログラムを作成してみましたが、処理待ちダイアログにメッセージを送信すること
    が出来ませんでした。なにか足りない点がありますでしょうか?

    //メインスレッド
    CWaitDlg* wait_dlg;                                                //メンバ変数   
    UINT KEISANThread(LPVOID lParam);             //グローバル関数

    //計算処理スレッド呼び出し
    AfxBeginThread(KEISANThread,wait_dlg->GetSafeHwnd());
    //処理待ちダイアログのポップアップ
    mes_dlg->DoModal();

    //計算スレッド
    UINT KEISANThread(LPVOID lParam)
    {
        CWaitDlg* w_dlg = (CWaitDlg*)lParam;
       //計算処理

    w_dlg->SendMessage(WM_MSG,MSG_CLOSE,0);                          ①
       return 0;
    }

    //CWaitDlgのメッセージ処理関数
    CWaitDlg::ONMyMsg(WPARAM wParam,LPARAM lParam)
    {
       switch(wParam){
          case WSG_CLOSE:
                      EndDialog(IDOK);                     //処理待ちダイアログを閉じる
                       break;
      }
    }

    という風なコードにしてみたのですが。。
    また上のプログラムの①の箇所をどうせならと思い、
    wait_dlg->EndDialog(IDOK);
    と変更してみたら、ハンドルエラーとなりました。ハンドルの渡し方が悪いのか、メッセージの送受信が悪いのか
    分からないのですが、ご指摘をお願い致します。
    2009年6月12日 4:31
  • 部分的なソースなので不明なところは多いですが
    問題点としては
    1.ウィンドウハンドルを渡していない CWaitDlg* ではなく HWND を渡してください == ウィンドウを作ってからスレッドを動かしてください
    2.メッセージマップは書いてあるか?
    ソースからは読み取れませんが(ONMyMsgの戻り値もないしね)、メッセージマップに ON_MESSAGE(WM_MSG,ONMyMsg) は定義していますか?
    あと、WM_MSG の値はWM_APP+? とかにしていますか?

    とりあえず怪しいところはこの2か所かな。
    ソースは一切書いていませんが、肝となる部分は全部最初のコメントで載せてますよ。
    よく読み直してみてください。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    2009年6月12日 4:38
  • ちょっと補足を。
    マルチスレッドで処理をする場合、スレッド間でCWndクラスのインスタンスをやり取りしてはいけないと
    Microsoftが公式に言っていますので、とっちゃんさんが言われるようにHWNDでやり取りするようにしてください。
    で、CWndクラスのメソッドをわざわざ使わずにWin32APIの方の::PostMessageでやってしまった方が良いです。
    基本的にSendMessageよりもPostMessageをお勧めします。


    私だったら処理中ダイアログはモードレスで出してメッセージはメインのウインドウで受けて
    メインのウインドウから処理中ダイアログを制御するかなぁ。
    メインウインドウの方を弄られたくなければ、メインウインドウをDisableにしておけば良いし。

    ユーザー定義のメッセージは通常のウインドウメッセージと被らない様に定義して(とっちゃんさんが書かれている通り)
    あと、メッセージマップの追加等は自分でやら無いと駄目ですよ。
    ウィザードでは追加できませんから。
    ユーザー定義メッセージを使うのは正規のメッセージと区別を付けるためですね。
    正規のウインドウメッセージが予想外のタイミングで来た時に対処が難しいので。

    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    • 編集済み PATIO 2009年6月12日 5:31 PostMessageを進める記述を追加
    2009年6月12日 5:15
  • ちなみに、モーダルダイアログを出してる状況でスレッドが動いていようがいまいが、よけなところのクリックは全部スルーされます。
    メッセージボックス出してる状態で周りをクリックしまくってもボタン押されたりしないでしょ?それと同じです。

    そういえばクリックとかキューには溜まらないですね、訂正します。でも余計なところ(親ウィンドウ)をクリックしてしまうと、最近の OS だとダイアログが点滅したりシステム警告音が鳴ったりするのですが、その辺は問題ないものなんでしょうか。CE は未知の世界なもので。
    2009年6月12日 5:27
  • えーと、警告音がなるところは同じだと思いますよ。
    この辺はよしなに処理されるのでキューに溜めると言う事は無いはずです。
    これはCEでなくても同じですよ。

    モーダルで表示している時は、親ウインドウはDisableになってますから警告音が鳴ったりしますけれど、
    ユーザーオペレーション系のウインドウメッセージは全て破棄されたと思います。
    そうでないとモードレスダイアログが閉じたとたん意味不明の動きを親ウインドウがしてしまいますから。

    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    2009年6月12日 5:39
  • 確かに部分的なソースで不明なところが多いですが、このソースが正しいとして気がついたこと。

    スレッド作成時に

    AfxBeginThread(KEISANThread,wait_dlg->GetSafeHwnd());

    としてウィンドウハンドルを渡しているのに、スレッド関数では、

    //計算スレッド
    UINT KEISANThread(LPVOID lParam)
    {
       CWaitDlg* w_dlg = (CWaitDlg*)lParam;

    とハンドルを強制的にCWaitDlgポインタへ変換してますね。これはどう考えてもaccess violationになるでしょう。
    スレッド終了時にメッセージを送りたいなら、

    //計算スレッド
    UINT KEISANThread(LPVOID lParam)
    {
       HWND hWnd = (HWND)lParam;
    // 計算処理

        ::SendMessage(hWnd, WM_MSG,MSG_CLOSE,0);   // <-- 計算終了メッセージ送信
         return 0;
      }

    ってな感じですかね。
    • 回答としてマーク 鏑木肆星 2009年6月12日 8:08
    2009年6月12日 6:05
  • とっちゃん様、ご回答ありがとうございます。

    ソースは部分的であった為、不明な箇所が多く申し訳ございませんでした。
    WM_MSGの処理の関数ですが、これは他のメッセージの処理で動作している為、コードは間違っていません。

    分からないのは
    >1.ウィンドウハンドルを渡していない CWaitDlg* ではなく HWND を渡してください == ウィンドウを作ってからスレッドを動かしてください
    ですが、ウィンドウを作成するというのはDoModal()をスレッドを呼ぶ前に呼べということでしょうか?
    DoModal()を読んでしまうと、処理待ちダイアログを閉じるまで、メインスレッドの処理が進まなくなってしまいます。
    HWNを渡すというのはwait_dlg->m_hwndを渡すということでしょうか?
    先ほど①の箇所でwait_dlg->EndDialog(IDOK)にすとハンドル例外エラーが発生すると、記述したのですが、EndDialogの前に
    Sleep(100)を記入すると、なぜかダイアログは正常に閉じることが出来ました。
    以前にもメッセージのことで、作られていないウィンドウにはメッセージは送れないと聞いてはいたのですが、どういう意味なのかよくわからず
    今回も行き詰っています。
    面倒を掛けますが、ウィンドウを作成するというのはどういう意味なのでしょうか?
    2009年6月12日 6:08
  • WaitForSingleObjectでも待ち時間をINFINITEにしなければいいだけのことです。そうすればメインスレッドは完全にブロックされるわけではありません。その代わり、ときどき「計算処理、終わったか~??」と何度も訊きに行かなくてはいけませんけどね。「ポーリング」みたいな感じで。

    今回の場合だと、メッセージを送信する方が適当だとは思いましたが、一般的にスレッドの終了を待つ方法として2つの方法を挙げたまでです。

    • 回答としてマーク 鏑木肆星 2009年6月12日 8:07
    • 回答としてマークされていない 鏑木肆星 2009年6月12日 8:07
    2009年6月12日 6:11
  • PATIO様、ご回答ありがとうございます。

    とっちゃん様にもお聞きしたのですが、HWNDでやり取りするというのと、wait_dlg->GetSafeHwnd()でやりとり
    するのでは何が違うのでしょうか?GetSafeHwnd()もHWNDを返すと思うのですが。。

    >私だったら処理中ダイアログはモードレスで出してメッセージはメインのウインドウで受けて
    メインのウインドウから処理中ダイアログを制御するかなぁ。
    メインウインドウの方を弄られたくなければ、メインウインドウをDisableにしておけば良いし。

    PATIO様のおっしゃる方法は、メインスレッドでモードレスダイアログを表示させ、ワーカースレッドで計算処理を
    させ、ワーカースレッド側で計算処理が終了したらメインウィンドウにモードレスダイアログを閉じるメッセージを送信
    するということでしょうか?

    モードレスダイアログにすることで、モーダルダイアログにくらべ何が違ってくるのでしょうか?また、これもモーダルダイアログ
    の場合と同様のメッセージの処理となるのでしょうか?
    2009年6月12日 6:12
  • WM_INITDIALOG を受けてと書いてあるんですが。。。?
    OnInitDialog()でスレッドを作れば、ウィンドウがある状態で作れますよね?
    しかもDoModalからは帰ってきていない状況。

    もし、そこでスレッドの作成に失敗したら、OnInitDialog()からEndDialog()を呼び出せばウィンドウも出ずに処理が終わります。

    このあたりは、一般的なプログラミングの問題なので、あえて言及していなかったんですが...


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    2009年6月12日 6:14
  • WM_INITDIALOG を受けてと書いてあるんですが。。。?
    OnInitDialog()でスレッドを作れば、ウィンドウがある状態で作れますよね?
    しかもDoModalからは帰ってきていない状況。

    もし、そこでスレッドの作成に失敗したら、OnInitDialog()からEndDialog()を呼び出せばウィンドウも出ずに処理が終わります。

    確かにそうですね。
    私の場合、モードレスでダイアログを作成する事が多いのでそっちの話で書いちゃいました。
    いまやっている処理と直接関係無い部分は弄れるようにしてくれと言う話が多いので。
    モーダルでやるならモーダルダイアログのOnInitDialogでワーカースレッドを起こせば、
    HWNDも既にあるはずなので無問題ですね。


    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    2009年6月12日 6:43
  • とっちゃん様、ご回答ありがとうございます。

    >WM_INITDIALOG を受けてと書いてあるんですが。。。?

    これはつまり、メインダイアログではなく、処理待ちダイアログ内で計算処理スレッドをもたせるということですね?

    初めの説明不足だったのですが、計算処理というのが1つではなく複数あり、さまざまな計算処理の待ち時間に
    同じ、計算処理待ちダイアログを表示させたいと思っています。ですので、できれば処理待ちダイアログに固有の計算
    処理部分を持たせたくないのですが。。。
    2009年6月12日 6:44
  • とっちゃん様にもお聞きしたのですが、HWNDでやり取りするというのと、wait_dlg->GetSafeHwnd()でやりとり
    するのでは何が違うのでしょうか?GetSafeHwnd()もHWNDを返すと思うのですが。。

    >私だったら処理中ダイアログはモードレスで出してメッセージはメインのウインドウで受けて
    メインのウインドウから処理中ダイアログを制御するかなぁ。
    メインウインドウの方を弄られたくなければ、メインウインドウをDisableにしておけば良いし。

    PATIO様のおっしゃる方法は、メインスレッドでモードレスダイアログを表示させ、ワーカースレッドで計算処理を
    させ、ワーカースレッド側で計算処理が終了したらメインウィンドウにモードレスダイアログを閉じるメッセージを送信
    するということでしょうか?

    モードレスダイアログにすることで、モーダルダイアログにくらべ何が違ってくるのでしょうか?また、これもモーダルダイアログ
    の場合と同様のメッセージの処理となるのでしょうか?
    モーダルダイアログとモードレスダイアログの制御に関してはスレッドとは別の話になりますし、
    一度御自分で勉強される事をお勧めします。
    やってみれば、そこまで難しいものでは有りませんし。

    Microsoftの寄るとHWNDとCWndのインスタンスのマッピングはスレッドごとで管理されているので
    あるスレッドで作成したCWndのインスタンス(派生クラスのインスタンスも含む)を別のスレッドに引き渡して
    使ってはいけないのだそうです。で、いろいろなチャレンジャーな方が実際にはインスタンスのスレッド跨ぎを
    やって見て今の実装ではうまく動くようだと言う話が出ていますが、Microsoftが公式に駄目といっている以上、
    動かないような実装に変更されても文句は言えません。(既に止めてねと言われているわけですから)
    Microsoftが仕様の変更を公式に認めない限り、前述の仕様は生きていますから将来にわたっての保証を
    考えるとスレッドを跨いだCWndのインスタンスの使用はしないようにするのが得策と言うのが私の意見です。

    ですから、引き渡す前にCWnd::GetSafeHwnd()を呼んで、得られたHWNDをワーカースレッドに
    引き渡すと言うのが実装として適当であろうと思います。


    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    • 編集済み PATIO 2009年6月12日 6:54
    2009年6月12日 6:51

  • > 初めの説明不足だったのですが、計算処理というのが1つではなく複数あり、さまざまな計算処理の待ち時間に
    > 同じ、計算処理待ちダイアログを表示させたいと思っています。ですので、できれば処理待ちダイアログに固有の計算
    > 処理部分を持たせたくないのですが。。。


    これも考え方次第かと思います。
    同じダイアログを流用したいのであれば、計算部分の関数はグローバル関数にしてしまい、
    呼び出す時に関数のポインタで外から渡してやるとか。

    ダイアログに計算処理を集めてしまって呼び出す時にどの計算処理を呼び出すのか指示をするとか。

    いずれにしても計算処理をワーカースレッド化すると言う事は、計算の元ネタ関連は関数に直接引き渡さないと
    いけないので大丈夫だと思います。ワーカースレッドで起動する関数はstatic関数ですからクラスのメンバーとして
    定義していてもメンバー変数の参照はできませんから。

    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    • 編集済み PATIO 2009年6月12日 7:01
    • 回答としてマーク 鏑木肆星 2009年6月12日 8:10
    2009年6月12日 6:59
  • 処理待ちのダイアログに、どの計算処理を実行するか?という情報を渡せるようにしてあげれば問題ないと思いますが?

    スレッドで呼び出す計算関数の形を合わせておけば関数ポインタで渡せます。
    クラスにしておけば、クラスオブジェクトのポインタとして渡すこともできます。

    このあたりはどういうだんどりか?によるので何とも言えませんね。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    2009年6月12日 6:59
  • CWnd のスレッド間受け渡しですが、CWnd を HWND を持つ単なるオブジェクトとして渡すだけなら問題はないですよ。
    ただし、使えるメソッドは、GetSafeHWnd() 程度ですけどね。

    それ以外も使いたいのなら、HWNDを取り出してきて、スレッド側で作成した別のCWnd(派生クラス)にアタッチしてください。
    その際、間違っても元のCWnd からデタッチしてはいけません。
    また、スレッドから抜ける前にどのような状況で抜ける場合でも、必ずデタッチしてください。そうしないとクラッシュします。

    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 鏑木肆星 2009年6月12日 8:04
    2009年6月12日 7:05
  • とっちゃん様、ご回答ありがとうございます。

    とっちゃん様のおっしゃるように、CWaitDlgのOnInitDialog()内でワーカースレッドの
    処理を入れたところ正常に動作致しました。
    計算処理関数については、判別するフラグ等を設定するか、今回初めて聞きました関数ポインタなるものを
    使用してみようかと思います。

    長々とお付き合いいただきまして、本当にありがとうございました。
    2009年6月12日 8:04
  • murano様、ご回答ありがとうございます。

    一般的なスレッドの待機方法に教えていただきありがとうございます。
    確かに、メインスレッドでハンドルで渡していて、サブスレッドでクラスのポインタにするのは
    おかしいですね。
    ご指摘の通りに、コードを編集することで、正常にメッセージを送信することが出来ました。

    ご指摘ありがとうございます。
    2009年6月12日 8:08
  • PATIO様、ご回答ありがとうございます。

    詳しいスレッドでのハンドルの受け渡しの説明や、ワーカースレッドでの処理についての詳しい説明をして
    頂きまして、大変ありがとうございます。
    とっちゃん様から教えて頂いた方法で、モーダルダイアログについては無事処理が成功致しましたので、今度は
    PATIO様のおっしゃっていたモードレスダイアログでも同様に処理が出来るか試してみます。

    長々とお付き合いいただきまして、大変ありがとうございました。

    ただ、モードレスダイアログで処理待ちダイアログを表示させた場合

    //モードレスダイアログ表示
    Wait_dlg.Create(this);
    //計算処理スレッド
    CWinThread* m_Therad = AfxBeginThread(KEISANThread,this->GetSafeHwnd());
    //スレッド処理待ち
    WaitForSingleObject(m_Thread,INFINITE);

    とコードを作成した場合に、スレッド処理待ちで、メインスレッド側の処理がとまらずに先に進んでしまいます。
    これはどこかおかしな点があるのでしょうか?

    • 編集済み 鏑木肆星 2009年6月12日 8:23 文章追加
    2009年6月12日 8:13
  • ただ、モードレスダイアログで処理待ちダイアログを表示させた場合

    //モードレスダイアログ表示
    Wait_dlg.Create(this);
    //計算処理スレッド
    CWinThread* m_Therad = AfxBeginThread(KEISANThread,this->GetSafeHwnd());
    //スレッド処理待ち
    WaitForSingleObject(m_Thread,INFINITE);

    とコードを作成した場合に、スレッド処理待ちで、メインスレッド側の処理がとまらずに先に進んでしまいます。
    これはどこかおかしな点があるのでしょうか?

    すいません、今更追記があったのを発見しました。
    ただ、上記のコードに関しては拙いと思います。
    CWinThread*とスレッドのハンドルは同じ物では無いと思います。
    CWinThread::m_hThreadを使うのであれば、話もわかるのですが。


    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    2009年6月22日 9:32
  • PATIO様、いつもご回答頂きありがとうございます。
    ネットで検索していたところ、上記のような処理でWaitForSingleObject()を呼び出して
    いたので、こうやって使うのかぁと思い、上記のようなコードとしました。思慮不足で申し訳
    ございません。
    その後、PATIO様のおっしゃるように
    CWinThread* thread = AfxBeginThread(KEISANThread,this->GetSafeHwnd());
    WaitForSingleObject(thread->m_hThread,INFINITE);

    とコードを入力したところ、今度は永遠と待ち続ける形となりました。
    現在は、モードレスダイアログにはWaitForSingleObjectは通常使わないのかなと思い、
    モーダルダイアログで対応しています。
    ですが、出来ればこの機会にWaitForSingleObjectの使い方を取得できればと思っていますので、
    もしお時間があれば、ご意見を頂ければと思っております。
    2009年6月22日 10:07
  • 永遠に待ち続ける原因かどうかは分かりませんが、

    CWinThread* thread = AfxBeginThread(KEISANThread,this->GetSafeHwnd());
    WaitForSingleObject(thread->m_hThread,INFINITE);

    だと WaitForSingleObject でおかしな領域にアクセスする可能性があります。

    AfxBeginThread で作成する CWinThread は m_bAutoDelete が TRUE のため、
    KEISANThread が終了した後で CWinThread のインスタンスを delete してしまいます。

    CWinThread のメンバーにアクセスするには、自動削除を解除する必要があります。
     ・AfxBeginThread の dwCreateFlags に CREATE_SUSPENDED を指定してスレッドを作成します。
     ・次にスレッドの m_bAutoDelete を FALSE にして、スレッドの ResumeThread でスレッドを実行します。
     ・CWinThread のインスタンスが不要になった際には自分で delete します。
    2009年6月22日 10:55
  • 現在は、モードレスダイアログにはWaitForSingleObjectは通常使わないのかなと思い、
    モーダルダイアログで対応しています。
    ですが、出来ればこの機会にWaitForSingleObjectの使い方を取得できればと思っていますので、
    もしお時間があれば、ご意見を頂ければと思っております。

    モードレスダイアログを使用する場合、基本的にはGUI側の操作はしたいからモードレスダイアログを使うわけです。
    ですから、私がモードレスダイアログを使うケースではWaitForSingleObjectで止めるような事はしません。
    親ウインドウの操作をして欲しくないケースですと、親ウインドウに対して無効化をする場合は有りますけれど。

    私がよくやるのは、ワーカースレッド側でモードレスダイアログに対して処理が終了した旨を(ウインドウメッセージ等で)返して
    受け取ったモードレスダイアログが終了する時に親の無効化を解除すると言う方法ですね。
    たいていの場合、モードレスで出すときは処理の中断ボタンを表示している事が多いのでモードレス側で
    ボタンが押されたらワーカースレッドとのやり取りの用の領域に中断フラグを立てるようにしています。
    後はワーカースレッドが終了すれば、終了の通知がダイアログに来て、ダイアログが終了する流れです。

    私がWaitForSingleObjectを使うケースとしては、終了通知が来た時にワーカースレッドの終了を確認する意味で
    使用します。あとはアプリの終了時に動作中のワーカースレッドがあった場合に中断指示を出した後、ワーカースレッドが
    終了した事を確認するためにも使いますね。

    あと、既にtotojoさんが書かれていますが、CWinThreadはメンバー変数のm_bAutoDeleteがTRUEだと
    処理が終了した時に自分自身をdeleteしてしまいます。注意が必要ですね。

    永遠に待ち続ける件ですが、ワーカースレッドから終了通知を送るのにSendMessageを使っていませんか?
    SendMessageはメッセージの送信を同期処理で行なうので受け取り側が処理を終了しないと帰って来ません。
    ところが、メインスレッド側はWaitForSingleObjectで止められているのでSendMessageされても処理が出来ません。
    結局、デッドロック状態になっていつまでたっても終わらないという状態になっているのではないでしょうか。
    SendMessageは、送信側で保持している変数のアドレスをメッセージのパラメータに入れて引き渡すケースや
    受信側の処理結果を受け取らないといけないケース以外では使わない方が良いと思いますよ。
    特に理由が無いのであれば、PostMessageでメッセージを送った方が良いです。
    但し、今回のメインスレッド側のWaitForSingleObjectは処理として意味が無いと思いますので
    やめた方が良いと思います。
    逆になぜWaitForSingleObjectで止める必要があると考えたのかが疑問です。



    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    • 編集済み PATIO 2009年6月24日 10:06
    2009年6月23日 4:54