在线程之间发送窗口消息时出现 ESP 错误
我有一个 Observer 类和一个 Subscriber 类。
出于测试目的,观察者创建一个线程来生成假消息并调用 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));
}
}
订阅者是一个 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 的值不正确 通过函数调用保存。这是 通常是调用的结果 通过一次调用声明的函数 函数指针约定 用不同的调用声明 约定。
我检查了 AfxBeginThread() 想要的函数指针:
typedef UINT (AFX_CDECL *AFX_THREADPROC)(LPVOID); // AFXWIN.H
static UINT AFX_CDECL MessageGeneratorThread(LPVOID pParam); // My thread function
对我来说,这看起来是兼容的,不是吗?
我不知道,我还需要寻找什么。有什么想法吗?
我做了另一个奇怪的观察,可能与之相关: 在 NotifySubscribers
方法中,我调用 IsWindow()
来检查句柄指向的窗口是否存在。显然确实如此。但调用 CWnd::FromHandlePermanent() 会返回 NULL 指针。
I have an Observer class and a Subscriber class.
For testing purposes, the observer creates a thread that generates fake messages and calls CServerCommandObserver::NotifySubscribers()
, which looks like this:
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));
}
}
The subscriber is a CDialog
derived class, that also inherits from CServerCommandSubscriber
.
In the derived class, I added a message map entry, that routes server commands to the subscriber class handler.
// 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 // ...
};
}
The problem is, that I see strange crashes in the HandleServerCommand() method:
It looks something like this:
Debug Error!
Program: c:\myprogram.exe
Module:
File: i386\chkesp.c
Line: 42The value of ESP was not properly
saved across a function call. This is
usually the result of calling a
function declared with one calling
convention with a function pointer
declared with a different calling
convention.
I checked the function pointer that AfxBeginThread() wants to have:
typedef UINT (AFX_CDECL *AFX_THREADPROC)(LPVOID); // AFXWIN.H
static UINT AFX_CDECL MessageGeneratorThread(LPVOID pParam); // My thread function
To me, this looks compatible, isn't it?
I don't know, what else I have to look for. Any ideas?
I made another strange observation, that might be related:
In the NotifySubscribers
method, I call IsWindow()
to check if the window to which the handle points, exists. Apparently it does. But calling CWnd::FromHandlePermanent()
returns a NULL pointer.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
来自
afxmsg_.h
:所以签名是
LRESULT ClassName::FunctionName(WPARAM, LPARAM)
,而你的签名是void ClassName::FunctionName(const WPARAM, const LPARAM)。这不应该编译,至少在 VS2008 下不能编译。
CServerCommandSubscriber 类(在头文件中)中的 HandleServerCommand 声明是什么?
From
afxmsg_.h
:So the signature is
LRESULT ClassName::FunctionName(WPARAM, LPARAM)
, while yours isvoid ClassName::FunctionName(const WPARAM, const LPARAM)
. This should not compile, at least under VS2008 it doesn't.What is your HandleServerCommand declaration in the CServerCommandSubscriber class (in the header file)?
从语法上看是这样的。
是的:在使用调试设置编译插件库并在发布编译的应用程序中使用时,我遇到了同样的问题。
基本上,问题看起来像是堆栈损坏。
由于您在单独的线程中运行
NotifySubscribers
,请考虑使用PostMessage
(或PostThreadMessage
)而不是SendMessage
。这可能不是崩溃的实际原因,但无论如何都应该进行更改(因为您正在使用
SendMessage
切换线程上下文,而没有任何数据保护。Syntactically it looks that way.
Yes: I've had the same problem when compiling a plugin library with debug settings and used in a Release-compiled application.
Basically, the problem looks like a stack corruption.
Since you're running
NotifySubscribers
in a separate thread, consider usingPostMessage
(orPostThreadMessage
) instead ofSendMessage
.This may not be the actual cause of the crash, but the change should be made anyway (as you're switching threading contexts by using
SendMessage
with no guarding of the data whatsoever.我最终决定不使用窗口消息,现在在这里发布我的解决方法。也许它会帮助别人。
我没有让观察者向其订阅者发送窗口消息,而是让观察者将数据放入同步订阅者缓冲区中。对话框类订阅者使用计时器定期检查其缓冲区,并在缓冲区不为空时调用适当的处理程序。
有一些缺点:
SendMessage()
调用期间存在一次。A - IMO - 巨大的优势是它具有更好的类型安全性。不必根据
wParam
的值将某些lParam
值转换为指针。因此,我认为这种解决方法即使不优于我原来的方法,也是非常可以接受的。I eventually decided to do it without window messages and am now posting my workaround here. Maybe it will help someone else.
Instead of letting the observer post window messages to its subscribers, I let the observer put data into synchronized subscriber buffers. The dialog class subscriber uses a timer to periodically check its buffers and call the apropriate handlers if those aren't empty.
There are some disadvantages:
SendMessage()
call.A - IMO - huge advantage is that it has better type-safety. One doesn't have to cast some
lParam
values into pointers depending onwParam
's value. Because of this, I think this workaround is very acceptable if not even superior to my original approach.