スレッド間でウィンドウメッセージを送信するときのESPエラー
-
29-09-2019 - |
質問
オブザーバークラスと加入者クラスがあります。
テストのために、オブザーバーは偽のメッセージを生成して呼び出すスレッドを作成します CServerCommandObserver::NotifySubscribers()
, 、これは次のようになります:
void CServerCommandObserver::NotifySubscribers(const Command cmd, void const * const pData)
{
// Executed in worker thread //
for (Subscribers::const_iterator it = m_subscribers.begin(); it != m_subscribers.end(); ++it)
{
const CServerCommandSubscriber * pSubscriber = *it;
const HWND hWnd = pSubscriber->GetWindowHandle();
if (!IsWindow(hWnd)) { ASSERT(FALSE); continue; }
SendMessage(hWnd, WM_SERVERCOMMAND, cmd, reinterpret_cast<LPARAM>(pData));
}
}
サブスクライバーはaです CDialog
派生したクラスも継承します CServerCommandSubscriber
.
派生クラスに、サブスクライバークラスハンドラーにサーバーコマンドをルーティングするメッセージマップエントリを追加しました。
// Derived dialog class .cpp
ON_REGISTERED_MESSAGE(CServerCommandObserver::WM_SERVERCOMMAND, HandleServerCommand)
// Subscriber base class .cpp
void CServerCommandSubscriber::HandleServerCommand(const WPARAM wParam, const LPARAM lParam)
{
const Command cmd = static_cast<Command>(wParam);
switch (cmd)
{
case something:
OnSomething(SomethingData(lParam)); // Virtual method call
break;
case // ...
};
}
問題は、HandleserverCommand()方法で奇妙なクラッシュが見られることです。
それは次のように見えます:
デバッグエラー!
プログラム:c: myprogram.exe
モジュール:
ファイル:i386 chkesp.c
ライン:42ESPの値は、関数呼び出し全体で適切に保存されませんでした。これは通常、異なる呼び出し条約で宣言された関数ポインターを使用して、1つの呼び出し条約で宣言された関数を呼び出した結果です。
afxbeginthread()が望んでいる関数ポインターを確認しました。
typedef UINT (AFX_CDECL *AFX_THREADPROC)(LPVOID); // AFXWIN.H
static UINT AFX_CDECL MessageGeneratorThread(LPVOID pParam); // My thread function
私にとって、これは互換性がありますね。
私は知りません、私は他に何を探しなければなりませんか。何か案は?
私は別の奇妙な観察をしました、それは関連しているかもしれません: NotifySubscribers
方法、電話します IsWindow()
ハンドルがポイントするウィンドウが存在するかどうかを確認します。どうやらそうです。しかし、電話 CWnd::FromHandlePermanent()
ヌルポインターを返します。
解決 3
私は最終的にウィンドウメッセージなしでそれをすることにし、今ここに私の回避策を投稿しています。多分それは他の誰かを助けるでしょう。
オブザーバーにサブスクライバーにウィンドウメッセージを投稿する代わりに、オブザーバーにデータを同期したサブスクライバーバッファーに入れさせます。ダイアログクラスのサブスクライバーは、タイマーを使用して定期的にバッファをチェックし、それらが空でない場合は適切なハンドラーを呼び出します。
いくつかの欠点があります:
- 各データ型について、バッファメンバーをサブスクライバーに追加する必要があるため、よりコーディングの取り組みです。
- また、各サブスクライバーにデータが存在するため、より多くのスペースが消費されます。
SendMessage()
電話。 - また、メッセージが処理されている間に中断されるオブザーバースレッドに依存するのではなく、手動で同期する必要があります。
A -IMO-大きな利点は、タイプセーフティが優れていることです。キャストする必要はありません lParam
に応じてポインターへの値 wParam
の価値。このため、この回避策は私の元のアプローチよりも優れていなくても非常に受け入れられると思います。
他のヒント
から afxmsg_.h
:
// for Registered Windows messages
#define ON_REGISTERED_MESSAGE(nMessageVariable, memberFxn) \
{ 0xC000, 0, 0, 0, (UINT_PTR)(UINT*)(&nMessageVariable), \
/*implied 'AfxSig_lwl'*/ \
(AFX_PMSG)(AFX_PMSGW) \
(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \
(memberFxn)) },
したがって、署名はです LRESULT ClassName::FunctionName(WPARAM, LPARAM)
, 、あなたがそうです void ClassName::FunctionName(const WPARAM, const LPARAM)
. 。少なくともVS2008では、これはコンパイルしないでください。
cservercommandsubscriberクラス(ヘッダーファイル)におけるハンドルサーバーコマンド宣言は何ですか?
私にとって、これは互換性がありますね。
構文的にはそのように見えます。
私は知りません、私は他に何を探しなければなりませんか。何か案は?
はい:デバッグ設定を備えたプラグインライブラリをコンパイルし、リリースコンパイルされたアプリケーションで使用したときに同じ問題が発生しました。
基本的に、問題はスタックの腐敗のように見えます。
あなたが走っているので NotifySubscribers
別のスレッドで、使用を検討してください PostMessage
(また PostThreadMessage
) それ以外の SendMessage
.
これはクラッシュの実際の原因ではないかもしれませんが、とにかく変更を行う必要があります(使用してスレッドのコンテキストを切り替えているため SendMessage
データをまったく保護することなく。