在线程之间发送窗口消息时出现ESP错误

时间:2010-06-23 12:56:55

标签: c++ multithreading mfc crash window-messages

我有一个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
  行:42

     

ESP的价值不合适   保存在函数调用中。这是   通常是调用的结果   用一个调用声明的函数   带有函数指针的约定   用不同的召唤声明   约定。

我检查了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指针。

3 个答案:

答案 0 :(得分:2)

来自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类(在头文件中)中的HandleServerCommand声明是什么?

答案 1 :(得分:1)

  

对我来说,这看起来兼容,不是   它?

从语法上讲,它看起来就是这样。

  

我不知道,还有什么我需要看的   对于。有什么想法吗?

是的:在使用调试设置编译插件库并在发布编译的应用程序中使用时,我遇到了同样的问题。

基本上,问题看起来像堆栈损坏。

由于您在单独的帖子中运行NotifySubscribers,请考虑使用PostMessage(或PostThreadMessage)代替SendMessage

这可能不是崩溃的真正原因,但无论如何都应该进行更改(因为您使用SendMessage切换线程上下文而不保护数据。

答案 2 :(得分:1)

我最终决定在没有窗口消息的情况下这样做,现在我在这里发布我的解决方法。也许它会帮助别人。

我让观察者将数据放入同步的订阅者缓冲区,而不是让观察者向其订阅者发布窗口消息。对话框类订阅者使用计时器定期检查其缓冲区并调用适当的处理程序(如果它们不为空) 有一些缺点:

  • 编码工作量更大,因为对于每种数据类型,都需要将缓冲区成员添加到订阅者。
  • 它也占用更多空间,因为每个订阅者都有数据,而SendMessage()来电期间不存在一次。
  • 还必须手动执行同步,而不是在处理消息时依赖于暂停的观察者线程。
< - > A - IMO - 巨大的优势在于它具有更好的类型安全性。根据{{​​1}}的值,不必将一些lParam值转换为指针。因此,我认为这种解决方法即使不优于我原来的方法也是可以接受的。