What's New in Windows 95 for VxD Writers?

Ruediger R. Asche
Microsoft Developer Network Technology Group

Created: April 29, 1994
Revised: October 16, 1995 (Updates made throughout to reflect the shipping version of the Windows 95 Device Driver Kit.)

Click to open or copy files in the Callback sample application for this technical article.

Abstract

With the transition to a fully preemptive, thread-based, 32-bit, user-mode kernel, the next version of Microsoft® Windows® (called Windows 95) has undergone quite a number of changes to the system architecture over previous versions of Windows. This article will explain the changes that VxD writers have to expect when porting their VxDs to Windows 95. The discussion will be illustrated with a sample VxD that demonstrates some of the new things to watch out for.

Introduction

VxDs have come quite a long way since they were introduced in Microsoft® Windows® version 3.0. Back then, about two dozen Microsoft employees (out of several thousand altogether) knew what a VxD was, let alone how to write one; all the information that outside developers had about writing VxDs was one book called the Virtual Device Adaptation Guide (VDAG) that shipped with the Windows Device Driver Kit (DDK) and a build environment that, including the debug kernel, easily fit onto two high-density floppy disks.

It turned out that aside from the original purpose that VxDs had of virtualizing hardware, they could also be employed to add some spice to virtual-machine-manager–aware (VMM-aware) applications, such as establishing communication between virtual machines or providing 32-bit memory access to MS-DOS®–based applications. Now, about every other Windows-based application seems to be shipped with a VxD, and there is a lot of sample source code available.

The press at first revealed its unfamiliarity with the system architecture fairly often—engineers at Microsoft used to joke about the VxD frequently after yet another magazine author had written something along the lines of "Microsoft has released a new product called the VxD." (If you do not know what is so funny about this, think about a gourmet magazine claiming that General Foods has announced a new product called juice. It means about the same thing.) Now VxDs are an integral part of PC programmers' folklore and receive a fair amount of coverage in the press.

As a VxD programmer, you will probably take this change in VxD popularity with a grain of salt. On the one hand, it is probably easier for you to obtain information about writing VxDs than it was before, but on the other hand, VxDs are now becoming much more powerful, and consequently, there is more you need to watch out for.

With Windows 95, the system architecture of Windows has changed, and that change will have an impact on you as a systems programmer in several respects. Along with this article goes a sample VxD that shows most of the novelties that Windows 95 has to offer for you as a VxD writer:

We will not look at Plug and Play here because that architecture deserves an article of its own.

Everything I discuss is illustrated in the sample VxD, Callback. As we go along, I will highlight the points I have been making by pointing to relevant parts of the Callback VxD, like so:

Callback   There are three projects in the Callback sample: Callback, the VxD itself; RECEIVER.DLL, a receiving DLL; and 16BIT.EXE, a front-end written in Visual C++™ using the Microsoft Foundation Class library (MFC). The front-end application interacts with the Callback VxD and monitors the creation and destruction of virtual machines and threads in an application window.

Building and Debugging VxDs

The good news is that you will not need to change anything in your build environment to build and debug Windows 95 VxDs. The old tool set—MASM5B, LINK386, and ADDHDR—will still work (but they are obsolete—see the section "Writing VxDs in C" below), and what's more, the resulting VxD will still run unmodified under Windows 95. As for debugging, good ol' WDEB386 is still around, and Nu-Mega has updated its SoftIce for Windows product to be compatible with Windows 95.

The only thing to change here is that Windows 95 by default boots into Windows right away, so in order to run Windows under the control of WDEB386 or SoftIce, you'll need to do one of the following:

The Windows 95 DDK also ships with the debug version of the VMM; but instead of WIN386.EXE, as it was called under Windows 3.1, the file will now be called VMM32.VXD. (In earlier pre-releases of Windows 95, you might find DOS386.EXE instead.) When you run the debug version of Windows 95, you will see the good ol' goofy debug banner on the serial terminal ("THIS IS A DEBUG RELEASE. Don't report bugs about it being big or slow or we'll put back in the huge, irritating debug banner message!") as before. If you remember what the huge, irritating debug banner message is, you qualify as a computer geezer.

