Calling a DLL in a Kernel-Mode Driver
How do you write a DLL you can call from your Windows kernel-mode driver? Avoid user-mode code and build it as an export driver using the Windows Driver Kit build environment.
If you've done any Windows programming, chances are you've written at least one DLL. If you've just started writing Windows drivers, you may have tried to call a DLL from your driver, without success. Why doesn't this work? The fundamental issue for a DLL in kernel mode is whether the DLL calls any user-mode code. If a DLL contains anything other than native kernel API calls, you'll get linker errors if you try to link your driver with it when you build (and the kernel wouldn't load it anyway). Obviously, a DLL built as a Win32 library will fall into this category. Even if you avoid making explicit user-mode API calls in the DLL source code, the compiler often generates implicit user-mode support calls when it does stack checking, overflow checking, and the like. So any DLL built in a Windows user-mode development environment such as Visual Studio is unlikely to qualify for use in kernel mode.
What should you do?
Use the Windows Driver Kit (WDK) build environment to build an export driver. An export driver is a kernel-mode DLL that provides routines for other drivers to call. Like any standard driver, an export driver contains only routines that resolve to kernel-mode functions. Unlike a standard driver, however, an export driver does not receive IRPs or occupy a place in the driver stack, nor is it considered to be a system service.
You can build any standard driver as an export driver—it will operate as a standard driver when loaded in the usual way, and also export functions that other drivers can call. However, if you just want to write the kernel-mode equivalent of a library, you can leave out many of the elements required for a full-blown driver. At a minimum, an export driver must have a DriverEntry routine; this can be an empty stub to satisfy build scripts--the export driver's DriverEntry is never called by Plug and Play. An export driver should also have the standard entry point and unload routines, DllInitialize and DllUnload, so the operating system can keep track of the number of times the export driver's functions have been imported by other drivers, and unload the export driver when the last calling driver unloads.
You can export functions by declaring them with the DECLSPEC_EXPORT macro in the export driver source code, or you can take the simpler approach of listing them as exports in a module-definition (.def) file. (Even if you use DECLSPEC_EXPORT, your .def file must contain at least DllInitialize and DllUnload so you can mark these functions as PRIVATE.)
In the driver's sources file, set TARGETTYPE to EXPORT_DRIVER and set DLLDEF to the path of your .def file, then build the driver. The build process generates an export library with a .lib extension and an export driver with a .sys extension. The .sys file is your kernel-mode DLL.
Static linking to an export driver is essentially the same as for a user-mode DLL: Add the export driver library to the target library list for the driver that will be calling the library. In the calling driver's source code, use the DECLSPEC_IMPORT macro to declare the functions it will call. (Also use EXTERN_C, defined in ntdef.h, to avoid any unwanted name decoration in the calls.) Install the export driver .sys file in the %windir%\system32\drivers directory. It will be loaded the first time any other driver calls into it.
Dynamic linking is a bit trickier, because there's no kernel-mode equivalent to GetProcAddress. (MmGetSystemRoutineAddress is similar, but it will dynamically resolve exports only from Ntoskrnl.exe or the HAL.) Here are some techniques for dynamic linking to an export driver:
- In the export driver, define an I/O control code (IOCTL) that instantiates a class and returns an interface pointer that other drivers can use to call methods on the interface object in the usual way. The class should provide a method for freeing the object, and the header file should contain only pure abstract methods.
- Design the export driver to export direct-call interfaces in response to IRP_MN_QUERY_INTERFACE requests from other drivers.