There are some minor differences between Windows 98 and Windows 2000 insofar as the material discussed in this chapter goes.
Windows 98 doesn't implement an error-logging file or an Event Viewer. When you call IoWriteErrorLogEntry in Windows 98, all that happens is that several lines of data appear on your debugging terminal. I find the formatting of this information unaesthetic, so I prefer to simply not use the error-logging facility under Windows 98. Refer to Appendix A, "Coping with Windows 98 Incompatibilities," for suggestions about how to determine whether you're running Windows 98 or Windows 2000.
A Win32 application can use DeviceIoControl to communicate with a Windows 98 virtual device driver (VxD) as well as a WDM driver. Three subtle and minor differences exist between IOCTLs for WDM drivers and IOCTLs for VxDs. The most important difference has to do with the meaning of the device handle you obtain from CreateFile. When working with a WDM driver, the handle is for a specific device, whereas you get a handle for the driver when you're talking to a VxD. In practice, a VxD might need to implement a pseudohandle mechanism (embedded within the IOCTL data flow) to allow applications to refer to specific instances of the hardware managed by the VxD.
Another difference between VxD and WDM control operations concerns the assignment of numeric control codes. As I discussed earlier, you define a control code for a WDM driver by using the CTL_CODE macro, and you can't define more than 2048 codes. For a VxD, all 32-bit values except 0 and -1 are available. If you want to write an application that can work with either a VxD or a WDM driver, use CTL_CODE to define your control codes, since a VxD will be able to work with the resulting numeric values.
The last difference is a pretty minor one: the second-to-last argument to DeviceIoControl—a PDWORD pointing to a feedback variable—is required when you call a WDM driver but not when you call a VxD. In other words, if you're calling a WDM driver, you must supply a non-NULL value pointing to a DWORD. If you're calling a VxD, however, you can specify NULL if you're not interested in knowing how many data bytes are going into your output buffer. It shouldn't hurt to supply the feedback variable when you call a VxD, though. Furthermore, the fact that this pointer can be NULL is something that a VxD writer might easily overlook, and you might provoke a bug if your application takes advantage of the freedom to say NULL.
If an application uses the pending IOCTL technique to wait for your driver to tell it about hardware events, the application necessarily has a handle open while it's running. If your device can be removed from the computer by surprise, you need to fail the pending IOCTL(s) to encourage the application to close its handles. In Windows 2000, you could delay handling the eventual IRP_MN_REMOVE_DEVICE request until all handles get closed. You don't dare delay in Windows 98, however, because of the deadlock possibility I described at the end of Chapter 6. If you look at my sample drivers, and at NOTIFY in particular, you'll see that they do not acquire the remove lock when they process IRP_MJ_CREATE. That means that they will allow themselves to be unloaded even though handles are open. Luckily, Windows 98 is able to deal with the aftermath without further incident.
Windows 98 doesn't support the use of a pointer to a thread object (a PKTHREAD) as an argument to KeWaitForSingleObject or KeWaitForMultipleObjects. Those support functions simply pass their object pointer arguments through to VWIN32.VXD without any sort of validity checking, and VWIN32 crashes because the thread objects don't have the structure members needed to support synchronization use.
If you need to wait for a kernel-mode thread to complete in Windows 98, therefore, you'll need to have the thread signal an event just before it calls PsTerminateSystemThread. It's possible that signalling this event will cause the terminating thread to lose control to a thread waiting for the same event. The terminating thread would then still be alive technically, but I don't think anything awful can happen as a result in Windows 98. In Windows 2000, however, you could easily find the driver unloaded out from under the terminating thread; be sure to wait on the thread object itself in Windows 2000.