为什么TVM_GETITEM消息在comctl32.ocx或mscomctl.ocx树视图上失败?

时间:2010-02-11 11:20:32

标签: windows winapi treeview comctl32

我写了一个函数,它可以生成树视图项的文本,即使树视图在远程进程中也是如此。该函数在远程进程中分配两个内存块,填充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_GETITEMATVM_GETITEMW),但这似乎没有帮助。

3 个答案:

答案 0 :(得分:5)

如果使用UNICODE定义编译,但代码不能按预期工作,但远程进程不是(或反过来)。您应首先在treeView句柄上调用IsWindowUnicode以检查远程端是否需要Unicode消息。

这是必需的,因为在这种情况下,SendMessage执行的标准双向编组是不够的:根据远程端是否是Unicode窗口,您必须发送两个完全不同的窗口消息。如果是Unicode,请将SendMessageW与TVM_GETITEMW一起使用。如果是ANSI,请将SendMessageA与TVM_GETITEMA一起使用。

这适用于所有常用控件,但不适用于基本控件集(使用窗口消息< 1024)。

我也相信如果代码被编译成64位二进制文​​件,代码就会中断,但远程进程是32位(或者反之亦然)。这是因为代码将其本地(例如:64位)TVITEM复制到远程进程中,然后期望远程进程在处理TVM_GETITEM(A | W)消息时按预期读取它。但是,结构的大小可能不同(由于指针大小不同)。

答案 1 :(得分:3)

好的,让我们再试一次。

较新的TreeViews期望TVITEMEX而不是TVITEM,并且由于没有通常的cbSize字段,因此控件无法判断它接收的版本并假定为TVITEMEX。也许有一个问题,树视图无法访问TVITEMEX的最后成员,因为没有分配内存。尝试使用TVITEMEX或为TVITEM分配比实际需要更多的内存。

还要考虑

  

返回的文字不一定   存储在原始缓冲区中   通过申请。它是   pszText可能指向的可能   文本在新的缓冲区而不是位置   它在旧的缓冲区中。

因此,您可能需要从不同的进程内存中读取。

并且,缓冲区归零,因为VirtualAllocEx会重置内存。

作为最后也可能无用的手段,请尝试使用MEM_RESERVE|MEM_COMMIT而非MEM_COMMIT

答案 2 :(得分:1)

使用Spy ++查看树视图是否使用NM_CUSTOMDRAW通知标志处理WM_NOTIFY消息。如果确实如此,那么,运气不好。实际数据以某种方式存储在内部,你几乎没有机会把它拉出来。

这同样适用于以前版本的Windows BTW。