【问题标题】:How to properly loop through / get text / select SysTreeView32 window item如何正确循环/获取文本/选择 SysTreeView32 窗口项
【发布时间】:2016-04-14 16:30:21
【问题描述】:

我花了几个小时浏览微软的开发中心;但是,我似乎无法弄清楚如何做以下两件事:

  1. 在“导航器”子窗口的“专家顾问”部分下循环并查看每个程序的名称(例如下面屏幕截图中的“MACD 示例”)

  2. 选择并双击程序(例如“MACD 样本”)。

Winspector(Left) | Application(Right)

我的主要问题似乎是我不知道如何正确使用 HTREEITEM 来访问信息。我注意到有一个函数 ListView_GetItemText,但我一直找不到 TreeView_GetItemText 或等效函数。

任何帮助将不胜感激。

下面是我的程序的主要功能:

int _tmain(int argc, _TCHAR* argv[])
{
    wcout << TEXT("Enumerating Windows...") << endl;
    HWND handle = NULL;

    //--- Success: gets application handle
    bool success1 = getHandle(L"MetaTrader", L"20", handle);

    cout << "Success1: " << success1 << endl;
    cout << "Result1: " << handle << endl;

    //--- Success: gets navigator window
    bool success2 = getChildHandle(handle, L"", L"Navigator", handle);

    cout << "Success2: " << success2 << endl;
    cout << "Result2: " << handle << endl;

    //--- Success: gets "SysTreeView32" handle
    handle = FindWindowEx(handle, 0, L"SysTreeView32", L"");

    cout << "Result3: " << handle << endl;

    //--- Success: get "SysTreeView32" root nod
    HTREEITEM root = TreeView_GetNextItem(handle, NULL, TVGN_ROOT);
    cout << "root: " << root << endl;

    return 0;
}

运行代码的结果似乎运行正常

完整的代码:

// MT4Terminal-test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#pragma once

#include "targetver.h"


#include <iostream>
#include <map>
#include <string>

namespace std {
#if defined _UNICODE || defined UNICODE
    typedef wstring tstring;
#else
    typedef string tstring;
#endif
}

#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <psapi.h>

#include <Windows.h>
#include <Commctrl.h>
#include <windows.system.h>

using namespace std;

HWND glb_handle;
tstring glb_searchWindowTitle;
tstring glb_seachClassName;

BOOL CALLBACK enumWindowsChildProc(
    __in  HWND hWnd,
    __in  LPARAM lParam
    ) {


    return TRUE;
}

BOOL CALLBACK enumWindowsProc(
    __in  HWND hWnd,
    __in  LPARAM lParam
    ) {

    int length = ::GetWindowTextLength(hWnd);
    if (0 == length) return TRUE;

    TCHAR* bufferA;
    bufferA = new TCHAR[length + 1];
    memset(bufferA, 0, (length + 1) * sizeof(TCHAR));

    TCHAR* bufferB;
    bufferB = new TCHAR[100];
    memset(bufferB, 0, 100 * sizeof(TCHAR));

    GetWindowText(hWnd, bufferA, length + 1);
    GetClassName(hWnd, bufferB, 100);
    tstring windowTitle = tstring(bufferA);
    tstring className = tstring(bufferB);
    delete bufferA;
    delete bufferB;

    if (windowTitle.find(glb_searchWindowTitle) < string::npos &&
        className.find(glb_seachClassName) < string::npos)
            glb_handle = hWnd;

    wcout.clear();

    return TRUE;
}

bool getHandle(wstring searchClassName, wstring searchWindowTitle, HWND &handle)
{
    handle = NULL;
    glb_handle = NULL;
    glb_searchWindowTitle = searchWindowTitle;
    glb_seachClassName = searchClassName;
    BOOL enumeratingWindowsSucceeded = EnumWindows(enumWindowsProc, NULL);

    if (enumeratingWindowsSucceeded)
    {
        if (glb_handle != NULL)
        {
            handle = glb_handle;
            return true;
        }
    }

    glb_handle = NULL;
    glb_searchWindowTitle = L"";
    glb_seachClassName = L"";
    return false;
}

