52964.fb2 Writing Windows WDM Device Drivers - читать онлайн бесплатно полную версию книги . Страница 5

Writing Windows WDM Device Drivers - читать онлайн бесплатно полную версию книги . Страница 5

Chapter 4WDM Driver Environment

This chapter writes a simple WDM device driver, called Wdm1. It shows how Wdm1 is built and installed in Windows 98 and Windows 2000. The basics of the driver are explained, but a full explanation of the guts has to wait until the following chapters.

The Wdm1 driver is for a virtual WDM device that does not correspond to any real hardware. For now, the Plug and Play and Power Management support is minimal. The Wdm1 driver implements a shared memory buffer for all Wdm1 devices.

First, I describe how to set up your computer for WDM driver development by installing the various development kits and by setting up Visual Studio. I describe the other useful tools that you will need.

The book software provides a Visual Studio workspace called WDM Book that you can use to compile the drivers and any associated user mode Win32 applications. Be careful if editing within this workspace as the different projects in this workspace often have files of the same name.

System Set Up

The section details what you will have to do to set up your development computer or computers for WDM driver development. This task is laborious, especially as you have to do it at least twice, once for Windows 2000 and once for Windows 98.

You will have to be an Administrative user to install the W2000 Driver Development Kit (DDK) and drivers in Windows 2000.

These instructions assume that you are using Visual C++ for development. While other compilers can almost certainly do the job, most Windows driver writers will be firmly in the Visual Studio camp. Visual C++ also has various useful tools that you will need such as rebase and guidgen. I used VC++ version 5.

You will need a Microsoft Developer Network (MSDN) Professional (or Universal) subscription to get the necessary development kits. While some of these kits are available free online, it is best to get an MSDN subscription to ensure that you receive the most recent releases and beta versions. The Installable File System (IFS) DDK costs extra and is only available currently in the USA and Canada.

DDKs

Install the W98 DDK in Windows 98. Install the W2000 DDK in Windows 2000. Install the Platform Software Development Kit (SDK) and MSDN Library in both versions of Windows. The Platform SDK is not vital, but it has quite a few useful tools.

The DDK tools and documentation are listed in the Start+Programs+Development kits menu. The Platform SDK tools are listed in the Start+Programs+Platform SDK menu.

The SETENV.BAT file in the DDK bin directory is used to set the environment variables for the build process[8]. Shortcuts to this batch file are set up in the Start menu. It calls other batch files in the bin directory. The current batch files install DosKey; I decided to comment these lines out as I already have a similar command processor installed.

The Platform SDK may have more up to date tools than are supplied with VC++ (e.g., the rebase utility). Put the MSSDK bin directory before the Visual Studio directories on your path.

You may need to add the DDK bin directory to the directories list in VC++.

Currently, the documentation is released in HTML Help format. Each help file is viewed in its own incarnation of the HTML Help viewer, and is not merged into the Visual Studio 97 documentation. The help files are probably merged together in Visual Studio 6.

Each function found in the W2000 DDK states which header files you can use. If wdm.h is listed, the routine can be used in a WDM driver for Windows 98 or Windows 2000. There are some routines that compile correctly in Windows 98 but have no effect, such as the event log writing routines. If the W2000 DDK lists only ntddk.h for a routine, it can be used only in Windows 2000 drivers, not Windows 98. The W98 DDK lists only routines that run in Windows 98.

The W98 DDK and the W2000 DDK are largely the same for WDM drivers. At the time of writing, the W98 DDK does not include the Windows Management Instrumentation libraries. Some header files are also different (e.g., SETUPAPI.H). For some reason, the checked build produced by W2000 seems quite a bit smaller than that produced by the W98 DDK.

Book Software Installation

The source code for the Wdm1 driver is supplied on the book's accompanying disk. Copy the source code directory tree to a fresh directory on your hard disk. These instructions assume that you have installed the software to C:\WDMBook. However, you can change the directory if you wish. If you use a different drive, you will need to alter the settings of every VC++ project, so it will be much easier for you if you can install the software on drive C:.

The WDM Book Visual Studio workspace in the book software base directory contains projects for each of the drivers in the book.

Three steps are needed before the book software drivers can be compiled.

• Ensure that the DDKROOT environment variable is set to the W2000 DDK or W98 DDK base directory.

• Set up an environment variable called WDMBOOK to point to the book software base directory (e.g., C:\WDMBook). In Windows 98, set environment variables by adding them to AUTOEXEC.BAT. You will probably need to reboot for the new definitions to come into effect. In Windows 2000, set environment variables in the Control Panel System proper ties applet "Advanced tab" Environment variables editor. You will need to restart Visual Studio and any DOS boxes for these changes to take effect.

• Ensure that the MakeDrvr.bat batch file in the book software base directory is available to be run by Visual Studio, as it is used to invoke the build make process. Either add MakeDrvr.bat to a directory that is on your path, or add the book software base directory to the list of "Executable files" directories in the Visual Studio Tools+Options Directories tab.

Recompiling the Book Software

You can check that you have installed the development kits and book software correctly by compiling the book software drivers[9]. From the Start+Programs+Dуvelopment Kits+Windows XX DDK menu, select either the free or checked build environment. Then move to the book software base directory and enter build –nmake /a. If you want to recompile the Win32 applications, you will have to do this in the Visual Studio workspace.

Check that the drivers are actually updated. In the worst case, it will appear as if the files have compiled but nothing will in fact have been done. If the example drivers do not compile, then there are two usual causes. The first possible problem is that the environment variables have not been set up correctly.

The second problem is that some of the source code explicitly includes header files from the Windows 98 DDK, which I assume has been installed in the C: \98DDK directory. Some of the linker settings have been also been set up to refer to this same directory. If you have used a different directory, or have only installed the Windows 2000 DDK, you will need to change each instance where C:\98DDK is mentioned, either in the source code or in the SOURCES file.

The Wdm3 example specifically refers to a library in the W2000 DDK in its SOURCES files. I assume that this DDK is installed in C:\NTDDK, so change this if need be.

The Wdm2Power Win32 application may need some special project settings before it will compile. See Chapter 10 for details.

Shortcuts

I find it useful to have shortcuts to common tools available, more quickly than through the Start menu.

One possible place for these shortcuts is in the Visual Studio Tools menu. Another option is to place the shortcuts in the Quick Launch folder on the Taskbar.

The option I prefer is to use the free QuickStartutility, available from my company, PHD Computer Consultants Ltd., website at www.phdcc.com. This displays an icon in the Taskbar tray. Clicking this icon shows a menu of various useful options, including submenus for each Desktop folder that you have. Make sure that you put QuickStart in the Startup folder so that it runs from start up.

I therefore suggest making a Tools Desktop folder with shortcuts copied from the Start menu. Table 4.1 shows the most useful tools that I put in this folder.

Table 4.1 Useful Tools

Command line promptcmd or command (or equivalent, e.g. 4NT)
Free build environmentDOS box set up for driver free builds
Checked build environmentDOS box set up for driver checked builds
Registry Editor%windir%\regedit.exe
W2000 Registry Editor%windir%\System32\regedt32.exeNT and W2000
WinObjC:\MSSDK\bin\winnt\Winobj.ExeNT and W2000
Computer Management%SystemRoot%\system32\compmgmt.msc /s (from Start+Programs+Administrative tools)W2000 only
Servicer%WDMBOOK%\Servicer\Release\Servicer.exeNT and W2000
WBEM Object Browser<WBEM>\Applications\browser.htm
DebugPrint Monitor%WDMBOOK%\DebugPrint\exe\Release\DebugPrintMonitor.exe
Wdm2Power%WDMBOOK%\Wdm2\Power\Release\Wdm2Power.exe
Driver VerifierStart+Programs+Development Kits+Windows 2000 DDK+Driver VerifierW2000 only

You might find a Documentation Desktop folder useful for all the DDK and SDK help shortcuts.

Utilities

This section gives a brief overview of the most useful tools for driver development and how they might best be used. Their detailed use will be covered later.

DOS Boxes

Although you can do most of your development work in Visual Studio, you will occasionally need to work at a command line prompt.

The supplied DDK build tool runs at a command prompt. The environment variables have to be set up appropriately as free or checked driver development. The DDKs provide two shortcuts to get these command prompt boxes.

Computer Management Console

The Windows 2000 Computer Management Console (from Start+Programs+Administrative tools) has a useful System Tools section with Event Viewer and Device Manager tools.

The Event Viewer displays the Windows 2000 events. Drivers should write to the System Log to inform administrators of informational, warning, or error events.

The Device Manager shows all your Windows 98 or Windows 2000 devices and lets you change their properties (e.g., change port settings and reallocate resources). You can change (i.e., update) the driver for a device, and uninstall a device. The Device Manager is also available in the Control Panel System applet.

NT Devices Applet

In NT 4 and NT 3.51, the Control Panel Devices applet lets you see what drivers are running. You can stop or start drivers and change their start up characteristics. In W2000, the Device Manager does not initially list the non-WDM drivers. Selecting the "Show hidden devices" checkbox brings up a list of these drivers. You can start or stop them and change their startup characteristics. The book Servicer program also lets you do some of these functions.

Hardware Wizard

Some drivers are loaded automatically when Windows detects a new device in the system. For others, you need to use the Control Panel Add New Hardware wizard to prompt the loading of the driver.

Registry Editors

You will probably need to inspect the registry at some point during driver development. The basic Registry Editor regedit is useful for most jobs, particularly as it can search the registry. However, for some jobs in NT and Windows 2000, you will need to use RedEdt32, as it can handle all the registry types, such as REG_MULTI_SZ strings.

This book assumes that you have a reasonable working knowledge of the registry. HKLM is used as abbreviation for HKEY_LOCAL_MACHINE. The DDKs sometimes refer to HKLM as Registry\MACHINE.

INF Editor

The InfEdit tool can be used (in Windows 98 only) to create and edit INF files. However, it seems to clobber some INF file information and does not display Windows 2000 specific sections correctly.

WBEM