However, instead of using the old build environment, you might want to think about switching to C to write your VxD, which is one of the new cool things that you can do under Windows 95.

Writing VxDs in C

Actually, using C to write your VxD is not that revolutionary. The main reason that you could not write VxDs in C before was the lack of a 32-bit C compiler that would fit well into the build environment. Actually, it was possible to write VxDs in C before with the help of humongous custom preprocessor files and third-party 32-bit compilers. But now the macro files are provided by Microsoft, and you can use our 32-bit C compiler to compile your VxDs. The 32-bit version of Visual C++ will work just fine for the compiler, and with the new /VxD linker option, you can now use the linker to build VxD executables. (Earlier versions of the linker did not support the linear executable (LE) file format, so you had to use LINK386 to build your VxD.)

The only caveat with using C to write your VxD is that a VxD does not have an entry point in the sense that applications have one; instead, the VxD exports a data structure called a device descriptor block (or DDB). There will be no _main function in your VxD, but instead, the VxD must export a DDB for the VxD loader to pick up. This is not a problem in itself—VMM.H contains a predefined data structure, VxD_Desc_Block, that you can use to build the DDB for your VxD, but the C compiler prefixes every variable that your source files define with an underscore. If you can live with a VxD name that begins with an underscore, you are fine; if not, you will need to write a very small assembly stub file that contains nothing but your virtual device declaration. This is the approach taken by the Callback VxD.

In order to assemble the stub, you will need an assembler. Versions of the Windows DDK prior to Windows 95 shipped MASM5B because at that point there was no 32-bit assembler; with the introduction of Macro Assembler 6.11, MASM5B became obsolete. Unfortunately, no canned stub object file can be provided since the stub contains information that is specific to the VxD, such as the VxD name or the VxD device ID, if applicable.

Callback   The Callback project contains one source file written in C, HELPER.C, and one written in assembler, CALLBACK.ASM. The latter is the stub file mentioned earlier that contains the device declaration. A preassembled version, CALLBACK.OBJ, is also provided in case you do not have access to an assembler.

Here is a caveat with programming your VxD in C: You cannot use the C run-time libraries in a VxD. That is, if you need to make any run-time calls to copy memory, format strings, access files, or I/O streams, you must provide the routines yourself, except for some of the C run-time routines that have intrinsic equivalents. There is no support for statically or dynamically linked run-time functions. The libraries provided for NT kernel-mode device drivers won't help here because the respective formats for NT driver executables and Windows 95 VxDs are different; hence, the library formats do not correspond. Note that some run-time functionalities, such as the ability to access files from a VxD, are now provided by VxD services.

Callback   Look for the use of #pragma intrinsic(memcpy,strlen) in HELPER.C for an illustration of how to use intrinsic functions.

The Microsoft Visual C++ 32-bit compiler supports a new calling convention, the __declspec(naked) directive. That one is extremely useful for writing VxDs because there are VxD functions that pass information in unusual registers—namely, V86 interrupt hooks, VxD callbacks, and VxD application programming interface (API) functions that pass the address of the virtual machine (VM) client structure in the EBP register. Normally, the EBP register is used to chain stack frames to provide for proper cleanup of stack frames and the like. With the __declspec(naked) declaration, no function prologue and epilogue is generated, so you are free to do whatever you want in your function body. Watch out, though—you probably still want to build a custom stack frame in case you use local variables (which are always referenced relative to EBP) or make nested calls to other service functions from inside the callback function. I have written two macros that define custom prologues and epilogues for VxD callback functions:

#define VxD_Prolog \
    struct Client_Word_Reg_Struc *crRegs; \
    _asm mov eax,ebp \
    _asm mov ebp,esp  \
    _asm sub esp, __LOCAL_SIZE \
    _asm mov [crRegs],eax;

#define VxD_Epilog \
    _asm mov eax,[crRegs] \
    _asm mov esp, ebp \
    _asm mov ebp,eax \
    _asm ret;

