Все не так безоблачно для Асинхронного COM. Использование асинхронного COM было предложено Microsoft при выпуске Windows 2000 и предназначалось для решения одной из проблем при использовании классического COM — подвисание клиента во время ожидания ответа от сервера. Последний может как долго обрабатывать запрос, так и вообще находиться в не рабочем состоянии, что приведет к повисанию клиента.
Все было здорово до момента выпуска Windows Vista. Потом начались проблемы…
Визуально стало наблюдаться странное поведение приложений, использующих асинхронный COM. Причем сбои проявлялись совершенно непредсказуемо. Иногда приложение просто не стартует, иногда вызывает недопустимую операцию и т.д. И только в результате долгой и мучительной работы с отладчиком выяснилось, что проблема — в OLE32.dll. Последняя стала перезаписывать память.
Поиск проблемы
Предлагаю вашему вниманию участок кода функции CAsyncUnknownMgr::IBegin_QueryMultipleInterfaces
xor edi,edi ... lea ecx,[ebp-8] push ecx push edi push 0Ch xor eax,eax push eax push ebx call __allmul push edx push eax call ULongLongToUInt cmp eax,edi push eax jge @f ... @@: push edi push dword ptr [g_hHeap] call dword ptr [pfnHeapAlloc]
Если написать это на C, то участок кода будет выглядеть таким образом:
int cb; void *p; UINT nBlockSize; ULONGLONG ullVar; ... cb = ULongLongToUInt(12 * ullVar, &nBlockSize); p = pfnHeapAlloc(hHeap, 0, cb);
Напомню вам прототипы функций HeapAlloc и ULongLongToUInt:
HRESULT ULongLongToUInt(ULONGLONG ullOperand, UINT *puiResult); LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, DWORD dwBytes);
Таким образом, видно, что CAsyncUnknownMgr::IBegin_QueryMultipleInterfaces передает в HeapAlloc в качестве размера блока значение, возвращенное из ULongLongToUInt. А так как ULongLongToUInt возвращает HRESULT а возвращаемый статус тут всегда S_OK, то всегда запрашивается выделение блока нулевого размера. Далее код CAsyncUnknownMgr::IBegin_QueryMultipleInterfaces копирует в этот блок nBlockSize байт данных, что приводит к перезаписываю памяти и, в итоге, к краху приложения в недалеком будущем.
О чем говорит тот факт, что эта проблема была не только пропущена при тестировании и выпуске Windows Vista, но и не исправлена в Windows 7? Вероятно, о том, что сам Microsoft почти не использует асинхронный COM. Либо не использует его в упрощенном виде (см. следующий параграф) без асинхронных QueryInterface. Думаю, что последнее.
Что делать?
Как я уже упоминал, приложения используют асинхронный COM для того, что бы избавиться от подвисания в момент вызова сервера. Повиснуть может не только вызов COM метода, но и попытка запросить интерфейс у сервера. Другими словами, речь об использовании AsyncIUnknown::QueryInterface. Именно с этим проблема. Что интересно, в Windows Vista появился баг с перезаписыванием памяти в результате вызова AsyncIUnknown::QueryInterface, а в Windows 7 вызов AsyncIUnknown::QueryInterface был просто заблокирован (если кому-то интересно, то были внесены соответствующие изменения в NdrpCloneInOnlyCorrArgs). Не исключено, что заблокировано было из-за жутких проявлений IBegin_QueryMultipleInterfaces, суть которых для Microsoft осталась загадкой.
Поскольку наш программный продукт использовался под Windows 2000, XP и по прежнему нуждается в полном использовании асинхронного COM, был написан код по модифицированию кода ole32.dll в момент запуска наших компонентов. При этом была использована технология, описанная мной в статье «Самомодифицирующиеся программы».