Перечисление работающих процессов
Временами возникает необходимость знать какие другие программы запущены на PC. Может быть вы захотите завершить работу, или может быть запустить задачу если она еще не запущена. Обычно вы можете это сделать путем перечисления всех окон верхнего уровня, но если вы смотрите программы которые не имеют окна - может быть это что-то работающее в панели задач - то вы должны придумать что-нибудь другое.
Проблема возникает из за того что методы перечисления запущенных процессов существенно разные под WinNT и Win95/98/2000. Windows NT использует Process Status API (PSAPI.DLL), который недоступен под 95/98/2000. Они используют библиотеку Toolhelp (TOOLHELP.DLL), которой нет под Windows NT.
Windows NT
Для перечисления процессов под NT вы должны использовать функцию EnumProcesses. Ей нужно передать указатель на массив из DWORDS и переменную, сообщающую ей размер массива, и она возвратить массив заполненный идентификаторами процесса (PID) и число действительно использованных элементов. Проблема в том что, если вы не передали достаточно большой массив (например, при 5-и работающих процессах и массиве только из 4-х элементов) она не скажет вам что массив слишком мал. Вы получите только 4 PID. Выдержка из MSDN:
"Хорошая идея передавать EnumProcesses большой массив DWORD, потому что сложно предугадать как много процессов будет на момент вызова EnumProcesses"
Читая между строк, вы просто декларируете достаточно большой массив - я использую массив из 1024 элементов. Если запущено более 1024 процесса, то это ужасно занятая машина.
Когда вы получили массив с заполненными PID, то, вероятно, вам захочется узнать имена процессов. Для того чтобы сделать это нужно вызвать EnumProcessModules и GetModuleBaseName. EnumProcessModules перечисляет все модули процесса, а GetModuleBaseName просто возвращает имя модуля. Фокус в том что для EnumProcessModules нужен хендл процесса, который EnumProcesses не дает. Вы должны вызвать OpenProcess с доступом PROCESS_VM_READ и PROCESS_QUERY_INFORMATION, и использовать возвращаемый хендл при вызове EnumProcessModules.
Если все прототипы и типы данных декларированы, то результирующий код будет выглядеть примерно так:
Loc:cbNeeded = Size(Loc:PID)
If EnumProcesses(Address(Loc:PID),Loc:cbNeeded,Address(Loc:cbUsed)) = 0
! Fail
Message('Fail')
Else
Loop x# = 1 To Loc:cbUsed
Loc:HandleToProcess = OpenProcess(PROCESS_VM_READ+PROCESS_QUERY_INFORMATION,False,Loc:PID[ x# ])
If Loc:HandleToProcess
If EnumProcessModules(Loc:HandleToProcess,Address(Loc:Module),Size(Loc:Module),Address(Loc:cbNeeded))
If GetModuleBaseName(Loc:HandleToProcess,Loc:Module,Address(Loc:ModuleName),Size(Loc:ModuleName))
ProcessQ:PID = Loc:PID[ x# ]
ProcessQ:FullPath = Loc:ModuleName
Add(ProcessQ)
End
End
CloseHandle(Loc:HandleToProcess)
End
End
End
Windows 95 / 98 / 2000
Под Windows 9x/2000 вам нужно использовать TOOLHELP.DLL, который не доступен под Windows NT. Для получения списка процессов сначала нужно сделать "мгновенный снимок" (snapshot) системы (используя CreateToolhelp32Snapshot ), сообщив ему какую информацию нужно включать (IE processes) в снимок. После создания снимка, используйте Process32First для получения первого процесса снимка, и затем Process32Next для 'прошагивания' через весь список процессов до его конца. Как вы можите видеть ниже, код довольно прост. Единственно, вы должны заполнить размер в структуре PROCESSENTRY32 перед вызовом Process32First и Process32Next:
hdl = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0)
If hdl = -1 Then
Message('Failed to create process snapshot','Error!')
Else
pe32:dwsize = size(pe32)
If Process32First(hdl, address(pe32) )
! Managed to get first process
Loop
pe32:dwsize = size(pe32)
If Process32Next(hdl, address(pe32) )
! Managed to get subsequent processes
Else
Break
End
End
End
If CloseHandle(hdl) = 0 Then
Message('Error ' & GetLastError() & ' closing process snapshot handle','Error!')
End
End
Соединим все вместе
Так как разные операционные системы используют два разных DLL, вы не можете статически слинковать их как для обычных приложений. Чтобы сделать эту работу, мы сначала определим под какой ОС мы работаем, и затем динамически вызовем соответствующий DLL. Я достигаю это путем вызова GetVersionEx и смотря какие значения содержатся в структуре OSVERSIONINFO:
OVI:dwOSVersionInfoSize = Size(OVI)
If GetVersionEx(Address(OVI)) = 0
Message('Unable to determine Windows version','Error!')
Else
Case OVI:dwPlatformId
Of VER_PLATFORM_WIN32S
Loc:OSType = 0
Message('Unable to run under Win32s - get a real OS','Error!')
Of VER_PLATFORM_WIN32_WINDOWS
Loc:OSType = 1
Of VER_PLATFORM_WIN32_NT
If OVI:dwMajorVersion = 5
Loc:OSType = 1 ! Windows 2000
Else
Loc:OSType = 2 ! Windows NT
End
Else
Message('Unable to determine which version of Windows you have','Error!')
End
End
После того как мы определили под какой ОС мы работаем, можно использовать LoadLibrary и GetProcAddress для динамической загрузки DLL и вызова нужной процедуры. Загрузить DLL и получить хендл легко, как показано ниже:
Loc:DLLName = 'PLISTNT.DLL'
Loc:DllNamePtr = Address(Loc:DLLName)
Loc:DLLInstance = LoadLibrary( Loc:DllNamePtr )
If Loc:DLLInstance = 0 Then
Message('Unable to find PLISTNT.DLL','Error!')
Else
Loc:ProcName = 'ViewProcessQ@F'
Loc:ProcNamePtr = Address(Loc:ProcName)
Loc:ProcPtr = GetProcAddress(Loc:DLLInstance,Loc:ProcNamePtr)
End
Главная проблема, которую мы встретили в том, что Clarion не может легко выполнить код по заданному адресу. Самый легкий путь преодоления этой проблемы, это написать простую функцию на C, которая делает это. Мой C код не самый лучший в мире, что то наверное плохо, но он работает:
long CallAddr(long procaddress)
{
DllFunc fpFuncAddress;
fpFuncAddress = procaddress;
if (fpFuncAddress != 0)
{
(*fpFuncAddress)();
return 0;
}
return 1;
}
Все что теперь осталось - это вызвать функцию CallAddr, передавая передав ей хендл процедуры, полученный нами ранше, и освободить DLL:
If Loc:ProcPtr = 0
Message('Unable to find entry point in DLL','Error!')
Else
CallAddr(Loc:ProcPtr)
x# = FreeLibrary(Loc:DLLInstance)
End
Back to my home page, or send me mail at paula@attglobal.net
Last update 15Dec99