This preprocessor code will reference the client register structure in a local variable csRegs and free up the EBP register so that you can use automatic variables in your function and still address the VM's client registers through the crRegs structure. Here is an example of how you would do that in order to install a V86 breakpoint for software Interrupt 60h, roughly as it is implemented in the Callback VxD:

    _asm mov eax,60h;
    _asm mov esi, OFFSET Int_60h_Hook_Procedure;
    VMMCall (Hook_V86_Int_Chain);  // Catch Vms int 60s...
    ...
    void __declspec(naked) Int_60h_Hook_Procedure()
    { WORD dVMAX;
      VxD_Prolog
      dVMAX = crRegs->Client_AX;
      ...            // Do something with client's AX here...
      VxD_Epilog 
    }

Notice that in some cases, the epilog has to be modified a little bit, namely, if the function is expected to set or clear the carry flag upon function return.

If your 32-bit C compiler does not support such a thing as the __declspec(naked) directive, you will need to write more of your VxD in assembly language; Chapter 10 in Bryan Woodruff's and David Thielen's Writing Windows Virtual Device Drivers (97-112) describes how to do it.

Using System Services in C

VMM or VxD services are internally implemented in Windows 95 by a dynamic-link mechanism using Interrupt 20h. From C, you can use either of two ways to call system services:

The approach you decide to follow depends on whether you are most interested in code-size savings (in which case you probably want to go for the second approach) or execution-time savings (in which case you probably want to use the preprocessor approach). In practice, you will most likely end up implementing a mixture of both.

The CVXD32 sample in the Windows 95 DDK is another example of how to write a VxD in C.

Dynamically Loadable VxDs

When the VMM was originally designed, the main purpose of a VxD was to virtualize a custom piece of hardware. Because at that time the only way to install a piece of hardware into a computer was to shut down the machine, open up its cover, insert the hardware device, and then restart the computer, VxDs had to be loaded exclusively at system boot time and unloaded at shutdown time.

As more and more VxDs were written for nonhardware virtualization purposes (for example, to support existing terminate-and-stay-resident programs (TSRs), to implement shared memory between virtual machines, to provide low-level services to Windows-based applications, and to provide 32-bit memory to virtual machines), it became apparent that it would make sense to provide a mechanism to dynamically load and unload VxDs. A special VxD, VXDLDR.386, will be provided to perform this task in Windows 95. The dynamic VxD loader is also the heart of the layered device-driver architecture of Windows 95 that can be employed, for example, to support so-called dockable workstations that allow for changing parts of their hardware configuration without shutting down the machine.

The major problem in providing dynamically loadable VxDs is that MS-DOS is not prepared to handle run-time changes in the memory layout. Whenever a virtual machine is created, the VMM will execute a copy of MS-DOS in it that is tricked into believing that it runs on a dedicated machine and has direct access to the hardware. The memory layout of every MS-DOS virtual machine is fixed throughout the lifetime of the MS-DOS virtual machine and determined, among other things, by requests that virtual devices made during initialization time. For example, a VxD may request participation in a global translation allocation buffer that is visible to all virtual machines. Upon device initialization, the VMM gathers the requests of all virtual devices and allocates the translation allocation buffer in memory that all MS-DOS virtual machines "see" as part of a TSR (VMM32). Since MS-DOS is not prepared to handle dynamically growing or shrinking TSRs, no dynamically loadable VxD can participate in this allocation buffer.

As a rule of thumb, all services that are available to VxDs only during initialization time cannot be used by dynamically loadable VxDs. A list of the services not available to those VxDs will be provided in the documentation that will be shipped with the Windows 95 DDK.

Consequently, dynamically loadable VxDs do not receive the Sys_Critical_Init, Device_Init or Init_Complete notifications and their corresponding termination notifications, but instead will receive the Sys_Dynamic_Device_Init and Sys_Dynamic_Device_Exit notifications. VxDs that support both the dynamic and the static initialization and termination notifications will be able to be loaded either statically or dynamically.

Some of the VMM services that assumed they would never have to be reversed in statically loadable VxDs (such as the Hook_V86_Int_Chain service) will be complemented by a corresponding "undo" service to account for clean dynamic loading and unloading.

