我正在编写一个使用Win32 API处理击键事件的库。每次按键发生时,几乎同时发生两个单独的事件(this和this)。在我的库中,我有两个单独的线程,每个线程对应一种事件(钩子和原始)。
线程1(挂钩)在线程2(原始)上等待,以便在它继续之前获取其数据。它目前使用类似屏障的机制来实现这一目标。因此,两个事件必须在实际处理之前进入。到目前为止一切都很好。
但是有一个问题(当然)。在一个理想的世界里,我保证会一直得到这两个事件。不幸的是,由于我不明白的原因,有时Windows决定不向我发送这两个事件中的一个(通常情况下,另一个应用程序暂时正在调整输入)。因此,如果我键入“Hello world”,则线程1可能正在处理“H”,而线程2可能从未得到“H”事件,并跳到“e”。因此事件不同步,所有地狱都破裂了。
基本上我想要的是:我想以一种有意义的方式配对事件。如果线程1获得“H”事件,并且线程2获得“e”,它应该(1)尝试等待正确的“H”事件,或者(2)超时(是的,线程1可以正常失败)如果必须)。由于我知道事件应该在某个时间窗口内进入,我想这使得它成为一个实时编程任务。
我对实时编程一无所知。是否已有解决方案/数据结构?如果是这样,他们是什么?如果没有,那么这种问题的一般方法是什么(保持两个暂时关闭的事件同步)?
感谢。
答案 0 :(得分:1)
我不是win32的专家,但我不禁觉得,如果你输掉了比赛,在任何情况下都没有什么是完全可靠的。根据我的理解,你试图根据原始的物理键盘来控制键盘事件的位置(即,它们去哪个窗口) - 也许最好不要过滤事件,而是先将它们注入第一个的地方。
也就是说,注册一个只删除所有注入事件的low-level keyboard hook。现在使用原始键盘事件处理程序将模拟的WM_KEYUP / WM_KEYDOWN消息注入最终应该接收它们的任何窗口。
但是,如果不这样做,我们需要(以某种方式)同步WM_INPUT和WM_KEY *事件流。我假设你已经解决了如何在你的'master'进程中注入的钩子DLL和WM_INPUT监听器之间共享数据。我们首先定义一个cooked-keystream互斥锁。处理WM_KEY *钩子的应用程序将在进行进一步处理之前立即使用此互斥锁,以避免与可能也在处理密钥的其他进程竞争。
您的WM_INPUT处理进程可以开始写入共享内存环缓冲区(由命名的互斥锁同步)按顺序通过WM_INPUT接收的密钥代码。每个键码都应该有一个时间戳; WM_KEY *钩子将忽略旧的密钥代码。 WM_KEY *处理程序将首先检查它们的事件是否与缓冲区中最旧的非过期事件匹配;如果是,则应向主进程发送通知,并且挂钩将执行所需的任何处理。没问题。
如果环形缓冲区中没有数据,则可以使用命名的信号量进行休眠。信号量的计数应该是环形缓冲区中剩余条目数的乐观计数;获取计数使进程有权利和义务从缓冲区中释放一个项目(如果存在),但如果缓冲区为空,则进程必须丢弃环形互斥锁并返回等待信号量。钩子程序然后可以在互斥锁上等待(短!)超时;如果超时到期,挂钩应认为存在异步,并继续正常处理。然后让钩子在短时间内禁用自身可能是一个好主意,以避免减慢快速键入速度。
如果钩子从环形缓冲区读取一个与它发送的击键不匹配的键码,那么就会出现一个desync。处理这个问题可能取决于你在做什么;例如,如果您正在实现一个热键系统,一种方法是简单地删除所有原始事件,并将钩子转换为纯粹的传递,直到所有键盘都在一段短时间内保持静音。
请记住,由于您正在处理未同步的线程,每个线程都会看到多个不同步的事件流的不同切片,因此desyncs始终是一个问题。事件注入可能仍然是避免这种情况的好方法,因为它为您提供了一个可以提供事件排序的单一同步点。