Figure 3-1 illustrates some of the components that make up the Microsoft Windows NT operating system. Each component exports service functions whose names begin with a particular two-letter prefix:
Figure 3-1. Overview of kernel-mode support routines.
Historically, the Windows NT architects have preferred that drivers not use the run-time libraries supplied by vendors of C compilers. In part, the initial disapproval arose from simple timing. Windows NT was designed at a time when there was no ANSI standard for what functions belonged in a standard library and when many compiler vendors existed, each with its own idea of what might be cool to include and its own unique quality standards. Another factor is that standard run-time library routines sometimes rely on initialization that can only happen in a user-mode application and are sometimes implemented in a thread-unsafe or multiprocessor-unsafe way.
Until now, the official rule has been that kernel-mode drivers should call only functions specifically documented in the DDK. Rather than call wcscmp, for example, one should call RtlCompareUnicodeString. It's been a pretty open secret, however, that the standard import library that one uses to build a driver (ntoskrnl.lib) defines many of the functions declared by application header files such as string.h, stdio.h, stdlib.h, and ctypes.h. So why not call them? In fact, there's no reason not to call them, provided you understand all the implications. Don't, for example, switch to always calling memcpy instead of RtlCopyBytes, because there's a subtle difference between the two. (RtlCopyBytes is guaranteed to proceed byte by byte instead of in larger chunks, which can matter on particular RISC [reduced instruction set computing] platforms.)
Many of the support "functions" that you use in a driver are defined as macros in the DDK header files. We were all taught to avoid using expressions that have side effects (that is, expressions that alter the state of the computer in some persistent way) as arguments to macros for the obvious reason that the macro can invoke the argument more or less than exactly once. Consider, for example, the following code:
int a = 2, b = 42, c; c = min(a++, b); |
What's the value of a afterward? (For that matter, what's the value of c?) Take a look at a plausible implementation of min as a macro:
#define min(x,y) (((x) < (y)) ? (x) : (y)) |
If you substitute a++ for x, you can see that a will equal 4 because the expression a++ gets executed twice. The value of the "function" min will be 3 instead of the expected 2 because the second invocation of a++ delivers the value.
You basically can't tell when the DDK will use a macro and when it will declare a real external function. Sometimes, a particular service function will be a macro for some platforms and a function call for other platforms. Furthermore, Microsoft is free to change its mind in the future. Consequently, you should follow this rule when programming a WDM driver:
Never use an expression that has side effects as an argument to a kernel-mode service function.