Почему сообщение TVM_GETITEM терпит неудачу в представлениях дерева comctl32.ocx или mscomctl.ocx?

Я написал функцию, которая может выдавать текст элемента дерева, даже если он находится в удаленном процессе. Функция выделяет два фрагмента памяти в удаленном процессе, заполняет структуру 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 ответа

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам