WM_PAINT
во время ожидания вызова COM-сервера.
Как написано в статье «Do you receive WM_PAINT when waiting for a COM call to return?», основной причиной для этого стала необходимость в правильной работе UAC (контроль учётных записей пользователей).
Если приложение не рассчитано на такое поведение, то это может привести к ошибкам.Например, я в своих приложениях часто использую компонент TVirtualStringTree, который запрашивает данные именно во время обработки сообщения
WM_PAINT
.
Получать все данные с удаленного сервера нерационально и довольно долго, компонент TVirtualStringTree для того и используется, чтобы получался только необходимый минимум данных, и тогда, когда в них возникнет необходимость.
Проблема с моим кодом была в том, что одно соединение использовалось для работы с несколькими базами данных, и для получения данных выполнялось два действия: подключение к базе данных и выборка данных из нее.
При этом во время первого вызова ConnectToDB приходило сообщение WM_PAINT
и выполнялся вызов ConnectToDB уже для подключения к другой базе. Во второй раз данные запрашивались из неправильной базы данных и возникала ошибка.
Есть несколько вариантов решения проблемы:
- Не обращаться к серверу из обработчика
WM_PAINT
. Помечать флагами узлы дерева, которые необходимо загрузить и отправлять специальное сообщение с помощьюPostMessage
, чтобы данные были загружены позже. - Официальный способ. Используя Application Compatibility Toolkit создать для приложения SDB-файл с флагом
DisableNewWMPAINTDispatchInOLE
и при установке приложения регистрировать SDB-файл в системе. Как это сделать написано в упомянутой выше статье «Do you receive WM_PAINT when waiting for a COM call to return?». - Приложение может самостоятельно при запуске установить флаг
DisableNewWMPAINTDispatchInOLE
. Проблема здесь только в том, что это недокументированная возможность и она может измениться в следующих версиях Winodws.
Скачав и установив Windows Symbol Packages, загрузил приложение в отладчик. В недрах ole32.dll был найден код функции _DisableNewWM_PAINTDispatch.
function _DisableNewWM_PAINTDispatch: LongBool; asm mov eax,dword ptr fs:[00000018h]// Thread Environment Block mov eax,dword ptr [eax+30h] // Process Environment Block mov eax,dword ptr [eax+1D8h] // AppCompatFlags and eax,100000h end;Немного изменив код, получаем необходимую процедуру.
procedure DisableNewWMPaintDispatch; asm mov eax,dword ptr fs:[00000018h] mov eax,dword ptr [eax+30h] mov edx,dword ptr [eax+1D8h] or edx,100000h mov dword ptr [eax+1D8h],edx end; initialization if CheckWin32Version(6) then DisableNewWMPaintDispatch();
Для получения указателя на информацию о потоке можно было бы использовать функцию
NtCurrentTeb
вместо mov eax,dword ptr fs:[18h]
, но это вряд ли добавит совместимости с будущими версиями Windows, так как структуры TEB и PEB не документированы, и в Windows SDK про них явно написано:
«The PEB and TEB structures are subject to changes between Windows releases, thus the fields offsets may change as well as the Reserved fields».
Код проверялся в Windows 7 и Windows 8. Дополнительно можно почитать комментарии к статье MSDN «IMessageFilter::MessagePending method (COM)» и топик «DCOM in Vista specifically processing WM_PAINT messages» в форумах MSDN.
Как вариант - проверка рекурсивности вызова обработчика WM_PAINT.
ОтветитьУдалить