LoginSignup
1
2

More than 5 years have passed since last update.

別プロセスのTreeViewコントロールの文字列を取得する

Posted at

別プロセスのTreeViewコントロール(SysTreeView32)をテキスト化する方法です。
プログラミングしたいのではなく、単にソフトウェアが必要であれば、INASOFTさんのListView to CSVがおすすめです。

TreeViewコントロールの文字列を取得する

同じプロセスのTreeViewコントロールであれば、TreeView_GetItem(TVM_GETITEM) マクロを使うだけで十分です。

文字列と合わせてノードの状態(選択状態や、ツリーの展開状態)もTreeView_GetItem(TVM_GETITEM)で取得しようとしたができなかったので、 TreeView_GetItemState マクロを使用しました。

別プロセスのメモリの読み書き

別プロセスなので、TreeView_*マクロで使用する(ハンドル以外の)ポインタはVirtualAllocEx関数 で確保した領域を渡す必要があります。
読み書きはReadProcessMemory / WriteProcessMemory 関数を使用します。。

TVM_GETITEM にわたすパラメータだけでなく、TVITEMEX構造体の pszText も、VirtualAllocEx で確保した領域内のポインタにする必要があります。

WoW64

地味に面倒なのが、WoW64(64bit OS上で動作する32bitプロセス)です。
まず、プロセスの判別をIsWow64Processでしなければならない。
ちゃんと判断しようとすると、 ダイナミックロードしたり、IsWow64Process2を用いなければならないと思いますが、今回はそこまでしていないません。(ARM版のほうのWoWはよくわからない)

取得する対象がプロセスがWoW64の場合、自プロセスは64bit、対象プロセスは32bitとなるので、同じTVITEM構造体でもメモリレイアウトが異なってしまいます。
そのため、32bit用の構造体を別途用意する必要があります。

#ifdef _WIN64
typedef struct {
    UINT      mask;
    DWORD32   hItem;
    UINT      state;
    UINT      stateMask;
    DWORD32   pszText;
    int       cchTextMax;
    int       iImage;
    int       iSelectedImage;
    int       cChildren;
    DWORD32   lParam;
    int       iIntegral;
#if (_WIN32_IE >= 0x0600)
    UINT      uStateEx;
    DWORD32   hwnd;
    int       iExpandedImage;
#endif
#if (NTDDI_VERSION >= NTDDI_WIN7)
    int       iReserved;
#endif
} TVITEMEX32;
#endif //_WIN64

ソースコード

以下全ソースです。

#include <exception>
#include <stdexcept>

#include <windows.h>
#include <CommCtrl.h>

template <class T>
class RemoteMemory
{
    HANDLE hProcess;
public:
    T* const ptr;
    RemoteMemory(const RemoteMemory&) = delete;
    RemoteMemory& operator=(const RemoteMemory&) = delete;

    explicit RemoteMemory(HANDLE hProcess)
        : hProcess(hProcess),
        ptr(reinterpret_cast<T*>(VirtualAllocEx(hProcess, nullptr, sizeof(T), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)))
    {
        //static_assert(std::is_pod<T>::value == true);
        if (!ptr)
            throw Win32Error(::GetLastError());
    }
    ~RemoteMemory()
    {
        if (ptr)
            VirtualFreeEx(hProcess, ptr, 0, MEM_RELEASE);
    }

    RemoteMemory& operator =(const T& value)
    {
        return write(&value, sizeof(T));
    }

    RemoteMemory& write(const void* value, SIZE_T size)
    {
        SIZE_T written;
        if (!WriteProcessMemory(hProcess, ptr, value, size, &written))
            throw Win32Error(::GetLastError());
        return *this;
    }

    operator T()
    {
        T tmp = {};
        read(&tmp, sizeof(T));
        return tmp;
    }
    RemoteMemory& read(void* value, SIZE_T size)
    {
        SIZE_T written;
        if (!ReadProcessMemory(hProcess, ptr, value, size, &written))
            throw Win32Error(::GetLastError());
        return *this;
    }
};

#ifdef _WIN64
typedef struct {
    UINT      mask;
    DWORD32 hItem;
    UINT      state;
    UINT      stateMask;
    DWORD32     pszText;
    int       cchTextMax;
    int       iImage;
    int       iSelectedImage;
    int       cChildren;
    DWORD32    lParam;
    int       iIntegral;
#if (_WIN32_IE >= 0x0600)
    UINT      uStateEx;
    DWORD32      hwnd;
    int       iExpandedImage;
#endif
#if (NTDDI_VERSION >= NTDDI_WIN7)
    int       iReserved;
#endif
} TVITEMEX32;
#endif //_WIN64

struct TREEVIEW_RPC_BUF
{
    union
    {
        BYTE tvitem[1];
#ifdef _WIN64
        TVITEMEX32 tvitemW32;
#endif
        TVITEMEXA tvitemA;
        TVITEMEXW tvitemW;
    };
    union
    {
        BYTE text[1];
        CHAR textA[256];
        WCHAR textW[256];
    };
};