The WBEM SDK includes the WBEM Object Browser. Use this to inspect the Windows Management Instrumentation data that drivers produce.

Debuggers

There are various tools available to help you test and debug drivers. Unfortunately, the Visual Studio debugger cannot be used on kernel mode code. The Microsoft WinDbg kernel mode debugger must be used between two NT or W2000 computers.

NuMega Compuware sells the SoftICE debugger, which can run on the same PC as the driver under test. Use the NuMega utility nmsym utility to build the necessary symbols for SoftICE.

Finally, some tools let you include trace statements in your driver code that can be viewed in a user mode application on the same PC. The Compuware Numega DriverWorks software includes its Driver Monitor tool for this job. The DebugPrint software from PHD Computer Consultants Ltd. provides a similar facility. DebugPrint is used extensively in this book and its innards are described in detail as an example of a fully working driver. The OSR DDK software from Open Systems Resources logs all your DDK function calls so that they can be viewed by their OSRTracer tool.

NT and Windows 2000 Utilities

WinObj is an NT and W2000 utility that displays various Windows objects, including device names and symbolic links.

The \device branch displays the device names. These device names are not directly visible to user applications. Non-WDM kernel mode device drivers must provide a symbolic link between a Win32 visible name and the underlying device name. The \?? branch displays these symbolic links. Double-clicking on \??\COM1 shows that it is a symbolic link to the underlying device \Device\Serial1. (The \?? branch used to be called \DosDevices, so this name appears in some of the DDK documentation).

Double-clicking most other WinObj entries yields no useful information.

ObjDir is a command line version of WinObj.

Drivers is a command line utility that lists all drivers and their memory usage.

Book Software Tools

DebugPrint

The DebugPrint software is used by test drivers to produce formatted print statements. The trace messages are viewed in the DebugPrint Monitor Win32 application. This tool is explained in full in Chapter 6, because the book software makes extensive use of this debugging technique. Chapter 14 describes the source for the DebugPrint driver.

Wdm2Power

The Wdm2Power tool firstly lets you inspect the AC and battery status of your system. It also displays any system power events. Finally, it lets you suspend or hibernate your computer.

MakeDrvr

This batch file is used from Visual Studio to initiate each build, as described in the following text.

Servicer

The Servicer utility lets you inspect the status of any running Windows 2000 service, including drivers. You can see if a driver is running or not, and attempt to start or stop it.

Driver Targets

Drivers can be built in free or checked versions, and in NT and Windows 2000 for different processors.

The free target is the final release retail version, optimized as necessary with all debug symbols removed.

The checked target is an unoptimised debug version that includes symbols to make debugging easier.

NT and Windows 2000 come in free and checked versions. If you use the Microsoft WinDbg debugger then you need two computers running NT or W2000. The development PC should be the faster computer running the free version of Windows. The driver should be running under test on the other target PC that is running the checked build. The fact that there are fewer resources available on the target system is good as it makes it easier to check that your driver will work in stressful situations.

Windows 2000 also runs on the Dec Alpha platform, so you can also build for the Alpha platform free and checked targets. This book only discusses the x86 platform.

In this book, the emphasis is on writing drivers that work in both Windows 98 and Windows 2000. However, a few features are present in only one operating system. The following preprocessor directives can be used to determine whether you are using the W2000 DDK or the W98 DDK. If you have separate versions of your driver, the installation files will have to be slightly different. As Chapter 11 shows, a single installation INF file can include separate instructions for W2000 and W98.

#if _WIN32_WINNT>=0x0500

 // W2000+ code

#else

// W98 code

#endif

Driver Language and Libraries

A driver is a Dynamic Link Library (DLL) with the file extension .sys. It is usually written in C or C++ and can include resources, such as a version block, event messages, and Windows Management Instrumentation (WMI) class definitions. In Windows 98, a driver executable must have an 8.3 filename.

Although drivers were traditionally written just in C, it is quite straightforward to use C++. The main requirement is to use the extern "C" directive in a couple of important places. However, do not use the new keyword in C++. The new keyword may be implemented using malloc, which is not available to kernel mode drivers.

In fact, most standard libraries and classes are not available to driver writers in either language, because they make inappropriate use of memory. If you are using one of the proprietary driver development kits, these provide various useful classes, including safe memory allocators.

Instead, you can use any of the routines provided by the operating system to kernel mode devices, as described in Chapter 3. While these are useful, it takes a while to get used to the different set of routines that are available.

Assembly code can be used if absolutely necessary. Obviously, this makes for more work if you port the driver to the Windows 2000 Alpha platform.

Resources

A resource .rc file should include a standard version block. Increment the version numbers as new builds are released. Make sure that you keep a full source backup of each version you release. Many version control packages can help you manage this task.

If you generate NT or Windows 2000 events, you should write these in an .mc file that is compiled using the mc utility and included in a driver's resource file. More details of this process are in Chapter 13.

Similarly, if you generate custom WMI classes, you need to write a .mof file that is compiled using the mofcomp tool and included in the driver's resource file. See Chapter 12 for more information.

A sophisticated driver might need to download microcode to its device and so would include the microcode as a binary resource in its executable.

Good Code

A driver is an integral part of the operating system, so it can easily crash Windows if it goes wrong. You do not have the protection of a Win32 address space to stop you from overwriting memory that does not belong to you.

Please be especially careful when you write your driver. Keep it as simple as possible and document it well.

Treat all compiler warnings as errors that need to be fixed. For example, whether an integer is signed or not can make all the difference.

Make sure that you check the return values of all kernel functions that you call, and act on them accordingly. For example, if you get an error after you create a device, make sure that the clean-up code deletes the device.

Make sure that you use the kernel resources carefully, particularly memory. Some resources are scarce and overuse may degrade system functioning.

build Utility

The DDK build command line utility is the primary tool for building drivers. It invokes the nmake make utility to build your driver using the correct compiler and linker settings. If necessary, build can be used to build standard user mode Win32 executables, etc.

The next section describes how to invoke build from within Visual Studio. However, you must still set up build so that it can be run at the command line. As well as your source code, you must specify a SOURCES file, a standard makefile, the directory structure, and optionally a makefile.inc file and a dirs file. All these steps are described in the following text.

build displays progress details and error results to its standard output. In addition, it lists the errors in a file called build.err, the warnings in build.wrn, and a log in build.log. In W2000 there are free and checked build versions of each of these files, i.e., buildfre.log, buildchk.log, etc.

makefiles

Younger readers may not have come across makefiles. In the days before Integrated Development Environments (IDEs) such as Visual Studio, you had to use makefiles to determine which files in a project needed recompiling. If you changed only one module in a project consisting of eight modules, you want to recompile only that one module and then link the whole lot together.

The nmake utility uses instructions in a file called makefile to determine what commands to run to update a project. The following makefile shows that if haggis.cpp has been updated, it is compiled into haggis.obj using the cl compiler. haggis.obj is linked to make haggis.exe using the link tool.

haggis.exe: haggis.obj

            link –o haggis.exe haggis.obj

haggis.obj: haggis.cpp

            cl haggis

Most makefiles are a good deal more complicated than this, which is why they were happily forgotten when IDEs came along. However, setting up the compiler and linker settings for drivers is quite a complicated task. Therefore, Microsoft has stuck with makefiles. See the Visual Studio nmake documentation for more details of makefiles.

SOURCES

build looks for an nmake macro file called SOURCES in the current directory for details of what to build.

Listing 4.1 shows the SOURCES file for the Wdm1 project. It specifies that the driver target name is Wdm1.sys, that it is a WDM driver, and that it should be built in the OBJ subdirectory. Source browser information should be generated. The DDK inc directory is added to the search list for header files. The SOURCES macro specifies a list of files to compile. NTTARGETFILES specifies some post build steps, as described in the following text.

Other less common SOURCES macro definitions can be found in the DDK documentation. Note that there must be no spaces between the SOURCES macro and its equal sign.

Listing 4.1 Wdm1 project SOURCES file

TARGETNAME=Wdm1

TARGETTYPE=DRIVER

DRIVERTYPE=WDM

TARGETPATH=OBJ

BROWSER_INFO=1

INCLUDES=$(BASEDIR)\inc;

SOURCES=init.cpp \

        dispatch.cpp \

        pnp.cpp \

        DebugPrint.c \

        version.rc

NTTARGETFILES=PostBuildSteps

makefile File

You must provide a standard file called makefile as shown in Listing 4.2. This invokes the standard make file makefile.def in the DDK inc directory.

As it says, do not edit this file at all. If you want to add to the list of files to compile, add them to the SOURCES macro in the SOURCES file.

Listing 4.2 Wdm1 project makefile

#

# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source

# file to this component. This file merely indirects to the real make file

# that is shared by all the driver components of the Windows NT DDK

#

!INCLUDE ${NTMAKEENV)\makefile.def

build Directories

Windows 98 and NT

build always puts the compiled object files in the OBJ\i386 subdirectory (for x86 targets). The SOURCES TARGETPATH macro specifies where the final executables go. If you specify OBJ for this, then the driver executables go in the OBJ\i386\free and OBJ\i386\checked subdirectories. There is a long-standing bug in build that means that you have to create these last two directories by hand for build. if you were making Wdm1 from scratch, then you would have to make subdirectories OBJ\i386\free and OBJ\i386\checked.

Both the free and checked builds put their object files in the same directory. If you switch between these build types, make sure that you rebuild all the files in the project (by putting "-nmake /a" on the build command line).

The checked build output file OBJ\i386\checked\Wdm1.sys contains the debug symbols.

The build process also generates a file called Wdm1.dbg with the debug symbols in the OBJ\i386\free directory.

Windows 2000

In W2000, build keeps the free and checked build object files separate. If the TARGETPATH is OBJ, the free build x86 object files and the final driver go in the OBJFRE\i386 directory. The checked build object files and driver go in the OBJCHK\i386 directory.

Wdm1 Directories

The end result is the following series of subdirectories for Wdm1.

