将线程输入附加到目标进程后,SendInput 不起作用

Posted

技术标签:

【中文标题】将线程输入附加到目标进程后,SendInput 不起作用【英文标题】:SendInput not working after attaching thread input to a target process 【发布时间】:2013-07-17 11:53:22 【问题描述】:

我正在制作一个需要与似乎没有实现 UI 自动化元素的程序的 UI 配合使用的应用程序(Inspect.Exe 仅显示主窗格,没有子窗格)。

所以我研究了实现我需要的功能的最佳方法是什么,发现了 SendInput(),它显然是 keybd_event() 和 mouse_event() 的更新版本。

但是,由于它需要键盘焦点,而且我无法将目标窗口设置为前台(以避免在它运行时打扰用户),我一直在搜索,直到找到this answer。我按照 Skurmedel 所说的做了,并将我的应用程序线程加入了目标的窗口线程。但是现在,即使我 SetFocus() 到目标然后 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() 的神秘艺术,请提前致谢。

【问题讨论】:

你最后是怎么解决的?我正在使用 PostMessage 将密钥发送到窗口。 @LifeH2O 遗憾的是,我认为我从来没有解决过它。要使用 PostMessage,您的应用程序需要运行 Windows 事件循环,但 IIRC 如果您尝试与之交互的窗口没有焦点,则它根本不起作用。 【参考方案1】:

确保您向其发送击键的特定控件的焦点正确。

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

SendMessage 和 PostMessage 也可用于发送击键,但应避免使用 BAD PRACTICE。

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

在很多情况下,如果您不需要按键本身,您可以使用 SendMessage 和 WM_SETTEXT 更改窗口上的文本(如果您希望这样做)。

【讨论】:

我正在使用记事本进行测试,但显然它不起作用。我会尝试使用 SendKeys 并报告。编辑:我认为你以一种意想不到的方式帮助了我。调用 Send 方法会产生以下异常: SendKeys 无法在此应用程序中运行,因为该应用程序未处理 Windows 消息。更改应用程序以处理消息,或使用 SendKeys.SendWait 方法。我认为这是问题所在。 Welp,看来我是对的。我刚刚将我的代码移植到一个 Windows 窗体项目并且它工作。我会感谢您的回答,但是,SetFocus 不仅设置键盘焦点,而且还将目标窗口置于前台。猜猜我又回到零根了。

以上是关于将线程输入附加到目标进程后,SendInput 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Apex 数据加载向导完成后如何运行附加进程

SendInput模拟键盘输入的问题

如何将新的 JVM 附加到生成的 Python 进程?

Windows调试1.WinDbg基本使用-异常基础知识

如何本机附加到python进程?

在多显示器环境中计算 SendInput() 的归一化坐标