Ошибка ESP при отправке оконных сообщений между потоками - PullRequest
0 голосов
/ 23 июня 2010

У меня есть класс 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 ]

2 голосов
/ 23 июня 2010

С 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 (в заголовочном файле)?

1 голос
/ 23 июня 2010

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

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

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

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

1 голос
/ 23 июня 2010

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

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

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

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

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

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

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

...