DirectoryContents
OBJHas build list of files to build in _objects.mac
OBJ\i386W98 Compiled object files
OBJ\i386\freeW98 Free build Wdm1.sys
OBJ\i386\checkedW98 Checked build Wdm1.sys
OBJFRE\i386W2000 Free build objects and Wdm1.sys
OBJCHK\i386W2000 Checked build objects and Wdm1.sys

Other build Steps

Another makefile called makefile.inc is invoked if you use certain optional macros in the SOURCES file. Table 4.2 shows the macro name and when the make target is invoked.

Table 4.2 SOURCES optional macros

SOURCES macro nameWhen invoked
NTTARGETFILE0after dependency scan
NTTARGETFILE1before linking
NTTARGETFILESduring link

Listing 4.3 shows the standard makefile.inc that is used in all the book software projects.

Listing 4.3 Book software projects makefile.inc

PostBuildSteps: $(TARGET)

!if "$(DDKBUlLDENV)"="free"

 rebase –B 0x10000 –X . $(TARGET)

!endif

 copy $(TARGET) $(WINDIR)\system32\drivers

The line in the Wdm1 SOURCES file that says NTTARGETFILES=PostBuildSteps ensures that the target PostBuildSteps is built during the link process. As the PostBuildSteps target depends on $(TARGET) — the driver executable — the build commands for PostBuildSteps are carried out after the driver is built. The PostBuildSteps output is displayed in the build.log file. Note that problems in PostBuildSteps may not evident unless you inspect this file.

The actual post-build steps in makefile.inc do two jobs. First, the rebase utility is run on the driver executable for free builds, and the driver is copied to the Windows system32\drivers directory. Copying the driver does not mean that it is installed.

rebase strips any remaining debug symbols that are left, even in the free driver executable. The base load address is kept at 0x10000 and the symbols are put in the .dbg file in the current directory.

DIRS File

The final main feature of build is that it can recursively build files in other directories. If a DIRS file is present, build looks at the DIRS directive in there for a list of directories to build. These directories may themselves contain further DIRS files.

The book software base directory has a DIRS file with the following line:

DIRS=Wdm1 Wdm2 WdmIo UsbKbd HidKbd DebugPrint PassThru

Running build in the book software base directory will compile the drivers in all these directories. The companion Win32 user mode applications are not built by this process.

The directories listed in the DIRS directive must be only one level below the current directory. Therefore, the Wdm1 directory has a DIRS file that instructs build to go to the SYS subdirectory.

VC++ Projects

You can set up VC++ to build drivers from within Visual Studio. It is possible to configure a project's settings so that Visual Studio can compile your driver directly. However, it is laborious changing all the settings and is error prone. The final problem is that Microsoft might change driver compile or link requirements in the DDK standard makefile. Such changes would not be reflected automatically in your settings.

The best way therefore is to use a Makefile project. This invokes a command line utility to build the driver. The downside of this approach is that some common tasks, such as adding a file to the compile list, have to be done in a different way, as described in the following text.

The book software projects are set up already to use the Makefile technique. This eventually invokes the build command as described previously. All the necessary build files must be set up correctly: SOURCES, makefile, the target directories, and possibly the makefile.inc and DIRS files.

You might find it useful to select the Visual Studio Tools+Options menu Editor tab "Automatic reload of externally modified files" checkbox so that changes to the build log files are loaded with no fuss.

Makefile Build Environment

When you make a new Makefile project, Visual Studio gives you two build configurations by default, "Win32 Debug" and "Win32 Release". I prefer to use the Build+Configurations menu to remove these and have configurations named "Win32 Checked" and "Win32 Free", instead.

For the free configuration, set the project settings as shown in Table 4.3. For the checked build, change "free" to "checked" in the build command line and the browse info filename.

If you installed the book software to a driver other than C: you will need to change the drive letter in the build command line.

The build command line runs the MakeDrvr.bat batch file, using the DDKROOT and WDMBOOK environment variables. The options –nmake /a are added to this command line if you request a complete rebuild in Visual Studio. The output filename is set so that the correct name is displayed in the build menu.

Table 4.3 Win32 Free configuration settings

Build command lineMakeDrvr %DDKR00T% c: %WuMBook%\wdm1\sys free
Rebuild all options-nmake /a
Output file nameWdm1.sys
Browse info file nameobj\i386\free\Wdm1.bsc (W98/NT) objfre\i386\Wdm1.bsc (W2000)

MakeDrvr

When you ask Visual Studio to build your driver, the batch file MakeDrvr.bat, listed in Listing 4.4, is run. This is always passed at least four parameters: the DDK base directory, the source drive, the source directory, and the build type ("free" or "checked"). Any further arguments are passed straight to build.

MakeDrvr first does some basic checks on the parameters that are passed. It then calls the DDK setenv command to set up the environment variables correctly for the build target, changes directory to the source drive and directory, and finally calls build. The –b build option ensures that the full error text is displayed. The –w option ensures that the warnings appear on the screen output, so that Visual Studio can find them in the build Output window.

The screen output of the MakeDrvr command file appears in the Visual Studio Output window. You can then use F4 as usual to go the next error or warning.

Listing 4.4 MakeDrvr.bat

@echo off

if "%1"=="" goto usage

if "%3"=="" goto usage

if not exist %1\bin\setenv.bat goto usage

call %1\bin\setenv %1 %4

%2

cd %3

build –b –w %5 %6 %7 %8 %9

goto exit

:usage

echo usage MakeDrvr DDK_dir Driver_Drive Driver_Dir free/checked [build_options]

echo eg MakeDrvr %%DDKROOT%% C: %%WDMBOOK%% free –cef

:exit

Directories

The build output goes into the OBJ, OBJFRE, or OBJCHK subdirectories.

In Windows 98, the free driver is in OBJ\i386\free\Wdm1.sys, with debug symbols in OBJ\ i386\free\Wdm1.dbg. The checked build products are in 0BJ\i386\checked. The intermediate object files for both builds are in the OBJ\i386 directory. If you change from the free to checked targets, make sure that you do a "Rebuild all" to recompile all the source files with the correct debug preprocessor defines.

In Windows 2000, the free build object files and driver are in OBJFRE\i386 and the checked build products are in OBJCHK\i386. A complete rebuild is not required if you switch between free and checked builds.

Common Tasks

As you are using a Visual Studio Makefile project, these common tasks must be done is a different way.

Add File to Project

If you add a file to your project, you must add it to the SOURCES file for it to be built.

Make Browse Information

Source browser information is generated alongside the target executable if the SOURCES file contains this line.

BROWSER_INFO=1

Build Steps

Various additional build steps can be defined in a makefile.inc file if the SOURCES file contains one or more of the NTTARGETFILE0, NTTARGETFILE1, or NTTARGETFILES macros.

The results of any additional build steps are not shown in the Visual Studio output window, but only in the build.log file. The build does not necessarily stop if one of these steps fail.

You could also alter MakeDrvr.bat to do tasks that are common to all your driver projects.

Compiling a Single File

You cannot compile a single file in Makefile projects.

The Wdm1 Driver Code

Table 4.4 lists all the source files used by the first driver called Wdm1. Table 4.5 lists all the build files that have already been described. In W2000 the build output files have slightly different names. These files are on the book CD-ROM.

This chapter looks at only some of the source files. As far as possible, the minimum possible functionality has been implemented in Wdm1. For example, a stub function has been written to handle Win32 create file requests. These stub functions usually make each request succeed.

If you were to put this Wdm1 driver to the test, it would not work in some circumstances. The succeeding chapters explain how the driver works and how its functionality has been enhanced to make it work better, as well as showing how to call the driver from Win32 code.

Table 4.4 Wdm1 source files

Wdm1.hDriver header
Init.cppEntry and unload code
Pnp.cppPlug and Play and Power handling code
Dispatch.cppMain IRP dispatch routines
DebugPrint.cDebugPrint code
DebugPrint.hDebugPrint header
Wdm1.rcVersion resource
GUIDs.hGUID definition
Ioctl.hIOCTL definition
resource.hVisual Studio resource editor header
Wdm1free.infFree build installation instructions
Wdm1checked.infChecked build installation instructions

Table 4.5 Wdm1 build files

SOURCESbuild instructions
makefile.incPost build steps for makefile
makefileStandard makefile
MakeDrvr.batMakefile project batch file
build.logbuild results log output
build.errbuild errors output
build.wrnbuild warnings output

Compiler Options

The code includes some directives to the compiler that need some explaining.

The extern "C" directive is used to ensure that the compiler uses the correct linkage to reference kernel routines. The driver entry point, DriverEntry, must have extern "C" to ensure it is found.

The #pragma code_seg preprocessor directive is used to force routines into certain code segments. The INIT code segment is discarded after the driver has initialized itself, and the PAGE segment contains code that can be paged out of kernel memory. Using segments helps to lower a driver's memory usage.

Only routines that run at PASSIVE_LEVEL IRQL can be paged from memory. All the dispatch routines in the Wdm1 driver operate at PASSIVE_LEVEL so they can be put in the PAGE segment. Routines that are not given a segment are never paged from memory.

If writing C code, you can use the alloc_text pragma instead to set the code segment of named routines.

Header Files

The Wdm1.h header file is included in all the source files. Wdm1.h first includes the main DDK header file for WDM projects, wdm.h. If you were writing NT style drivers, you would use the similar ntddk.h. You may find that you need to include some other DDK header files for particular types of drivers.

Next, a device extension structure is defined. This structure is where a driver can hold any information it needs about a device (more on this later).

The GUIDs.H header defines a Globally Unique Identifier (GUID) for the Wdm1 device interface. The next chapter shows how this GUID is used by Win32 user mode applications to find Wdm1 devices. I used the guidgen utility to generate this GUID in the DEFINE_GUID format. I defined several consecutive GUIDs at once for all the examples in this book.

Finally, the IOCTL.H header defines the IOCTL codes that Wdm1 supports. These are explained in Chapter 7.

Driver Entry Module

Init.cpp contains the driver entry point. Listing 4.5 shows this routine, which must be called DriverEntry and use C linkage.

