使用SendMessage

时间:2018-08-07 14:23:20

标签: c# pinvoke

我需要更改外部应用程序拥有的TreeView(恰好是“ SysTreeView32”)项中的复选框状态-为了实现自动化。我已经有TreeView句柄和TreeViewItem句柄。我还找到了一些示例,可以用来设置复选框状态,但是由于某些原因,它无法正常工作(SendMessage返回0或导致整个应用程序崩溃)。但是要代码。我已经尝试过的是这个:

TVITEM结构:

[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Auto)]
internal struct TVITEM
{
    public int mask;
    public IntPtr hItem;
    public int state;
    public int stateMask;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string pszText;
    public int cchTextMax;
    public int iImage;
    public int iSelectedImage;
    public int cChildren;
    public IntPtr lParam;
 }

为SendMessage拨叫

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref TVITEM lParam);

我的方法:

internal static void SetTreeNodeState(int treeViewHandler, int treeViewItemHandler, bool state)
{
    TVITEM tvItem = new TVITEM();
    tvItem.mask = TVIF_STATE | TVIF_HANDLE;
    tvItem.hItem = (IntPtr)treeViewItemHandler;
    tvItem.stateMask = TVIS_STATEIMAGEMASK;
    tvItem.state = (state ? 2 : 1) << 12;
    var result = SendMessage((IntPtr)treeViewHandler, TVM_SETITEMW, IntPtr.Zero, ref tvItem);
}

这是最接近的方法(我认为,最后我没有使目标应用程序崩溃过一次)。当然,我尝试使用Spy ++嗅探目标树视图的消息。让我担心的是Spy ++显示,用于SendMessage的LParam实际上是“ TVITEMEXW”,但是我可以通过beryl找到有关该结构的任何信息。

通常,我也尝试使用TVM_GETITEMW进行相同的思考,但是我没有使应用程序崩溃,SendMessage始终返回零。

我做错了什么?

2 个答案:

答案 0 :(得分:1)

发送此特定消息时,应提供结构的地址。由于该窗口由另一个进程拥有,因此您提供的地址无效。 Windows进程具有隔离的虚拟内存地址空间。您提供的地址在您的流程中有效,但仅在您的流程中有效。

为了解决此问题并发送此消息,您需要使用VirtualAllocEx在目标进程中分配内存。您还需要使用WriteProcessMemory来填充结构。如果您的过程和目标过程的位数不同,则需要注意结构布局的所有可能问题。您需要对像pszText这样本身就是指针的成员执行相同的技巧。

这里已经有很多问题涉及跨进程消息编组。我相信您将能够找到它们。同样,在知道问题后,您会在网上找到很多教程。

也许更大的问题是,其他进程可能不会以这种方式响应您期望从外部戳入的方式。如果您发现能够编写自己的跨过程自动化非常困难,请不要感到惊讶。为何不使用UI自动化,而不是这样做?

答案 1 :(得分:1)

Okey,感谢David Heffernan,我弄清楚了这一点。我已经为SendMessage创建了重载,它通过引用将lParam接受为对象:

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] buffer, Int32 nSize, out IntPtr lpNumberOfBytesRead);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType dwFreeType);

private static IntPtr SendMessage<T>(Process process, IntPtr hWnd, int msg, int wParam, ref T lParam)
{
    uint size = (uint)Marshal.SizeOf(lParam);
    byte[] buffer = new byte[size];
    IntPtr processHandle = process.Handle;

    IntPtr pPointer = VirtualAllocEx(processHandle, IntPtr.Zero, size, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.ReadWrite);

    IntPtr inputPtr = Marshal.AllocHGlobal((int)size);
    IntPtr outputPtr = Marshal.AllocHGlobal((int)size);

    Marshal.StructureToPtr(lParam, inputPtr, false);

    WriteProcessMemory(processHandle, pPointer, inputPtr, size, out UIntPtr nNbBytesWritten);
    IntPtr resultPtr = SendMessage(hWnd, msg, wParam, pPointer);
    ReadProcessMemory(processHandle, pPointer, buffer, buffer.Length, out IntPtr nNbBytesRead);

    Marshal.Copy(buffer, 0, outputPtr, (int)size);
    T result = Marshal.PtrToStructure<T>(outputPtr);
    lParam = result;

    Marshal.FreeHGlobal(inputPtr);
    Marshal.FreeHGlobal(outputPtr);
    VirtualFreeEx(processHandle, pPointer, 0, AllocationType.Release);
    return resultPtr;
}

用法示例

设置给定树视图项目的复选框状态:

internal static void SetTreeNodeState(IntPtr treeViewHandle, IntPtr treeViewItemHandle, bool state)
{
    TVITEM tvItem = new TVITEM
    {
        mask = TVIF_STATE | TVIF_HANDLE,
        hItem = treeViewItemHandle,
        stateMask = TVIS_STATEIMAGEMASK,
        state = (uint)(state ? 2 : 1) << 12
    };

    Process process = Process.GetProcessesByName("ProcessName")[0];
    IntPtr ptr = SendMessage(process, treeViewHandle, TVM_SETITEMW, 0, ref tvItem);
}

获取给定树视图项目的复选框状态:

internal static bool GetTreeNodeState( IntPtr treeViewHandle, IntPtr treeViewItemHandle)
{
    TVITEM tvItem = new TVITEM
    {
        mask = TVIF_STATE | TVIF_HANDLE,
        hItem = treeViewItemHandle,
        stateMask = TVIS_STATEIMAGEMASK,
        state = 0
    };

    Process process = Process.GetProcessesByName("ProcessName")[0];
    IntPtr ptr = SendMessage(process, treeViewHandle, TVM_GETITEMW, 0, ref tvItem);
    if (ptr != IntPtr.Zero)
    {
        uint iState = tvItem.state >> 12;
        return iState == 2 ? true : false;
    }
    return false;
}

TVITEM:

[StructLayout(LayoutKind.Sequential)]
internal struct TVITEM
{
    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;
}