This post is the first in a series from our newest Software Design Engineer, Chris Barr. Chris has lots of cool low level programming experience (including UEFI), though not necessarily on Windows. We’ve asked him to post about anything that comes across as interesting, unexpected, fun, or strange while learning Windows kernel mode development.
It’s always confusing to jump into a new coding paradigm. One of the first things you need to do before starting to touch code is to understand how the code around it works. To do that, you need to be able to read it.
Windows driver code is somewhat unique in that the types are not what you expect when opening a C/C++ file. Not only are most of the stdlib functions you’d normally call not there, but none of the types are what you’ve seen before unless you’ve done application development that pokes at Windows itself.
One of the first things that jumps out is that the areas around function or structure implementations are REALLY LOUD:
NTSTATUS
KeQueryLogicalProcessorRelationship(
PPROCESSOR_NUMBER ProcessorNumber,
LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType,
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX Information,
PULONG Length
);
Types and #defines are all upper case. The #defines as upper case is pretty standard in C/C++, but the types also being upper case is somewhat unique to Windows (I’ve run across it in the UEFI Development Kit, but otherwise nowhere else). There are a few caveats, but for the most part these all map to types that are familiar to any C programmer. It’s not much of a leap to get from VOID to void, or from CHAR to char.
I’m not going to list them all out in this article (there’s an MSDN article for that, and you can always check the headers for the typedefs), but here are some of the key differences I’ve run across:
- LONG and LONGLONG are the main fixed size types, rather than something like int32_t and int64_t (although you could use these if you felt like breaking the Windows convention).
- The *_PTR types like LONG_PTR are pointer-width types. Pointer width varies per architecture.
- The standard return code type has an actual type – NTSTATUS. If your function can fail in more than one way, it should probably return NTSTATUS.
- Most character arrays that hold readable strings are going to be WCHARs, or wide (16-byte) characters. Strings internal to the OS will often be a UNICODE_STRING type, which is a structure rather than a character array.
- *_EX types are typically the newer way to do things. If you’re writing code for older versions of windows you’ll want to be careful when you see these, as what you’re calling/using may not be supported.
The real unique thing about Windows types is when you start getting into things like this:
typedef CONST ULONG *PCULONG;
Windows tends to collapse any modifiers sitting in front of the type, as well as the whether it is a pointer or not, into the type itself through a typedef. So, rather than this:
const unsigned long* foo;
We get this:
PCULONG foo;
The *, const, and unsigned modifiers end up getting prepended to the name as P, C, and U. This ends up keeping declarations short, but also ends up looking a little alien coming from a different environment.
Note that if there’s more than one level of indirection, we don’t keep adding P’s to the front:
PCULONG* foo;
Also, some types will use LP rather than just P, even though they mean exactly the same thing (now):
LPCWSTR bar;
And… if you’re working on the application side of things, well – good luck:
typedef _NullNull_terminated_ CONST WCHAR UNALIGNED *PCUZZWSTR;