Почему сообщение TVM_GETITEM терпит неудачу в представлениях дерева comctl32.ocx или mscomctl.ocx? - PullRequest
2 голосов
/ 11 февраля 2010

Я написал функцию, которая может выдавать текст элемента представления дерева, даже если представление дерева находится в удаленном процессе. Функция выделяет два фрагмента памяти в удаленном процессе, заполняет структуру TVITEM (которая копируется в удаленный процесс), отправляет сообщение TVM_GETITEM и, наконец, считывает содержимое второго фрагмента удаленной памяти обратно в локальный буфер. Это код:

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;
}

Этот код очень хорошо работает с простыми представлениями дерева, которые вы получаете при передаче имени класса WC_TREEVIEW в CreateWindow. Однако я заметил, что он не работает с более новыми деревьями, как предусмотрено MS Common Controls v5 (comctl32.ocx) или MS Common Controls v6 (mscomctl.ocx). В этих случаях возвращаемый текст всегда пуст (в буфере все нули). Я также заметил, что вызов SendMessage возвращает ноль (отсюда и возникает обработка ошибок, обозначенная // handle error комментариями выше). Мне неясно, действительно ли это указывает на ошибку, в любом случае буфер заполнен всеми нулями.

Все другие сообщения в виде дерева (например, TVM_GETITEMRECT), кажется, работают отлично.

Кто-нибудь знает, почему это так? Я попытался поиграть с флагом UNICODE (я заметил, что TVM_GETITEM определен как TVM_GETITEMA или TVM_GETITEMW), но это, похоже, не помогло.

Ответы [ 3 ]

5 голосов
/ 18 февраля 2010

Код не работает должным образом, если он скомпилирован с определением UNICODE, но удаленный процесс не работает (или наоборот). Сначала вы должны вызвать IsWindowUnicode на дескрипторе treeView, чтобы проверить, ожидает ли удаленная сторона сообщения Unicode.

Это необходимо, поскольку стандартная двусторонняя сортировка, которую выполняет SendMessage, в этом случае недостаточна: вам нужно отправить два совершенно разных оконных сообщения в зависимости от того, является ли удаленная сторона окном Unicode или нет. Если это Unicode, используйте SendMessageW с TVM_GETITEMW. Если это ANSI, используйте SendMessageA с TVM_GETITEMA.

Это относится ко всем общим элементам управления, но не к базовому набору элементов управления (в котором используются оконные сообщения <1024). </p>

Я также считаю, что код сломается, если он будет скомпилирован в 64-разрядный двоичный файл, но удаленный процесс является 32-разрядным (или наоборот). Это связано с тем, что код копирует локальный (скажем, 64-битный) TVITEM в удаленный процесс, а затем ожидает, что удаленный процесс прочитает его, как и ожидалось, при работе с сообщением TVM_GETITEM (A | W). Однако размер структуры может отличаться (из-за разного размера указателя).

3 голосов
/ 18 февраля 2010

Хорошо, давайте сделаем еще один выстрел.

Более новые TreeView ожидают TVITEMEX вместо TVITEM, и, поскольку нет обычного поля cbSize, элемент управления не может сказать, какую версию он получает и принимает TVITEMEX. Возможно, проблема в том, что древовидная структура не может получить доступ к последним элементам TVITEMEX, так как память не выделена. Попробуйте использовать TVITEMEX или выделить немного больше памяти для TVITEM, чем фактически требуется.

Также учтите, что

Возвращенный текст не обязательно храниться в исходном буфере прошло по заявке. это Возможно, что pszText будет указывать на текст в новый буфер, а не место это в старом буфере.

Поэтому вам, возможно, придется читать данные из другой части памяти процесса.

И буфер обнуляется, потому что VirtualAllocEx сбрасывает память.

И в качестве последнего и, вероятно, бесполезного средства, попробуйте использовать MEM_RESERVE|MEM_COMMIT вместо просто MEM_COMMIT.

1 голос
/ 11 февраля 2010

Используйте Spy ++, чтобы увидеть, обрабатывает ли древовидное представление сообщения WM_NOTIFY с флагом уведомления NM_CUSTOMDRAW. Если это так, то неудача. Фактические данные каким-то образом хранятся внутри, и у вас мало шансов их извлечь.

Это в равной степени относится и к предыдущим версиям Windows BTW.

...