跨进程读取SysTreeView32控件内容的技术疑问
跨进程读取SysTreeView32控件的疑问解答
1. wparam/lparam对应十六进制值的官方来源
这些数值的官方定义主要有两个渠道:
- Windows SDK头文件:相关常量、消息值都定义在对应头文件中,比如树控件的
TVM_*消息、TVGN_*/TVIF_*枚举值在commctrl.h中;进程权限(如PROCESS_VM_READ)、内存分配常量(如MEM_COMMIT)在winbase.h中。安装Windows SDK后,可在本地路径(如C:\Program Files (x86)\Windows Kits\10\Include\<版本>\um)找到这些头文件查看原始定义。 - 微软官方文档:learn.microsoft.com的对应控件/API文档中会明确标注数值:
- 树控件消息数值:Tree View Control Messages页面中,每个消息会给出
TV_FIRST + 偏移量的定义,结合TV_FIRST=0x1100可算出十六进制值; - 枚举常量:Tree View Item Constants、Tree View Get Next Item Constants等页面会直接列出每个枚举对应的数值;
- 进程权限、内存参数:在Process Security and Access Rights、VirtualAllocEx的参数说明中也会给出对应数值。
- 树控件消息数值:Tree View Control Messages页面中,每个消息会给出
2. 更优的实现方式
AutoIt
AutoIt针对Windows控件做了封装,内置大量简化的控件操作函数,无需手动处理跨进程内存分配、读写等底层细节。比如读取TreeView节点文本,只需获取控件句柄后调用_ViewTree_GetItemText这类函数即可完成跨进程读取,代码量大幅减少,上手成本低。
UI Automation
这是微软推荐的现代UI自动化框架,兼容性更强:
- 无需依赖控件类名(如
SysTreeView32)或Windows消息,通过控件的自动化属性(如节点Name属性对应文本)获取内容; - 天然支持跨进程,对自定义控件兼容性更好(只要控件实现UI自动化提供者接口);
- C#中可直接使用
System.Windows.Automation命名空间的类,核心流程:- 通过
AutomationElement.FindFirst定位目标树控件; - 使用
TreeWalker.ControlViewWalker遍历树节点; - 通过节点
Current.Name获取文本,Current.HasChildren判断是否有子节点。
- 通过
当前实现代码
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; namespace ConsoleApp8 { public class Program { [DllImport("kernel32.dll")] static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll", SetLastError = true)] static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten); [DllImport("kernel32.dll", SetLastError = true)] static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesRead); [DllImport("kernel32.dll")] public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint dwFreeType); [DllImport("user32.dll")] public static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpClassName, string lpWindowName); [DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, int lParam); [DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); private const int TV_FIRST = 0x1100; public enum TVM { TVM_GETNEXTITEM = (TV_FIRST + 10), TVM_GETITEMA = (TV_FIRST + 12), TVM_GETITEM = (TV_FIRST + 62), TVM_GETCOUNT = (TV_FIRST + 5), TVM_SELECTITEM = (TV_FIRST + 11), TVM_DELETEITEM = (TV_FIRST + 1), TVM_EXPAND = (TV_FIRST + 2), TVM_GETITEMRECT = (TV_FIRST + 4), TVM_GETINDENT = (TV_FIRST + 6), TVM_SETINDENT = (TV_FIRST + 7), TVM_GETIMAGELIST = (TV_FIRST + 8), TVM_SETIMAGELIST = (TV_FIRST + 9), TVM_GETISEARCHSTRING = (TV_FIRST + 64), TVM_HITTEST = (TV_FIRST + 17), } public enum TVGN { TVGN_ROOT = 0x0, TVGN_NEXT = 0x1, TVGN_PREVIOUS = 0x2, TVGN_PARENT = 0x3, TVGN_CHILD = 0x4, TVGN_FIRSTVISIBLE = 0x5, TVGN_NEXTVISIBLE = 0x6, TVGN_PREVIOUSVISIBLE = 0x7, TVGN_DROPHILITE = 0x8, TVGN_CARET = 0x9, TVGN_LASTVISIBLE = 0xA } [Flags] public enum TVIF { TVIF_TEXT = 1, TVIF_IMAGE = 2, TVIF_PARAM = 4, TVIF_STATE = 8, TVIF_HANDLE = 16, TVIF_SELECTEDIMAGE = 32, TVIF_CHILDREN = 64, TVIF_INTEGRAL = 0x0080, TVIF_DI_SETITEM = 0x1000 } [StructLayout(LayoutKind.Sequential)] public struct TVITEMEX { public uint mask; public IntPtr hItem; public uint state; public uint stateMask; public IntPtr pszText; public int cchTextMax; public int iImage; public int iSelectedImage; public int cChildren; public IntPtr lParam; public int iIntegral; public uint uStateEx; public IntPtr hwnd; public int iExpandedImage; public int iReserved; } // privileges const int PROCESS_CREATE_THREAD = 0x0002; const int PROCESS_QUERY_INFORMATION = 0x0400; const int PROCESS_VM_OPERATION = 0x0008; const int PROCESS_VM_WRITE = 0x0020; const int PROCESS_VM_READ = 0x0010; // used for memory allocation const uint MEM_COMMIT = 0x00001000; const int MEM_DECOMMIT = 0x4000; const uint MEM_RESERVE = 0x00002000; const uint PAGE_READWRITE = 4; public static void Main() { Process[] p = Process.GetProcessesByName("GD2"); IntPtr hMain = p[0].MainWindowHandle; IntPtr hWnd1 = FindWindowEx(hMain, IntPtr.Zero, "XTPDockingPaneTabbedContainer", null); IntPtr hWnd2 = FindWindowEx(hWnd1, IntPtr.Zero, "XTPShortcutBar", null); IntPtr hWnd3 = FindWindowEx(hWnd2, IntPtr.Zero, "Afx:00400000:0:00010007:00000000:00000000", null); IntPtr hWnd4 = FindWindowEx(hWnd3, IntPtr.Zero, "SysTreeView32", null); IntPtr test1 = SendMessage(hWnd4, (int)TVM.TVM_GETNEXTITEM, (int)TVGN.TVGN_CARET, 0); //IntPtr test2 = SendMessage(hWnd4, (int)TVM.TVM_GETCOUNT, 0, 0); NodeData testNode = AllocTest(p[0], hWnd4, test1); } ///<summary>Returns the tree node information from another process.</summary> ///<param name="hwndItem">Handle to a tree node item.</param> ///<param name="hwndTreeView">Handle to a tree view control.</param> ///<param name="process">Process hosting the tree view control.</param> private static NodeData AllocTest(Process process, IntPtr hwndTreeView, IntPtr hwndItem) { // code based on article posted here: http://www.codingvision.net/miscellaneous/c-inject-a-dll-into-a-process-w-createremotethread // handle of the process with the required privileges IntPtr procHandle = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, false, process.Id); // Write TVITEM to memory // Invoke TVM_GETITEM // Read TVITEM from memory var item = new TVITEMEX(); item.hItem = hwndItem; item.mask = (int)(TVIF.TVIF_HANDLE | TVIF.TVIF_CHILDREN | TVIF.TVIF_TEXT); item.cchTextMax = 1024; item.pszText = VirtualAllocEx(procHandle, IntPtr.Zero, (uint)item.cchTextMax, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // node text pointer byte[] data = getBytes(item); uint dwSize = (uint)data.Length; IntPtr allocMemAddress = VirtualAllocEx(procHandle, IntPtr.Zero, dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // TVITEM pointer uint nSize = dwSize; UIntPtr bytesWritten; bool successWrite = WriteProcessMemory(procHandle, allocMemAddress, data, nSize, out bytesWritten); var sm = SendMessage(hwndTreeView, (int)TVM.TVM_GETITEM, IntPtr.Zero, allocMemAddress); UIntPtr bytesRead; bool successRead = ReadProcessMemory(procHandle, allocMemAddress, data, nSize, out bytesRead); UIntPtr bytesReadText; byte[] nodeText = new byte[item.cchTextMax]; bool successReadText = ReadProcessMemory(procHandle, item.pszText, nodeText, (uint)item.cchTextMax, out bytesReadText); bool success1 = VirtualFreeEx(procHandle, allocMemAddress, dwSize, MEM_DECOMMIT); bool success2 = VirtualFreeEx(procHandle, item.pszText, (uint)item.cchTextMax, MEM_DECOMMIT); var item2 = fromBytes<TVITEMEX>(data); String name = Encoding.Unicode.GetString(nodeText); int x = name.IndexOf('\0'); if (x >= 0) name = name.Substring(0, x); NodeData node = new NodeData(); node.Text = name; node.HasChildren = (item2.cChildren == 1); return node; } public class NodeData { public String Text { get; set; } public bool HasChildren { get; set; } } private static byte[] getBytes(Object item) { int size = Marshal.SizeOf(item); byte[] arr = new byte[size]; IntPtr ptr = Marshal.AllocHGlobal(size); Marshal.StructureToPtr(item, ptr, true); Marshal.Copy(ptr, arr, 0, size); Marshal.FreeHGlobal(ptr); return arr; } private static T fromBytes<T>(byte[] arr) { T item = default(T); int size = Marshal.SizeOf(item); IntPtr ptr = Marshal.AllocHGlobal(size); Marshal.Copy(arr, 0, ptr, size); item = (T)Marshal.PtrToStructure(ptr, typeof(T)); Marshal.FreeHGlobal(ptr); return item; } } }
内容的提问来源于stack exchange,提问作者oso0690
相关产品推荐
相关产品推荐

