Ошибка 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
без какой-либо защиты данных.