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

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

### 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用の構造体を別途用意する必要があります。

```c
#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

```

# ソースコード
以下全ソースです。

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