0

我有一个观察者类和一个订阅者类。
出于测试目的,观察者创建了一个生成虚假消息和调用的线程,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 指针。

4

3 回答 3

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

于 2010-06-23T14:21:21.127 回答
1

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

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

我不知道,我还需要寻找什么。有任何想法吗?

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

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

由于您NotifySubscribers在单独的线程中运行,请考虑使用PostMessage(or PostThreadMessage) 而不是SendMessage.

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

于 2010-06-23T13:47:24.980 回答
1

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

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

  • 这是更多的编码工作,因为对于每种数据类型,都需要将缓冲区成员添加到订阅者。
  • 它也更占用空间,因为数据存在于每个订阅者中,而不仅仅是在SendMessage()通话期间存在一次。
  • 还必须手动进行同步,而不是依赖于在处理消息时暂停观察者线程。

A - IMO - 巨大的优势是它具有更好的类型安全性。不必lParam根据wParam的值将某些值转换为指针。因此,我认为这种解决方法即使不优于我原来的方法也是非常可接受的。

于 2010-06-23T16:41:49.890 回答