C# 是否可以等待其他线程进程向其发送输入消息?

C# 是否可以等待其他线程进程向其发送输入消息?,c#,.net,windows,winapi,C#,.net,Windows,Winapi,我想可靠地模拟其他窗口的用户输入。我使用SendInput进行此操作,但我需要等到目标应用程序处理输入后再发送更多。据我所知,SendInput,不管它的名字是什么,都会将消息发送到队列,并且不会等到消息被处理 我的尝试基于这样一种想法,即至少要等到消息队列清空一次。由于我不能直接检查其他线程的消息队列(至少我不知道怎么做),我使用AttachThreadInput将目标线程的队列附加到此线程的队列,然后使用peek消息进行检查 为了检查功能,我使用带有一个窗口和一个按钮的小应用程序。单击按钮时

我想可靠地模拟其他窗口的用户输入。我使用
SendInput
进行此操作,但我需要等到目标应用程序处理输入后再发送更多。据我所知,
SendInput
,不管它的名字是什么,都会将消息发送到队列,并且不会等到消息被处理

我的尝试基于这样一种想法,即至少要等到消息队列清空一次。由于我不能直接检查其他线程的消息队列(至少我不知道怎么做),我使用
AttachThreadInput
将目标线程的队列附加到此线程的队列,然后使用
peek消息
进行检查

为了检查功能,我使用带有一个窗口和一个按钮的小应用程序。单击按钮时,我调用
Thread.Sleep(15000)
有效地停止消息处理,从而确保接下来15秒的消息队列不能为空

代码如下:

    public static void WaitForWindowInputIdle(IntPtr hwnd)
    {
        var currentThreadId = GetCurrentThreadId();
        var targetThreadId = GetWindowThreadProcessId(hwnd, IntPtr.Zero);

        Func<bool> checkIfMessageQueueIsEmpty = () =>
        {
            bool queueEmpty;
            bool threadsAttached = false;

            try
            {
                threadsAttached = AttachThreadInput(targetThreadId, currentThreadId, true);

                if (threadsAttached) 
                {
                    NativeMessage nm;
                    queueEmpty = !PeekMessage(out nm, hwnd, 0, 0, RemoveMsg.PM_NOREMOVE | RemoveMsg.PM_NOYIELD);
                }
                else
                    throw new ThreadStateException("AttachThreadInput failed.");
            }
            finally
            {
                if (threadsAttached)
                    AttachThreadInput(targetThreadId, currentThreadId, false);
            }
            return queueEmpty;
        };

        var timeout = TimeSpan.FromMilliseconds(15000);
        var retryInterval = TimeSpan.FromMilliseconds(500);
        var start = DateTime.Now;
        while (DateTime.Now - start < timeout)
        {
            if (checkIfMessageQueueIsEmpty()) return;
            Thread.Sleep(retryInterval);
        }
    }

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool PeekMessage(out NativeMessage lpMsg,
                                   IntPtr hWnd,
                                   uint wMsgFilterMin,
                                   uint wMsgFilterMax,
                                   RemoveMsg wRemoveMsg);

    [StructLayout(LayoutKind.Sequential)]
    public struct NativeMessage
    {
        public IntPtr handle;
        public uint msg;
        public IntPtr wParam;
        public IntPtr lParam;
        public uint time;
        public System.Drawing.Point p;
    }

    [Flags]
    private enum RemoveMsg : uint
    {
        PM_NOREMOVE = 0x0000,
        PM_REMOVE = 0x0001,
        PM_NOYIELD = 0x0002,
    }

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);

    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId);

    [DllImport("kernel32.dll")]
    private static extern uint GetCurrentThreadId();