bool getChildHandle(HWND parent_handle, wstring searchClassName, wstring searchWindowTitle, HWND &handle)
{
    handle = NULL;
    glb_handle = NULL;
    glb_searchWindowTitle = searchWindowTitle;
    glb_seachClassName = searchClassName;
    BOOL enumeratingWindowsSucceeded = EnumChildWindows(parent_handle, enumWindowsProc, NULL);

    if (enumeratingWindowsSucceeded)
    {
        if (glb_handle != NULL)
        {
            handle = glb_handle;
            return true;
        }
    }

    glb_handle = NULL;
    glb_searchWindowTitle = L"";
    glb_seachClassName = L"";
    return false;
}

int _tmain(int argc, _TCHAR* argv[])
{
    wcout << TEXT("Enumerating Windows...") << endl;
    HWND handle = NULL;

    //--- Success: gets application handle
    bool success1 = getHandle(L"MetaTrader", L"20", handle);

    cout << "Success1: " << success1 << endl;
    cout << "Result1: " << handle << endl;

    //--- Success: gets navigator window
    bool success2 = getChildHandle(handle, L"", L"Navigator", handle);

    cout << "Success2: " << success2 << endl;
    cout << "Result2: " << handle << endl;

    //--- Success: gets "SysTreeView32" handle
    handle = FindWindowEx(handle, 0, L"SysTreeView32", L"");

    cout << "Result3: " << handle << endl;

    //--- Success: get "SysTreeView32" root nod
    HTREEITEM root = TreeView_GetNextItem(handle, NULL, TVGN_ROOT);
    cout << "root: " << root << endl;

    return 0;
}

选择 SysTreeView32 项目

(为澄清起见,当我说选择 SysTreeView32 项目时,我指的是模拟树节点上的双击操作——类似于双击桌面上的图标打开程序的方式)

看完文档,我深信不疑:

  1. 不存在显式消息,该消息将使用树视图项的句柄模拟双击树上的节点

  2. 一种可能的解决方法是发送 TVM_GETITEMRECT 消息以获取树节点的坐标,然后使用 SendInput() 发送点击

以上两种说法正确吗?

在实现 Barmak Shemirani 的代码后,我尝试使用与 Barmak Shemirani 的修复相同的方法来实现上面的 #2。具体来说,我尝试使用 VirtualAllocEx() 在其他应用程序的内存中分配一个 Rect 结构,使用指向该矩形的指针在我的程序中调用 TreeView_GetItemRect 宏,然后使用 ReadProcessMemory() 读取结果。

但是,当我调用 TreeView_GetItemRect(),同时将指针传递给其他应用程序内存中的 Rect 时,我的程序崩溃了。很可能是因为 TreeView_GetItemRect() 试图将 Rect 坐标写入无效的内存地址。这让我意识到我并不真正理解宏在做什么:

  1. 因此,查看源代码,我发现:

    #define HELLO
    #define TV_FIRST                0x1100      // TreeView messages
    
    #define TVM_GETITEMRECT         (TV_FIRST + 4)
    #define TreeView_GetItemRect(hwnd, hitem, prc, code) \
    (*(HTREEITEM *)(prc) = (hitem), (BOOL)SNDMSG((hwnd), TVM_GETITEMRECT, (WPARAM)(code), (LPARAM)(RECT *)(prc)))
    

除了 SNDMSG 函数之前的部分,我基本都懂:

