Tutoriais e Programação

Linguagem C - Projeto Espião III

Dom

30

Nov

2008


21:20

  • Imprimir
(1 voto de 5.00) 


C

Se você seguiu os tutoriais anteriores, então seu programa espião.exe já deve estar funcionando. Mas está meio tímido, porque não fala com o usuário - só mostra o menu e, quando solicitado, a árvore com as janelas abertas no momento da solicitação. Calado demais, este nosso espião. Neste tutorial vamos fazer com que entregue e ouro wink

Quem é quem no pedaço

Nosso projeto inicial incluía informações sobre as janelas na barra de status. Uma das formas de pedir informações é clicando num dos itens da árvore. Mas, como saber quando o usuário clica um item?

O controle de árvore, assim como muitos outros controles, envia mensagens do tipo WM_NOTIFY para nossa central de mensagens toda vez que um evento acontece, ou seja, com qualquer coisinha o controle grita. É justamente esta mensagem que precisamos interceptar com o auxílio do caso WM_NOTIFY (linhas 11 e 12):

LRESULT CALLBACK MainWndProc( HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) { static HWND hwndTree; RECT rc, rcStatus; switch (msg) { case WM_CREATE: hwndTree = janelaTree(hwnd,IDJANELATREE); break; case WM_NOTIFY: return TrataWmNotify(hwnd,wParam,lParam); ...

Fiéis ao estilo "top-down", chamamos a função TrataWmNotify, a qual deverá fazer o serviço final de espionagem: obter informações precisas sobre a janela indicada pelo usuário.

Catando gritos

O código da função é um tantinho mais complicado que os anteriores, mas não é nada que assuste. Veja abaixo:

LRESULT TrataWmNotify(HWND hwnd, WPARAM wParam, LPARAM lParam) { NMHDR *apontaGrito; TV_HITTESTINFO testaClique; HWND hTree = GetDlgItem(hwnd,IDJANELATREE); HTREEITEM hti; HWND hwndAlvo; apontaGrito = (NMHDR *)lParam; switch (apontaGrito->code) { case NM_CLICK: memset(&testaClique,0,sizeof(TV_HITTESTINFO)); GetCursorPos(&testaClique.pt); MapWindowPoints(HWND_DESKTOP,hTree,&testaClique.pt,1); hti = TreeView_HitTest(hTree,&testaClique); if (hti == (HTREEITEM)0) break; hwndAlvo = PegaItemInfo(hTree,hti); PoeTextoStatus(hwnd,hwndAlvo); break; } return DefWindowProc(hwnd,WM_NOTIFY,wParam,lParam); }

De todos os gritos que o controle de árvore possa dar, o único que nos interessa é o NM_CLICK. Como este controle é muito nervoso (por ser muito complexo), a quantidade de gritos que ele é capaz de dar é muito grande. O tipo de grito dado é sempre armazenado no parâmetro lParam da mensagem WM_NOTIFY. Este parâmetro nada mais é do que um ponteiro indicando o endereço de memória de uma estrutura chamada de NMHDR:

typedef struct tagNMHDR { HWND hwndFrom; UINT idFrom; UINT code; } NMHDR;

O campo HWND hwndFrom contém o manipulador da janela que está gritando (ou enviando a mensagem, como queira), o campo UINT idFrom contém o identificador do controle e o campo UINT code contém o código do grito. Existem oito tipos de grito:

  • NM_CLICK: o controle foi clicado com o botão esquerdo do mouse
  • NM_DBLCLK: o controle recebeu um duplo clique com o botão esquerdo do mouse
  • NM_KILLFOCUS: o controle perdeu o foco de entrada
  • NM_OUTOFMEMORY: o controle não pode completar uma operação por falta de memória (dá-lhe gynko biloba smile )
  • NM_RCLICK: o controle recebeu um clique com o botão direito do mouse
  • NM_RDCLICK: o controle recebeu um duplo clique com o botão direito do mouse
  • NM_RETURN: o controle tem o foco e o usuário digitou a tecla Enter
  • NM_SETFOCUS: o controle recebeu o foco

Selecionando gritos

Como já disse, o único grito que nos interessa é o NM_CLICK, mas não todos os NM_CLICKs. Se o usuário clicar sobre um item, sim; se clicar na janela do controle, mas não sobre um item, não! Então, vamos por partes:

  1. Sabendo que vamos precisar de um ponteiro que nos indique o endereço da estrutura NMHDR, manisfestamos a variável *apontaGrito do tipo NMHDR. Mais abaixo, definimos apontaGrito como o ponteiro para a estrutura do tipo MNHDR do parâmetro lParam (apontaGrito = (NMHDR *)lParam;).
  2. Tendo como acessar a estrutura NMHDR, usamos um switch apontando para o campo code da estrutura para interceptar o caso NM_CLICK (lembre-se que ponteiros usam -> para referenciar um campo, e não um ponto, por isso switch (apontaGrito->code) ).
  3. No caso do switch interceptar um NM_CLICK, a primeira providência é limpar a área de memória reservada para a estrutura testaClique, do tipo TV_HITTESTINFO, que foi previamente manifestada.

Antes de prosseguir, vamos dar uma olhada na definição da estrutura TV_HITTESTINFO, usada para determinar a localização de um ponto relativo a um controle de árvore:

typedef struct _TVHITTESTINFO { POINT pt; UINT flags; HTREEITEM hItem; } TV_HITTESTINFO, FAR *LPTV_HITTESTINFO;

POINT pt contém as coordenadas-cliente do ponto testado, UINT flags recebe as informações do resultado do ponto que foi testado e HTREEITEM hItem contém o manipulador (handle) do item que ocupa o ponto. Agora podemos continuar com a análise:

  1. Chamando a função da API GetCursorPos, colocamos as coordenadas do ponto clicado na estrutura testaClique. Como precisamos passar como parâmetro o endereço do campo pt da estrutura, a chamada é feita com &testaClique.pt.
  2. A função MapWindowPoints transforma (mapeia) um conjunto de pontos de um espaço de coordenadas relativos a uma janela para um espaço de coordenadas relativos a outra janela. Se o primeiro parâmetro for NULL ou HWND_DESKTOP, os pontos serão mapeados como coordenadas de tela. O segundo parâmetro (htree) indica a janela para os quais os pontos serão mapeados. O terceiro parâmetro é o endereço onde se encontram os pontos que devem ser mapeados e o último indica quantos conjuntos de pontos existem (no caso, temos apenas 1 conjunto de coordenadas).
  3. Mapeadas as coordenadas de tela do clique, usamos a macro TreeView_HitTest que determina a localização do ponto especificado em relação à área cliente de um controle de árvore. Esta macro retorna o item ao qual o ponto pertence ou NULL se o ponto não estiver em um dos itens. Este valor de retorno é armazenado na variável hti, do tipo HTREEITEM, anteriormente manifestada.
  4. Se o valor de hti for 0 (zero), é sinal que o clique foi dado fora da área de um item da árvore. Neste caso interrompe-se o caso com break. Se hti contiver um item da árvore, então o grito do controle nos interessa.
  5. Com um NM_CLICK válido, chamamos duas funções, a PegaItemInfo e a PoeTextoStatus, das quais falaremos adiante.
  6. Finalmente passamos todas as mensagens para o procedimento padrão da janela chamando o procedimento DefWindowProc com todos os parâmetros iniciais. Isto é uma atitude não invasiva, altamente aconselhável porque o controle pode precisar das mensagens e porque queremos manter os distúrbios no menor nível possível para não afetar negativamente o ambiente.

Identificando a janela

De posse de um grito válido, precisamos identificar a janela correspondente ao item clicado e extrair algumas informações da mesma. É aí que entra a função PegaItemInfo que irá analisar a estrutura TV_ITEM, a qual contém todas as informações dos itens da árvore. Esta estrutura da API do Windows consta como:

typedef struct _TV_ITEM { UINT mask; HTREEITEM hItem; UINT state; UINT stateMask; LPSTR pszText; int cchTextMax; int iImage; int iSelectedImage; int cChildren; LPARAM lParam; } TV_ITEM, FAR *LPTV_ITEM;

Dos 10 campos desta estrutura, os que nos interessam são os campos UINT mask, HTREEITEM hItem e LPARAM lParam. O primeiro, mask, é um array de flags que especifica os atributos do item. O segundo, hItem, identifica o item ao qual esta estrutura se refere e o terceiro, lParam, é um valor de 32 bits associado ao item. Vejamos como a função PegaItemInfo usa estes campos:

static HWND PegaItemInfo(HWND hwndTree,HTREEITEM hti) { TV_ITEM tvi; memset(&tvi,0,sizeof(TV_ITEM)); tvi.mask = TVIF_PARAM; tvi.hItem = hti; TreeView_GetItem(hwndTree,&tvi); return (HWND) tvi.lParam; }
  1. Inicialmente manifestamos a variável tvi do tipo estrutura TV_ITEM.
  2. Limpamos a área de memória alocada para a estrutura tvi com memset.
  3. Definida a estrutura, atribuímos o valor TVIF_PARAM ao campo mask, indicando com isso que o campo lParam é um campo válido.
  4. Atribuímos ao campo hItem o item hti recebido como parâmetro. É o item que foi clicado.
  5. Com a macro TreeView_GetItem preenchemos a estrutura tvi com o restante dos atributos do item em questão.
  6. Retornamos o valor do campo lParam para a função chamadora. lParam contém o manipulador da janela associada ao item, por isso retornamos este valor como HWND.

Pondo informações da janela na barra de status

Se temos o manipulador da janela associada ao item clicado, temos tudo que precisamos para extrair informações a seu respeito. A função PoeTextoStatus obtém algumas das informações e as coloca na barra de status:

void PoeTextoStatus(HWND hParent,HWND hwnd) { RECT rc; HANDLE pid; char info[4096],*pNomeProcesso; GetWindowRect(hwnd,&rc); GetWindowThreadProcessId(hwnd,&pid); pNomeProcesso = NomeIDProcesso((ULONG)pid); wsprintf(info, "Handle: 0x%x %s, esq %d, topo %d, dir %d, base %d, altura %d, larg %d, Processo: %s", hwnd, IsWindowVisible(hwnd)? "Visível" : "Escondida", rc.left,rc.top,rc.right,rc.bottom, rc.bottom-rc.top, rc.right-rc.left, pNomeProcesso); UpdateStatusBar(info, 0, 0); }

O algoritmo desta função é:

  1. Obter o retângulo da janela.
  2. Obter o ID do processo da linha de execução (thread) da janela.
  3. Chamar uma subrotina para obter o nome do processo referente ao ID do processo.
  4. Formatar os dados numa string.
  5. Atualizar a barra de status.

A função UpdateStatusBar é uma função preparada pelo Wedit que podemos chamar diretamente. A função para se obter o nome do processo da linha de execução da janela conhecendo-se a ID do processo se baseia numa técnica de programação mais avançada que será descrita a seguir:

static char * NomeIDProcesso( DWORD processoID ) { static char szNomeProcesso[MAX_PATH]; HMODULE hMod; DWORD cbNeeded; HANDLE hProcesso = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processoID ); szNomeProcesso[0] = 0; if ( hProcesso ) { if ( EnumProcessModules( hProcesso, &hMod, sizeof(hMod), &cbNeeded) ) { GetModuleBaseName( hProcesso, hMod, szNomeProcesso, sizeof(szNomeProcesso) ); } CloseHandle( hProcesso ); } return szNomeProcesso; }
  1. A função da API OpenProcess retorna o manipulador (handle) de um objeto processo existente. O primeiro parâmetro é uma flag que indica o tipo de acesso desejado: PROCESS_QUERY_INFORMATION permite obter informações do objeto processo e PROCESS_VM_READ permite a leitura da memória virtual do processo. O parâmetro FALSE se refere à flag de herança do manipulador e o último é o identificador do processo.
  2. O primeiro elemento do array szNomeProcesso é zerado.
  3. Se existir um handle para o processo, avaliamos o retorno da função EnumProcessModules da API, manifestada em Psapi.h. A função cria uma lista dos módulos usados por um aplicativo. Esta lista define o conjunto de arquivos necessários para que o módulo seja carregado e executado. A função é manifestada da seguinte forma:
    BOOL EnumProcessModules( HANDLE hProcess, // manipulador do processo HMODULE* lphModule, // ponteiro para o array que recebe a // lista de manipuladores dos módulos DWORD cb, // tamanho do array lphModule LPDWORD lpcbNeeded // número de bytes necessários para // armazenar todos os manipuladores dos // módulos no array lphModule );
    Pelo cuidado que se tem com o tamanho do array fica claro que é preciso tomar cuidado para que seu espaço não estoure devido a um excesso de manipuladores.
  4. Se EnumProcessModules retornar verdadeiro, chamamos a função, também da API Psapi, GetModuleBaseName, que retorna o nome básico de um módulo no terceiro parâmetro (szNomeProcesso).
  5. Fechamos o manipulador com CloseHandle e retornamos o nome do processo.

Observações da vó

Estas são as surpresas que podemos ter quando programamos para o sistema operacional Windows: coisas aparentemente simples, como por algumas informações na barra de status de uma janela, podem se tornar um tanto complicadas dependendo do tipo da informação. Por outro lado, coisas aparentemente complicadas podem ser resolvidas com uma única chamada a uma função tipo "prato pronto". De qualquer maneira, acho que já ficou claro que a análise cuidadosa da API é imprescindível.

Voltando ao Projeto Espião, adicione o código apresentado acima e não se esqueça: é preciso incluir psapi.h, porque chamamos algumas funções desta biblioteca, e é preciso declarar o protótipo das funções criadas (linhas 2 e 14 a 17).

#include #include #include "espiaores.h" #define IDJANELATREE 10545 /*<---------------------------------------------------------------------->*/ HINSTANCE hInst; // manipulador da instância HWND hwndMain; // manipulador da janela principal LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam); static HWND _stdcall janelaTree(HWND hWnd,int ID); int criaTree(HWND parent); void Scan(HWND hTree,HTREEITEM hNoPai,HWND Inicio); LRESULT TrataWmNotify(HWND hwnd, WPARAM wParam, LPARAM lParam); static HWND PegaItemInfo(HWND hwndTree,HTREEITEM hti); void PoeTextoStatus(HWND hParent,HWND hwnd); static char * NomeIDProcesso( DWORD processoID );

Agora mais um detalhe: é preciso adicionar a psapi.lib à linha de comando do linker (senão ele não vai saber de qual biblioteca tirar as funções correspondentes). Para isso, clique em |Project|Configuration| e depois clique na aba "Linker". No campo "Additional files to be included in the link" digite psapi.lib e clique em [OK]. O Wedit refaz alguns arquivos para incorporar a nova informação.

Salve o código fonte. Compile e execute o projeto espião. Clique em |Farejar| para montar a árvore, clique num item qualquer e observe a barra de status. Se tudo tiver corrido bem, PARABÉNS, seu projeto está começando a ficar com "cara de gente grande"!

Status

No último tutorial da série Espião você encontra o código fonte completo do Projeto. Tem muita coisa ainda que pode ser implementada, por exemplo, melhorar o menu, passar as explicações dos itens do menu para o Português, trocar o ícone padrão por um personalizado, adicionar uma caixa de mensagem para o about, e por aí vai. Fica a seu critério, pois a esta altura você já sabe como lidar com isto biggrin

Вадим Логофет Sberbankквадратная сковородалобановский александр харьковхолдинг Рекламныйпластиковые карнизыноутбук для графикидепутат лобановский