Перечисление работающих процессов


Введение

Временами возникает необходимость знать какие другие программы запущены на 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

Download

PROCVIEW.ZIP (578K)


Back to my home page, or send me mail at paula@attglobal.net

Last update 15Dec99