Under Windows 95, a VxD can be loaded dynamically from another VxD, from a 16-bit user-mode Windows- or DOS-based application, or from a Win32-based application. To load a VxD from another VxD, you can use the services provided by the VXDLDR VxD. A 16-bit user-mode application obtains the VXDLDR's entry point and passes the location of the VxD to load to the VxD loader. Once the VxD needs to be unloaded, the application passes the module name of the VxD to unload to the VxD loader. Unfortunately, there is no such thing as a VxD handle that the user-mode application could use for that purpose; either the module name or the VxD ID must be known to the application in order to unload the VxD. A Win32-based application must open the VxD using the CreateFile Win32 API to obtain a handle to the VxD, and use the DeviceIOControl API to communicate with the VxD. Please refer to the article "Enhancing WDEB386 with External Debugger Commands" in the Development Library for details on this strategy.

Callback   Look into the 16BITVW.CPP file of the 16BIT project to see how the user-mode application loads the VxD.

New VxD Segments

Under Windows 3.1, VxDs are not only not dynamically loadable and unloadable, but they are not pageable either. This means that all VxD code and data (except for the data that is dynamically allocated) resides permanently in memory, even if it is not accessed at interrupt time or does not need to be locked into memory permanently for whatever other reasons.

Windows 95 provides a number of new segments for VxDs—aside from the familiar VxD_LOCKED_CODE_SEG, VxD_LOCKED_DATA_SEG, VxD_ICODE_SEG, and VxD_IDATA_SEG segments, there will be pageable code and data segments VxD_PAGEABLE_CODE_SEG and VxD_PAGEABLE_DATA_SEG, as well as static segments VxD_STATIC_CODE_SEG and VxD_STATIC_DATA_SEG (which will remain in memory even if a VxD gets unloaded and reloaded), and VxD_DEBUG_ONLY_CODE_SEG and VxD_DEBUG_ONLY_DATA_SEG segments, which will only load in the debug version of Windows.

Static segments are kind of tricky. One of the things I like about dynamically loadable VxDs is that the development cycle for them is shorter: You do not need to reboot the machine in order to test a new version of the VxD—just unload the current version and rebuild and reload the next version. However, if you rebuild the VxD and have static code or data segments whose layouts change, you may confuse the VxD loader. Therefore, you may have to reboot the machine in order to clean up existing static segments when developing dynamic VxDs.

Callback   In the Callback VxD, look out for the #pragma xxx_SEG directives, which sort the code and data out into the respective segments. In particular, there is one variable, dOpenCount, which keeps track of the number of times the VxD has been loaded. This variable resides in the static data segment.

Multithreading Support

The way I like to look at the system architecture of Windows 95 is in terms of how it evolved from the early days of Windows until now. So let us look at it now.

The Evolution of Windows

Let us use color coding to be more illustrative in this section. Whatever runs in real or V86 mode is drawn with a solid brush, 16-bit protected mode in a cross-hatched brush, and 32-bit protected mode in a squared brush.

Remember Windows real mode, which was alive and kicking until version 3.0? Here is a rough sketch of the memory layout of a machine running Windows real mode:

Figure 1. Windows real mode

The dotted circle with the arrow in it indicates that Windows nonpreemptively multitasks its applications.

Then there was Windows in standard mode, which, using MS-DOS protected mode interface (DPMI), broke the 640K barrier. (The novelties are drawn in black from now on with the earlier architecture grayed.)

Figure 2. Windows standard mode

Then there was Windows enhanced mode, which added another operating system on top of (or, to be more precise, below) this architecture.

Figure 3. Windows enhanced mode

The solid circle indicates that the VMM preemptively time slices the virtual machines. Notice that it is possible for an MS-DOS VM to use DPMI to make cooperative use of protected mode (just like the Windows system virtual machine), but initially, every MS-DOS VM executes in virtual 86 mode.

Windows 95 builds upon this architecture, but treats the system virtual machine in a special way compared to the other virtual machines.

Figure 4. Windows 95 system architecture

As far as nonsystem virtual machines are concerned, the picture is about the same as before, only the VMM has now switched to scheduling "threads" instead of "VMs." Since there is a one-to-one correspondence between MS-DOS virtual machines and their respective threads, the distinction seems to be fairly academic, doesn't it?