The Plug and Play Manager locates the correct driver and calls DriverEntry to initialize the driver (at PASSIVE_IRQL). In DriverEntry, the main job is to store a series of call back routine pointers in the passed DriverObject. This DRIVER_OBJECT structure is used by the operating system to store any information relevant to the driver. A separate structure is used later to store information about each device.

In Wdm1, DriverEntry sets a whole series of callback routines. These routines are called by the kernel when a device is added and when IRPs need to be sent to the driver. The WdmUnload routine, later in Init.cpp, does nothing at this stage.

Finally, DriverEntry returns an NTSTATUS value of STATUS_SUCCESS. Almost all driver routines have to return a NTSTATUS value, from the list in the NTSTATUS.H DDK header file. Note that these error codes do not correspond to Win32 error codes. The kernel does the necessary mapping between the two types of error code.

Listing 4.5 DriverEntry routine

extern "C"

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {

 NTSTATUS status = STATUS_SUCCESS;

 //…

 // Export other driver entry points…

 DriverObject->DriverExtension->AddDevice = Wdm1AddDevice;

 DriverObject->DriverUnload = Wdm1Unload;

 DriverObject->MajorFunction[IRP_MJ_CREATE] = Wdm1Create;

 DriverObject->MajorFunction[IRP_MJ_CLOSE] = Wdm1Close;

 DriverObject->MajorFunction[IRP_MJ_PNP] = Wdm1Pnp;

 DriverObject->MajorFunction[IRP_MJ_POWER] = Wdm1Power;

 DriverObject->MajorFunction[IRP_MJ_READ] = Wdm1Read;

 DriverObject->MajorFunction[IRP_MJ_WRITE] = Wdm1Write;

 DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Wdm1DeviceControl;

 DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = Wdm1SystemControl;

 // …

 return status;

}

Version Resource

Wdm1.rc simply defines a version resource block with version and copyright information.

Accessing the Registry

The RegistryPath parameter to DriverEntry contains the registry key of the driver. The Wdm1 code does not currently use its RegistryPath parameter. However, it is common for drivers to use its registry key to store parameters for the whole driver. Therefore, I present the ReadReg routine shown in Listing 4.6. This reads two values from the driver's registry path Parameters subkey. The first value is a ULONG obtained from the value named UlongValue. The second value is a string obtained from the default value for the Parameters key.

Eventually ReadReg needs to call the RtlQueryRegistryValues kernel routine to read both the registry values in one fell swoop. One of the parameters is the absolute registry path, as a NULL-terminated wide string. The driver registry path is supplied in a UNICODE_STRING structure. Although this contains a wide string buffer, it may not necessarily be NULL-terminated. Unfortunately, this means that we have to laboriously make a copy of the string, simply to add on that dratted NULL-terminating character.

The first section of ReadReg does this job. It works out the length of buffer required and uses ExAllocatePool to allocate the memory from the paged pool. The RtlCopyMemory function is used to copy the bulk of the string over and RtlZeroMemory zeroes that all-important last character. You can do the copy and zero by hand if you want, though it should be more efficient to call the kernel functions.

Listing 4.6 ReadReg

void ReadReg(IN PUNICODE_STRING DriverRegistryPath) {

 // Make zero terminated copy of driver registry path

 USHORT FromLen = DriverRegistryPath->Length;

 PUCHAR wstrDriverRegistryPath = (PUCHAR)ExAnocatePool(PagedPool, FromLen+sizeof(WCHAR));

 if( wstrDriverRegistryPath==NULL) return;

 RtlCopyMemory(wstrDriverRegistryPath, DriverRegistryPath->Buffer, FromLen);

 RtlZeroMemory(wstrDriverRegistryPath+FromLen, sizeof(WCHAR));

 // Initialise our ULONG and UNICODE_STRING values

 ULONG UlongValue = –1;

 UNICODE_STRING UnicodeString;

 UnicodeString.Buffer = NULL;

 UnicodeString.MaximumLength = 0;

 UnicodeString.Length = 0;

 // Build up our registry query table

 RTL_QUERY_REGISTRY_TABLE QueryTable[4];

 RtlZeroMemory(QueryTable, sizeof(QueryTable));

 QueryTable[0].Name = L"Parameters";

 QueryTable[0].Flags = RTL_QUERY_REGISTRY_SUBKEY;

 QueryTable[0].EntryContext = NULL;

 QueryTable[1].Name = L"UlongValue";

 QueryTable[1].Flags = RTL_QUERY_REGISTRY_DIRECT;

 OueryTable[1].EntryContext = &UlongValue;

 QueryTable[2].Name = L""; // Default value

 QueryTable[2].Flags = RTL_QUERY_REGISTRY_DIRECT;

 QueryTable[2].EntryContext = &UnicodeString;

 // Issue query

 NTSTATUS status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE, (PWSTR)wstrDriverRegistryPath, QueryTable, NULL, NULL);

 // Print results

 DebugPrint("ReadReg %x: UlongValue %x UnicodeString %T", status.UlongValue, &UnicodeString);

 // Do not forget to free buffers

 if (UnicodeString.Buffer!=NULL) ExFreePool(UnicodeString.Buffer);

 ExFreePool(wstrDriverRegistryPath);

}

The UNICODE_STRING Structure

This is how the UNICODE_STRING type is defined.

typedef struct _UNICODE_STRING {

 USHORT Length;

 USHORT MaximumLength;

 PWSTR Buffer;

} UNICODE_STRING, *PUNICODE_STRING;

The Buffer field points to a wide 16-bit character buffer. The character string is not usually NULL-terminated. Instead, the Length field gives the current size of the string in bytes. The MaximumLength field gives the maximum size of the string that can fit in the buffer in bytes. This design is used to avoid reallocating string buffers too often. However, it does make manipulating Unicode strings a bit awkward. Table 4.6 shows all the kernel routines that you can use with Unicode strings.

Just in case you were interested, you do not have to use these kernel routines to access Unicode strings. You can fiddle with a string structure however you like, as long as it is in a valid format when passed to the kernel.

Table 4.6 UNICODE_STRING functions

RtlAnsiStringToUnicodeStringConverts an ANSI string to a Unicode string, optionally allocating a buffer.
RtlAppendUnicodeStringToStringAppend one Unicode string to another, up to the length ofthe destination buffer.
RtlAppendUnicodeToStringAppend a wide string to a Unicode string, up to the length of the destination buffer.
RtlCompareUnicodeStringCompares two Unicode strings, optionally case-insensitive.
RtlCopyUnicodeStringCopies one Unicode string to another, up to the length of the destination buffer.
RtlEqualUnicodeStringReturns TRUE if the two Unicode strings are equal, optionally case-insensitive.
RtlFreeUnicodeStringFrees the Unicode string buffer memory from the pool.
RtlInitUnicodeStringSets the Unicode string buffer to point to the given wide string, and sets the length fields to match.
RtlIntegerToUnicodeStringConverts a ULONG value to a Unicode string in the specified base. The string buffer must have been initialized beforehand.
RtlPrefixUnicodeString†Sees if one Unicode string is a prefix of another, optionally case-insensitive.
RtlUnicodeStringToAnsiStringConverts a Unicode string into ANSI. If you ask for the destination ANSI buffer to be allocated, free it eventually with RtlFreeAnsiString.
RtlUnicodeStringToIntegerConverts a Unicode string to an integer.
RtlUpcaseUnicodeString†Converts a Unicode string into uppercase, optionally allocating a buffer.

†NT/W2000 only

ReadReg declares the variables that will receive the values from the registry. For a UNICODE_STRING, its Buffer, Length, and MaximumLength fields have to be initialized. In this case, these fields are initialized to NULL and zero. The call to RtlQueryRegistryValues will allocate a Buffer and set the length fields.

In most cases, a UNICODE_STRING's buffer needs to be set up correctly. If you want to store an unchanging wide string value in a Unicode string, use the RtlInitUnicodeString function. The Unicode string Buffer is set to point to the passed string and the Length and Maximum-Length strings are set to the length of the string[10].

RtlInitUnicodeString(&UnicodeString, L"\\Device\\Wdm1");

If you wish to work with the contents of a Unicode string, you need to provide the wide string buffer. Use code like this.

const int MAX_CHARS = 30;

UNICODE_STRING UnicodeString;

WCHAR UnicodeStringBuffer[MAX_CHARS];

UnicodeString.Buffer = UnicodeStringBuffer;

UnicodeString.MaximumLength = MAX_CHARS*2;

UnicodeString.Length = 0;

In this case, the buffer is on the stack. If you want to use the Unicode string after this routine has completed, you must allocate the buffer from pool memory. Do not forget to free this memory once you have finished using the string.

Calling RtlQueryRegistryValues

You must send a query table array to RtlQueryRegistryValues. This array details the actions that you want to do. The last entry in the query table must be zeroed to indicate the end of the list. This is achieved when the entire query table is zeroed using RtlZeroMemory in ReadReg.

There is a wide range of options available when querying the registry. The Flags field in each query table element indicates the action you want to do. The first query listed previously uses RTL_QUERY_REGISTRY_SUBKEY, which means that the Name field contains the subkey for subsequent queries.

The following queries both set the Flags field to RTL_QUERY_REGISTRY_DI RECT. In this case, the Name field contains the registry value name, and the EntryContext field contains a pointer to the variable to receive the value. You have to trust that no one has fiddled with the value types so that a string is returned when you were expecting a ULONG.

A safer approach (not used here) is to pass the name of a callback routine in the QueryRoutine field and set the Flags field to zero. Your routine is called for each value found, and indicates the type of the found value.

The remaining parameters to RtlQueryRegistryValues give even more flexibility. RTL_REGISTRY_ABSOLUTE indicates that the Path parameter is an absolute registry path. Various other useful options can be given (e.g., if the Path is relative to the HKLM\Systcm\CurrentControlSet\Services key).

RtlQueryRegistryValues only returns STATUS_SUCCESS if all the queries were processed correctly and all the registry values were found. The code in ReadReg simply displays the return status and the values retrieved. If using this routine for real, you will probably want to store the values in global variables.

Operating system version

