Использование GetOpenFile и GetKeyState в консольном приложении

Я испытываю странное поведение при использовании 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;
}

Код WaitForSpaceBar

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», и программа завершается.

Любые идеи относительно того, что происходит?


person Brian Davis    schedule 10.12.2013    source источник
comment
Вы не можете надежно использовать функции USER32 в консольном приложении. USER32 нацелен на подсистему GUI Windows. GetKeyState требует, чтобы приложение считывало ключевые сообщения из своей очереди сообщений. Очередь сообщений — это концепция приложения с графическим интерфейсом.   -  person IInspectable    schedule 11.12.2013
comment
Вместо этого попробуйте GetAsyncKeyState. GetKeyState возвращает состояние, которое было при обработке последнего сообщения, а не состояние, которое есть сейчас.   -  person Jonathan Potter    schedule 11.12.2013
comment
Я не могу воспроизвести проблему. Нажатие пробела обнаруживается у меня в обоих сценариях. Windows 7, VS 2010, сборки Debug и Release, 32- и 64-разрядные версии.   -  person Adrian McCarthy    schedule 11.12.2013


Ответы (2)


Функция GetKeyState бесполезна, если ваше сообщение не прокачивается.

Из документации:

Примечания

Состояние ключа, возвращаемое этой функцией, изменяется по мере того, как поток считывает ключевые сообщения из своей очереди сообщений. Состояние не отражает состояние уровня прерывания, связанного с оборудованием. Используйте функцию 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);     
    }
}
person manuell    schedule 11.12.2013
comment
@AdrianMcCarthy Это бит 0 для GetKeyState и бит 15 для GetAsyncKeyState. Спящий режим настоятельно рекомендуется в таком коде, остерегайтесь горящих процессоров! - person manuell; 11.12.2013
comment
Вы правы насчет бита. Я неправильно прочитал. Тем не менее, у меня без Sleep код работает нормально, так что я так и не понял, в чем проблема. - person Adrian McCarthy; 11.12.2013
comment
@AdrianMcCarthy, а какова загрузка процессора, если вы сидите здесь, не нажимая пробел? - person manuell; 11.12.2013
comment
Я не возражаю против перехода в спящий режим для снижения нагрузки на процессор. Я думал, что ваш ответ подразумевает, что Sleep вызывает перекачку сообщений и тем самым включает GetKeyState. Это не так. Код в вопросе у меня работает без изменений. - person Adrian McCarthy; 11.12.2013

Из документации MSDN похоже, что GetKeyState имеет смысл только тогда, когда ваш поток перекачивает сообщения и вызывает его в ответ на сообщение клавиатуры.

Состояние ключа, возвращаемое этой функцией, изменяется по мере того, как поток считывает ключевые сообщения из своей очереди сообщений.

Я подозреваю, что GetOpenFile запускает свой собственный поток, и этот поток каким-то образом становится основным потоком пользовательского интерфейса для вашего процесса (поскольку он единственный выполняет работу с графическим интерфейсом).

Если вы используете вызовы Windows API, вы также можете использовать что-то специфичное для консоли, например ReadConsole.

Обновление: я вставил код вопроса в VS 2010 и не могу воспроизвести проблему. Функция GetKeyState работает должным образом, независимо от того, был ли вызван GetOpenFileName.

person Adrian McCarthy    schedule 11.12.2013
comment
GetOpenFileName() отображает модальный диалог и, таким образом, обрабатывает диалоговые сообщения в том же потоке, который его вызывает. Он не создает свой собственный поток. - person Remy Lebeau; 11.12.2013
comment
Хм, не так быстро, он никогда не вызывал CoInitializeEx(). - person Hans Passant; 11.12.2013
comment
@HansPassant: вам не нужно инициализировать COM, чтобы использовать традиционные API-интерфейсы Win32. По крайней мере, я не могу найти никакой документации, в которой говорится, что вам это нужно, и у меня не было проблем с выполнением таких вещей в моем собственном коде без инициализации COM. - person Adrian McCarthy; 11.12.2013
comment
@RemyLebeau: Да, модальные диалоги режима запускаются в вызывающем потоке. Но это детали реализации. Взгляд с помощью отладчика во время диалога показывает, что создается несколько дополнительных потоков, включая пару потоков Win32, один из которых перекачивает сообщения. - person Adrian McCarthy; 11.12.2013
comment
Однако это не означает, что диалог использует потоки для обработки диалоговых сообщений или состояний клавиатуры. Эти дополнительные потоки обычно создаются оболочкой для собственной внутренней работы, особенно когда диалоговое окно работает в режиме проводника. В этом режиме GetOpenFileName() отображает объекты оболочки, которые реализованы как COM-объекты. Поэтому важно позвонить OleInitialize/Ex() или CoInitialize/Ex(). - person Remy Lebeau; 12.12.2013
comment
@RemyLebeau: это не означает, что диалог использует потоки для обработки диалоговых сообщений или состояний клавиатуры. Согласовано. Я просто сказал, что это возможно, и что есть доказательства того, что это может происходить. Дело в том, что это деталь реализации, и она может объяснить заявленное поведение. На самом деле я не могу воспроизвести заявленное поведение, поэтому я подозреваю, что в вопросе упущена важная деталь. - person Adrian McCarthy; 13.12.2013