开发者

Why does the TVM_GETITEM message fail on comctl32.ocx or mscomctl.ocx tree views?

开发者 https://www.devze.com 2022-12-19 17:35 出处:网络
I wrote a function which can yield the text of a tree view item, even if the tree view is in a remote process. The function allocates two chunks of memory in the remote process, po开发者_运维问答pulat

I wrote a function which can yield the text of a tree view item, even if the tree view is in a remote process. The function allocates two chunks of memory in the remote process, po开发者_运维问答pulates a TVITEM structure (which is the copied into the remote process), sends a TVM_GETITEM message and finally reads the contents of the second remote memory chunk back into a local buffer. This is the code:

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

This code works very nicely with the plain tree views which you get when passing the WC_TREEVIEW class name to CreateWindow. However, I noticed that it does not work with the newer trees as provided by MS Common Controls v5 (comctl32.ocx) or MS Common Controls v6 (mscomctl.ocx). In those cases, the returned text is always empty (the buffer is all zeroes). I also noticed that the SendMessage call returns zero (hence the error handling denoted by the // handle error comments above kicks in). It's unclear to me whether this really indicates an error, in any case the buffer is filled with all zeroes.

All other tree view messages (like TVM_GETITEMRECT) seem to work perfectly well.

Does anybody know why that is? I tried playing around with the UNICODE flag (I noticed that TVM_GETITEM is either defined to TVM_GETITEMA or TVM_GETITEMW) but that didn't seem to help.


The code doesn't work as expected if it's compiled with UNICODE defined, but the remote process isn't (or the other way round). You should call IsWindowUnicode first on the treeView handle to check whether the remote side expects Unicode messages.

This is needed since the standard two-way marshalling which SendMessage does is not sufficient in this case: you have to send two entirely different window messages depending on whether the remote side is a Unicode window or not. If it's Unicode, use SendMessageW with TVM_GETITEMW. If it's ANSI, use SendMessageA with TVM_GETITEMA.

This applies to all common controls, but not to the basic set of controls (which uses window messages < 1024).

I also believe that the code will break if it's compiled into a 64bit binary, but the remote process is 32bit (or the other way round). This is because the code copies it's local (say: 64bit) TVITEM into the remote process and then expects the remote process toread it as expected while dealing with the TVM_GETITEM(A|W) message. However, the size of the structure might be different (due to different pointer sizes).


Ok, let's give it another shot.

Newer TreeViews expect TVITEMEX instead of TVITEM, and since there's no usual cbSize field, the control is not able to tell which version it receives and assumes TVITEMEX. Maybe there's an issue with the treeview not being able to access last members of TVITEMEX because no memory is allocated. Try using TVITEMEX or allocating a bit more memory for TVITEM than actually required.

Also consider that

The returned text will not necessarily be stored in the original buffer passed by the application. It is possible that pszText will point to text in a new buffer rather than place it in the old buffer.

You therefore might need to read from a different piece of process memory.

And, the buffer is zeroed because VirtualAllocEx resets the memory.

And as a last and probably useless resort, try using MEM_RESERVE|MEM_COMMIT instead of just MEM_COMMIT.


Use Spy++ to see whether the treeview handles WM_NOTIFY messages with NM_CUSTOMDRAW notification flag. If it does, then, bad luck. The actual data is stored internally somehow and you've got little chance to pull it out.

This equally applies to previous versions of Windows BTW.

0

精彩评论

暂无评论...
验证码 换一张
取 消