(*(HTREEITEM *)(prc) = (hitem),

上面的陈述究竟是什么意思?这是否将我传递给 HTREEITEM 指针的矩形指针转换为导致程序崩溃的某种方式?

Screenshot of console freezing

新代码

int _tmain(int argc, _TCHAR* argv[])
{
    wcout << TEXT("Enumerating Windows...") << endl;
    HWND handle = NULL;

    //--- Success: gets application handle
    bool success1 = getHandle(L"MetaTrader", L"20", handle);

    //--- Success: gets navigator window
    bool success2 = getChildHandle(handle, L"", L"Navigator", handle);

    //--- Success: gets "SysTreeView32" handle
    handle = FindWindowEx(handle, 0, L"SysTreeView32", L"");

    //--- Success: get "SysTreeView32" root nod
    HTREEITEM root = TreeView_GetNextItem(handle, NULL, TVGN_ROOT);

    unsigned long pid;

    GetWindowThreadProcessId(handle, &pid);

    HANDLE process = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE |
    PROCESS_QUERY_INFORMATION, FALSE, pid);

    TVITEM item, *_item;

    wchar_t buf[CHAR_BUF_LEN];
    wchar_t *_buf;
    memset(buf, 0, sizeof(buf) / sizeof(buf[0]));

    _item = (TVITEM*)VirtualAllocEx(process, NULL, sizeof(TVITEM), MEM_COMMIT, PAGE_READWRITE);
    _buf = (wchar_t*)VirtualAllocEx(process, NULL, CHAR_BUF_LEN, MEM_COMMIT, PAGE_READWRITE);

    item.cchTextMax = CHAR_BUF_LEN;
    item.pszText = _buf;
    item.mask = TVIF_TEXT;

    //--- find Experts Advisors branch in tree
    HTREEITEM node = TreeView_GetNextItem(handle, root, TVGN_CHILD);
    node = TreeView_GetNextItem(handle, node, TVGN_NEXT);
    node = TreeView_GetNextItem(handle, node, TVGN_NEXT);

    RECT rect, *_rect;

    _rect = (RECT*)VirtualAllocEx(process, NULL, sizeof(RECT), MEM_COMMIT, PAGE_READWRITE);

    rect = { 0 };

    WriteProcessMemory(process, _rect, &rect, sizeof(RECT), NULL);

    //--- step into Expert Advisors
    node = TreeView_GetNextItem(handle, node, TVGN_CHILD);

    //--- target program to open
    wchar_t ea_name[] = L"MACD Sample";

    while (node != NULL)
    {
        ZeroMemory(buf, CHAR_BUF_LEN);

        item.hItem = node;

        //Binds item and _item
        WriteProcessMemory(process, _item, &item, sizeof(TVITEM), NULL);

        TreeView_GetItem(handle, _item);

        //Read buffer back to this program's process memory
        ReadProcessMemory(process, _buf, buf, CHAR_BUF_LEN, NULL);

        //Print program name
        wcout << buf << endl;

        if (wcscmp(ea_name, buf) == 0)
        {
            cout << "Found target program: " << ea_name << endl;
            cout << "get rectangle coordinates: " << TreeView_GetItemRect(handle, node, _rect, TRUE) << endl;
        }

        node = TreeView_GetNextItem(handle, node, TVGN_NEXT);
    }

    VirtualFreeEx(process, _item, 0, MEM_RELEASE);
    VirtualFreeEx(process, _buf, 0, MEM_RELEASE);
    VirtualFreeEx(process, _rect, 0, MEM_RELEASE);

    return 0;
}

