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

发布于 2024-09-06 09:51:21 字数 1980 浏览 13 评论 0原文

我有一个 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 指针。

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: 42

The 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 NotifySubscribersmethod, 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

ζ澈沫 2024-09-13 09:51:22

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

From 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)) },

So the signature is LRESULT ClassName::FunctionName(WPARAM, LPARAM), while yours is void 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)?

罪歌 2024-09-13 09:51:22

对我来说,这看起来兼容,但事实并非如此
是吗?

从语法上看是这样的。

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

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

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

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

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

To me, this looks compatible, isn't
it?

Syntactically it looks that way.

I don't know, what else I have to look
for. Any ideas?

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 using PostMessage (or PostThreadMessage) instead of SendMessage.

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.

伤感在游骋 2024-09-13 09:51:22

我最终决定不使用窗口消息,现在在这里发布我的解决方法。也许它会帮助别人。

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

  • 它需要更多的编码工作,因为对于每种数据类型,都需要将缓冲区成员添加到订阅者。
  • 它还消耗更多空间,因为每个订阅者都存在数据,而不仅仅是在 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:

  • It's more coding effort because for each data type, a buffer member needs to be added to the subscriber.
  • It's also more space consuming, as the data exists for each subscriber and not just once during the SendMessage() call.
  • One also has to do the synchronization manually instead of relying on the observer thread being suspended while the messages are handled.

A - IMO - huge advantage is that it has better type-safety. One doesn't have to cast some lParam values into pointers depending on wParam's value. Because of this, I think this workaround is very acceptable if not even superior to my original approach.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文