Por que a mensagem TVM_GETITEM falha nas visualizações de árvore comctl32.ocx ou mscomctl.ocx?
Pergunta
Eu escrevi uma função que pode produzir o texto de um item de exibição de árvore, mesmo que a visualização da árvore esteja em um processo remoto. A função aloca dois pedaços de memória no processo remoto, preenche uma estrutura de TVItem (que é copiada no processo remoto), envia uma mensagem TVM_GETITEM e finalmente lê o conteúdo do segundo pedaço de memória remota de volta para um buffer local. Este é o código:
std::string getTreeViewItemText( HWND treeView, HTREEITEM item )
{
DWORD pid;
::GetWindowThreadProcessId( treeView, &pid );
HANDLE proc = ::OpenProcess( PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, pid );
if ( !proc )
// handle error
TVITEM tvi;
ZeroMemory( &tvi, sizeof(tvi) );
LPVOID tvi_ = ::VirtualAllocEx( proc, NULL, sizeof(tvi), MEM_COMMIT, PAGE_READWRITE);
if ( !tvi_ )
// handle error
TCHAR buffer[100] = { 'X' };
LPVOID txt_ = ::VirtualAllocEx( proc, NULL, sizeof(buffer), MEM_COMMIT, PAGE_READWRITE );
if ( !txt_ )
// handle error
tvi.mask = TVIF_TEXT | TVIF_HANDLE;
tvi.pszText = (LPTSTR)txt_;
tvi.cchTextMax = sizeof(buffer) / sizeof(buffer[0] );
tvi.hItem = item;
if ( !::WriteProcessMemory( proc, tvi_, &tvi, sizeof(tvi), NULL ) )
// handle error
if ( !::SendMessage( treeView, TVM_GETITEM, 0, (LPARAM)tvi_ ) )
// handle error
if ( !::ReadProcessMemory( proc, (LPCVOID)txt_, buffer, sizeof( buffer ), NULL ) )
// handle error
::VirtualFreeEx( proc, tvi_, 0, MEM_RELEASE );
::VirtualFreeEx( proc, txt_, 0, MEM_RELEASE );
::CloseHandle( proc );
return buffer;
}
Este código funciona muito bem com as vistas simples da árvore que você recebe ao passar o WC_TREEVIEW
Nome da classe para CreateWindow
. No entanto, notei que ele não funciona com as árvores mais recentes, conforme fornecido pelo MS Common Controls V5 (COMCTL32.OCX) ou MS Common Controls V6 (msComctl.ocx). Nesses casos, o texto retornado está sempre vazio (o buffer é todo zeros). Também notei que a chamada de sendmessage retorna zero (daí o manuseio de erros denotado pelo // handle error
Comentários acima entram em ação). Não está claro para mim se isso realmente indica um erro, em qualquer caso o buffer está preenchido com todos os zeros.
Todas as outras mensagens de exibição de árvores (como TVM_GetItemrect) parecem funcionar perfeitamente bem.
Alguém sabe por que isso é? Eu tentei brincar com a bandeira Unicode (notei que TVM_GETITEM
ou é definido para TVM_GETITEMA
ou TVM_GETITEMW
) Mas isso não parecia ajudar.
Solução
Ok, vamos dar outra chance.
As visualizações de árvores mais recentes esperam TVITEMEX
ao invés de TVITEM
, e já que não há usual cbSize
Campo, o controle não é capaz de dizer qual versão recebe e assume TVITEMEX
. Talvez haja um problema com o Treeview não poder acessar os últimos membros de TVITEMEX
Porque nenhuma memória é alocada. Tente usar TVITEMEX
ou alocar um pouco mais de memória para TVITEM
do que realmente necessário.
Também considere isso
O texto retornado não será necessariamente armazenado no buffer original aprovado pelo aplicativo. É possível que o PSZText aponte para o texto em um novo buffer, em vez de colocá -lo no buffer antigo.
Portanto, você pode precisar ler a partir de uma memória de processo diferente.
E, o buffer é zerado porque o VirtualAlocex redefine a memória.
E como último e provavelmente inútil resort, tente usar MEM_RESERVE|MEM_COMMIT
em vez de apenas MEM_COMMIT
.
Outras dicas
O código não funciona como esperado se for compilado com o Unicode definido, mas o processo remoto não é (ou o contrário). Você deve ligar Iswindowunicode primeiro no treeView
Puxa para verificar se o lado remoto espera mensagens unicode.
Isso é necessário, pois o marechaling bidirecional padrão que o SendMessage faz não é suficiente neste caso: você deve enviar duas mensagens de janela totalmente diferentes, dependendo se o lado remoto é uma janela Unicode ou não. Se for unicode, use sendMessageW com tvm_getItemw. Se for Ansi, use o sendMessagea com tvm_getItema.
Isso se aplica a todos os controles comuns, mas não ao conjunto básico de controles (que usa mensagens de janela <1024).
Eu também acredito que o código quebrará se for compilado em um binário de 64 bits, mas o processo remoto é de 32 bits (ou o contrário). Isso ocorre porque o código copia, é local (digamos: 64bits) TVItem no processo remoto e, em seguida, espera que o processo remoto o torne o esperado ao lidar com a mensagem TVM_GETITEM (a | W). No entanto, o tamanho da estrutura pode ser diferente (devido a diferentes tamanhos de ponteiro).
Use SPY ++ para ver se o TreeView lida com as mensagens WM_Notify com o sinalizador de notificação NM_CUSTOMDRAW. Se isso acontecer, então, má sorte. Os dados reais são armazenados internamente de alguma forma e você tem poucas chances de retirá -los.
Isso se aplica igualmente às versões anteriores do Windows BTW.