需在外部程式(如金山詞霸 2002 附錄)的 TreeView 中查找特定節點(古詩標題)。由於 TreeView 內容駐留於目標進程記憶體,必須跨進程傳遞 TVITEM 結構與字串緩衝區。本文分兩階段詳細實現:順序遍歷所有可見節點 與 指定名稱查找節點。
| 訊息/函數 | 用途 | 關鍵參數 |
|---|---|---|
TVM_GETNEXTITEM | 取得節點控制碼 | TVGN_ROOT 根節點, TVGN_NEXTVISIBLE 下一個可見節點 |
TVM_EXPAND | 展開/摺疊節點 | TVE_EXPAND 展開 |
TVM_SELECTITEM | 選中節點 | TVGN_CARET 焦點選中 |
TVM_GETITEM | 讀取節點資訊 | 需 TVITEM* 指向目標進程記憶體 |
VirtualAllocEx | 在目標進程配置記憶體 | MEM_COMMIT | PAGE_READWRITE |
WriteProcessMemory | 寫入結構/緩衝區位址 | - |
ReadProcessMemory | 讀回字串內容 | - |
SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_ROOT, 0) 取得根節點。TVM_EXPAND(TVE_EXPAND) 展開 → TVM_GETNEXTITEM(TVGN_NEXTVISIBLE) 取下一個 → TVM_SELECTITEM 選中 → 直到無下一個節點。HTREEITEM hItem = (HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_ROOT, 0);
::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem);
while (hItem) {
// 嘗試展開
while (::SendMessage(hwnd, TVM_EXPAND, TVE_EXPAND, (LPARAM)hItem)) {
hItem = (HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_NEXTVISIBLE, (LPARAM)hItem);
::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem);
}
// 無法展開,移至下一個可見
hItem = (HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_NEXTVISIBLE, (LPARAM)hItem);
::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem);
}
// 1. 取得目標進程 ID 並開啟
DWORD dwPID; GetWindowThreadProcessId(hwnd, &dwPID);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
// 2. 在目標進程配置 TVITEM 結構
TVITEM* pRemoteItem = (TVITEM*)VirtualAllocEx(hProcess, NULL, sizeof(TVITEM), MEM_COMMIT, PAGE_READWRITE);
// 3. 在目標進程配置字串緩衝區 (512 bytes)
char* pRemoteText = (char*)VirtualAllocEx(hProcess, NULL, 512, MEM_COMMIT, PAGE_READWRITE);
// 4. 本地填寫 TVITEM,指定 pszText 指向遠端緩衝區
TVITEM localItem = {0};
localItem.mask = TVIF_TEXT;
localItem.cchTextMax = 512;
localItem.pszText = pRemoteText; // 關鍵:指向目標進程位址
// 5. 寫入目標進程
WriteProcessMemory(hProcess, pRemoteItem, &localItem, sizeof(TVITEM), NULL);
BOOL bFound = FALSE;
HTREEITEM hItem = (HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_ROOT, 0);
::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem);
// 更新遠端 hItem 並讀取根節點文字
localItem.hItem = hItem;
WriteProcessMemory(hProcess, pRemoteItem, &localItem, sizeof(TVITEM), NULL);
::SendMessage(hwnd, TVM_GETITEM, 0, (LPARAM)pRemoteItem);
ReadProcessMemory(hProcess, pRemoteText, szLocalBuf, 512, NULL);
if (_strnicmp(szLocalBuf, szTarget, strlen(szTarget)) == 0) { bFound = TRUE; goto END; }
while (hItem && !bFound) {
while (::SendMessage(hwnd, TVM_EXPAND, TVE_EXPAND, (LPARAM)hItem)) {
hItem = (HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_NEXTVISIBLE, (LPARAM)hItem);
::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem);
localItem.hItem = hItem;
WriteProcessMemory(hProcess, pRemoteItem, &localItem, sizeof(TVITEM), NULL);
::SendMessage(hwnd, TVM_GETITEM, 0, (LPARAM)pRemoteItem);
ReadProcessMemory(hProcess, pRemoteText, szLocalBuf, 512, NULL);
if (_strnicmp(szLocalBuf, szTarget, strlen(szTarget)) == 0) { bFound = TRUE; break; }
}
if (bFound) break;
hItem = (HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_NEXTVISIBLE, (LPARAM)hItem);
::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem);
localItem.hItem = hItem;
WriteProcessMemory(hProcess, pRemoteItem, &localItem, sizeof(TVITEM), NULL);
::SendMessage(hwnd, TVM_GETITEM, 0, (LPARAM)pRemoteItem);
ReadProcessMemory(hProcess, pRemoteText, szLocalBuf, 512, NULL);
if (_strnicmp(szLocalBuf, szTarget, strlen(szTarget)) == 0) { bFound = TRUE; break; }
}
END:
if (bFound) MessageBox(_T("找到目標節點!"));
// 釋放資源
VirtualFreeEx(hProcess, pRemoteItem, 0, MEM_RELEASE);
VirtualFreeEx(hProcess, pRemoteText, 0, MEM_RELEASE);
CloseHandle(hProcess);



OpenProcess(PROCESS_ALL_ACCESS))。TVM_GETITEM 批量讀取、多執行緒分段遍歷。跨進程 TreeView 操作的核心在於 VirtualAllocEx/WriteProcessMemory/ReadProcessMemory 構建遠端 TVITEM 與字串緩衝區,配合 TVM_GETNEXTITEM/EXPAND/GETITEM 訊息實現遍歷與查找。此技術同樣適用於 ListView、ListBox 等支援類似訊息的控制項。