【问题标题】:Using GetOpenFile and GetKeyState in console application在控制台应用程序中使用 GetOpenFile 和 GetKeyState
【发布时间】:2013-12-10 23:57:33
【问题描述】:

在控制台应用程序中使用 GetKeyState(或 GetAsyncKeyState,就此而言)时,我遇到了奇怪的行为。该应用程序的一个方面是要求用户使用 GetFileOpen 打开文件。在程序结束时,GetKeyState 会监控空格键的状态。每当按下空格键时,GetKeyState(或 GetAsyncKeyState)函数从不设置高位。如果我不调用 GetOpenFile 然后监视 GetKeyState,一切都会按预期工作。

这是两个基本场景。

场景 1:

#include <windows.h>
int main(int argc, char *argv[])
{
    char filename[ 512 ] = {0};
    OPENFILENAME ofn = {0};
    int filenameSize = 512;
    char title[1000] = {0};

    strcpy(title, "Open File");

    ZeroMemory(&ofn, sizeof(OPENFILENAME));

    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = null;
    ofn.lpstrFile = filename;
    ofn.nMaxFile = filenameSize;
    ofn.lpstrFilter = "All files (*.*)\0*.*\0\0";
    ofn.nFilterIndex = 1;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.lpfnHook = NULL;
    ofn.lpstrTitle = title;
    ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

    GetOpenFileName(&ofn);  // filename obtained

    WaitForSpaceBar(); // return value's upper bit is never set for 
                       // GetKeyState(VK_SPACE);
    return 0;
}

场景 2:

int main(int argc, char *argv[])
{
    WaitForSpaceBar(); // returns immediately after spacebar is pressed
    return 0;
}

WaitForSpace条形码

void WaitForSpaceBar()
{
#define KEY_PRESSED_FLAG 1
    SHORT spacePressed = GetKeyState(VK_SPACE);

    printf("\nPress spacebar to continue...\n");
    while (!(spacePressed & KEY_PRESSED_FLAG))
    {
        Sleep(1);
        spacePressed = GetKeyState(VK_SPACE);

        // for debugging purposes only
        printf("spacePressed = 0x%04x\n", spacePressed);     
    }
}

无论我按多少次空格键,第一个场景都会无限期地输出“spacePressed = 0x0000”。

第二种情况输出“spacePressed = 0x0000”,直到实际按下空格键。按下后,输出为“spacePressed = 0xffffff81”,程序终止。

对正在发生的事情有什么想法吗?

【问题讨论】:

  • 您无法在控制台应用程序中可靠地使用 USER32 函数。 USER32 针对 Windows 的 GUI 子系统。 GetKeyState 要求应用程序从其消息队列中读取关键消息。消息队列是一个 GUI 应用程序概念。
  • 改用GetAsyncKeyStateGetKeyState 返回处理最后一条消息时的状态,而不是现在的状态。
  • 我无法重现该问题。在这两种情况下,我都检测到空格键按下。 Windows 7,VS 2010,调试和发布版本,32 位和 64 位。

标签: winapi console-application


【解决方案1】:

如果您不发送消息,GetKeyState 函数将毫无用处。

来自documentation

备注

从这个函数返回的键状态随着线程读取而改变 来自其消息队列的关键消息。状态不反映 与硬件相关的中断级状态。使用 GetAsyncKeyState 函数来检索该信息。

为您的WaitForSpaceBar 函数使用以下代码(注意KEY_PRESSED_FLAG 的新值):

void WaitForSpaceBar()
{
    SHORT spacePressed = GetAsyncKeyState(VK_SPACE);
#define KEY_PRESSED_FLAG 0x8000
    printf("\nPress spacebar to continue...\n");
    while (!(spacePressed & KEY_PRESSED_FLAG))
    {
        Sleep(1);
        spacePressed = GetAsyncKeyState(VK_SPACE);

        // for debugging purposes only
        printf("spacePressed = 0x%04x\n", spacePressed);     
    }
}

【讨论】:

  • @AdrianMcCarthy GetKeyState 的位 0 和 GetAsyncKeyState 的位 15。强烈建议在此类代码中使用睡眠,小心烧毁 CPU!
  • 你说得对。我看错了。尽管如此,没有睡眠,代码对我来说很好,所以我仍然不清楚问题是什么。
  • @AdrianMcCarthy,如果你坐在这里不占用空间,CPU 使用率是多少?
  • 我没有任何争论可以通过休眠来减少 CPU 负载。我认为您的回答暗示睡眠会导致消息泵送,从而启用 GetKeyState。事实并非如此。问题中的代码无需更改即可为我工作。
【解决方案2】:

MSDN documentation 看来,GetKeyState 似乎仅在您的线程正在抽取消息并调用它以响应键盘消息时才有意义。

当线程从其消息队列中读取关键消息时,此函数返回的关键状态会发生变化。

我怀疑 GetOpenFile 会启动自己的线程,并且该线程会以某种方式成为您进程的主 UI 线程(因为它是唯一执行 GUI 工作的线程)。

如果您使用的是 Windows API 调用,您不妨使用特定于控制台的东西,例如 ReadConsole

更新:我将问题代码粘贴到 VS 2010 中,无法重现问题。无论是否调用 GetOpenFileName,GetKeyState 函数都按预期工作。

【讨论】:

  • GetOpenFileName() 显示一个模态对话框,因此在调用它的同一线程中处理对话框消息。它不会创建自己的线程。
  • 嗯,没那么快,他从来没有调用过 CoInitializeEx()。
  • @HansPassant:您无需初始化 COM 即可使用传统的 Win32 API。至少,我找不到任何说明您需要这样做的文档,而且我在自己的代码中执行此类操作也没有任何问题,而无需初始化 COM。
  • @RemyLebeau:是的,模式模式对话框在调用线程上运行。但这是一个实现细节。在对话框中查看调试器会发现创建了几个额外的线程,包括几个 Win32 线程,其中一个正在发送消息。
  • 但这并不意味着对话框使用线程来处理对话框消息或键盘状态。这些额外的线程通常由 Shell 创建用于其自身的内部工作,尤其是当对话框在 Explorer 模式下运行时。在该模式下,GetOpenFileName() 显示 Shell 对象,这些对象被实现为 COM 对象。所以打电话给OleInitialize/Ex()CoInitialize/Ex() 很重要。
猜你喜欢
  • 1970-01-01
  • 2017-07-04
  • 2010-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多