Essentials Of Building Windows Drivers
September 15, 2003
James Antognini

Copyright © 2003 James Antognini. All rights reserved.

If you are new to building drivers or other software via the Windows build mechanism, there is much to learn. If you are an experienced developer, you may not know about some useful features that the standard build procedure provides. This article lays out the essentials of building drivers for Win2000, WinXP and Win2003 with the Windows DDK and describes a number of advanced features in building.

“Build” is the creation of executables and other binaries from source code, with constraints such as target OS level, optimization and debugging capabilities. The process picks up changes made since the last build operation, with an option to rebuild completely. The DDK’s build.exe invokes utilities like the compiler and linker with command-line options appropriate for type type of target you are building. You control the process by putting directives in environment variables and in control files. Then, usually in a command window you open by a Start menu drop-down created by DDK installation, you launch build. But you don’t have to build in a command window; you can, if you wish, build in Visual Studio by having it run DDK build.

Getting ready to use the DDK

Practically speaking, you need the Driver Development Kit (DDK) in order to build drivers. This isn’t the time or place to discuss whether you could somehow get by with header and library files obtained some other way, or whether you can use a different compiler or linker. Follow these steps to install the DDK:

  1. Obtain the DDK that Microsoft released with the latest version of the Windows operating system. At the time I’m writing this, that would be the Server 2003 edition. Always use the most recent DDK because it will let you build drivers destined for that OS level and for levels back through Win2000. In addition, that DDK will contain the accumulated fixes for all those levels. 
  2. If you have an MSDN subscription at Professional level or higher, you can download the DDK from the Microsoft Web site. With or without a subscription, you can order the DDK from Microsoft and pay only for shipping. Having the CD is a painless form of backup. See http://www.microsoft.com/whdc/ddk/winddk.mspx.
  3. Run the setup program on the system where you will be building. This system can be Win2000, WinXP or Win2003; that is, the level of system on which you build does not constrain your choice of target. Note, however, that the tools in the most recent DDKs will not run on Windows 98 or Millennium systems.

You will have some choices to make when you run setup:

  1. It is prudent to accept the default high-level directory, \WINDDK\3790. If you specify a name of your own, do not use one with imbedded blanks, because some build tools will parse commands incorrectly. 
  2. It is probably best to choose Win2000, WinXP and Win2003 as targets, so that you can build for any of them. (If you are planning on a 64-bit target, select the options for that, too.) For example:

  1. You should choose all the documentation and all the tools. 
  2. You should install all the samples. You may want to study them from time to time, and they will make it easy to verify that you have installed the DDK correctly. 
  3. If the instructions warn against installing over a previous DDK, uninstall that earlier version first.
  1. The result of installation is a set of directories and build environments. 
    1. Under the DDK directory will be subdirectories and files, including utilities and headers. It is wise to mark the high-level directory and everything under it as read-only so that you do not inadvertently alter a file whilst browsing it with an editor. 
    2. A “build environment” is a command window that, in being started, tailors environmental variables for the target OS. You start an environment by doing:

      Start
        Programs
          Development Kits
            Windows DDK 3790
              Build Environments
                <Win2000 | WnXP | Win2003>
                  <Free build | Checked build>

(It may be handy to create a shortcut on the desktop for a target that you often use.)

  1. Verify your installation. This is not required, but it tells you if you’ve installed the DDK correctly, and it is readily done with the samples. If you have made the DDK read-only, copy all the samples— the \src subdirectory — to \Temp. Start a build environment such as WinXP checked. In the command window, make \src the current directory, e.g., cd \Temp\src. Now type build –cZ. This will build all the samples, using DDK utilities and headers and putting the output under the current directory; the process will take a few minutes. If there are no errors, your installation was good. If you copied the samples, you can delete the copied \src subdirectory.

Note: The SDK and Visual Studio do not need to be installed to do DDK build.

The basic layout of a driver project

