別プロセスの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文字より長いとコピーできません。
- 再帰で処理しているので、階層が非常に深いツリービューをコピーする場合は、プログラムが落ちる可能性があります。
- サブクラス化しているコントロールは対応できません。