将线程输入附加到目标进程后,SendInput无法工作

本文关键字:SendInput 工作 进程 目标 输入 线程 | 更新日期: 2023-09-27 18:22:42

我正在制作一个应用程序,该应用程序需要使用一个似乎没有实现UI Automation元素的程序的UI(Inspect.Exe只显示主窗格,没有显示子窗格)。

因此,我研究了实现所需功能的最佳方法,并找到了SendInput(),它显然是keybd_event()和mouse_event的新版本。

然而,由于它需要键盘聚焦,而且我负担不起将目标窗口设置为前台(以避免在运行时打扰用户),我一直在搜索,直到找到这个答案。我按照Skurmedel所说的做了,并将应用程序的线程连接到目标的窗口线程。但现在,即使我将Focus()设置为目标,然后再设置SendInput(),目标窗口也不会受到影响。

我的问题要么是"为什么这不起作用?"要么是"我做错了什么?",但我想一个代码示例会帮助解决这个问题:

线程处理程序类

class ThreadHandler
{
    #region P/Invoking and constants definition
    const uint WM_GETTEXT = 0x000D;
    [DllImport("user32.dll")]
    static extern IntPtr SetFocus(IntPtr hWnd);
    [DllImport("user32.dll")]
    private static extern int GetWindowThreadProcessId(IntPtr hWnd, uint lpdwProcessId = 0);
    delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
    [DllImport("user32.dll")]
    static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn,
        IntPtr lParam);
    [DllImport("user32.dll")]
    static extern bool AttachThreadInput(int idAttach, int idAttachTo, bool fAttach);
    [DllImport("kernel32.dll")]
    static extern int GetCurrentThreadId();
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, int wParam, StringBuilder lParam);
    #endregion
    public readonly string ProcessName, WindowName;
    protected readonly int TargetThreadID, CurrentThreadID;
    protected readonly IntPtr TargetWindowHandle;
    public ThreadHandler(string processName, string windowName)
    {
        CurrentThreadID = GetCurrentThreadId();
        ProcessName = processName;
        WindowName = windowName;
        object[] objs = GetWindowThread(processName, windowName);
        if (objs == null)
        {
            throw new ArgumentException("Could not find the specified process/window.");
        }
        TargetThreadID = (int)objs[0];
        TargetWindowHandle = (IntPtr)objs[1];
    }
    public ThreadHandler(string processName)
    {
        CurrentThreadID = GetCurrentThreadId();
        ProcessName = processName;
        var processes = Process.GetProcessesByName(ProcessName);
        if (processes.Length == 0)
        {
            throw new ArgumentException("Could not find the specified process.");
        }
        var appProc = processes[0];
        WindowName = appProc.MainWindowTitle;
        TargetThreadID = GetWindowThreadProcessId(appProc.MainWindowHandle);
        TargetWindowHandle = appProc.MainWindowHandle;
    }
    public bool AttachThreadInput()
    {
        return AttachThreadInput(CurrentThreadID, TargetThreadID, true);
    }
    public bool DetachThreadInput()
    {
        return AttachThreadInput(CurrentThreadID, TargetThreadID, false);
    }
    public void SetFocus()
    {
        SetFocus(TargetWindowHandle);
    }
    static object[] GetWindowThread(string processName, string windowName)
    {
        var processes = Process.GetProcessesByName(processName);
        if (processes.Length > 0)
        {
            //Fill a list of handles
            var handles = new List<IntPtr>();
            foreach (ProcessThread thread in processes[0].Threads)
                EnumThreadWindows(thread.Id,
                    (hWnd, lParam) => { handles.Add(hWnd); return true; }, IntPtr.Zero);
            //Create a stringbuilder to function as storage unit
            StringBuilder nameBuffer = new StringBuilder(64);
            foreach (var hWnd in handles)
            {
                //And finally compare the caption of the window with the requested name
                nameBuffer.Clear();
                SendMessage(hWnd, WM_GETTEXT, nameBuffer.Capacity, nameBuffer);
                if (nameBuffer.ToString() == windowName)
                {
                    return new object[2] { GetWindowThreadProcessId(hWnd), hWnd };
                }
            }
        }
        return null;
    }
}

应用的主要方法

static void Main(string[] args)
    {
        Console.WriteLine("Please input the name of the process to hook: ");
        string pName = Console.ReadLine();
        Console.WriteLine("Input the name of a specific window, or leave blank: ");
        string pWnd = Console.ReadLine();
        ThreadHandler threadHandler;
        try
        {
            if(!String.IsNullOrWhiteSpace(pWnd))
                threadHandler = new ThreadHandler(pName, pWnd);
            else
                threadHandler = new ThreadHandler(pName);
        }
        catch
        {
            Console.WriteLine("Error: " + pName +" does not seem to be running.");
            Console.ReadKey();
            return;
        }
        if (!threadHandler.AttachThreadInput())
        {
            Console.WriteLine("Error: The application tried to attach its Input Processing Mechanism to " + threadHandler.ProcessName + ", but failed.");
            Console.ReadKey();
            return;
        }
        Console.WriteLine("Input Processing Mechanism correctly attached to " + threadHandler.ProcessName + ".");
        threadHandler.SetFocus();
        InputSimulator.SimulateTextEntry("test"); //InputSimulator is a seemingly famous SendInput wrapper. Replacing this line with the code for a keystroke also doesn't work.
        Console.ReadLine();
        Console.WriteLine("Detaching Input Processing Mechanism.");
        threadHandler.DetachThreadInput();
    }

如果你能向我解释SendInput()的奥秘艺术,请提前感谢。

将线程输入附加到目标进程后,SendInput无法工作

确保将击键发送到的特定控件正确聚焦

您应该能够使用SetFocus将焦点放在您发送击键的控件上。

SendMessage和PostMessage也可以用于发送击键,但这是一种糟糕的做法,应该避免。

有关通过.NET.中的Forms类发送击键的信息,请查看System.Windows.Forms.SendKeys

在很多情况下,如果你不需要按键本身,你可以使用带有WM_SETTEXT的SendMessage更改窗口上的文本,如果这是你想要做的