template <class T, bool isUnicode>
inline ULONG_PTR ProcessTreeViewItem(RemoteMemory<TREEVIEW_RPC_BUF>& remoteMemory, HWND hWnd, ULONG_PTR hTreeItem, TREEVIEW_RPC_BUF &rpc_buf, int indent = 1)
{
    rpc_buf = {};
    {
        auto tvitem = reinterpret_cast<T*>(rpc_buf.tvitem);
        auto pszText = ((LONG_PTR)remoteMemory.ptr) + offsetof(TREEVIEW_RPC_BUF, text);
        tvitem->mask = (TVIF_TEXT | TVIF_HANDLE | TVIF_CHILDREN);
        tvitem->hItem = decltype(tvitem->hItem)(hTreeItem);
        tvitem->cchTextMax = _countof(rpc_buf.textW);
        tvitem->pszText = decltype(tvitem->pszText)(pszText);
    }

    remoteMemory = rpc_buf;
    auto n = SendMessage(hWnd, isUnicode ? TVM_GETITEMW : TVM_GETITEMA, 0, reinterpret_cast<ULONG_PTR>(remoteMemory.ptr));
    rpc_buf = remoteMemory;

    auto state = TreeView_GetItemState(hWnd, hTreeItem, 0xFFFFFFFF);

    {
        auto tvitem = reinterpret_cast<T*>(rpc_buf.tvitem);
        printf("%*c", indent,
            state & TVIS_SELECTED ? '*' :
            tvitem->cChildren > 0 && state & TVIS_EXPANDED ? '-' :
            tvitem->cChildren > 0 ? '+' :
            ' ');
        if (isUnicode)
            printf("%ls\n", rpc_buf.textW);
        else
            printf("%s\n", rpc_buf.textA);

        if (tvitem->cChildren > 0 && state & TVIS_EXPANDED)
        {
            auto hChild = reinterpret_cast<ULONG_PTR>(TreeView_GetChild(hWnd, hTreeItem));
            while (hChild)
            {
                hChild = ProcessTreeViewItem<T, isUnicode>(remoteMemory, hWnd, hChild, rpc_buf, indent + 1);
            }
        }

        return reinterpret_cast<ULONG_PTR>(TreeView_GetNextSibling(hWnd, hTreeItem));
    }
}

void PrintRemoteTreeView(HWND hWnd)
{
    bool isUnicode = TreeView_GetUnicodeFormat(hWnd);
    DWORD pid = 0;
    GetWindowThreadProcessId(hWnd, &pid);

    HANDLE hProcess = pid ? OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, pid) : nullptr;

    if (hProcess)
    {
        BOOL Wow64Process = FALSE;
        IsWow64Process(hProcess, &Wow64Process);
        printf("Wow64Process: %d\n", Wow64Process);

        printf("==== %d(%d,%d) ====\n", pid, isUnicode, Wow64Process);

        RemoteMemory<TREEVIEW_RPC_BUF> remoteMemory(hProcess);
        auto hTreeItem = reinterpret_cast<ULONG_PTR>(TreeView_GetRoot(hWnd));
        try
        {
            TREEVIEW_RPC_BUF rpc_buf = {};
            while (hTreeItem)
            {
                if (isUnicode)
                {
#ifdef _WIN64
                    if (Wow64Process)
                    {
                        hTreeItem = ProcessTreeViewItem<TVITEMEX32, true>(remoteMemory, hWnd, hTreeItem, rpc_buf);
                    }
                    else
#endif
                    {
                        hTreeItem = ProcessTreeViewItem<TVITEMEXW, true>(remoteMemory, hWnd, hTreeItem, rpc_buf);
                    }
                }
                else
                {
#ifdef _WIN64
                    if (Wow64Process)
                    {
                        hTreeItem = ProcessTreeViewItem<TVITEMEX32, false>(remoteMemory, hWnd, hTreeItem, rpc_buf);
                    }
                    else
#endif
                    {
                        hTreeItem = ProcessTreeViewItem<TVITEMEXA, false>(remoteMemory, hWnd, hTreeItem, rpc_buf);
                    }
                }
            }
        }
        catch (const Win32Error& dwError)
        {
            printf("Error: %d\n", dwError.ErrorCode);
        }
    }
}
int main()
{
    setlocale(LC_ALL, "");

    EnumWindows([](HWND hWnd, LPARAM lParam) -> BOOL {
        EnumChildWindows(hWnd, [](HWND hWnd, LPARAM lParam) ->BOOL {
            WCHAR classname[256] = {};
            GetClassName(hWnd, classname, _countof(classname));
            if (lstrcmp(classname, WC_TREEVIEW) == 0)
            {
                PrintRemoteTreeView(hWnd);
            }
            return TRUE;
        }, lParam);
        return TRUE;
    }, 0);
}

対応できないケース

  • WPFアプリのツリービューは取得できません。
  • ノードの文字列の長さが255文字より長いとコピーできません。
  • 再帰で処理しているので、階層が非常に深いツリービューをコピーする場合は、プログラムが落ちる可能性があります。
  • サブクラス化しているコントロールは対応できません。
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2