Perché il messaggio TVM_GETITEM fallire su una vista comctl32.ocx o albero mscomctl.ocx?
Domanda
ho scritto una funzione che può cedere il testo di un elemento ad albero, anche se la vista albero è in un processo remoto. La funzione alloca due blocchi di memoria del processo remoto, popola una struttura TVITEM (che rappresenta l'copiato nella processo remoto), invia un messaggio TVM_GETITEM e infine legge il contenuto del secondo blocco di memoria remota di nuovo in un buffer locale. Questo è il codice:
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;
}
Questo codice funziona molto bene con i punti di vista degli alberi di pianura che si ottiene quando si passa il nome della classe WC_TREEVIEW
a CreateWindow
. Tuttavia, ho notato che non funziona con gli alberi più recenti come previsto dalla MS Common Controls v5 (comctl32.ocx) o MS Common Controls v6 (mscomctl.ocx). In tali casi, il testo restituito è sempre vuota (il buffer è tutti zeri). Ho anche notato che la chiamata SendMessage restituisce zero (da qui la gestione degli errori indicata con le osservazioni sopra // handle error
calci). Non è chiaro per me se questo significa davvero un errore, in ogni caso, il buffer è pieno di tutti zeri.
Tutti gli altri messaggi vista albero (come TVM_GETITEMRECT) sembrano funzionare perfettamente.
Qualcuno sa perché? Ho provato a giocare con la bandiera Unicode (ho notato che TVM_GETITEM
o è definito in modo da TVM_GETITEMA
o TVM_GETITEMW
), ma che non sembra aiutare.
Soluzione
Ok, diamo un altro colpo.
TreeViews recenti aspettano TVITEMEX
invece di TVITEM
, e dal momento che non c'è al solito campo cbSize
, il controllo non è in grado di dire quale versione riceve e assume TVITEMEX
. Forse c'è un problema con il controllo TreeView non essere in grado di accedere ultimi membri della TVITEMEX
perché nessuna memoria è allocata. Provate ad usare TVITEMEX
o assegnazione di un po 'di più memoria per TVITEM
che effettivamente richiesto.
Si consideri inoltre che
Il testo restituito non sarà necessariamente essere memorizzati nel buffer originale superato dall'applicazione. È possibile che pszText punterà testo in un nuovo buffer, piuttosto che luogo nel vecchio buffer.
È quindi potrebbe essere necessario per leggere da un altro pezzo di memoria di processo.
E, il buffer è azzerato perché VirtualAllocEx azzera la memoria.
E come ultima e probabilmente inutile resort, provare a utilizzare MEM_RESERVE|MEM_COMMIT
invece di MEM_COMMIT
.
Altri suggerimenti
Il codice non funziona come previsto se è compilato con UNICODE definito, ma il processo remoto non è (o viceversa). Si dovrebbe chiamare IsWindowUnicode prima sul manico treeView
a verificare se il lato remoto si aspetta messaggi Unicode.
Questo è necessario in quanto il marshalling standard a due vie che SendMessage fa non è sufficiente in questo caso: è necessario inviare due completamente diversi messaggi di finestra a seconda che il lato remoto è una finestra Unicode o meno. Se si tratta di Unicode, utilizzare SendMessageW con TVM_GETITEMW. Se si tratta di ANSI, utilizzare SendMessageA con TVM_GETITEMA.
Questo vale per tutti i controlli comuni, ma non al set di base di controlli (che utilizza i messaggi di finestra <1024).
Credo anche che il codice si romperà se è compilato in un binario 64 bit, ma il processo remoto è 32 bit (o viceversa). Questo perché il codice copia è locale (diciamo: 64bit) TVITEM nel processo remoto e quindi si aspetta che il processo remoto Toread come previsto nei rapporti con la TVM_GETITEM (A | W) messaggio. Tuttavia, la dimensione della struttura potrebbe essere diversa (a causa di differenti dimensioni puntatore).
Usa Spy ++ per vedere se la vista ad albero gestisce i messaggi WM_NOTIFY con NM_CUSTOMDRAW bandiera notifica. Se lo fa, poi, la sfortuna. I dati effettivi vengono memorizzati internamente in qualche modo e hai poche possibilità di tirarlo fuori.
Questo vale anche per le versioni precedenti di Windows BTW.