[Previous] [Next]
In Chapter 5, "The I/O Request Packet," I explained the mechanics of passing IRPs down the driver stack in two situations: one in which you care about the result and therefore need a completion routine, and the other in which you don't care about the result and therefore don't install a completion routine. Many of the PnP requests fit into the second of these categories—you're receiving the IRP and passing it down, but you don't care what happens to it afterward. To begin with, then, I suggest writing a helper function that you can use to pass a request down in the "don't care" scenario—see the code below.
NTSTATUS DefaultPnpHandler(PDEVICE_OBJECT fdo, PIRP Irp) { IoSkipCurrentIrpStackLocation(Irp); PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; return IoCallDriver(pdx->LowerDeviceObject, Irp); } |
A simplified version of the dispatch function for IRP_MJ_PNP might look like the following:
1 2 3 4 5 | NTSTATUS DispatchPnp(PDEVICE_OBJECT fdo, PIRP Irp) { PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); ULONG fcn = stack->MinorFunction; static NTSTATUS (*fcntab[])(PDEVICE_OBJECT, PIRP) = { HandleStartDevice, // IRP_MN_START_DEVICE HandleQueryRemove, // IRP_MN_QUERY_REMOVE_DEVICE <etc.>, }; if (fcn >= arraysize(fcntab)) return DefaultPnpHandler(fdo, Irp); return (*fcntab[fcn])(fdo, Irp); } |
Using a Function Pointer Table
Using a table of function pointers to dispatch handlers for minor function codes as I'm showing you in DispatchPnp entails some danger. A future version of the operating system might change the meaning of some of the codes. That's not a practical worry except during the beta test phase of a system, though, because a later change would invalidate an unknown number of existing drivers. I like using a table of pointers to subdispatch functions because having separate functions for the minor function codes seems like the right engineering solution to me. If I were designing a C++ class library, for instance, I'd define a base class that used virtual functions for each of the minor function codes.
Most programmers would probably place a switch statement in their DispatchPnp routine. You can simply recompile your driver to conform to any reassignment of minor function codes. Recompilation will also highlight—by producing compilation errors!—name changes that might signal functionality shifts. That happened a time or two during the Microsoft Windows 98 and Windows 2000 betas, in fact. Furthermore, an optimizing compiler should be able to use a jump table to produce slightly faster code for a switch statement than for calls to subdispatch functions.
I think the choice between a switch statement and a table of function pointers is mostly a matter of taste, with readability and modularity winning over efficiency in my own evaluation. You can avoid uncertainty during a beta test by placing appropriate assertions into your code. For example, the HandleStartDevice function could assert that stack->MinorFunction == IRP_MN_START_DEVICE. If you recompile your driver with each new beta DDK, you'll catch any number reassignments or name changes.