The picture changes significantly when it comes to the system virtual machine. Not only does it run 32-bit protected mode now instead of 16-bit protected mode (in reality, it runs a mixture of both), but the system VM also runs Windows-based applications in separate threads. This works through an API interface through which the system virtual machine requests a new thread from the VMM. Whenever any thread that belongs to a Windows-based application or the Windows system executes, the system virtual machine runs, as far as the VMM is concerned. As we will see later on, this is important for communication strategies between VxDs and Windows-based applications.

About every VMM service and notification that is related to VMs now has a counterpart for threads: You can schedule an event for a particular thread; your control dispatch function can register a handler that is invoked whenever a thread is created or destroyed, and a callback can be registered that is invoked each time a thread switch occurs.

The system virtual machine creates and destroys threads through a VMM-provided interface, namely, the VMMCreateThread and VMMTerminateThread system services. You cannot use those services to create and destroy user-mode threads. As a VxD writer, you will most likely never need to create a thread; if you do, you should probably use the new Call_Ring_3 service to ask the Windows kernel to create a thread for you. At the point this article is being written, there is a proposed interface that would allow you to create and manipulate threads that execute in kernel mode, but that topic will be covered later in a separate article.

And you need to take the new thread-related services with a grain of salt. All threads that your VxD can be made aware of are system virtual machine threads. For example, your VxD will receive the Create_Thread notification only for a thread that is created on behalf of the system virtual machine. When the user launches a new MS-DOS box, for example, every VxD will receive the Create_Thread notification only once, namely, for the thread in which the instance of WINOLDAP runs that monitors the MS-DOS box. Likewise, the Terminate_Thread notification broadcast will only be sent out in response to a call to VMMTerminateThread, and therefore, you cannot monitor the creation and destruction of all threads.

When retrieving the VM for the new thread, you will find that that virtual machine will always be the system virtual machine. The other thread—the real thread for the MS-DOS VM—can be retrieved, but there is not much use for it.

Callback   The sample VxD Callback processes the creation and destruction notifications for both VMs and threads. When it is informed that a new VM is created, the VxD will retrieve the initial thread for the VM from the VM control block. Likewise, the VM that corresponds to a newly created thread will be retrieved from the thread control block.

What Does This Mean for Me?

The outcome of the above discussion is basically that the VMM in Windows 95 has redefined its time slicing to something with a finer granularity than a VM, which is apparently only relevant in the context of the system virtual machine. As a VxD writer, you had to be aware of the creation and destruction of virtual machines primarily when you had to create instanced (per-VM) data, such as the virtual display device or the virtual mouse device. In those cases, you will not need to change your VxD because input and output devices are still maintained on a per-VM basis, and the existence of multiple threads within one particular VM is irrelevant to the VxD. Per-VM instance data will be shared by all processes that run under control of the system virtual machine.

It is very unlikely that a VxD will need to hook into thread creation and destruction unless its purpose is to keep track of system resources. Where you probably do need to change your VxD in this respect, though, is scheduling. Under Windows 3.1, you would, say, reflect a software interrupt only into a particular virtual machine. Under Windows 95, you should probably check to see whether the target virtual machine is the system virtual machine—if yes, schedule a priority event into the current thread (so that the hardware interrupt gets executed in the context of the currently executing thread); otherwise, schedule the event for the current virtual machine. This saves context-switch overhead between threads and ensures that no high-priority thread will block the processing of the simulated interrupt.

Appy Time Events

The Knowledge Base article Q89705, "How VMs can communicate with VxDs," outlines the techniques by which a VM can communicate with a VxD under Windows 3.1. For Windows 95, the techniques mentioned there are complemented by the appy time architecture that is described in this section; also, some remarks must be made about 32-bit compatibility, which is addressed in the following section.

Roughly speaking, what happens with appy time events in Windows 95 is that a VxD can register a callback function that can call code in the system VM when that VM is in a safe state (that is, a state in which a function call does not cause reentrancy problems). Under Windows 3.1, it was possible for a VxD to obtain the address of an arbitrary DLL function (with the collaboration of a DLL that provided this address) and simulate a far call into that library routine in the system virtual machine; however, that far call would behave very much like an interrupt in that it would interrupt whatever code was currently running. Therefore, the only APIs that could be called into from a VxD were interrupt-safe APIs, which were only a handful, most notably the infamous PostMessage API.

