代码之家  ›  专栏  ›  技术社区  ›  foraidt

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

  •  0
  • foraidt  · 技术社区  · 14 年前

    我有一个观察者类和一个订阅者类。
    为了测试的目的,观察者创建了一个线程来生成假消息和调用。 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() 返回空指针。

    3 回复  |  直到 14 年前
        1
  •  2
  •   Aoi Karasu    14 年前

    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声明是什么?

        2
  •  1
  •   utnapistim    14 年前

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

    从句法上看是这样的。

    我不知道,我还要看什么 为了。有什么想法吗?

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

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

    因为你在跑步 NotifySubscribers 在单独的线程中,考虑使用 PostMessage (或) PostThreadMessage )而不是 SendMessage .

    这可能不是导致崩溃的实际原因,但无论如何都应该进行更改(因为您使用 发送消息 没有任何数据保护。

        3
  •  1
  •   foraidt    14 年前

    我最终决定不发窗口消息就这么做,现在我在这里发布我的工作区。也许它能帮助别人。

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

    • 因为对于每种数据类型,都需要向订阅服务器添加一个缓冲区成员,所以需要进行更多的编码工作。
    • 它也更占用空间,因为数据存在于每个订阅服务器上,而不仅仅是在 SendMessage() 打电话。
    • 还必须手动执行同步,而不是依赖于在处理消息时挂起的观察者线程。

    IMO最大的优点是它具有更好的类型安全性。一个不需要铸造一些 lParam 指针值取决于 wParam 的价值。因此,我认为这种变通方法是可以接受的,即使比我原来的方法还差。