You can use a registry setting to determine at run time whether you are running in W98 or not. In NT and W2000 the following registry value is available, HKLM\System\CurrentControlSet\Control\ProductOptions\ProductType. The value is "WinNT" for the Workstation/Professional Windows version and either "LanmanNT" or "ServerNT" for the Server/Enterprise version. In W98 this registry value should not be available.

Installing Wdm1

That's enough on the Wdm1 code so far. The compiled driver, Wdm1.sys, is provided on the CD in free and checked build versions. However, you can recompile it if you wish.

Normally, Windows detects when a device is installed and prompts for the necessary drivers if they cannot be found already in the system. Full details of the driver selection process are given later.

For the virtual Wdm1 device, use the Control Panel "Add New Hardware" applet wizard. The process is basically the same for Windows 98 and Windows 2000.

Click Next two times, select "No, I want to select hardware from a list" and click Next. Select "Other devices" and click Next. Click "Have Disk…" and browse to the path of the Wdm1 driver (e.g., C:\WDMBook\Wdm1\Sys) and click "OK".

Two models are listed from the found installation INF files, one for the Wdm1 checked build and one for the free build. Select the model you want to install and select Next. Select "Finish" to complete installation.

The installation process does whatever the INF file specifies. The Wdm1 INF files copy the relevant driver to the Windows system32\drivers directory, adds registry settings, etc.

Windows should now have created a Wdm1 device for you. Check that it appears in the Device Manager "Other devices" category (e.g., named "WDM Book: WDM1 Example, free build"). The next chapter describes a Win32 user mode program that you can use to test that the driver is working.

In the Wdm1 code, the DriverEntry routine has been called along with various other Plug and Play callback routines, as described later.

Installation Details

You may be interested to know precisely what happens as a result of installing the Wdm1 driver and one Wdm1 device.

INF Files

Windows 98

Windows 98 copies the INF file to the Windows INF\OTHER directory. The INF file is renamed, after the manufacturer name, to "WDM BookWDM1.INF".

Windows 98 remembers that the INF file has been installed in the registry. The HKLM\ Software\Microsoft\Windows\CurrentVersion\Setup\SetupX\INF\OFM Name key has a value C:\W98\INF\OTHER\WDM BookWDM1.INF set to "WDM BookWDM1.INF". This entry is not deleted if you remove the Wdm1 device.

Windows 98 also keeps a note of the most recently used install locations in the registry.

Windows 2000

Windows 2000 copies the INF file and renames it to the next available OEM filename in the Windows INF directory. For example, WDM1.INF might be copied and renamed as C:\WINNT\INF\OEM1.INF.

Windows 2000 also remembers install locations in the registry.

Registry

The device interface for Wdm1 devices has a registry key at HKLM\System\CurrentControlSet\ Control\DeviceClasses\{C0CF0640-5F6E-11d2-B677-00C0DFE4C1F3) along with a series of subkeys for each device and their associated symbolic link(s). This key includes the WDM1_GUID {C0CF0640…}.

Windows 98

The HKLM\Enum\Root\Unknown\0000 key is the entry for the Wdm1 device. The digits 0000 will increment for each Unknown device. The HKLM\System\CurrentControlSet\Services\Class\Unknown\0000 key is the entry for the Wdm1 driver. This key is passed to DriverEntry in the RegistryPath string.

These entries are removed if you remove the Wdm1 device.

The ClassGUID value in the Wdm1 device key is {4D36E97E…} for "Unknown" type devices. This has a registry entry at HKLM\System\CurrentControlSet\Services\Class\{4D36E97E…}.

Windows 2000

The HKLM\System\CurrentControlSet\Control\Class\{4D36E97E…}\0000 key is the entry for the Wdm1 device. {4D36E97E…} is the GUID for "Unknown" type devices.

The driver service entry key is HKLM\System\CurrentControlSet\Services\Wdm1. This key is passed to DriverEntry in the RegistryPath string. The Enum subkey has values named 0 onwards such as Root\UNKNOWN\0000 for each device instance.

Windows 2000 Objects

The winobj program lets you see what Windows 2000 objects are present for the driver and the device.

The driver has an entry \driver\wdm1.

There is a symbolic link object \??\Root#UNKNOWN#0000#{C0CF0640…}. If you double-click this, you will see that this links to one of the listed devices (e.g., to \device\004059). Symbolic links are covered in the next chapter.

Managing Devices and Drivers

Having installed Wdm1, there are various device and driver management jobs that you can perform.

Add Another Device

You can open another device with the same driver using the Add New Hardware wizard again. Windows made a copy of the INF file so you can select the device from the list without having to specify your driver location. Additional entries are made in the registry for the second Wdm1 device and the device interface to it.

Removing a Device

In the Device Manager, select the device you want to remove. Click Uninstall or Remove.

Most of the device and driver registry entries are removed if the Wdm1 device is removed. However, the registry entry for the device interface and the Windows 2000 Services entry persist after a device is removed.

When you remove a device, the INF file is not removed from the Windows INF directory structure. Similarly, the Wdm1.sys driver file remains in place in the System32\Drivers directory. This makes it easy to reinstall a Wdm1 device.

If all the driver's devices have been removed, the driver is unloaded from memory. At this stage, if you update the driver in the Windows System32\drivers directory and reinstall the device, the new driver is used.

Updating the Driver

The simplest way to update a driver is to use the Update/Change driver option in the Device Manager properties for a device. The book software projects always make a copy of the latest build of a driver in the Windows System32\Drivers directory so you can reinstall a new driver without having to select the Have disk option. W2000 uses the files in this directory while W98 requires you to specify the location of the new files.

Alternatively, remove all devices and invoke the Add New Hardware wizard again.

NT Style Drivers

NT style (non-Plug and Play) drivers must use a special installation process, as described in Chapter 11.

Updating an NT style driver happens in a different way, as well. First, you must get your driver into the Windows System32\drivers directory.

In NT 3.51 and NT 4, run the Control Panel Devices applet. Find your driver, stop it, and start it again. In Windows 2000 you must opt to show hidden devices in the Device Manager before you can start or stop NT style drivers. Alternatively, you can run the book software Servicer program; type in the name of your driver; press Stop and then Start. If you run an NT style driver in W98, you must reboot the computer to use an updated driver.

Conclusion

This chapter has shown how to set up a development computer for device driver development. A very basic WDM driver has been written and installed in Windows 98 and Windows 2000.

The following chapters explain how to access this driver from a user program and enhance this driver to implement the correct Plug and Play and Power Management handling.

Listing 4.7 Wdm1.h

///////////////////////////////////////////////////////////////////////

//Copyright © 1998 Chris Cant, PHD Computer Consultants Ltd

//WDM Book for R&D Books, Miller Freeman Inc

//

//Wdm1 example

///////////////////////////////////////////////////////////////////////

//wdm1.hCommon header

///////////////////////////////////////////////////////////////////////

//Version history

//27-Apr-991.0.0CCcreation

///////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////

//Include WDM standard header with C linkage

#ifdef __cplusplus

extern "C" {

#endif

#include "wdm.h"

#ifdef __cplusplus

}

#endif

///////////////////////////////////////////////////////////////////////

//DebugPrint and Guid headers

#include "DebugPrint.h"

#include "GUIDs.h"

///////////////////////////////////////////////////////////////////////

//Spin lock to protect access to shared memory buffer

extern KSPIN_LOCK BufferLock;

extern PUCHAR Buffer;

///////////////////////////////////////////////////////////////////////

//Our device extension

typedef struct _WDM1_DEVICE_EXTENSION {

 PDEVICE_OBJECT fdo;

 PDEVICE_OBJECT NextStackDevice;

 UNICODE_STRIN GifSymLinkName;

} WDM1_DEVICE_EXTENSION, *PWDM1_DEVICE_EXTENSION;

///////////////////////////////////////////////////////////////////////

// Forward declarations of global functions

VOID Wdm1Unload(IN PDRIVER_OBJECT DriverObject);

NTSTATUS Wdm1Power(IN PDEVICE_OBJECT fdo, IN PIRP Irp);

NTSTATUS Wdm1Pnp(IN PDEVICE_OBJECT fdo, IN PIRP Irp);

NTSTATUS Wdm1AddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT pdo);

NTSTATUS Wdm1Create(IN PDEVICE_OBJECT fdo, IN PIRP Irp);

NTSTATUS Wdm1Close(IN PDEVICE_OBJECT fdo, IN PIRP Irp);

NTSTATUS Wdm1Write(IN PDEVICE_OBJECT fdo, IN PIRP Irp);

NTSTATUS Wdm1Read(IN PDEVICE_OBJECT fdo, IN PIRP Irp);

NTSTATUS Wdm1DeviceControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp);

NTSTATUS Wdm1SystemControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp);

///////////////////////////////////////////////////////////////////////

// NTSTATUS CompleteIrp(PIRP Irp, NTSTATUS status, ULONG info);

///////////////////////////////////////////////////////////////////////

Listing 4.8 Init.cpp

///////////////////////////////////////////////////////////////////////

//Copyright © 1998 Chris Cant, PHD Computer Consultants Ltd

//WDM Book for R&D Books, Miller Freeman Inc

//

//Wdm1 example

///////////////////////////////////////////////////////////////////////

//init.cpp:Driver initialization code

///////////////////////////////////////////////////////////////////////

//DriverEntryInitialisation entry point

//Wdm1UnloadUnload driver routine

///////////////////////////////////////////////////////////////////////

//Version history //27-Apr-991.0.0CCcreation

///////////////////////////////////////////////////////////////////////

#include "wdm1.h"

#pragma code_seg("INIT") // start INIT section

///////////////////////////////////////////////////////////////////////

//DriverEntry:

//

//Description:

//This function initializes the driver, and creates

//any objects needed to process I/O requests.

//

//Arguments:

//Pointer to the Driver object

//Registry path string for driver service key

//

//Return Value:

//This function returns STATUS_XXX

