20 de outubro de 2009

Obtendo informações sobre processos em execução

Há certas situações quando se constrói um programa onde é preciso interagir com outros programas que também estejam executando no mesmo computador. Embora não seja obrigatório, quase sempre a interação ocorre com executáveis que o próprio programa colocou no ar, tipicamente para observar se o outro programa ainda está em execução e está responsivo ou, no caso de não estar mais em execução, qual foi o código retornado por ele. Podemos, então, usar esse código para determinar se o programa em questão terminou com sucesso ou se encontrou alguma situação que tenha gerado erro.

A Microsoft disponibiliza como parte da API do Windows uma série de funções para se trabalhar com processos neste Sistema Operacional. Vou mostrar como usar algumas delas neste texto.

Há uma função em especial que permite listar todos os processos que estão em execução na máquina. Ela chama-se EnumProcesses e aceita 3 parâmetros, como mostrado na declaração C++ abaixo (extraída do fonte PSAPI.H):
BOOL WINAPI EnumProcesses (DWORD *pProcessIds, DWORD cb, DWORD *pBytesReturned);
Caso você não tenha o arquivo que contem essa declaração, pode mapear manualmente a função a partir da biblioteca PSAPI.DLL, como mostrei no post sobre carga dinâmica de bibliotecas em Delphi.

O primeiro parâmetro dessa função é um array onde são retornados os identificadores dos processos existentes. Note que os valores retornados são identificadores e não Handles para os processos. O parâmetro seguinte é o tamanho em bytes do array que você passou no primeiro parâmetro, enquanto o último parâmetro retorna quantos bytes foram preenchidos no array. Assim, a quantidade de identificadores retornados pode ser calculada divindo o número de bytes retornados pelo tamanho em bytes de cada identificador, representado num DWORD (double word, como mostra a declaração da função).

O trecho de código que segue mostra em C++ o uso típico do EnumProcesses. Em outras linguagens, a sequência de passos será a mesma, bastando respeitar a sintaxe da linguagem.
BOOL ret;
DWORD lProcess[1024], lCount, lNeed, lIndex;
lCount = 1024;
ret = EnumProcesses(lProcess, lCount*sizeof(DWORD), &lNeed);

if (ret)
{
lIndex = 0;
lCount = lNeed / sizeof(DWORD);
while ( lIndex < lCount )
{
/* Use o identificador de cada processo aqui para fazer o tratamento desejado */
lIndex++;
}
}
return ret;

Como não é possível determinar de antemão quantos identificadores serão retornados, recomenda-se criar um array grande. No exemplo, criei o array com capacidade para 1024 identificadores e uso o retorno da função para calcular quantos de fato foram retornados.

A maior parte das funções da API para processos exige um handle para o processo que se quer acessar. Se foi você mesmo quem iniciou o processo através de uma chamada à função CreateProcess, ela já te retorna o handle necessário numa estrutura PROCESS_INFORMATION. No entanto, quando você lista os processos através do EnumProcesses, é preciso converter o identificador de processo que é retornado para um handle associado ao processo. Essa conversão é obtida através de uma chamada a OpenProcess. Usando o array retornado no código acima, teremos a seguinte linha:
HANDLE hProc;
hProc = OpenProcess (PROCESS_QUERY_INFORMATION, false, lProcess[lIndex]);

Nesta chamada, PROCESS_QUERY_INFORMATION indica a permissão desejada para acessar o processo. O valor que passei informa ao Handle do processo minha intenção de usá-lo para obter outras informações sobre esse processo. Outros valores válidos e seus significados podem ser encontrados aqui.

A forma como usei o OpenProcess acima me permite agora obter informações sobre cada processo através da função NtQueryInformationProcess. Dentre as informações possíveis, destaco o nome do executável e os parâmetros que foram usados para chamá-lo, isto é, consigo obter toda a linha de comando para cada processo.

Passando os parâmetros apropriados ao OpenProcess, poderia até mesmo interromper a execução de um processo (através de uma chamada a TerminateProcess) ou descobrir o código retornado pelo processo quando sua execução foi finalizada (chamando GetExitCodeProcess).

Nenhum comentário :

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.