A driver project consists of at least one directory, one or more source files, possibly header files and sundry other files. Here are typical contents of a project:

  1. Source files. At least one of these is required for any practical project. The file types are usually .c or .cpp or both. Assembler (.asm) files are possible, too, but I’m not going to discuss them in this article. 
  2. Header files. A typical project will have one or more header files of its own. You often put them in their own subdirectory with a name like \inc
  3. A .rc (resource) file. If you have one, you usually put it in the project directory. I’m not going to discuss resource files. You should have one, though, because you should provide a VERSION resource to make it easy for end users to discover the basic facts about your driver file.
  4. A .mof (WMI resource) file. If used, this is usually placed under the project directory and will be processed by the MOF compiler, as discussed further on under the header WMI.
  5. The sources file. You have to have this file, and I’ll discuss it in depth further on under sources.
  6. The dirs file. This is an optional file. I’ll describe what it’s for under dirs.
  7. The makefile file. This very short file is required. You can copy it from practically any DDK sample, since all drivers use exactly the same one-line make file. Here’s what this file looks like:

!INCLUDE $(NTMAKEENV)\makefile.def

Here is how a driver project might look before the first build. (Note: you can download this project from the WD-3 site at this URL: http://www.wd-3.com/downloads/WinBuild.zip.)

(I’m not showing the contents of \DriverPart2, \inc and \TalkDriver.)

After you have a successful build, there will be some new files:

There will be several new subdirectories, a log file and a browser (.bsc) file. Under the new directories there are (not shown) .obj files, a .lib file, a .sys file (the driver executable), an .exe file (a user-space program to call the driver) and symbol files.

Note: It’s not a good idea to put your projects under the DDK directory, as you are likely to be installing a newer DDK from time to time and could lose projects located under the DDK directory. It’s unfortunate that the standard build environment shortcuts in the Start menu leave you at a subdirectory within the DDK. Just remember to issue a CD command to move to a more sensible place.

Build macros

You control build by providing directives, which the DDK documentation calls “macros.” The documentation attempts to distinguish between directives supplied in a sources file (where they allow substitution) and directives in the form of environmental variables in the command window. The documentation admits that the distinction is a fine one. I’ll ignore it, because you can specify many of the directives in either form. Sometimes it makes sense to specify them in the sources file, and sometimes it is easier to set them up via environmental variables.

It will help to know a little about how build does what it does. The process begins when you invoke build.exe. This program figures out dependencies in the project files and then repeatedly calls nmake.exe to make parts. Build (build.exe and nmake.exe taken as a unit) functions in stages, with compilation, link-edit and other operations each constituting a stage. At each stage, build scans DDK makefiles, sets up command-line option strings and calls a compiler, the link-editor or another utility to do the real work of producing an .obj, .lib, .sys, .exe, .dll or other file.

Build has no memory between stages, and this lack of memory can produce some oddities. One peculiarity is that directives occasionally behave inconsistently with respect to each other. TARGETPATH and TARGETLIBS are such a case, in that you have to specify path information for the same binary differently in each (see below).

The sources file

This is where build gets most of its directives. Here are some of the more important ones — and note that the first four are required:

  1. TARGETNAME: This required directive specifies the name of the intended binary. For example, you might give TARGETNAME=MyDriver (build will append .sys to the driver’s name). 
  2. TARGETTYPE: This required directive dictates the type of binary to be built. Most common are: 
    1. TARGETYPE=DRIVER for a plain-vanilla driver (a .sys file). 
    2. TARGETTYPE=EXPORT_DRIVER for a driver that exports entry points as a DLL does. (The resulting binary is a .sys file.) A kernel DLL is such a creature. See Tim Roberts’ article about kernel-mode DLLs for more information about this option.
    3. TARGETTYPE=DRIVER_LIBRARY (or TARGETTYPE=LIBRARY) for a .lib binary that will be link-edited into an executable. This feature is typically used when there are two or more source directories (perhaps because each is owned by a different developer). The sources file in one directory could have TARGETTYPE=DRIVER (for a .sys file); the sources in the second (or third, etc.) directory would give TARGETTYPE=DRIVER_LIBRARY. A complete sources file has an example. 
    4. TARGETTYPE=PROGRAM for a user-space program (an .exe file), for example, a program to call the driver. 
    5. TARGETTYPE=DYNLINK for a user-space DLL, which an .exe file might use.

In case you’re wondering how names like ntoskrnl.exe and hal.dll are generated for kernel modules, the answer is that build has special-case logic. Don’t try to create kernel modules of your own with an extension of .exe or .dll.

  1. TARGETPATH: This required directive says where to put the binary.

Note: The specific form TARGETPATH=lib$(BUILD_ALT_DIR) is often employed, and the resulting directory path is one beneath the one where the sources file is located; for a WinXP checked-build target, that path would be libchk_wxp_x86\i386. The path name comes from concatenating “lib” with the evaluated environmental variable BUILD_ALT_DIR (see environmental variables) and with “\i386,” which designates the platform type. If you’re using this form to produce a .lib file, you will specify the file’s path in TARGETPATH differently from the way you will specify the path for that same file in building an executable incorporating the .lib file (see TARGETLIBS).

  1. SOURCES: This required directive names the source files. For example,

SOURCES=        \
  BozoStuff.cpp \
  miniport.c    \
  passthru.c    \
  passthru.rc   \
  protocol.c    \
  UtilRtns.c

Notice the mixture of .c and .cpp files: The compiler’s default action is to apply C or C++ language rules according to file type.

Note: If a directive is to have more than one entry, you may specify all on a single line, or you may put them on several lines, but then you must end each line with a backslash and no following characters, not even blanks. This restriction applies to directives in sources files and in dirs files.

A further note: In SOURCES, names can take the form filename.ext, .\ filename.ext and ..\ filename.ext, but not ..\..\ filename.ext and not a fully qualified name like c:\dir\ filename.ext. That is another quirk of build.

  1. INCLUDES: This specifies the directories for any header files (other than the target OS’s standard directories). For example,

INCLUDES= \
  .\inc   \
  C:\JA.pgm\src\C\Drivers\JADriver\inc

Note: Unlike SOURCES, INCLUDES allows forms like ..\..\Dir and fully qualified directory paths. Do not, however, specify paths with imbedded blanks. Instead, use the 8.3 name (you can discover the 8.3 names of directories and files below a directory by issuing the command dir /x <directory> in a command window).

  1. C_DEFINES: A directive to affect compilation. Its value is passed to the C compiler (and to the MIDL compiler). See compiler parameters for some possibilities. 
  2. USER_C_FLAGS: Another directive to affect compilation. Its value is passed to the C/C++ compiler. For example,

USER_C_FLAGS=$(USER_C_FLAGS) /FAsc

would pick up any value defined in the environmental variable USER_C_FLAGS in the command window and would append /FAsc to that value. See compiler parameters for other possibilities.

  1. MSC_OPTIMIZATION: This overrides the default compiler optimization of build. A checked build will have /Od /Oi, to prevent optimization. 
  2. MSC_WARNING_LEVEL: This controls the compiler’s warning level. The default is /W3, but it is better practice to set it to /W3 /Wx so that warnings are considered errors. 
  3. TARGETLIBS: This names one or more libraries needed by link-edit to resolve references (APIs, sections of code, variables). Many OS libraries like ntoskrnl.lib are automatically searched, but you may need to specify some. If you were building an NDIS intermediate driver, for example, you would specify TARGETLIBS=$(DDK_LIB_PATH) \ndis.lib. And you would of course name any libraries of your own.

Note: TARGETLIBS does not have the quite same special handling that TARGETPATH does. If you created a .lib file with a path of TARGETPATH=lib$(BUILD_ALT_DIR), in the sources file for the .sys file you would designate the .lib file’s path more fully, by lib$(BUILD_ALT_DIR)\*\ (asterisk means implicit platform type).

To make the point above clear, let’s suppose you have this for the .lib file:

TARGETNAME=DriverPart2
TARGETTYPE=DRIVER_LIBRARY                    # output is a .lib file
TARGETPATH=lib$(BUILD_ALT_DIR)               # where to put the .lib file.

For the .sys file, you would have:

TARGETNAME=MyDriver
TARGETTYPE=DRIVER                            # output is a .sys file
TARGETLIBS=lib$(BUILD_ALT_DIR)\*\DriverPart2 # where to find the .lib file.
TARGETPATH=lib$(BUILD_ALT_DIR)               # where to put the .sys file.

  1. LINKER_FLAGS: This is information supplied to the linker as a parameter. If you want the linker to produce a map, for example, specify "-MAP" as the value of this parameter. 
  2. Browser information: You specify this if you intend to use the Visual Studio browser to find definitions of APIs, symbols and variables (see browsable defintions); in a large project, especially in one to which you are new, this capability is a very handy one. You need two directives to get browser information: the first to make build create it, and the second to indicate the name of the browser database file.

BROWSER_INFO=1
BROWSERFILE=$(TARGETNAME).bsc -n

  1. You can use conditional logic. For example:

!if !defined(DDK_TARGET_OS) || "$(DDK_TARGET_OS)"=="Win2K"
# The driver is for Win2K. Build with NDIS 4.0
C_DEFINES=$(C_DEFINES) -DNDIS40_MINIPORT=1
C_DEFINES=$(C_DEFINES) -DNDIS40=1
!else
# The driver is for WinXP or Win2003, so build# with NDIS 5.1.
C_DEFINES=$(C_DEFINES) -DNDIS51_MINIPORT=1
C_DEFINES=$(C_DEFINES) -DNDIS51=1
!endif

Putting it all together, you might have this in a complete sources file:

TARGETNAME=MyPassthru
TARGETYPE=DRIVER
TARGETPATH=lib$(BUILD_ALT_DIR)
TARGETLIBS=$(DDK_LIB_PATH)\ndis.lib

SOURCES=      \
BozoStuff.cpp \
miniport.c    \
passthru.c    \
passthru.rc   \
protocol.c    \
UtilRtns.c

INCLUDES= \
  .\inc   \
  C:\JA.pgm\src\C\Drivers\JADriver\inc

USER_C_FLAGS=$(USER_C_FLAGS) /FAsc

!if !defined(DDK_TARGET_OS) || "$(DDK_TARGET_OS)"=="Win2K"
C_DEFINES=$(C_DEFINES) -DNDIS40_MINIPORT=1
C_DEFINES=$(C_DEFINES) -DNDIS40=1
!else
C_DEFINES=$(C_DEFINES) -DNDIS51_MINIPORT=1
C_DEFINES=$(C_DEFINES) -DNDIS51=1
!endif

BROWSER_INFO=1
BROWSERFILE=$(TARGETNAME).bsc -n

You can find a full list of build directives in the DDK documentation under the heading “Build Utility Macros.”

Note: After you make a change in a sources file, it is usually necessary to rebuild completely to pick up the changes.

The dirs file

The dirs file is another ancillary file that build uses. The file can serve several purposes. It is needed when a project contains several distinct pieces, such as a driver proper (a .sys binary) and a kernel DLL (also a .sys binary; you can find an example on my website at kernel DLL). Another such case is a driver and a user-space program that communicates with the driver. A dirs file (or maybe more than one) is needed when different parts of a project require different build parameters; the sources file in each part can specify these. Finally, a dirs file makes it possible to break up a large project into smaller pieces owned by different developers, with each piece under a separate subdirectory. In this type of project, the sources file in each developer’s subdirectory would build a library. There would be a master subdirectory whose sources file would assemble the complete driver using the libraries.

The usual arrangement of a multi-piece project is that the dirs file is in the project’s top directory, and each piece is in a subdirectory. For example, the dirs file might contain:

DIRS=    \
  Driver \
  DriverDLL

A project with a driver and a kernel DLL might have components like these:

dirs
  .\Driver
  BozoStuff.cpp
  makefile
  miniport.c
  passthru.c
  passthru.rc
  protocol.c
  sources
  UtilRtns.c
.\DriverDLL
  DriverDLL.cpp
  makefile
  sources
.\inc
  OtherHdrs.h
  passthru.h

In the above, the sources file in the Driver subdirectory would specify TARGETTYPE=DRIVER, resulting in a .sys file. The sources file in the DriverDLL subdirectory would specify TARGETTYPE=EXPORT_DRIVER, resulting in another .sys file.

A project with two pieces for a driver and a user-space program might take this form:

dirs
.\Driver
  BozoStuff.cpp
  makefile
  miniport.c
  passthru.c
  passthru.rc
  protocol.c
  sources
  UtilRtns.c
.\DriverRtns
  DriverRtn1.cpp
  DriverRtn2.cpp
  makefile
  sources
.\UserProgram
  TalkDriver.cpp
.\inc
  OtherHdrs.h
  passthru.h

The Driver sources file would specify TARGETTYPE=DRIVER; the DriverRtns sources file, TARGETTYPE=DRIVER_LIBRARY; and the UserProgram sources file, TARGETTYPE=PROGRAM. For Driver, a .sys file would be built; for DriverRtns, a .lib file would be built (and later incorporated into the Driver .sys file); and for UserProgram, an .exe file would be built.

One last point: It is possible for a dirs file to point to one or more dirs files, in a tree, with a sources file in each leaf. You took advantage of this feature when you built the DDK samples for verification (you did verify, right?).

Environment variables

Build will respect certain environment variables in the command window. You can refer to such variables in a sources file with this syntax: $(variable_name). You can also redefine them with syntax like ‘variable_name = value’.

Here are some of the environment variables that build uses:

  1. CL: One or more compiler options. For example, cl=/FAsc to indicate pseudo-assembler output (which you’d use to see just what code the compiler is generating for that inner loop you were trying to optimize).

You can define your own compiler preprocessor variables this way. If, for example, you want to have a variable called MyVarLvl (value “2”) visible to the preprocessor, say this: set cl=/DMyVarLvl=2. (See compiler parameters to understand how this works.)

  1. NTDEBUG: This controls whether the compiler macro variable DBG is set to 1. NTDEBUG=ntsd means DBG == 1 and thus a checked build; NTDEBUG=ntsdnodebug denotes DBG != 1 and so a free build. Ordinarily, the start menu shortcut you use to open a command window for building sets this variable. It’s best to leave it alone, since its value has to be coordinated with other things. I mention it because it is a determinant of whether the build is checked or free.

You can see the full set of environmental variables in force in a build window by issuing set.

Note: Capitalization of environmental variables is generally unimportant, but capitalization of their values may well be significant.

Free versus checked build

In choosing the target environment, you must decide whether to do a free (a/k/a “release”) or checked (a/k/a “debug”) build. Header files are used a little differently in the two types. The principal effect is whether debug code is produced. A secondary effect is that specifying a checked build tells the compiler not to optimize code.

Note: Whether your driver is built free or checked has nothing to do with whether your code will be running in the free or checked build of the OS itself. You can debug the “checked” version of the driver on either the free or the checked OS. You can run the “free” version of the driver in either place, too.

Compiler parameters

Here are a few compiler options that might be useful when you build a driver:

  1. TC: This indicates that the source files are to be compiled with C-language rules, overriding the inference the compiler would draw from the file extension.
  2. TP: This indicates that the source files are to be compiled with C++-language rules. 
  3. D<something>: This defines a preprocessor variable. For example, DMyVar=1 would create a preprocessor variable named MyVar with a value of 1. 
  4. Fa: This names a file for pseudo-assembler output, which can assist in understanding problems with generated code and in following what an interactive debugger does.

Prefix an option with slash (“/”) or dash (“-“) when you specify it. You can set options in the build window via the CL environment variable, e.g., set cl=/Fasc /TC /DMyVar=1; or you can utilize C_DEFINES or USER_C_FLAGS in a sources file.

Build program parameters

You build your project by starting the target environment of your choice, using the CD command to make project directory current, and typing build. This causes build to recreate any project binary with a constituent that has changed or that uses anything (e.g., an include file) that has changed since the previous build. If this is the first build for the project or if you wish to recreate everything, type build –cef.

You can pass parameters to build.exe so as to affect results in that invocation. Some useful parameters are:

  1. c: Delete all object files. 
  2. e: Produce log files. 
  3. f: Rescan source and header files. 
  4. Z: Inhibit dependency checking. 
  5. M: Use all available CPUs in building on an MP machine.

Miscellany

Here are a few other tips for building drivers.

  1. WMI support requires the MOF compiler. Build will invoke the MOF compiler where needed. The DDK contains a version of this compiler, and that should be the one you use in a manual MOF compilation. That version will be employed automatically if you invoke the MOF compiler in a build window. 
  2. Build always creates some information for the Windbg debugger. But you may have to tell Windbg where to find source files, symbol files and executables if Windbg cannot determine that information from the executable being debugged. 
  3. The DDK documentation and its proper use could almost be an article in themselves. Let a couple of points suffice: 
    1. To start the online documentation program, do:

              Start
                Programs
                  Development Kits
                    Windows DDK 3790
                      Help
                        DDK Documentation

      You may find it handy to create a desktop shortcut for starting the online documentation.

    2. If you employ the documentation’s search facility, you may get “No topics found” when in reality something is there. Or you may get fewer hits than actually exist. You will often get more useful results by searching in MSDN documentation (presuming that when you installed it, you included DDK documentation). For example, searching on “DRIVER_LIBRARY” in DDK documentation yielded 1 hit, but in MDSN documentation, search produced 4 hits; and searching on “KeNumberProcessors” gave 0 hits in DDK documentation and 1 hit in MSDN documentation.

When your driver won’t build

Start by looking at the command window output, which will make many problems clear, e.g., problems in a source-code file. Next, look at your log file! You will find it under whatever was in the current directory when you built. This file will often tell you all you need to know. Examine the error message(s) in it. If you have a compilation problem, look at the parameters with which build invoked the compiler (“cl”). If you have a link-edit problem, look at the parameters given to the linker (“link”) or the library manager (“lib”).

Here’s the explanation for a few common errors:

  1. “C1083: Cannot open include file. …” Pretty much what the compiler says. Check your INCLUDES directive and, if the missing file is part of your project, where you put it. 
  2. “U1073: don’t know how to make ….” Build reports this when it does not know where to find or how to make an .obj or .lib file. Check that you specified SOURCES, INCLUDES and TARGETLIBS correctly. If the file was in fact built, look at the log to see where it was placed. 
  3. “BK1506 : cannot open file …: Permission denied.” When this appears in your log (this error is unfortunately not reported in the build window), it means the browser information file couldn’t be rebuilt. The difficulty may be that a .bsc file is in use by another process, e.g., the Visual Studio browser (usually closing the VS workspace will cure this). Process Explorer from http://www.sysinternals.com/ntw2k/freeware/procexp.shtml is handy in determining what has an open handle to the file.

Browsable definitions

A browser file enables Visual Studio to show you where a variable, function, API, type or whatever was defined. You first get build to produce the browser file (see browser information). Then you set up a VS project for browsing (you don’t need to build in that project, although that is possible, too, as described below under the heading DDKBUILD):

  1. Create a VS workspace. Let’s name it DemoDriver and, for convenience, put it (the .dsw file) in the same directory as the sources (or dirs) file. If that directory is, say, c:\Temp\WD3Article\DemoDriver, specify location as c:\Temp\WD3Article. 
  2. Add a VS project to the workspace. Click on Makefile as the kind of project. Next, it is best (and necessary if you’re going to do DDKBUILD) to put the project (.dsp) file in the same directory as the sources (or dirs) file. Again, let’s name the project DemoDriver and, again, specify location as c:\Temp\WD3Article. Accept the rest of VS’s defaults (unless you’re setting things up for DDKBUILD). 
  3. Open up the settings for the new project and add DemoDriver.bsc (or whatever you specified in the sources file) in the place for Browse info file name. Click OK. 
  4. Add source files in the left pane, e.g., BozoStuff.cpp. Click OK and save the project.

With the VS project set up, double-click on a source file in the left pane to open the file in an editor window. Find something whose definition you want to locate, for example, PDEVICE_OBJECT, right-mouse-click on it and then click on Go To Definition. (Note: In VS .NET, you will sometimes have to choose Go To Reference rather than Go To Definition.) The editor will show the definition that build used. If you right-click on KDPC in the definition of PDEVICE_OBJECT, you will be taken to the definition of KDPC.

A tip for finding an OS-supplied definition before you’ve built your driver is this: find a DDK sample that uses the API or type or whatever. In the sources file for that sample, specify a browser file. Build the sample. Set up a VS project for the sample and find the definition. And note that you can utilize the same VS project over and over to track down definitions, simply by adding new source files from a built sample and updating the name of the browser file.

DDKBUILD, Visual Studio and other ways to build

You may wish to build in Visual Studio. To do this whilst adhering to the Microsoft-approved process, you can employ DDKBUILD. This is basically a front-end to DDK build, and you are going to get Visual Studio to employ DDKBUILD to do most of VS’s work. DDKBUILD consists of a .bat file written by Mark Roddy (get this at http://www.hollistech.com/Resources/ddkbuild/ddkbuild.htm. Read more about Mark’s thinking about building drivers here: http://www.wd-3.com/archive/HowBuild.htm). Here is Mark’s method.

  1. You must install Visual Studio, which supplies the GUI as well as utilities such as bscmake, which will be needed in creating the browser information file (and which is not part of the DDK, by the way, even though DDK build will use it). 
  2. Get DDKBUILD.bat and put it in a suitable place such as the program path for Visual Studio, e.g., \Program Files\Microsoft Visual Studio\VC98\bin. 
  3. In a global environmental variable (go to Control Panel-System and click on the Advanced tab), indicate where the DDK is located. For a WinXP target, you would create the variable XPBASE whose value is the DDK directory name, e.g., c:\WINDDK\3790. 
  4. Create a new Visual Studio workspace, Driver1, for example. 
  5. Add a new VS project to the workspace.

Note: Where the VS project (.dsp) file is located is important! I warned you about this in browsable definitions, but let me repeat the point. The project file must be where the sources (or dirs) file is. Let’s suppose your driver directory is c:\temp\Driver1. You should name the VS project Driver1 and give its location as c:\Temp. In that case, the project file will be c:\Temp\Driver1\Driver1.dsp and so will be under the same directory as the sources file. Do not give the location as c:\Temp\Driver1, because then you would get c:\Temp\Driver1\Driver1\Driver1.dsp, which is in a different directory from the sources file.

Since the VS project must be a makefile, click on Makefile and OK. If this project is for an XP target, enter ddkbuild -XP checked . in the Debug command line; observe that a period is part of the command parameters. (The Output and Rebuild lines may be left as they are.) Click Next and enter ddkbuild -XP free . in the Release command line (again, observe the period). Click OK and finish.

  1. Now, going to the VS project settings for both Debug and Release, add the name of the browser file (the same name as the one specified in the sources file) on Browse info file name under the General tab, e.g., Driver1.bsc. (This step is not necessary to build in Visual Studio, but why skip it?) 
  2. Clicking on the FileView tab to left, add source files (not the sources file) so that you will be able to browse them for definitions. 
  3. Type alt-b-b (the Build/Build command) to build. This will build from any source files changed from the last build. To get a complete rebuild — which you should do the first time — do alt-b-r (the Build/Rebuild All command). You will see the results of DDKBUILD in VS’s build pane. If you need to inspect the build log, you will find it in the same directory as the VS project file.

You’ve built a driver in Visual Studio.

A difficulty with running DDKBUILD in Visual Studio is that you cannot readily change environmental variables to affect build. You can set such variables statically in Environment Variables in the Advanced tab under System in Control Panel. For more flexibility, put the invocation of DDKBUILD in another .bat file and specify that outer file on the command line when you create the project; the latter file would set some environment variables and then invoke DDKBUILD itself.

A demonstration driver

Here is a demonstration driver that you can download. It has source files, header files, a dirs file, sources files and so forth.

About the author

James Antognini is a software engineer in White Plains, New York, and a frequent contributor to online support forums. You can reach him at antognini@mindspring.com. You can find some of his work at http://home.mindspring.com/~antognini.