extern "C"

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {

 NTSTATUS status = STATUS_SUCCESS;

#if DBG

 DebugPrint Init("Wdm1 checked");

#else

 DebugPrintInit("Wdm1 free");

#endif

 DebugPrint("RegistryPath is %T", RegistryPath);

 // Export other driver entry points…

 DriverObject->DriverExtension->AddDevice = Wdm1AddDevice;

 DriverObject->DriverUnload = Wdm1Unload;

 OriverObject->MajorFunction[IRP_MJ_CREATE] = Wdm1Create;

 DriverObject->MajorFunction[IRP_MJ_CLOSE] = Wdm1Close;

 DriverObject->MajorFunction[IRP_MJ_PNP] = Wdm1Pnp;

 DriverObject->MajorFunction[IRP_MJ_POWER] = Wdm1Power;

 DriverObject->MajorFunction[IRP_MJ_READ] = Wdm1Read;

 DriverObject->MajorFunction[IRP_MJ_WRITE] = Wdm1Write;

 DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Wdm1DeviceControl;

 DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROl] = Wdm1SystemControl;

 //Initialise spin lock which protects access to shared memory buffer

 KeInitializeSpinLock(&BufferLock);

 DebugPrintMsg("DriverEntry completed");

 return status;

}

#pragma code_seg() // end INIT section

///////////////////////////////////////////////////////////////////////

//Wdm1Unload

//

//Description:

//Unload the driver by removing any remaining objects, etc.

//

//Arguments:

//Pointer to the Driver object

//

//Return Value:

//None

#pragma code_seg("PAGE") // start PAGE section

VOID Wdm1Unload(IN PDRIVER_OBJECT DriverObject) {

 // Free buffer (do not need to acquire spin lock)

 if (Buffer!=NULL) ExFreePool(Buffer);

 DebugPrintMsg("WdmlUnload");

 DebugPrintClose();

}

///////////////////////////////////////////////////////////////////////

#pragma code_seg() // end PAGE section

Listing 4.9 Pnp.cpp

///////////////////////////////////////////////////////////////////////

//Copyright © 1998 Chris Cant, PHD Computer Consultants Ltd

//WDM Book for R&D Books, Miller Freeman Inc

//

//Wdm1 example

///////////////////////////////////////////////////////////////////////

//pnp.cpp:Plug and Play and Power IRP handlers

///////////////////////////////////////////////////////////////////////

//Wdm1AddDeviceAdd device routine

//Wdm1PnpPNP IRP dispatcher

//Wdm1PowerPOWER IRP dispatcher

///////////////////////////////////////////////////////////////////////

//Version history //27-Apr-991.0.0CCcreation

///////////////////////////////////////////////////////////////////////

#define INITGUID// initialize WDM1_GUID in this module

#include "wdm1.h"

#pragma code_seg("PAGE")// start PAGE section

///////////////////////////////////////////////////////////////////////

//Wdm1AddDevice:

//

//Description:

//Cope with a new Pnp device being added here.

//Usually just attach to the top of the driver stack.

//Do not talk to device here!

//

//Arguments:

//Pointer to the Driver object

//Pointer to Physical Device Object

//

//Return Value:

//This function returns STATUS_XXX

NTSTATUS WdmlAddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT pdo) {

 DebugPrint("AddDevice");

 NTSTATUS status;

 PDEVICE_OBJECT fdo;

 // Create our Functional Device Object in fdo

 status = IoCreateDevice(DriverObject, sizeof(WDM1_DEVICE_EXTENSION),

  NULL,// No Name

  FILE_DEVICE_UNKNOWN, 0,

  FALSE,// Not exclusive

  &fdo);

 if (!NT_SUCCESS(status)) return status;

 // Remember fdo in our device extension

 PWDM1_DEVICE_EXTENSION dx = (PWDM1_DEVICE_EXTENSION)fdo->DeviceExtension;

 dx->fdo = fdo;

 DebugPrint("FDO is %x",fdo);

 // Register and enable our device interface

 status = IoRegisterDeviceInterface(pdo, &WDM1_GUID, NULL, &dx->ifSymLinkName);

 if (!NT_SUCCESS(status)) {

  IoDeleteDevice(fdo);

  return status;

 }

 IoSetDeviceInterfaceState(&dx->ifSymLinkName, TRUE);

 DebugPrint("Symbolic Link Name is %T", &dx->ifSymLinkName);

 // Attach to the driver stack below us

 dx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo.pdo);

 // Set fdo flags appropriately

 fdo->Flags &= ~DO_DEVICE_INITIALIZING;

 fdo->Flags |= DO_BUFFERED_IO;

 return STATUS_SUCCESS;

}

///////////////////////////////////////////////////////////////////////

//Wdm1Pnp:

//

//Description:

//Handle IRP_MJ_PNP requests

//

//Arguments:

//Pointer to our FDO

//Pointer to the IRP

//Various minor codes

//IrpStack->Parameters.QueryDeviceRelations

//IrpStack->Parameters.QueryInterface

//IrpStack->Parameters.DeviceCapabilities

//IrpStack->Parameters.FilterResourceRequirements

//IrpStack->Parameters.ReadWriteConfig

//IrpStack->Parameters.SetLock

//IrpStack->Parameters.QueryId

//IrpStack->Parameters.QueryDeviceText

//IrpStack->Parameters.UsageNotification

//

//Return Value:

//This function returns STATUS_XXX

NTSTATUS Wdm1Pnp(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 DebugPrint("PnP %I", Irp);

 PWDM1_DEVICE_EXTENSION dx=(PWDMl_DEVICE_EXTENSION)fdo->DeviceExtension;

 // Remember minor function

 PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

 ULONG MinorFunction = IrpStack->MinorFunction;

 // Just pass to lower driver

 IoSkipCurrentIrpStackLocation(Irp);

 NTSTATUS status = IoCallDriver(dx->NextStackDevice, Irp);

 // Device removed

 if (MinorFunction==IRP_MN_REMOVE_DEVICE) {

  DebugPrint("PnP RemoveDevice");

  // disable device interface

  IoSetDeviceInterfaceState(&dx->ifSymLinkName, FALSE);

  RtlFreeUnicodeString(&dx->ifSymLinkName);

  // unattach from stack

  if (dx->NextStackDevice) IoDetachDevice(dx->NextStackDevice);

  // delete our fdo IoDeleteDevice(fdo);

 }

 return status;

}

///////////////////////////////////////////////////////////////////////

//Wdm1Power:

//

//Description:

//Handle IRP_MJ_POWER requests

//

//Arguments:

//Pointer to the FDO

//Pointer to the IRP

//IRP_MN_WAIT_WAKE:IrpStack->Parameters.WaitWake.Xxx

//IRP_MN_POWER_SEOUENCE:IrpStack->Parameters.PowerSequence.Xxx

//IRP_MN_SET_POWER:

//IRP_MN_QUERY_POWER:IrpStack->Parameters.Power.Xxx

//

//Return Value:

//This function returns STATUS_XXX

NTSTATUS Wdm1Power(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 DebugPrint("Power %I",Irp);

 PWDM1_DEVICE_EXTENSION dx = (PWDM1_DEVICE_EXTENSION)fdo->DeviceExtension;

 // Just pass to lower driver

 PoStartNextPowerIrp(Irp);

 IoSkipCurrentIrpStackLocation(Irp);

 return PoCallDriver(dx->NextStackDevice, Irp);

}

#pragma code_seg()// end PAGE section

Listing 4.10 Dispatch.cpp

///////////////////////////////////////////////////////////////////////

//Copyright © 1998 Chris Cant, PHD Computer Consultants Ltd

//WDM Book for R&D Books, Miller Freeman Inc

//

//Wdm1 example

///////////////////////////////////////////////////////////////////////

//dispatch.cpp:Other IRP handlers

///////////////////////////////////////////////////////////////////////

//Wdm1CreateHandle Create/Open file IRP

//Wdm1CloseHandle Close file IRPs

//Wdm1ReadHandle Read IRPs

//Wdm1WriteHandle Write IRPs

//Wdm1DeviceControlHandle DeviceIoControl IRPs

//Wdm1SystemControlHandle WMI IRPs

///////////////////////////////////////////////////////////////////////

//Version history

//27-Apr-991.0.0CCcreation

///////////////////////////////////////////////////////////////////////

#include "wdm1.h"

#include "Ioctl.h"

///////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////

//Buffer and BufferSize and guarding spin lock globals (in unpaged memory)

KSPIN_LOCK BufferLock;

PUCHARBuffer = NULL;

ULONGBufferSize = 0;

///////////////////////////////////////////////////////////////////////

//Wdm1Create:

//

//Description:

//Handle IRP_MJ_CREATE requests

//

//Arguments:

//Pointer to our FDO

//Pointer to the IRP

//IrpStack->Parameters.Create.xxx has create parameters

//IrpStack->FileObject->FileName has file name of device

//

//Return Value:

//This function returns STATUS_XXX

