【问题标题】:How can I recognize keyboard input in C++ with Windows API using GetMessage() in a console application?如何在控制台应用程序中使用 GetMessage() 通过 Windows API 识别 C++ 中的键盘输入?
【发布时间】:2014-01-15 14:13:02
【问题描述】:

首先是大局:我正在尝试通过 Windows 7(64 位)上的控制台程序同步两个外部设备。也就是说,Device 1 应该触发 Device 2。(好奇的是,Device 1 是 NI USB-6525,我正在使用它的 Change Detection 功能。)

我在 while 循环中使用 GetMessage() 来等待由设备 1 触发的消息。(由于我没有为该硬件编写软件,我无法改变我必须阅读的事实此消息。)一旦读取该消息,它就是dispatched。此分派导致对使用设备 2 执行测量并将measurementComplete 设置为true 的函数进行回调。一旦回调返回,循环结束。然后我执行清理,应用程序退出。

问题是,用户还应该能够在等待消息时中止,例如通过按键。我尝试检查接收到的消息是来自其他线程还是来自键盘,但它永远无法识别键盘输入:

#include <cstdio>
#include <windows.h>

using namespace std;

bool measurementComplete = false;
BOOL bRet = 0;
MSG threadMessage;

signed long __cdecl callbackFunction(type1 param1, type2 param2) // (pseudo args)
{
    measurementComplete = 1;
    performMeasurement();
    return 0;
}

int main (int argc, char* argv[])
{
    /* Load libraries, set up hardware,
       call NI function that looks for signal from Device 1 in separate thread
       and sends a Windows message upon signal detection */

    while (!measurementComplete) { // measurement has not yet been performed
        // wait for message
        puts("Waiting for message.");
        if ((bRet = GetMessage(&threadMessage, NULL, 0, 0)) != 0) { // if message available
            if (bRet == -1) {
                puts("Error: GetMessage() returned -1. The program will now exit.");
                break;
            } else {
                DispatchMessage(&threadMessage);
                if ((TranslateMessage(&threadMessage)) != 0) // if character message (indicates key press)
                    break;
            }
        }
        puts("Message handled.");
    }

/* perform cleanup */
return 0;
}

我是 Windows API 的新手,所以对它不是很熟悉。我正在 Code::Blocks 13.12 IDE 中编程并使用 GCC。我没有 MFC 或任何类似 Microsoft 的付费产品,所以我不能使用 MFC 函数或类。不幸的是,我发现的许多类似问题的答案都包含 MFC 函数。

根据我对这个问题的研究,键盘消息似乎没有可以访问的窗口。我尝试创建一个仅消息窗口,例如描述的here,但在调用 CreateWindowEx() 时我总是得到Error 18: There are no more files。如果需要,我可以提供该代码,但我什至不确定我是否真的需要创建一个窗口。当我运行FindWindowEx(HWND_MESSAGE, NULL, NULL, NULL); 时,我可以看到这样一个窗口已经存在。我不知道该函数找到的窗口是否是由我的二进制文件以某种方式自动创建的窗口,或者它是否是由我计算机上运行的另一个程序创建的仅消息窗口。另外,我不是已经有一个窗口(控制台窗口)了吗?

关于如何在我的控制台应用程序中将键盘输入定向到消息传递系统,是否有人有任何提示?任何帮助将不胜感激。

【问题讨论】:

  • 这是不可能的。控制台窗口由一个完全不同的可执行文件管理,你不能乱用它。使用 ReadConsoleInput() 或 _getch()。
  • @HansPassant:为什么不回答?仅仅两行的解释和提示仍然是完全有效的答案,即使这么短;)
  • 您好,感谢您的回复。不幸的是,GetMessage() 是一个阻塞函数,所以我想对信号事件或使用该函数的按键做出反应。我可以在循环中使用 PeekMessage() 代替,但那样我会忙于闲置。我可以在那个循环中暂停一段时间,但立即检测信号消息是我同步的关键。我现在明白我将无法检测到控制台窗口的输入;那么为此目的创建我自己的(仅消息)窗口会更有意义吗?或者有没有其他我没有考虑过的处理方式?
  • @HansPassant 那么,kbhit()PeekMessage()? (或者使用普通消息泵切换到 Windows 可执行文件?)

标签: c++ windows winapi console


【解决方案1】:

确实,“控制台应用”没有“窗口”,因此它们无法接收任何消息(以简单的方式)。

如果我没记错的话,您实际上可以编写代码来创建一个带有消息泵的不可见窗口,以便发送所有“消息”。 *)

但是,您需要研究、编写和维护大量与窗口相关的代码。从“Windows 应用程序”开始并在启动时隐藏其窗口要容易得多。您将从项目代码模板中免费获得所有 messagepump 代码。如果您喜欢这种 UI 样式,您还可以从窗口应用程序分配和打开控制台窗口。

