Замена WaitForInputIdle() для консольных процессов
Известно, что после создания процесса в Windows с помощью CreateProcess поток не ожидает его инициализации, и управление возвращается сразу. Для того, чтобы дождаться инициализации, многие используют функцию WaitForInputIdle, но она не умеет работать с консольными процессами и процессами без очереди сообщений:
If this process is a console application or does not have a message queue, WaitForInputIdle returns immediately.
Я решил эту проблему с помощью Native API. В расширенной версии структуры PEB имеются два поля, Ldr и LoaderLock, заполняемые загрузчиком PE в процессе инициализации. Таким образом, для полноценной проверки нам достаточно определить адрес PEB в другом процессе с помощью Native API NtQueryInformationProcess, считать структуру PEB, используя ReadProcessMemory, и проверить оба поля на NULL. В случае, если какое-либо из полей равно нулю, загрузка PE ещё не завершена; иначе, процесс уже инициализирован и загрузчиком передано управление на точку входа.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// // IsProcessLoaderInited() // проверка на инициализацию процесса // hProcess - процесс // BOOL IsProcessLoaderInited( HANDLE hProcess ) { PPEB lpPEB = NULL; PEB PEBData = { 0 }; PROCESS_BASIC_INFORMATION ProcessInfo = { 0 }; static PNtQueryInformationProcess _NtQueryInformationProcess = NULL; // получим адрес функции, если этого не было сделано ранее if(!_NtQueryInformationProcess) _NtQueryInformationProcess = (PNtQueryInformationProcess)GetProcAddress( GetModuleHandle("ntdll.dll"), "NtQueryInformationProcess"); // получаем информацию о процессе if(0 != _NtQueryInformationProcess( hProcess, ProcessBasicInformation, &ProcessInfo, sizeof(ProcessInfo), NULL) ) return FALSE; // запишем адрес PEB lpPEB = ProcessInfo.PebBaseAddress; // прочитаем PEB if(!ReadProcessMemory(hProcess, lpPEB, &PEBData, sizeof(PEBData), NULL)) return FALSE; // если Ldr или LoaderLock равны нулю, значит, процесс еще не инициализирован if(PEBData.Ldr == NULL || PEBData.LoaderLock == NULL) return FALSE; // процесс инициализирован return TRUE; } |
Теперь можно написать функцию ожидания. Вот мой вариант (возвращает 0 в случае успешного ожидания и WAIT_TIMEOUT, если время ожидания истекло):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// // WaitForProcessInit() // ожидаем инициализации процесса // hProcess - процесс // dwTime - время ожидания в миллисекундах // DWORD WaitForProcessInit( HANDLE hProcess, DWORD dwTime ) { DWORD dwTickCount = 0; // запишем текущее значение счетчика dwTickCount = GetTickCount(); // проверка в цикле, пока не истечет таймаут while(GetTickCount() - dwTickCount < dwTime) { // если процесс инициализирован if(IsProcessLoaderInited(hProcess)) return 0; // ожидаем Sleep(1); } // процесс не инициализирован - таймаут вышел return WAIT_TIMEOUT; } |
Работает стабильно. Один из комментаторов в MSDN указывает на возможные проблемы с WOW64, но я никаких проблем пока не обнаружил.