NTSTATUS Wdm1Create(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

 DebugPrint("Create File is %T", &(IrpStack->FileObject->FileName);

 // Complete successfully

 return CompleteIrp(Irp,STATUS_SUCCESS,0);

}

///////////////////////////////////////////////////////////////////////

//Wdm1Close:

//

//Description:

//Handle IRP_MJ_CLOSE requests

//

//Arguments:

//Pointer to our FDO

//Pointer to the IRP

//

//Return Value:

//This function returns STATUS_XXX

NTSTATUS Wdm1Close(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 DebugPrintMsg("Close");

 // Complete successfully

 return CompleteIrp(Irp,STATUS_SUCCESS,0);

}

///////////////////////////////////////////////////////////////////////

//Wdm1Read:

//

//Description:

//Handle IRP_MJ_READ requests

//

//Arguments:

//Pointer to our FDO

//Pointer to the IRP

//IrpStack->Parameters.Read.xxx has read parameters

//User buffer at:AssociatedIrp.SystemBuffer(buffered I/O)

//MdlAddress(direct I/O)

//

//Return Value:

//This function returns STATUS_XXX

NTSTATUS Wdm1Read(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

 NTSTATUS status = STATUS_SUCCESS;

 LONG BytesTxd = 0;

 // Get call parameters

 LONGLONG FilePointer = IrpStack->Parameters.Read.ByteOffset.QuadPart;

 ULONG ReadLen = IrpStack->Parameters.Read.Length;

 DebugPrint("Read %d bytes from file pointer %d",(int)ReadLen,(int)FilePointer);

 // Get access to the shared buffer

 KIRQL irql;

 KeAcquireSpinLock(&BufferLock,&irql);

 // Check file pointer

 if (FilePointer<0) status = STATUS_INVALID_PARAMETER;

 if (FilePointer>=(LONGLONG)BufferSize) status = STATUS_END_OF_FILE;

 if (status==STATUS_SUCCESS) {

  // Get transfer count

  if ( ((ULONG)FilePointer)+ReadLen>BufferSize) {

   BytesTxd = BufferSize – (ULONG)FilePointer;

   if (BytesTxd<0) BytesTxd = 0;

  } else BytesTxd = ReadLen;

  // Read from shared buffer

  if (BytesTxd>0 && Buffer!=NULL) RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, Buffer+FilePointer, BytesTxd);

 }

 // Release shared buffer

 KeReleaseSpinlock(&BufferLock,irql);

 DebugPrint("Read: %d bytes returned",(int)BytesTxd);

 // Complete IRP

 return Completelrp(Irp.status.BytesTxd);

}

///////////////////////////////////////////////////////////////////////

//Wdm1Write: //

//Description:

//Handle IRP_MJ_WRITE requests

//

//Arguments:

//Pointer to our FDO

//Pointer to the IRP

//IrpStack->Parameters.Write.xxx has write parameters

//User buffer at:AssociatedIrp.SystemBuffer(buffered I/O)

//MdlAddress(direct I/O)

//

//Return Value:

//This function returns STATUS_XXX

NTSTATUS Wdm1Write(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

 NTSTATUS status = STATUS_SUCCESS;

 LONG BytesTxd = 0;

 // Get call parameters

 LONGLONG FilePointer = IrpStack->Parameters.Write.ByteOffset.QuadPart;

 ULONG WriteLen = IrpStack->Parameters.Write.Length;

 DebugPrint("Write %d bytes from file pointer %d",(int)WriteLen,(int)FilePointer);

 if (FilePointer<0) status = STATUS_INVALID_PARAMETER;

 else {

  // Get access to the shared buffer

  KIRQL irql;

  KeAcquireSpinLock(&BufferLock,&irql);

  BytesTxd = WriteLen;

  // (Re)allocate buffer if necessary

  if ( ((ULONG)FilePointer)+WriteLen>BufferSize) {

   ULONG NewBufferSize = ((ULONG)FilePointer)+WriteLen;

   PVOID NewBuffer = ExAllocatePool(NonPagedPool.NewBufferSize);

   if (NewBuffer==NULL) {

    BytesTxd = BufferSize – (ULONG)FilePointer;

    if (BytesTxd<0) BytesTxd = 0;

   } else {

    RtlZeroMemory(NewBuffer,NewBufferSize);

    if (Buffer==NULL) {

     RtlCopyMemory(NewBuffer,Buffer,BufferSize);

     ExFreePool(Buffer);

    }

    Buffer = (PUCHAR)NewBuffer;

    BufferSize = NewBufferSize;

   }

  }

  // Write to shared memory

  if (BytesTxd>0 && Buffer!=NULL) RtlCopyMemory(Buffer+FilePointer, Irp->AssociatedIrp.SystemBuffer, BytesTxd);

  // Release shared buffer

  KeReleaseSpinLock(&BufferLock,irq1);

 }

 DebugPrint("Write: %d bytes written", (int)BytesTxd);

 // Complete IRP

 return CompleteIrp(Irp,status,BytesTxd);

}

///////////////////////////////////////////////////////////////////////

//WdmlDeviceControl:

//

//Description:

//Handle IRP_MJ_DEVICE_CONTROL requests

//

//Arguments:

//Pointer to our FDO

//Pointer to the IRP

//Buffered:AssociatedIrp.SystemBuffer (and IrpStack-Parameters.DeviceIoControl.Type3InputBuffer)

//Direct:MdlAddress

//

//IrpStack->Parameters.DeviceIoControl.InputBufferLength

//IrpStack->Parameters.DeviceIoControl.OutputBufferLength

//

//Return Value:

//This function returns STATUS_XXX

NTSTATUS Wdm1DeviceControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

 NTSTATUS status – STATUS_SUCCESS;

 ULONG BytesTxd = 0;

 ULONG ControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;

 ULONG InputLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength;

 ULONG OutputLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;

 DebugPrint("DeviceIoControl: Control code %x InputLength %d OutputLength %d", ControlCode, InputLength, OutputLength);

 // Get access to the shared buffer KIRQL irql ;

 KeAcquireSpinLock(&BufferLock,&irql);

 switch (ControlCode) {

 ///////Zero Buffer

 case IOCTL_WDM1_ZERO_BUFFER:

  // Zero the buffer

  if (Buffer!=NULL && BufferSize>0) RtlZeroMemory(Buffer,BufferSize);

  break;

 ///////Remove Buffer

 case IOCTL_WDM1_REMOVE_BUFFER:

  if (Buffer!=NULL) {

   ExFreePool(Buffer);

   Buffer = NULL;

   BufferSize = 0;

  }

  break;

 ///////Get Buffer Size as ULONG

 case IOCTL_WDM1_GET_BUFFER_SIZE:

  if (OutputLength<sizeof(ULONG)) status = STATUS_INVALID_PARAMETER;

  else {

   BytesTxd = sizeof(ULONG);

   RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer,&BufferSize,sizeof(ULONG));

  }

  break;

 ///////Get Buffer

 case IOCTL_WDM1_GET_BUFFER:

  if (OutputLength>BufferSize) status = STATUS_INVALID_PARAMETER;

  else {

   BytesTxd = OutputLength;

   RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer,Buffer,BytesTxd);

  }

  break;

 ///////Invalid request

 default:

  status = STATUS_INVALID_DEVICE_REQUEST;

 }

 // Release shared buffer

 KeReleaseSpinlock(&BufferLock,irql);

 DebugPrint("DeviceIoControl: %d bytes written", (int)BytesTxd);

 // Complete IRP

 return CompleteIrp(Irp,status.BytesTxd);

}

///////////////////////////////////////////////////////////////////////

//Wdm1SystemControl:

//

//Description:

//Handle IRP_MJ_SYSTEM_C0NTROL requests

//

//Arguments:

//Pointer to our FDO

//Pointer to the IRP

//Various minor parameters

//IrpStack->Parameters.WMI.xxx has WMI parameters

//

//Return Value:

//This function returns STATUS_XXX

NTSTATUS WdmlSystemControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 DebugPrintMsg("SystemControl");

 // Just pass to lower driver

 IoSkipCurrentIrpStackLocation(Irp);

 PWDM1_DEVICE_EXTENSION dx = (PWDM1_DEVICE_EXTENSION)fdo->DeviceExtension;

 return IoCallDriver(dx->NextStackDevice, Irp);

}

///////////////////////////////////////////////////////////////////////

//Wdm1Cleanup:

//

//Description:

//Handle IRP_MJ_CLEANUP requests

//Cancel queued IRPs which match given FileObject

//

//Arguments:

//Pointer to our FDO

//Pointer to the IRP

//IrpStack->FileObject has handle to file

//

//Return Value:

//This function returns STATUS_XXX

//Not needed for Wdm1

///////////////////////////////////////////////////////////////////////

//CompleteIrp:Sets IoStatus and completes the IRP

NTSTATUS CompleteIrp(PIRP Irp, NTSTATUS status, ULONG info) {

 Irp->IoStatus.Status = status;

 Irp->IoStatus.Information = info;

 IoCompleteRequest(Irp,IO_NO_INCREMENT);

 return status;

}

///////////////////////////////////////////////////////////////////////

Listing 4.11 Wdm1.rc

//Microsoft Developer Studio generated resource script.

//

#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS

///////////////////////////////////////////////////////////////////////

//

// Generated from the TEXTINCLUDE 2 resource.

//

#include "afxres.h"

///////////////////////////////////////////////////////////////////////

#undef APSTUDIO_READONLY_SYMBOLS

///////////////////////////////////////////////////////////////////////

// English (U.S.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)

#ifdef _WIN32

LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

#pragma code_page(1252)

#endif //_WIN32

#ifndef _MAC

///////////////////////////////////////////////////////////////////////

//

// Version

//

VS_VERSION_INFO VERSIONINFO

 FILEVERSION 1,0,5,0

 PRODUCTVERSION 1,0,0,0

 FILEFLAGSMASK 0x3fL

#ifdef _DEBUG

 FILEFLAGS 0x1L

#else

 FILEFLAGS 0x0L

#endif

 FILEOS 0x4L

 FILETYPE 0x2L

 FILESUBTYPE 0x0L

BEGIN

 BLOCK "StringFileInfo"

 BEGIN

  BLOCK "080904b0"

  BEGIN

   VALUE "Comments", "Chris Cant\0"

   VALUE "CompanyName", "PHD Computer Consultants Ltd\0"

   VALUE "FileDescription", "Wdm1\0"

   VALUE "FileVersion", "1, 0, 5, 0\0"

   VALUE "InternalName", "Wdm1 driver\0"

   VALUE "LegalCopyright", "Copyright © 1998,1999 PHD Computer Consultants Ltd\0"

   VALUE "OriginalFilename", "Wdm1.sys\0"

   VALUE "ProductName", "WDM Book\0"

   VALUE "ProductVersion", "1, 0, 0, 0\0"

  END

 END

 BLOCK "VarFileInfo"

 BEGIN

  VALUE "Translation", 0x809, 1200

 END

END

#endif // !_MAC

#endif // English (U.S.) resources

///////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////

// English (U.K.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)

#ifdef _WIN32

LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK

#pragma codepage(1252)

#endif //_WIN32

#ifdef APSTUDIO_INVOKED

///////////////////////////////////////////////////////////////////////