【问题讨论】:

    标签: windows user-interface visual-c++


    【解决方案1】:

    这是您通常用来读取 TreeView 项目文本的方法:

    wchar_t buf[100];
    memset(buf, 0, sizeof(buf));
    TVITEM item = { 0 };
    item.hItem = hitem;
    item.cchTextMax = 100;
    item.pszText = buf;
    item.mask = TVIF_TEXT;
    TreeView_GetItem(hwnd, &item);
    

    这在您的程序中不起作用TreeView_GetItem 是基于SendMessage 的宏,它通过LPARAM 参数复制数据。但是不同进程之间不允许这种交换。

    您可能会花费数小时甚至数天时间来尝试破解它 (See this example)

    或者你可能想研究一下目标程序是否支持UI Automation


    编辑,这里是获取HTREEITEM 文本的示例。除非:

    • 调用者和目标程序都是 32 位的,或者都是 64 位的
    • 调用者和目标程序都是 unicode

    如果目标程序是ANSI,则将此函数更改为ANSI。

    HTREEITEM hitem = TreeView_GetSelection(hwndTree);
    if (!hitem)
        debug << "!hitem\n";
    
    const int buflen = 512;
    
    DWORD pid;
    GetWindowThreadProcessId(hwndTree, &pid);
    HANDLE process = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE 
        | PROCESS_QUERY_INFORMATION, FALSE, pid);
    TVITEMEX* ptv = (TVITEMEX*)VirtualAllocEx(process, NULL, sizeof(TVITEMEX), 
        MEM_COMMIT, PAGE_READWRITE);
    wchar_t* pbuf = (wchar_t*)VirtualAllocEx(process, NULL, buflen, 
        MEM_COMMIT, PAGE_READWRITE);
    
    TVITEMEX tv = { 0 };
    tv.hItem = hitem;
    tv.cchTextMax = buflen / 2;
    tv.pszText = pbuf;
    tv.mask = TVIF_TEXT | TVIF_HANDLE;
    
    WriteProcessMemory(process, ptv, &tv, sizeof(TVITEMEX), NULL);
    
    if (SendMessageW(hwndTree, TVM_GETITEM, 0, (LPARAM)(TVITEMEX*)(ptv)))
    {
        wchar_t buf[buflen / 2];
        ReadProcessMemory(process, pbuf, buf, buflen, 0);
        debug << "Result:" << buf << "\n";
    }
    else
        debug << "!SendMessageW\n";
    
    VirtualFreeEx(process, ptv, 0, MEM_RELEASE);
    VirtualFreeEx(process, pbuf, 0, MEM_RELEASE);
    CloseHandle(process); //*** I forgot this line before
    

    【讨论】:

    • 可以在不同的进程之间完成,这并不简单。我记得writing 很久以前如何为 ListView 做这件事。 ListViews 和 TreeViews 非常相似,了解 Win32 的人可以调整代码来做他们想做的事。当然,.NET CLR 帮助器类使它更容易。
    • 非常感谢您的帖子;我从来没有想过 SendMessage() 不能在不同的进程中本地使用。我查看了 UI 自动化,但它是一个非常旧的应用程序,似乎不支持它。我毫不费力地实施了提出的建议;但是,我在尝试进行次要扩展时遇到了麻烦。
    • 我重新编辑了我的原始帖子以包括:选择一个 SysTreeView32 项目
    • 我之前忘了CloseHandle,请参阅编辑。 -- 不要尝试双击,那样会增加更多的问题。要展开 Tree 项目,找到 hitem 然后使用 TreeView_SelectItem(hwndTree, hitem); 后跟 TreeView_Expand(hwndTree, hitem, TVE_EXPAND); 树视图应该会改变,希望目标进程应该响应它。这些命令不需要像GetText 方法那样的虚拟内存。您不需要查找项目的矩形。
    【解决方案2】:

    投票最多的答案解决了您的问题,但我想对该声明添加一些评论:

    (*(HTREEITEM *)(prc) = (hitem),
    

    TVM_GETITEMRECT 解释说:

    发送此消息时,lParam 参数包含正在为其检索矩形的项目的句柄。

    在宏TreeView_GetItemRect中,prc将被替换为_rect,在其他进程中分配。所以程序崩溃了。 对于你的情况,你可以替换代码:

    TreeView_GetItemRect(handle, node, _rect, TRUE) 
    

    作者:

    RECT rect, *_rect;
    
    _rect = (RECT*)VirtualAllocEx(process, NULL, sizeof(RECT), MEM_COMMIT, PAGE_READWRITE);
    
    *(HTREEITEM*)&rect = node;
    
    WriteProcessMemory(process, _rect, &rect, sizeof(RECT), NULL);
    SendMessage(handle, TVM_GETITEMRECT, true, (LPARAM)_rect);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-07
      • 1970-01-01
      相关资源
      最近更新 更多