*) 您可以启动一个新的窗口应用程序项目并窥视内部,以查看窗口是如何注册到将处理消息的“wndproc”例程中的。在您的 main() 代码的某个时刻,您将需要执行与空的“窗口”项目执行的操作相同的操作。另外,请记住,您将需要实际进入调度循环,并且这阻塞您的线程,直到调度循环关闭(使用 PostQuitMessage 等) - 通常当应用程序获得关闭“信号”时(注意通常是你的代码必须监听这些“信号”并做出反应,当你决定退出时,你必须致电PostQuitMessage

你无法逃避,这里没有魔法!必须有一些东西来收听消息。如果您希望消息通过并且还有一些并行工作,您将需要:

  • 两个线程,一个用于消息泵,一个用于作业,在它们之间进行安全通信时需要进行所有同步操作
  • 或者,只有一个线程运行消息泵,然后重写作业以通过消息与通知异步,因此任何通知/回调也由消息循环处理

选择你最不喜欢的:)

【讨论】:

  • 感谢您的回复。我不担心消息循环(如果这就是“调度循环”阻塞的意思。callbackFunction() 中的所有内容实际上都在第一个线程中运行(与我发布的所有其他代码相同的线程),那就是完美,因为我只希望它在程序退出之前运行一次。按照您的建议,我刚刚在 Code::Blocks 中创建了一个新的“Win32 GUI 项目”,并正在审查那里的代码。
  • @Cerran 我不得不说这个“退出前运行一次”让我感到不安。 Windows 消息循环旨在处理所有消息。例如,在普通的 Windows 应用程序中,有与退出和清理有关的 WM_QUIT 和 WM_EXIT 消息。只是发出say exit(0) 可能是程序异常终止。
  • @ebyrob 好的,我仍然习惯于整个消息传递,但这是我当然可以实现的。我想我可以将我的清理代码移动到回调的末尾,然后在我的回调返回之前添加 PostQuitMessage()。我的意思是我只希望我的回调函数(执行测量)在程序退出之前运行一次。将来,我可能会对其进行更改以使其更加用户友好,但我希望首先让它以这种方式工作,作为同步的概念验证。
  • 我也对“运行一次”的事情感到非常困惑。要么你误解了某些东西,要么你试图实现一些“不自然”的东西。至于“调度循环”,我使用了一个恰当的术语。调度循环和消息循环是一回事。这是因为循环的结构是这样的:loop{read message, translate it, dispatch it}。最重要的部分是“read”和“dispatch”,这形成了备用名称。 “调度”是 wndproc 处理程序链检查消息并根据消息类型调用各种处理程序的阶段。
  • @quetzalcoatl 为残疾计算机用户和 GUI 测试人员提供了一个更新的界面,可以获取此类信息(至少在 C# 中)。但是,在这个级别上,我认为“只需旋转并使用kbhit()”会胜出。
【解决方案2】:

这是从 MSDN 站点获取的正常消息循环:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644928%28v=vs.85%29.aspx

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{ 
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }
} 

忽略BOOL 在这里发挥作用的值不是true 和false 的可怕事实,并注意GetMessage() 的返回是消息循环何时退出的最外层控制。这对于 Win32 来说是很自然的。在其周围放置其他东西(例如测试是否进行了某些测量)会破坏这种简单的模式。

注意:正如前面所讨论的,让您的DispatchMessage() 实现引发将启动应用程序退出的消息是完全可以接受的(也是预期的)。 (比如在主线程上发出PostQuitMessage()

最后:在 Win32 应用程序中没有过滤器的情况下,您肯定会收到大量来自 GetMessage() 的键盘消息。这并不是说TranslateMessage() 和/或DispatchMessage() 默认实现可能不会进一步隐藏它们。

立即检测信号消息是我同步的关键

从任何接近实时的角度来看,这似乎都是冒险的,因为那些显然是您唯一界面的 Windows 消息会与任何和所有 GUI 消息排队 /strong> 在系统上。

基本上,时机很难:How to make thread sleep less than a millisecond on Windows

【讨论】:

  • 如果 cerran 想要尽可能“实时”,那么我很确定,即 DirectX DInput 及其回调比 win-messages 有更好的时机。另外,让我们检查一下零假设:cerran 想要精确地同步测量keyboard?就像三个人在三台机器上同时按空格并获得 3 个读数?除非您经过大量训练(Quake 等;)),否则约 150-200 毫秒的典型快速反射延迟仍然比上下文切换和所有获胜消息要差得多。..
  • @quetzalcoatl 我认为他没有使用键盘进行同步。 2 个位置的 2 个设备坐在那里。一个人触发一个信号,该信号由于某种原因被转换为 WINDOWS 事件。另一台设备希望立即通过一些 API 通知这发生了。我相信键盘事件只是一个辅助输入,可以取消整个过程或修改它。
  • 你对情况的理解是正确的,ebyrob。键盘不用于同步;我想用它以正常方式退出程序。目前退出程序只有两种方式:从Device 1发送信号或ctrl+c。
  • 我同意我打破消息循环的设计对于这样的应用程序来说显然是不正常的。我还不知道如何让接收到诸如 WM_KEYDOWN 之类的消息触发 PostQuitMessage 的执行。此外,这样做似乎需要第二个线程,而且我没有多线程方面的经验,所以这是另一个需要克服的挑战。不过,我非常感谢您的指导,明天我会看看是否可以进一步使用模板 Win32 应用程序代码。不幸的是,我今天不能再访问代码了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-29
  • 2020-01-21
  • 1970-01-01
  • 1970-01-01
  • 2015-02-08
  • 1970-01-01
  • 2011-11-23
相关资源
最近更新 更多