publicstaticvoid WaitForWindowInputIdle(IntPtr hwnd)
{
var currentThreadId=GetCurrentThreadId();
var targetThreadId=GetWindowThreadProcessId(hwnd,IntPtr.Zero);
Func checkIfMessageQueueIsEmpty=()=>
{
布尔队列为空;
bool threadsAttached=错误;
尝试
{
threadsAttached=AttachThreadInput(targetThreadId,currentThreadId,true);
如果(螺纹连接)
{
本地消息nm;
queueEmpty=!PeekMessage(out nm,hwnd,0,0,RemoveMsg.PM_NOREMOVE | RemoveMsg.PM_NOYIELD);
}
其他的
抛出新的ThreadStateException(“AttachThreadInput失败”);
}
最后
{
如果(螺纹连接)
AttachThreadInput(targetThreadId,currentThreadId,false);
}
返回队列为空;
};
var超时=TimeSpan.From毫秒(15000);
var retryInterval=TimeSpan.From毫秒(500);
var start=DateTime.Now;
while(DateTime.Now-开始<超时)
{
if(checkIfMessageQueueIsEmpty())返回;
睡眠(重试区间);
}
}
[DllImport(“user32.dll”)]
[返回:Marshallas(UnmanagedType.Bool)]
静态外部布尔消息(out NativeMessage lpMsg,
IntPtr hWnd,
uint wMsgFilterMin,
uint wMsgFilterMax,
RemoveMsg(移动Msg);
[StructLayout(LayoutKind.Sequential)]
公共结构NativeMessage
{
公共IntPtr句柄;
公共信息;
公共IntPtr wParam;
公共IntPtr LPRAM;
公共时间;
公共系统图点p;
}
[旗帜]
私有枚举移除消息:uint
{
PM_NOREMOVE=0x0000,
PM_REMOVE=0x0001,
PM_NOYIELD=0x0002,
}
[DllImport(“user32.dll”,SetLastError=true)]
私有静态外部bool AttachThreadInput(uint-idAttach、uint-idAttachTo、bool-fAttach);
[DllImport(“user32.dll”)]
私有静态外部单元GetWindowThreadProcessId(IntPtr hWnd,IntPtr processId);
[DllImport(“kernel32.dll”)]
私有静态外部uint GetCurrentThreadId();
现在,由于某种原因,它不起作用了。它总是返回消息队列为空。是否有人知道我做错了什么,或者是否有其他方法来实现我的需求

编辑:关于我首先需要等待的原因。 如果立即模拟其他动作而不暂停,我会遇到只部分输入文本的情况。例如,当焦点位于某个文本框时,我通过SendInput模拟“abcdefgh”,然后点击鼠标。我得到的是“abcde”键入,然后单击。如果我在SendInput之后放置Thread.Sleep(100)——这个问题在我的机器上是不可再现的,但在硬件较低的VM上很少可再现。所以我需要更可靠的方法来等待正确的时间

我对可能发生的事情的推测与以下方面有关:

将虚拟密钥消息转换为字符消息。字符消息被发布到调用线程的消息队列中,以便在线程下次调用GetMessage或PeekMessage函数时读取


因此,我调用
SendInput
来获取“abcdefgh”——一组发送到线程队列的输入消息。然后它开始按FIFO顺序处理这些消息,翻译“abcde”,并将每个字符的消息发布到队列的尾部。然后模拟鼠标点击,并在“abcde”字符消息后发布。然后翻译完成,但“fgh”的翻译消息在鼠标单击后发生。最后,应用程序会看到“abcde”,然后单击,然后单击“fgh”-显然是去了某个错误的地方…

这是UI自动化中的常见需求。事实上,它是由

如果使用System.Windows.Automation命名空间来实现这一点,您会很满意。然而,这种方法很容易自己实现。您可以从参考源或反编译器中查看。他们是怎么做到的,这让我有点吃惊,不过看起来很结实。它只查看拥有该窗口的UI线程的状态,而不是试图猜测消息队列是否为空。如果它被阻止,并且没有等待内部系统操作,那么您会收到一个非常强烈的信号,即线程正在等待Windows传递下一条消息。我是这样写的:

using namespace System.Diagnostics;
...
    public static bool WaitForInputIdle(IntPtr hWnd, int timeout = 0) {
        int pid;
        int tid = GetWindowThreadProcessId(hWnd, out pid);
        if (tid == 0) throw new ArgumentException("Window not found");
        var tick = Environment.TickCount;
        do {
            if (IsThreadIdle(pid, tid)) return true;
            System.Threading.Thread.Sleep(15);
        }  while (timeout > 0 && Environment.TickCount - tick < timeout);
        return false;
    }

    private static bool IsThreadIdle(int pid, int tid) {
        Process prc = System.Diagnostics.Process.GetProcessById(pid);
        var thr = prc.Threads.Cast<ProcessThread>().First((t) => tid == t.Id);
        return thr.ThreadState == ThreadState.Wait &&
               thr.WaitReason == ThreadWaitReason.UserRequest;
    }

    [System.Runtime.InteropServices.DllImport("User32.dll")]
    private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int pid);
使用名称空间系统。诊断;
...
公共静态bool WaitForInputIdle(IntPtr hWnd,int timeout=0){
int-pid;
int tid=GetWindowThreadProcessId(hWnd,输出pid);
如果(tid==0)抛出新ArgumentException(“未找到窗口”);
var tick=Environment.TickCount;
做{
如果(IsThreadIdle)(pid