Visual C++ 2008 MFCです。
MFCアプリケーションのプロジェクトに作成していたダイアログクラスを
MFC標準DLLに移し、そのダイアログをモーダル表示する関数をエクスポートし、
EXEからそれをコールしてモーダル表示するようにしました。
それ自体は正しく動いているのですが、
このダイアログからメッセージボックスを出そうとすると、
モニタ中央に表示されてるようになってしまいました。
void CDllDialog::OnBnClickedButton1()
{
AfxMessageBox(_T(1));
CWnd::MessageBox(_T(2));
::MessageBox(m_hWnd, _T(3), _T(3), MB_OK);
}
上記のどの方法でも結果は同じでした。
http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+200304/03040023.txt
こちらのログを読ませていただいた限り、MFC自身が勝手に
親ウィンドウの中央に合わせてくれるものなんだと思っていましたが、
MFC標準DLLではそのようにはならないものなのでしょうか?
それとも、なにか忘れているものがありますでしょうか?
私の思っているとおりなら気になることが。
・メッセージボックスを表示していても親ウィンドウが非ロック状態では?
当たりならDLLのメインウィンドウ登録が必要です。
AfxGetApp()->m_pMainWnd にEXE側のフレームウィンドウを登録してやる必要
があります。
その際、m_pMainWndはCWnd*型ですが、ウィンドウハンドルのアタッチをDLL
側でしてやらないとうまくいかないと思います。
(CWndのウィンドウハンドル管理がスレッドセーフでないため)
情報ありがとうございます。
> メッセージボックスを表示していても親ウィンドウが非ロック状態では?
いえ、親ウィンドウ(今回の例ではDLL内のCDllDialog)はロックされていて、
メッセージボックス表示中にクリックすると、メッセージボックスが点滅します。
また、CDllDialog自身はちゃんとEXE側のウィンドウの中央に表示され、
その親ウィンドウもロックされています。
思っていたものと違うようです。
そこで、ちょっと検証してみました。
AfxMessageBox()、CWnd::MessageBox()とも、
最終的には、APIのMessageBox()を呼んでいます。
なので、検証は::MessageBox()だけで十分と踏んでいます。
普通にEXEからMessageBox()を呼んだ場合は、は第一引数に渡したウィンドウの
中央に表示されます。
第一引数をNULLにすると、デスクトップ中央(モニタ中央)に表示されます。
元々のコードにおいて、
::MessageBox(m_hWnd, _T(3), _T(3), MB_OK);
のm_hWndがNULLになっていることはありませんか?
> 元々のコードにおいて、
> ::MessageBox(m_hWnd, _T(3), _T(3), MB_OK);
>
> のm_hWndがNULLになっていることはありませんか?
この部分にブレークポイントを置いて調べてみましたが、
NULLでない値がちゃんと入っていました。
なお、このDLL内のダイアログをモーダル表示するエクスポート関数は
void Test(HWND hWnd)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CDllDialog dlg(CWnd::FromHandle(hWnd));
dlg.DoModal();
}
と書いています。
EXE側のウィンドウがこのエクスポート関数を呼ぶときに
自身のm_hWndを引数に渡していて、
CDllDialog自体はEXE側のウィンドウの中央に表示されています。
うーん、問題は無さそうですねぇ。
> AFX_MANAGE_STATE(AfxGetStaticModuleState());
が出てくるなら、最後にもう一つだけアドバイスできることがあるかな。
AFX_MANAGE_STATE(AfxGetStaticModuleState()); の直後に以下の2行を
入れてみてください。
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
pState->m_pCurrentWinThread = AfxGetApp();
これで駄目なら、私的にはお手上げです。
VS2003で追ってみました。
wincore.cppのAfxCbtFilerHook(int, WPARAM, LPARAM)で
1)exe中の::MessageBoxは、bContextIsDLL==FALSE
2)mfcdll中の::MessageBoxは、bContextIsDLL==TRUE
ついでに作った
3)非mfcDLL中の::MessageBoxは、bContextIsDLL==FALSE
と、なります(bContextIsDLLはAfxGetModuleState()->m_bDLLの値です)。
1),3)の場合はこの関数の下の方の_AfxActivationWndProc()呼び出しから
CheckAutoCener(),CenterWindow()が呼ばれます。
とりあえず、mfcdll側で
BOOL bDLL = AfxGetModuleState()->m_bDLL;
AfxGetModuleState()->m_bDLL=FALSE;
::MessageBox(....);
AfxGetModuleState()->m_bDLL=bDLL
これでメインウィンドウのセンターになりました。
これがどんな悪影響をもたらすのかはわかりません。
# ソースを追うのが楽しかったので実験したのです。
# 私はMFCの経験がないので、正当性は分かりません。
情報ありがとうございます。
> AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
> pState->m_pCurrentWinThread = AfxGetApp();
こちらの方法ではやはりモニタ中央になってしまいましたが、
> とりあえず、mfcdll側で
> BOOL bDLL = AfxGetModuleState()->m_bDLL;
> AfxGetModuleState()->m_bDLL=FALSE;
> ::MessageBox(....);
> AfxGetModuleState()->m_bDLL=bDLL
> これでメインウィンドウのセンターになりました。
Visual C++ 2008でもこれで親ウィンドウの中央に表示されました。
詳しい解析ありがとうございます。
リンク元に書かれていたログに書いてある内容は、
MFC標準DLLには当てはまらないことだったということですね。
> これがどんな悪影響をもたらすのかはわかりません。
このafxContextIsDLLという判定、デバッグ中にMFCのソースの中でたまに出会い、
「なぜここで処理を分けているんだ?」と疑問に思うことがありますが、
今回の件もこの判定が関わっていたのですね。
「DLLだよ」という情報を偽ってしまってよいのか、たしかに不安ですが、
なぜDLLでは意図的に中央に移動する処理を通らないようにしているかも疑問です。
教えていただいた処理を入れて、
いろいろなケースでメッセージボックスを出して試してみたいと思いますが、
このへんの実情をご存じのかたがいらっしゃれば、
引き続き情報をいただけると嬉しいです。
変な方法を書いてしまい、気掛りですので、合法な対案を示します。
本格的にやるにはフックですが、簡単に済ませるには、
非MFCのDLLをつくり、
int MyMessageBoxA(HWND hwnd, LPCSTR lpText, LPCSTR lpCpation, UINT uType)
{
return MessageBoxA(hwnd, lpText, lpCaption, uType);
}
int MyMessageBoxW(HWND hwnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)
{
return MessageBoxW(hwnd, lpText, lpCaption, uType);
}
をExportします。このDLLはRelease版で良いです。
# 屋上屋な感じではあります。
すみません、頭おかしかったです。前記取り消します。
MFC標準DLLの中で
AFX_MANAGE_STATE(AfxGetStaticModuleState());
::MessageBox(hwnd, text, caption, MB_OK);
のようになっているのが原因と思います。
APIのMessageBox()呼び出しの前後でステートを戻すことが出来れば
なんとかなりそうな気がします。
> 前記取り消します。
情報ありがとうございます。
この「前記」というのは、
新たに提案していただいたMyMessageBox()のほうでしょうか?
> MFC標準DLLの中で
> AFX_MANAGE_STATE(AfxGetStaticModuleState());
> ::MessageBox(hwnd, text, caption, MB_OK);
> のようになっているのが原因と思います。
前述のエクスポート関数を以下のようにしてみたところ、
void Test(HWND hWnd)
{
AfxMessageBox(_T(1));
AFX_MANAGE_STATE(AfxGetStaticModuleState());
AfxMessageBox(_T(2));
CDllDialog dlg(CWnd::FromHandle(hWnd));
dlg.DoModal();
}
「1」のメッセージボックスは親ウィンドウの中央に出ましたが、
「2」のほうは画面中央になりました。
この部分が、挙動が変わるきっかけになっているのですね。
ただ、これを入れないとCDllDialog自体が表示されませんし、
そのダイアログ中から状態を元に戻さないといけないということになります。
ちなみに、MFC拡張DLLでCDllDialogクラス自体をエクスポートしたときは
メッセージボックスはウィンドウ中央に表示されました。
同じDLLでも非MFCのDLLだったりMFC拡張DLLであればウィンドウ中央に出るのなら
MFC標準DLLでもウィンドウ中央に出てくれてもよいと思うのですが、
なんでMFC標準DLLのときだけこんな仕様になっているのでしょうかね。