//

// TEXTINCLUDE

//

1 TEXTINCLUDE DISCARDABLE

BEGIN

 "resource.h\0"

END

2 TEXTINCLUDE DISCARDABLE

BEGIN

 "#include ""afxres.h""\r\n" "\0"

END

3 TEXTINCLUDE DISCARDABLE

BEGIN

 "\r\n" "\0"

END

#endif // APSTUDIO_INVOKED

#endif // English (U.K.) resources

///////////////////////////////////////////////////////////////////////

#ifndef APSTUDIO_INVOKED

///////////////////////////////////////////////////////////////////////

//

// Generated from the TEXTINCLUDE 3 resource.

//

///////////////////////////////////////////////////////////////////////

#endif // not APSTUDIO_INVOKED

Listing 4.12 Ioctl.h

//DeviceIoControl IOCTL codes supported by Wdm1

#define IOCTL_WDM1_ZERO_BUFFER CTL_CODE(\

 FILE_DEVICE_UNKNOWN,\

 0x801,\

 METHOD_BUFFERED,\

 FILE_ANY_ACCESS)

#define IOCTL_WDM1_REMOVE_BUFFER CTL_CODE(\

 FILE_DEVICE_UNKNOWN,\

 0x802,\

 METHOD_BUFFERED,\

 FILE_ANY_ACCESS)

#define IOCTL_WDM1_GET_BUFFER_SIZE CTL_CODE(\

 FILE_DEVICE_UNKNOWN,\

 0x803, \

 METHOD_BUFFERED,\

 FILE_ANY_ACCESS)

#define IOCTL_WDM1_GET_BUFFER CTL_CODE(\

 FILE_DEVICE_UNKNOWN,\

 0x804,\

 METHOD_BUFFERED,\

 FILE_ANY_ACCESS)

#define IOCTL_WDM1_UNRECOGNISED CTL_CODE(\

 FILE_DEVICE_UNKNOWN,\

 0x805,\

 METHOD_BUFFERED,\

 FILE_ANY_ACCESS)

Listing 4.13 GUIDs.h

///////////////////////////////////////////////////////////////////////

//Wdm1 device interface GUID

// {C0CF0640-5F6E-11d2-B677-00C0DFE4C1F3}

DEFINE_GUID(WDM1_GUID, 0xc0cf0640, 0x5f6e, 0x11d2, 0xb6, 0x77, 0x0, 0xc0, 0xdf, 0xe4, 0xc1, 0xf3);

///////////////////////////////////////////////////////////////////////

//Wdm2 device interface GUID

// {C0CF0641-5F6E-11d2-B677-00C0DFE4C1F3}

DEFINE_GUID(WDM2_GUID, 0xc0cf0641, 0x5f6e, 0x11d2, 0xb6, 0x77, 0x0, 0xc0, 0xdf, 0xe4, 0xd, 0xf3);

///////////////////////////////////////////////////////////////////////

//Wdm3 device interface GUID

// {C0CF0642-5F6E-11d2-B677-00C0DFE4C1F3}

DEFINE_GUID(WDM3_GUID, 0xc0cf0642, 0x5f6e, 0x11d2, 0xb6, 0x77, 0x0, 0xc0, 0xdf, 0xe4, 0xd, 0xf3);

///////////////////////////////////////////////////////////////////////

//Wdm3 WMI data block GUID

// {C0CF0643-5F6E-11d2-B677-00C0DFE4C1F3}

DEFINE_GUID(WDM3_WMI_GUID, 0xc0cf0643, 0x5f6e, 0x11d2, 0xb6, 0x77, 0x0, 0xc0, 0xdf, 0xe4, 0xc1, 0xf3);

///////////////////////////////////////////////////////////////////////

//Wdm3 WMI event block GUID

// {C0CF0644-5F6E-11d2-B677-00C0DFE4C1F3}

DEFINE_GUID(WDM3_WMI_EVENT_GUID, 0xc0cf0644, 0x5f6e, 0x11d2, 0xb6, 0x77, 0x0, 0, 0xdf, 0xe4, 0xc1, 0xf3);

/*

// {C0CF0645-5F6E-11d2-B677-00C0DFE4C1F3}

DEFINE_GUID(<<name>>,

0xc0cf0645, 0x5f6e, 0x11d2, 0xb6, 0x77, 0x0, 0xc0, 0xdf, 0xe4, 0xc1, 0xf3);

// {C0CF0646-5F6E-11d2-B677-00C0DFE4C1F31

DEFINE_GUID(<<name>>,

0xc0cf0646, 0x5f6e, 0x11d2, 0xb6, 0x77, 0x0, 0xc0, 0xdf, 0xe4, 0xc1, 0xf3);

// {C0CF0647-5F6E-11d2-B677-00C0DFE4C1F3}

DEFINE GUID(<<name>>,

0xc0cf0647, 0x5f6e, 0x11d2, 0xb6, 0x77, 0x0, 0xc0, 0xdf, 0xe4, 0xc1, 0xf3);

// {C0CF0648-5F6E-11d2-B677-00C0DFE4C1F3}

DEFINE_GUID(<<name>>,

0xc0cf0648, 0x5f6e, 0x11d2, 0xb6, 0x77, 0x0, 0xc0, 0xdf, 0xe4, 0xc1, 0xf3);

*/

///////////////////////////////////////////////////////////////////////

Listing 4.14 resource.h

//{{NO_DEPENDENCIES}}

// Microsoft Developer Studio generated include file.

// Used by Wdm1.rc

//

// Next default values for new objects

//

#ifdef APSTUDIO_INVOKED

#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NO_MFC              1

#define _APS_NEXT_RESOURCE_VALUE 101

#define _APS_NEXT_COMMAND_VALUE  40001

#define _APS_NEXT_CONTROL_VALUE  1000

#define _APS_NEXT_SYMED_VALUE    101

#endif

#endif

Listing 4.15 Wdm1free.inf

; Wdm1free.Inf – install information file

; Copyright © 1998,1999 Chris Cant, PHD Computer Consultants Ltd

[Version]

Signature="$Chicago$"

Class=Unknown

Provider=%WDMBook%

DriverVer=04/26/1999,1.0.5.0

[Manufacturer]

%WDMRook% = WDM.Book

[WDM.Book]

%Wdm1%=Wdm1.Install, *wdmBook\Wdm1

[DestinationDirs]

Wdm1.Files.Driver=10,System32\Drivers

Wdm1.Files.Driver.NTx86=10,System32\Drivers

[SourceDisksNames]

1="Wdm1 build directory",,,

[SourceDisksFiles]

Wdm1.sys=1,obj\i386\free

[SourceDisksFiles.x86] Wdm1.sys=1,objfre\i386

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Windows 98

[Wdm1.Install]

CopyFiles=Wdm1.Files.Driver

AddReg=Wdm1.AddReg

[Wdm1.AddReg]

HKR,,DevLoader,,*ntkern

HKR,,NTMPDriver,,Wdm1.sys

[Wdm1.Files.Driver]

Wdm1.sys

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Windows 2000

[Wdm1.Install.NTx86]

CopyFiles=Wdm1.Files.Driver.NTx86

[Wdm1.Files.Driver.NTx86]

Wdm1.sys,,,%COPYFLG_NOSKIP%

[Wdm1.Install.NTx86.Services]

AddService = Wdm1, %SPSVCINST_ASSOCSERVICE%, Wdm1.Service

[Wdm1.Service]

DisplayName = %Wdm1.ServiceName%

ServiceType = %SERVICE_KERNEL_DRIVER%

StartType = %SERVICE_DEMAND_START%

ErrorControl = %SERVICE_ERROR_NORMAL%

ServiceBinary = %10%\System32\Drivers\Wdm1.sys

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Strings

[Strings]

WDMBook="WDM Book"

Wdm1="WDM Book: Wdm1 Example, free build"

Wdm1.ServiceName="WDM Book Wdm1 Driver"

SPSVCINST_ASSOCSERVICE=0x00000002; Driver service is associated with device being installed

COPYFLG_NOSKIP=2; Do not allow user to skip file

SERVICE_KERNEL_DRIVER=1

SERVICE_AUTO_START=2

SERVICE_DEMAND_START=3

SERVICE_ERROR_NORMAL=1

Listing 4.16 SOURCES

TARGETNAME=Wdm1

TARGETTYPE=DRIVER

DRIVERTYPE=WDM

TARGETPATH=OBJ

INCLUDES=$(BASEDIR)\inc;

SOURCES=init.cpp \

 dispatch.cpp \

 pnp.cpp \

 DebugPrint.c \

 Wdm1.rc

NTTARGETFILES=PostBuildSteps

Listing 4.17 makefile.inc

PostBuildSteps: $(TARGET)

!if "$(DDKBUILDENV)"="free"

 rebase –B 0x10000 –X . $(TARGET)

!endif

 copy $(TARGET) $(WINDIR)\system32\drivers

Listing 4.18 makefile

#

# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new. source

# file to this component. This file merely indirects to the real make file

# that is shared by all the driver components of the Windows NT DDK

#

!INCLUDE $(NTMAKEENV)\makefile.def

Listing 4.19 MakeDrvr.bat

@echo off

if "%1"=="" goto usage

if "%3"=="" goto usage

if not exist %1\bin\setenv.bat goto usage

call %1\bin\setenv %1 %4

%2

cd %3

build –b –w %5 %6 %1 %8 %9

goto exit

:usage

echo usage MakeDrvr DDK_dir Driver_Drive Driver_Dir free/checked [build_options]

echo eg MakeDrvr %%DDKROOT%% C: %%WDMBOOK%% free –cef

:exit


  1. You will probably need to ensure that the Windows 98 environment space size is at least 2048 for device driver development.

  2. The PHDIo and Wdm3 drivers are not included in the compilation list as they do not compile in Windows 98.

  3. Be careful if initializing strings in paged code segments or code segments that are discarded after initialization. The Buffer data has the attributes of the underlying code segment and so may not be available when you want to access it. Chapter 14 gives an example in which I initially got this wrong.