現在、VC2008で、UIスレッド(ためしにモードレスダイアログ)を別途持った
SDIアプリケーションを実装できるか調査しています。
終了時の処理などは後回しとして、動き自体はとりあえずできているのですが、
・フレームウィンドウ側でメニューを表示
・そのままメニューを消さずにUIスレッド側のダイアログをクリック
とすると、ASSERTが発生してしまいます。
コールスタックで確認してみたところ、
CDialogEx::PreTranslateMessage()
└CDialogImpl::PreTranslateMessage
└CDialogImpl::ProcessMouseClick()
の中に
if (!CMFCToolBar::IsCustomizeMode() && CMFCPopupMenu::m_pActivePopupMenu != NULL
&& ::IsWindow(CMFCPopupMenu::m_pActivePopupMenu->m_hWnd)) …(1)
{
CMFCPopupMenu::MENUAREA_TYPE clickArea =
CMFCPopupMenu::m_pActivePopupMenu->CheckArea(pt); …(2)
という箇所があり、(2)の箇所で
CMFCPopupMenu::m_pActivePopupMenuがNULLになっていました。
(1)の箇所を通過しているということは、その時点ではNULLではなかったはずです。
CMFCPopupMenu::m_pActivePopupMenu自体は、staticメンバ変数で、
その名の通り「現在表示しているメニュー」だと思われ、
CFrameWndEx::OnClosePopupMenu()の中でNULLに戻されていました。
そうなるとこの変数は、複数のスレッドから同時にアクセスされることを
全く想定していないということになってしまうのでしょうか。
また、上記の自分の推測が間違っていないとすると、
2008以降のMFC Feature Packは、
UIスレッドを作成することは不可能ということになるのでしょうか?
どなたかこのへんの調査をされたり
実際にUIスレッドを作成されたかたはいらっしゃいませんでしょうか。
うーん、
UIスレッド側のHWNDとフレームウィンドウ側のHWNDは別にないますよね。
現在、UI側のダイアログがクリックされているということは、HWNDはUI側ですね。
しかしフレームウィンドウ側のメニューの項目はUIスレッド側のHWNDのリソースから
検索しようとしているため、見つからないはずですよね。
そのため、検索不能なので
> CMFCPopupMenu::m_pActivePopupMenuがNULLになっていました。
という結果になるのでは?
と思うのですがいかがでしょうか?
おそらく、フレームウィンドウ側のHWNDでUI側のダイアログを作成すると、そこで
HWNDの関連付けができるのでリソースも検索できるようにならないか(?)と思います
がどうでしょうか。
他にもUIまわりの静的変数は多々あると思いますが、基本的に並列操作できないもの
が該当しているように思われます。
(Feature Pack以降はアプリケーション全体の設定という意味合いの物も少なくあり
ませんが)
今回のm_pActivePopupMenuの場合は、UI操作のマウスなりキーボードなりで、ポップ
アップメニューを出したまま他の操作を行うなんて事はできませんよね?
よって、並列操作されることも無かろう・・・という設計思想なんじゃないかと推測
しております。
ですから今回の事例ではCMFCPopupMenuではなく、CreatePopupMenuなどを使い、自前
で制御機構を実装すべきだと思います。
私自身の考えとしては、ユーザーが同時に二つのウインドウを操作することは
無いと考えているのでUIスレッドを使ったマルチスレッドは殆ど使わないと
考えています。殆どの場合、UIスレッドがメインスレッドになり、
ワーカースレッドをサブスレッドにする構成で事足りると考えています。
ウインドウハンドルが必要になるからUIスレッドをと言うケースがあるかも
とは思うのですが、
実際の話、UIスレッドを二つ以上使ってマルチスレッドを実装するケースって
あるんでしょうか。
実験的な意味では試してみても良いとは思うんですが、
実際には実装が面倒になる割にはメリットが少ないような気がしています。
発生条件がよくわからないので、以下のように試してみました
●環境
Win7 VS2008sp1
●プロジェクト条件
MFCアプリケーション
SDI (ドキュメント/ビューあり) Unicode
MFC:(スタティックor共有)
メニュー バーとツール バーを使用する
●ダイアログ
・CMainFrameのメンバ変数として管理するモードレスダイアログ
継承元はCDialog or CDialogEx で2種(*1)
ダイアログの親はCMainFrame(this)
・CWinAppExに、CWinThread派生のスレッドを持たせる
(スレッド生成はnew→CreateThread(0)の2行)
このスレッドのメンバ変数として管理するモードレスダイアログ
こちらも継承元はCDialog or CDialogEx の2種
ダイアログの親はNULL指定
以上、計4つのダイアログ
●メニュー
メインのウィンドウのメインメニュー or 右クリックポップアップメニュー
の2つ
作業内容
1:メニューを表示。
2:メニューの項目を選択せず、
そのまま、マウスカーソルをダイアログに移動しクリック
3:フォーカス(アクティブウィンドウ)がダイアログにうつることで、
メニューを自消滅させる
以上の動作を、試した結果
メニュー・ダイアログのどの組み合わせでも、ASSERTは発生しませんでした。
#(*1)の場合、クライアント領域へのクリックではすぐにはメニューは消えなかった
(ASSERTはしない)
NORさんが行った作業(最小限でASSERTが再現できるもの)は
どんな内容だったのでしょうか?
みなさん情報ありがとうございます。
ご意見もじっくり参考にさせていただきますが、
まずは以下に、問題が発生する最低限の記述を書かせていただきます。
なお、自分の環境はXPなのですが、
ryoさんの情報を元に、Windows7の上で実行してみたところ、
こちらはASSERTが発生しませんでした。
なぜ発生しないのかまでは調査しきれていません。
環境:XP VC2008
プロジェクト名:Test
アプリケーションの種類:SDI(ドキュメント/ビューあり)
プロジェクト形式:MFC標準
あとはデフォルトのまま
リソースビューで「ダイアログを挿入」し、IDD_TEST_DIALOGに変更し、
クラスの追加でCTestDialogを作成(基本クラスはあとからCDialogExに変更)
以下は最低限の記述のみに変更したソースです
(コンストラクタやデストラクタなども消していて、
OKボタンやキャンセルボタンなどの対応もしていません)
TestDialog.h
----------------------------------------
#pragma once
class CTestDialog : public CDialogEx
{
DECLARE_DYNAMIC(CTestDialog)
public:
enum { IDD = IDD_TEST_DIALOG };
BOOL Create(CWnd* pParentWnd) { return CDialogEx::Create(IDD, pParentWnd); }
};
----------------------------------------
TestDialog.cpp
----------------------------------------
#include stdafx.h
#include Test.h
#include TestDialog.h
IMPLEMENT_DYNAMIC(CTestDialog, CDialogEx)
----------------------------------------
CWinThread派生クラスCTestThreadを作成し、
CTestThread::InitInstance()に以下の2行を追加
(m_dlgTestはCTestDialog型のメンバ)
m_dlgTest.Create(NULL);
m_dlgTest.ShowWindow(SW_SHOWNA);
CMainFrame::OnCreate()の最後に以下の1行を追加
AfxBeginThread(RUNTIME_CLASS(CTestThread));
ビルドして実行し、後ろに隠れているモードレスダイアログをどこかに移し、
CMainFrame側のメニューを出しっぱなしの状態で
モードレスダイアログをクリックすると、やはりASSERTが発生します。
XP(x86)+VS2008(sp1)で試してみたのですがやはり発生しませんでした。
ryoさんの示した作業内容の3番目をやってないですね。
>作業内容
>1:メニューを表示。
>2:メニューの項目を選択せず、
> そのまま、マウスカーソルをダイアログに移動しクリック
------- ココカラ ---------
>3:フォーカス(アクティブウィンドウ)がダイアログにうつることで、
> メニューを自消滅させる
------- ココマデ ---------
>ITOさんへ
その3番は(1~3全部)、ビルドして完成したものに対して、
操作・実行するテスト項目です
・・と書いていてふと疑問にぶつかった
「CMFCPopupMenu」や「CMainFrame側のメニューを出しっぱなしの状態で」
という単語から、NORさんが問題としているメニューの表示というは
___________________
|ファイル(F) 編集(E)・・・・
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
のことではなく、
___________________
|ファイル(F) 編集(E)・・・・
|保存 | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
|名前をつけ|
 ̄ ̄ ̄ ̄ ̄ ̄ ̄
と、ポップアップメニューが表示されている状態のことだと思います
この状態で、他のダイアログなどをいじれば、ポップアップ分が消えるはずだが
なぜかASSERTしてしまう
と、私は解釈したのですが、NORさん あってますでしょうか?
> ___________________
> |ファイル(F) 編集(E)・・・・
> |保存 | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
> |名前をつけ|
>  ̄ ̄ ̄ ̄ ̄ ̄ ̄
> と、ポップアップメニューが表示されている状態のことだと思います
> この状態で、他のダイアログなどをいじれば、ポップアップ分が消えるはずだが
> なぜかASSERTしてしまう
>
> と、私は解釈したのですが、NORさん あってますでしょうか?
そのとおりです。
こちらの説明がわかりにくかったようで大変失礼しました。
イラスト付きで問題を解説していただき、ありがとうございます。
そのように理解していただいた上で、ryoさんの環境では、
XPでも再現しないということになりますでしょうか?
>そのように理解していただいた上で、ryoさんの環境では、
>XPでも再現しないということになりますでしょうか?
はい。
前提を確認する必要があるかもしれません。
一般にVS2005以降においては、ウイザードを使うとCMFCMenuBarクラスを
使用するようにコードされると思いますが、どうなってますでしょうか。
この場合、メインフレームのいわゆるメニューは一時的なオブジェクトに
なっており、表示されているメニューの実体はCMFCMenuBarクラスの
派生クラスが保持しているはずです。
何をしようとしているのかいまいち不明ですが、メインでないUIスレッドが、
メインのメニューをどうこうしたいのであれば、対象とすべきなのは、
実体のあるCMFCMenuBarクラスの派生クラスにすべきなのかもしれません。
もっとも、一般的に言うのであれば、下位の処理であるUIスレッドが、
メインメニューにアクセスしようとすること自体が、やや変な設計と言えるかも
しれません(vv;)。
>>ITOさんへ
> その3番は(1~3全部)、ビルドして完成したものに対して、
> 操作・実行するテスト項目です
了解です。
> もっとも、一般的に言うのであれば、下位の処理であるUIスレッドが、
> メインメニューにアクセスしようとすること自体が、やや変な設計と言えるかも
> しれません(vv;)。
MFCのソースコードを見た限りの理解ですが、最初の書き込みの通り、
CDialogExのマウス処理(CDialogImpl::ProcessMouseClick())が、
ポップアップ表示中のCMFCPopupMenuにアクセスに行っているのであって、
自分自身で意図的にメインメニューにアクセスしているわけではありません。
なお、問題が発生するテストプログラムのプロジェクトを
以下の場所にアップロードしてみました。
どなたかお時間があれば、これを直接ビルドして
自分と同じ問題が発生するか試してみていただけませんでしょうか。
http://firestorage.jp/download/0a662321f3729db7b2a65205d13b22f6e88d1abb
「2011/08/03(水) 14:49:29」の書き込みのときに作成したもので、
VS2008が入っているXPマシン3台で試してみましたが、同じように発生しました。
失礼しました。m(__)m
確かに、子スレッドのPreTranslateMessage()で、
メインフレームのAssertValid()が呼ばれちゃってますね(vv;)。
原因はCDialogExクラスのPreTranslateMessage()で、不用意に
CDialogImpl::PreTranslateMessage()を呼んでいるせいで、
困ったもんです。
従って、このクラスはUIスレッドのターゲット
(=メイン)ウインドウオブジェクトととしては使用できません。
あきらめて、CDialogクラスを使いましょう。