Walter is a freelance developer and software consultant based in Boston. He specializes in system tools and in interfacing complex applications to Windows, NT, and DOS. Walter can be reached on CompuServe at 73730,553.
The concept of a virtual device driver arose in Windows 3.0 386 Enhanced mode as a way to "virtualize" hardware devices so that multiple DOS and Windows applications could share them. If I type on the keyboard, for example, my keystrokes might at one time belong to the active Windows application, and at another, to a character-mode program running in a DOS box. Microsoft's designers built a multitasking operating system--WIN386.EXE--around the idea of "virtual machines," a familiar paradigm for academics and others who had long ago used IBM's own CP-67 (later VM/370). Handling the virtual keyboard attached to a virtual machine calls for a virtual keyboard device (VKD) which can direct the actual keystrokes from physical hardware to the correct program in such a way that each consumer of keystrokes believes it's dealing directly with hardware. Handling some other hypothetical "x" device calls for a Virtual "x" Device--a VxD (in the cyberspeak shorthand of the folks from Redmond).
Since VxDs are 32-bit, flat-model programs running in the same privileged ring-0 world of the true operating system, programmers who need to do hardcore systems programming will gravitate toward this level of programming. Do you need to control math-coprocessor emulation in a 3.0 system, where the DPMI 0Exx series of services hadn't yet been implemented? Simple. Just write a VxD that intercepts software interrupt 31h and provides the necessary virtualization of the processor's CR0 control register. Do you want to provide demand paging of executables for a 32-bit Windows extender? A VxD is part of the solution.
Writing virtual device drivers is generally the arcane specialty of trained stunt programmers--and David Thielen and Bryan Woodruff's Writing Windows Virtual Device Drivers does nothing to dispel that notion. Organizationally, the book shows great initial promise. The first three sections of the book, comprising 13 chapters and 170 pages, contain tutorial material aimed at teaching how to combine the myriad of possible services into usable components. The remaining three quarters of the book contain reference material, including register-by-register instructions about how to use those services. The reference material duplicates Microsoft's own documentation but, at least in the section on VMM services, follows an obvious, alphabetic plan that seems to have escaped the Microsoft writers as a preferable organizing principle. The book breaks down, unfortunately, in precisely the area of tutorial exposition for which potential readers have been thirsting for years.
Regrettably, Thielen and Woodruff don't develop the theme of why anyone might want or need to write a VxD in any straightforward way. In the first two pages, you are confronted with: the abbreviation VxD without any explanation of where the "x" comes from; the gratuitous proposal that VMM (mysteriously equated to WIN386.EXE without further explanation) might launch COMMAND.COM instead of KRNL386.EXE (which is what, exactly?); advice not to tamper directly with the IDT; the acronym IRQ; and many other low-level concepts that belong somewhere in the book, but not right at the start. As a programmer who's written many VxDs and who teaches VxD programming on occasion, I wasn't startled by anything in this introductory material, of course; but right from the start, I knew that this is a book by and for programmers who aren't afraid to trap I/O ports, deal with a virtualized programmable interrupt controller, or impale themselves on the "suicide" fence of device initialization.
If the introduction made me feel that I had stepped into the middle of a manuscript, the ensuing discussion of the mechanics of building a VxD left me hopelessly confused. The Microsoft DDK's Virtual Device Adaptation Guide explains that a VxD can contain a real-mode initialization part, a protected-mode initialization part, a set of handlers for noteworthy events in the life of a virtual machine, and a collection of service routines for use by other VxDs and by application programs. Using macros in VMM.INC (a DDK component), a programmer creates an assembly-language program that contains one USE16 segment for real-mode initialization and several USE32 code and data segments for everything else. You assemble the program nowadays with MASM 6.1 and link it with a LINK386 left over from the early beta program of OS/2 2.0. The resulting LE signature file is then postprocessed to make it usable by the VxD loader in WIN386 and by the WDEB386 debugger. None of the actual mechanics of building a VxD are discussed in Writing Windows Virtual Device Drivers, however. Even a make script with some minimal commentary would be helpful.
The potentially complex subject of real-mode initialization becomes two paragraphs in the book. The first paragraph reminds you that it's actually V86-mode initialization, if you happen to be using a memory manager such as EMM386, 386Max, or QEMM. The other paragraph supplies the information that certain segment sizes cause unspecified "problems" within VMM. I was glad to know this (although I would have appreciated more information about what the "problems" were, so I could diagnose failures in my own code better), but I've never had a real-mode initialization section that was large enough to trip on the restrictions. On the other hand, I didn't read about any of the things I've actually done with real-mode initializers. How would I learn the potential benefits of the 2F/1607 device callout function? How to claim owned pages, prevent a duplicate driver from being loaded, or halt the startup of Windows altogether? How would I learn about passing reference data to the protected-mode initialization part of the driver, or about communicating with a TSR whose 2F/1605 hook caused me to be loaded?
Thielen and Woodruff have fallen into the common trap of programmer/authors, in which they assume that their readers know almost as much about the subject as they do. Hence, they leave out many steps of reasoning and explanation. Since their readers won't, in fact, know very much about the subject (why buy the book otherwise?), this becomes the book's major failing. Another example, drawn from Chapter 3 on memory management, illustrates the problem:
The MMGR manages instance data for VMs. Instance data is a range of V86 address space that VMM maintains separately for each VM. It is used frequently for MS-DOS and some TSRs.
For example, if an MS-DOS device driver maintains an input buffer, it may be useful to have the buffered input directed to the VM that was active when the buffer was filled. In this case, the VxD would query the device driver for the buffer address and maximum size and add an instance data area as shown here_.
This text, which is the entire description provided of instance data, is followed by an example (in C) that calls the authors' own VMM_AddInstanceItem function.
I find several problems with this snippet, primarily in regard to what isn't said. "Instance data" is data that must be private to each virtual machine even though it has the same real-mode address in every machine. This is conceptually similar to automatic data in a reentrant subroutine or to thread local storage in NT. The buffer used by DOSKEY is a good example: It won't do for a command typed in one DOS box to show up in the recall buffer of another. The virtualized video RAM is another good example. VMM implements instance data differently, depending on how large the instanced area is. If an entire page is instanced, each VM has its own physical page buffer that gets mapped into the V86 region at the appropriate common address. If a data area smaller than a page is instanced, VMM marks the containing page "not present" on every VM switch, and it thereafter copies the per-VM information if the page is ever touched. You never learn about these implementation details, however.
You also never learn about one of the most important ways of establishing a region of instanced address space: the response to the 2F/1605 startup broadcast. These details appear, to be sure, in an appendix that discusses the important INT 2F interface. But a novice needs an indication about how to use vocabulary like 2F/1605 in the sentences and paragraphs of a real application.
This example alludes to directing input to the right virtual machine, which seems to indicate the 2F/1685 (switch virtual machines and call back) interface created originally for network vendors. While this interface is undoubtedly implicated in some instance-data situations as well, it's surely not the main feature requiring explanation. In any case, the code sample doesn't address VM switching anyway.
The code sample itself provides another small bone of contention. In principle, you should be able to write a VxD in either assembler or any high-level language for which you can find a 32-bit compiler. (Visual C++ 32-bit edition would not be a good choice because its COFF-format output is unintelligible to the aging LINK386 linker.) VMM employs a dynamic linking scheme that uses data in the instruction stream which is then replaced as links are "snapped" to their run-time locations. Assembly language is the only way to achieve the linkage, and an assembly language header file (VMM.INC) is the only official place for finding the right macros and equates. Any high-level interface for VxD writers must speak to this issue, and the authors have provided their own C-callable API for this purpose. Thus, VMM_AddInstanceItem is a C wrapper for the _AddInstanceItem service found in VMM.INC. Unfortunately, I find this particular sample, as well as all of the others written in C, too cluttered for expository purposes. The complication of interfacing in C with extraordinarily long function names subsumes the logic of the program, especially when the code contains adequate error handling. In this situation, I think the sufficiently evocative assembly language macros in VMM.INC would more clearly express the ideas.
Writing Windows Virtual Device Drivers is nonetheless a plausible addition to the bookshelf of an experienced VxD writer. The authors have unique insight into infrequently visited areas, like direct memory access (DMA) programming. Their exposition of how they implemented a C-callable interface for VxDs, a communications driver, and an inter-VM linkage driver makes interesting reading that won't fit in a magazine format. And the reorganized and reprinted reference material is independently useful for people like me who prefer hard copy to electronic media. As I said at the outset, however, I fear that this book's terseness makes it virtually unusable for beginners.
Copyright © 1994, Dr. Dobb's Journal