With appy time events, it is now possible for a VxD to call almost any 16-bit Windows DLL. From the point of view of the system architecture, appy time events are realized through a close interaction between the shell VxD and a user-mode message server module. From the point of view of your VxD, appy time events work similarly to scheduled events: The VxD registers an appy time event with the shell VxD, and as soon as the user-mode message server finds a time at which it is safe for the VxD to call into a Windows DLL, the VxD-supplied callback is invoked. At this time, the appy-time callback may load a user-mode DLL, call the API, and unload the DLL.

Appy time events are documented in the SHELLVXD.DOC file that will come with the Windows 95 DDK. The Callback VxD demonstrates a possible use of appy callbacks. The last section of this article explains the functionality of the VxD with respect to appy time events.

Callback   In the Callback VxD, the appy time event calls into a user-mode DLL (RECEIVER.DLL) that does a SendMessage into an application (not a PostMessage—this way you can see that appy time events are truly not restricted to reentrant calls). The SendMessage call is received by an MFC application that collects the data and eventually displays it on the application screen. Note that the receiving DLL is implemented as a 16-bit DLL, not a 32-bit DLL—we will see later why this is the case.

32-Bit Application Support

The VMM implements a DPMI provider that allows virtual machines that initially run in virtual 86 mode to switch to protected mode while still not violating system integrity. The interesting point here is that it is possible for several virtual machines to coexist in different modes cooperatively—for example, under Windows 3.1, the system virtual machine ran as a 16-bit DPMI client, and it was possible for another virtual machine to run an MS-DOS–based application in V86 mode and still another VM to run a 32-bit extended protected-mode application at the same time.

This VM-based granularity poses some very subtle problems to the VMM and VxDs. Each VM is flagged as running V86 mode, 16-bit, or 32-bit extended protected mode. Depending on those flags, if a VM calls a VxD's API entry point, the call will be dispatched either to the VxD's V86 or protected mode API entry, and likewise, if a VxD manipulates a VM's client stack frame, for example in order to simulate a function call return into the VM, it will assume a 16-bit or 32-bit return frame, depending on the flags. Now under both Windows version 3.1 and Windows 95, the system virtual machine is flagged as running 16-bit protected mode, so it is not possible for 32-bit applications to communicate directly with a VxD. In other words, a Win32-based application under Windows 95 cannot:

Likewise, a VxD cannot:

Note that in some of the above cases, it would theoretically be possible for the receiving VxD to determine whether the application submitting the call is running 16- or 32-bit code and manipulate the stack frame of the client application. However, doing so would require a very thorough understanding of the architecture and work only under very specific circumstances.

There are two ways to get around this problem:

Miscellaneous Issues

There is better support for diagnostics VxDs in Windows 95. For example, the new service Get_DDB allows you to obtain the device descriptor blocks of a specific VxD; thus, by enumerating VxDs by ID, a diagnostic VxD can obtain information about all installed VxDs now.

Software interrupts are rather tricky. Under Windows 3.1, all Interrupt 21h calls are eventually reflected down to real-mode MS-DOS; consequently, any file access could be monitored by a VxD through a V86 interrupt hook. This no longer holds true under Windows 95 because most MS-DOS calls will be processed in protected mode. VxDs that used to monitor file I/O (for example, VxDs that supported security hardware) will have to be redesigned. You will probably hook yourself into the layered device-driver architecture through the block-driver interface that is documented in the WIN95IFS.DOC file in the Windows 95 DDK. Stan Mitchell's article, "Monitoring Windows 95 File Activity in Ring 0," in the Windows/DOS Developer's Journal is excellent supplemental information (1995, pp. 6-24).

The Code Sample Architecture

Let us look at the sample VxD and application quickly. There is a fairly close interaction between the VxD and the application, demonstrating communication techniques in both directions. Note that I chose to write the sample application in 16-bit MFC because at time the sample application was designed, the DeviceIOControl architecture that allows 32-bit applications to communicate with VxDs had not been designed yet. Please refer to the CVXD32 sample in the Windows 95 DDK and to the VDYNDEBD sample in the Development Library for samples that show how to communicate between Win32 applications and VxDs. Alternatively, it would have been possible to encapsulate all communications into the 16-bit DLL RECEIVER.DLL and implement the front-end application as a 32-bit application that communicates with the DLL via thunks. However, the thunking layer that Windows 95 provides is fairly complex, so I leave its discussion to another time.

