You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

跨进程读取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的参数说明中也会给出对应数值。

2. 更优的实现方式

AutoIt

AutoIt针对Windows控件做了封装,内置大量简化的控件操作函数,无需手动处理跨进程内存分配、读写等底层细节。比如读取TreeView节点文本,只需获取控件句柄后调用_ViewTree_GetItemText这类函数即可完成跨进程读取,代码量大幅减少,上手成本低。

UI Automation

这是微软推荐的现代UI自动化框架,兼容性更强:

  • 无需依赖控件类名(如SysTreeView32)或Windows消息,通过控件的自动化属性(如节点Name属性对应文本)获取内容;
  • 天然支持跨进程,对自定义控件兼容性更好(只要控件实现UI自动化提供者接口);
  • C#中可直接使用System.Windows.Automation命名空间的类,核心流程:
    1. 通过AutomationElement.FindFirst定位目标树控件;
    2. 使用TreeWalker.ControlViewWalker遍历树节点;
    3. 通过节点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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 04:47:37