Gamepad and Keyboard Input
So what's with all that "#define" and "typedef" mumbo-jumbo there?
You may have found yourself confused when looking at this code:
#define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState)
typedef X_INPUT_GET_STATE(x_input_get_state);
X_INPUT_GET_STATE(XInputGetStateStub)
{
return(0);
}
global_variable x_input_get_state *XInputGetState_ = XInputGetStateStub;
#define XInputGetState XInputGetState_
What on God's green earth is happening here? Well, don't despair. It's really quite simple, we just need to unwrap a few things and break it down line by line.
#define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState)
In C (as well as C++ and Objective C), the first step of the compilation process is a code transformation pass by the preprocessor. As we have covered previously in the series, any command which begins with a "#" is what is known as a preprocessor directive. This is just a command which will tell the preprocessor to do something, such as #include a file. In this case, "#define" is a directive which creates a macro. A macro can be thought of as a code substitution, anytime the processor finds the macro used in your code, it will swap it out for whatever you defined.
The two types of macros
There are two types of macros, the first type is very simple and defines a constant value:
#define PI 3.14159265359
This macro will cause the preprocessor to convert any instances of "PI" in the code directly into "3.14159265359". If we break it down a bit, on the left hand side we have what is called the identifier ("PI"), and on the right hand side we have what is known as the token string ("3.14159265359"). The preprocessor searches the code for the identifier, and replaces it with the token string. This means that the literal text "PI" will no longer appear in our code, having been replaced with "3.14159265359". This can be useful for constant values that we may want to use repeatedly, but not type in full all the time. Note that the preprocessor will not convert PI if it appears in a comment or as part of a longer identifier such as "PieceOfPI".
So let's go back to the macro we were looking at earlier.
#define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState)
It is somewhat more complex and appears to have a function definition on the right for a function called "name". This is the second type of macro, which uses a token string as part of it's definition. As a simpler, but somewhat different example:
#define ADD(argument1, argument2) argument1 + argument2
Looking at this, we can see that it can be broken down into two parts just the same as the previous macro. First, on the left side we have the identifier "ADD(argument1, argument2)". This works similarly to the first kind of macro, but because we have written it like a function, the preprocessor will save whatever it finds in place of "argument1" and "argument2", and then plug those in for where they were found in the token string.
For example, if later on in the code we were to type:
int c = ADD(a,b);
Our macro would expand this out to:
int c = a + b;
So, again we will go back to our original macro, but this time we are going to pair it with its first use:
#define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState)
typedef X_INPUT_GET_STATE(x_input_get_state);
Based on what we learned earlier we can see that the second line will expand out into the following:
typedef DWORD WINAPI x_input_get_state(DWORD dwUserIndex, XINPUT_STATE *pState);
What's a typedef?
So that's easy enough to see, but what does 'typedef' mean?
In C, typedef declares a different name for a type. This can almost be thought of as similar to #define, but it is limited to type names only and is performed by the compiler instead of the preprocessor.
As a basic example, if we were to look at the following code:
typedef char CHARACTER;
CHARACTER x;
The compiler will treat the type of x as though it were 'char.' Why would we want to do this? Well, because declaring our own types allows us to make use of the compilers strict type checking. This means that if we were to declare a function which took a CHARACTER as the type of one of it's arguments then we would not be able to pass it a char without the compiler warning us about it, even though a CHARACTER was defined as a char and they are both signed one byte values.
typedef DWORD WINAPI x_input_get_state(DWORD dwUserIndex, XINPUT_STATE *pState);
But in the Handmade Hero code, we are using typedef with a function declaration. This declares a function signature as a type. It is important to understand that although we can use that type to declare a function like this:
x_input_get_state _XInputGetState()
Due to the rules of C, we are not allowed to then go on to define the function as follows:
//INVALID C CODE FOLLOWS
x_input_get_state _XInputGetState()
{
//Do some things.
}
We can define a function using our typedef and also declare the same function by using the original function type information, however, this makes the typedef itself unnecessary.
Using typedef for function pointers
So why are we using a typedef with a function declaration here? Because we are planning on using it to create a function pointer. A function pointer is just what it sounds like, a pointer to a function. The main benefit of function pointers is that they can be treated like variables, that is, they can be reassigned or passed as arguments.
So this is how we would use our "x_input_get_state" typedef to declare a function pointer:
x_input_get_state *PointerToXInputGetStateFunction;
We can then assign that pointer to any function which matches the signature of x_input_get_state:
typedef DWORD WINAPI x_input_get_state(DWORD dwUserIndex, XINPUT_STATE *pState);
DWORD XInputGetStateStub(DWORD dwUserIndex, XINPUT_STATE *pState);
XInputGetStateStub
{
return(0);
}
global_variable x_input_get_state *XInputGetState_ = XInputGetStateStub;
Which if we were to unwrap our macros from the original code, is almost exactly what we would have:
#define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState)
typedef X_INPUT_GET_STATE(x_input_get_state);
X_INPUT_GET_STATE(XInputGetStateStub)
{
return(0);
}
global_variable x_input_get_state *XInputGetState_ = XInputGetStateStub;
Now all we have left is the last line:
#define XInputGetState XInputGetState_
This is another simple macro which will substitute "XInputGetState_" for "XInputGetState" any time we use it in the code. We do this because XInputGetState is a function that has already been declared in xinput.h, and C does not allow us to declare functions multiple times. However, by using the preprocessor, we can type out our code as though we were just using XInputGetState we are actually using a function pointer which will either point to 'XInputGetStateStub' in the case that we are unable to dynamically load in the XInput library, or will point to the correct 'XInputGetState' function as defined by Microsoft in the case that we can load it.
Hope that's helped clear some things up for you. Happy Heroing!