Ошибка ESP при отправке оконных сообщений между потоками

У меня есть класс Observer и класс подписчика.
В целях тестирования наблюдатель создает поток, который генерирует поддельные сообщения и вызовы. 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 ответа

Решение

В конце концов я решил сделать это без оконных сообщений и сейчас публикую свое решение здесь. Может быть, это поможет кому-то еще.

Вместо того, чтобы позволить наблюдателю публиковать оконные сообщения своим подписчикам, я позволяю наблюдателю помещать данные в синхронизированные абонентские буферы. Подписчик класса диалога использует таймер для периодической проверки своих буферов и вызова соответствующих обработчиков, если они не пусты.
Есть некоторые недостатки:

  • Это больше усилий по кодированию, потому что для каждого типа данных необходимо добавить элемент буфера к подписчику.
  • Это также занимает больше места, так как данные существуют для каждого абонента, а не только один раз во время SendMessage() вызов.
  • Также необходимо выполнить синхронизацию вручную, а не полагаться на приостановку потока наблюдателя во время обработки сообщений.

A - IMO - огромное преимущество в том, что он имеет лучшую безопасность типов. Не нужно бросать некоторые lParam значения в указатели в зависимости от wParamценность. Поэтому я думаю, что этот обходной путь очень приемлем, если даже не превосходит мой первоначальный подход.

От 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 это не так.

Какова ваша декларация HandleServerCommand в классе CServerCommandSubscriber (в файле заголовка)?

Для меня это выглядит совместимым, не так ли?

Синтаксически это выглядит так.

Я не знаю, что еще мне нужно искать. Есть идеи?

Да: у меня была такая же проблема при компиляции библиотеки плагинов с настройками отладки, которая использовалась в скомпилированном с релизом приложении

По сути, проблема выглядит как повреждение стека.

Так как ты бежишь NotifySubscribers в отдельном обсуждении рассмотрите возможность использования PostMessage (или же PostThreadMessage) вместо SendMessage,

Это не может быть реальной причиной сбоя, но изменения должны быть внесены в любом случае (так как вы переключаете контексты потоков с помощью SendMessage без какой-либо защиты данных.

Другие вопросы по тегам