c++windowswinapi

What is the difference between using GetAsyncKeyState() by checking with its return value as -32767 and 0x8000?


I am extremely confused by the sheer amount of how many different answers there are about GetAsyncKeyState(), I know what it does, but the confusing part is it's return value. I've seen many different examples on how people use this, but the main two are the following:

First:

if (GetAsyncKeyState(KEY) == -32767)

Second:

if (GetAsyncKeyState(KEY) & 0x8000)

The first confusing bit about this is that when using the Windows Calculator and converting -32767 to a hexadecimal, it is equal to 0x8001, so already they are not comparing the same thing. As far as I am aware (and please correct me if I understood incorrectly), 0x8000 is checking that the key is currently pressed but 0x8001 is doing the same while at the same time while telling you that it had been pressed after the last time GetAsyncKeyState() was called on that key.

Here is some sample code of the second confusion/problem with these different ways of essentially doing the same thing, yet at the same time achieving different results...

#include <iostream>
#include <Windows.h>
int main() {
        
            while (true) {
                for (int KEY = 8; KEY <= 190; KEY++)
                {
                    if (GetAsyncKeyState(KEY) == -32767) {
                        std::cout << char(KEY) << std::endl;
                    }
                }
            }
            return 0;
        }

As you can see in the code above, I am using the "First" option to check if the key has been pressed. With this option no matter how fast you type (only tested under 120wpm) it won't repeat letters, as long as you are not holding them for a strangely longer time than a normal person holds a letter to type, if you do hold them you get the expected behaviour of it just repeating it (printing it out to the screen). It works perfectly when typing at normal speeds or fast speeds up to 120wpm tested, no repeated keys (I will explain what I mean by 'repeated keys'). Here is some code using the "Second" way...

#include <iostream>
#include <Windows.h>    
int main() {

    while (true) {
        for (int KEY = 8; KEY <= 190; KEY++)
        {
            if (GetAsyncKeyState(KEY) & 0x8000) {
                std::cout << char(KEY) << std::endl;
            }
        }
    }
    return 0;
}

With this one I run into a problem where no matter how fast I tap the key it will repeat a few times at minimum, you can try it yourself if you want to see what I mean, but as an example: If I were to type H once this would be the result for First and Second respectively:

First:
H
Second:
H
H
H
H
H
.....etc maybe a few more times maybe one or two less times..... highly inconsistent

Why do they have very different outcomes even though in theory they should do the same thing (even though -32767 is not the same as 0x8000, it is the thing I've more commonly seen out there apart from 0x8000).


Solution

  • The function GetAsyncKeyState returns a SHORT, which is a 16-bit signed integer.

    The highest bit of the returned value represents whether the key is currently being held down. You can test this bit with GetAsyncKeyState(KEY) & 0x8000.

    The lowest bit also has a meaning: It specifies whether the key has been pressed since the last call to GetAsyncKeyState. But this feature is only provided for backwards-compatibility with 16-bit Windows and should not be relied upon for modern 32-bit or 64-bit applications. Therefore, it should be ignored. See the official Microsoft documentation for this function for further information. If you really want to test this bit (which you should generally not do), then you could do it with the expression GetAsyncKeyState(KEY) & 0x0001.

    The expression

    GetAsyncKeyState(KEY) == -32767
    

    will only be true if the highest bit and the lowest bit are set, and all other bits are zero.

    This is bad programming practice. Even if this code currently works in all version of Microsoft Windows, it should not be assumed that all other bits will be zero, as the meaning of the other bits is undefined. For example, Microsoft may assign a meaning to one of these currently unused bits in a future version of Windows. Therefore, as a general rule, only the defined bits should be tested.

    As stated above, there are only two bits defined, but one is unreliable and only provided for backwards compatibility; it should therefore not be used. For this reason, only the highest bit should be tested with the expression GetAsyncKeyState(KEY) & 0x8000.

    The reason why your first program behaves differently from your second program is that your first program requires both bits to be set, whereas your second program requires only one bit to be set and ignores the other bit. In other words, your first program only prints something if GetAsyncKeyState reports that the button is currently being held down AND was pressed since the previous call to the function, whereas the second program only requires that the button is currently being held down.

    It is not quite clear what the Microsoft documentation means by "was pressed since the previous call". It probably means whether a WM_KEYDOWN message was generated since the previous function call. That is probably why your first program only prints something when you start holding the button, instead of printing something continuously while the button is being held down.

    Even if your first program does exactly what you want, for the reasons stated above, it is unreliable. Therefore, if you only want one notification per keystroke, I recommend that you do not use GetAsyncKeyState, but instead process WM_KEYDOWN messages in your message loop.