The application is written in MFC. Using inline assembly code in MFC is fairly straightforward; the only problem is to correctly reference variables. When you wish to store the offset of a particular variable in a register, you do the following:

_asm mov ax, offset [variablename]

As long as the variable in question is either a global application variable or an automatic function variable, you will be fine because the offset will be computed relative to the data segment (DS) for application global variables (if you compile your application for small model) or relative to the BP register for automatic variables. But what if you need to compute the address of a C++ member variable to pass to a VxD? This is not trivial because offsets of member variables are always relative to the this pointer, which MFC passes in the SI register. I have stuck to application global variables in a small model application because I did not want to rely on an implementation detail that might change.

When the application starts up, it first checks to see whether the VXDLDR VxD is loaded, and if yes, saves its entry point to a local variable. When the user selects "Load diagnostic VxD" from the application's main menu, the VXDLDR entry point is called with the VXDLDR_Load_VxD constant and the name of the VxD that is read from WIN.INI. If the load turned out to be successful, the application calls INT 60h, which was hooked by the VxD, passing to the VxD the address of the string that describes the location of the receiver DLL. In the interrupt hook, the VxD computes the linear address of the string and copies it to an internal location.

Note that there is a slight problem with the code—when a thread or VM is created or destroyed before the INT 60h is processed, the appy callback will fail because the location string is not initialized yet. This is a very typical asynchronous synchronization problem and would normally be solved by a semaphore or another means of mutual exclusion. In the Callback VxD, I left out this additional check for clarity, but in your shipping VxD, you should make sure to get it to work right. I will discuss synchronization on the VxD level in a separate article.

Whenever any of the Create_Thread, Destroy_Thread, Create_VM, or Destroy_VM notifications is received, the VxD allocates a data structure of type CONTBLOCK, which is defined in HELPER.H and is known to both the VxD and the application, fills it with the information to display to the user, and requests an appy time event. At appy time, the CONTBLOCK structure is copied to a memory location that is visible to the application via a selector that was allocated on behalf of the system virtual machine, and the receiver DLL is invoked and passed the address of that memory location. The DLL sends a message to the application, which in turn copies the information in the CONTBLOCK structure to an internal queue. Upon successful completion of the appy time event, the VxD deallocates the memory of the data structure. Note that this type of architecture could not be realized through an asynchronous (PostMessage) call because then the server data structure in the VxD might have changed before it has gotten processed in the application.

This architecture looks a little bit awkward, but it demonstrates the important aspects fairly well.

One thing I would like to point out to you is that you should be careful when using the Visual C++ linker to build your VxD. The linker will convert the module name of the VxD—in our case, "callback"—to all uppercase. Thus, when the sample application passes the name "callback" in lowercase to the VXDLDR to free the VxD, the call will fail because the name of the VxD is "CALLBACK" as far as the VMM is concerned. Thus, if you unload a VxD by name, you should be prepared to try an uppercase name if a mixed-case name does not work.

And if you'll allow, I must make one more pitch for MFC applications at this time. Whenever I do low-level work, I tend to be pretty impatient with user-interface concerns; I do not feel like spending too much time on a front-end that is only to display some data on the screen. The front end I came up with took about two hours to implement, including the scroll logic, thanks to MFC!

Bibliography

Microsoft Windows version 3.1 Device Driver Kit (DDK) Virtual Device Adaptation Guide. Microsoft Corporation, 1987-1992.

Microsoft Windows 95 Device Driver Kit (DDK) Device Driver Programmer’s Reference. Microsoft Corporation, 1995.

Mitchell, Stan. "Monitoring Windows 95 File Activity in Ring 0." Windows/DOS Developer's Journal 6 (July 1995).

Thielen, D., and Bryan Woodruff. Writing Windows Virtual Device Drivers. Reading, MA: Addison-Wesley, 1993