[Previous] [Next]

WDM Drivers and WMI

The kernel-mode support for WMI is based primarily on IRPs with the major code IRP_MJ_SYSTEM_CONTROL. You must register your desire to receive these IRPs by making the following call:

IoWMIRegistrationControl(fdo, WMI_ACTION_REGISTER);

The appropriate time to make the registration call is in the AddDevice routine at a point when it would be safe for the system to send the driver a system control IRP. In due course, the system will send you an IRP_MJ_SYSTEM_CONTROL request to obtain detailed registration information about your device. You'll balance the registration call with another call at RemoveDevice time:

IoWMIRegistrationControl(fdo, WMI_ACTION_DEREGISTER);

If any WMI requests are outstanding at the time you make the deregistration call, IoWMIRegistrationControl waits until they complete. It's therefore necessary to make sure that your driver is still capable of responding to IRPs when you deregister. You can fail new IRPs with STATUS_DELETE_PENDING, but you have to respond.

Before explaining how to service the registration request, I'll describe how you handle system control IRPs in general. An IRP_MJ_SYSTEM_CONTROL request can have any of the minor function codes listed in Table 10-1.

Table 10-1. Minor function codes for IRP_MJ_SYSTEM_CONTROL.

Minor Function Code Description
IRP_MN_QUERY_ALL_DATA Get all instances of every item in a data block
IRP_MN_QUERY_SINGLE_INSTANCE Get every item in a single instance of a data block
IRP_MN_CHANGE_SINGLE_INSTANCE Replace every item in a single instance of a data block
IRP_MN_CHANGE_SINGLE_ITEM Change one item in a data block
IRP_MN_ENABLE_EVENTS Enable event generation
IRP_MN_DISABLE_EVENTS Disable event generation
IRP_MN_ENABLE_COLLECTION Start collecting "expensive" statistics
IRP_MN_DISABLE_COLLECTION Stop collecting "expensive" statistics
IRP_MN_REGINFO Get detailed registration information
IRP_MN_EXECUTE_METHOD Execute a method function

The Parameters union in the stack location includes a WMI substructure with parameters for the system control request:

struct {
  ULONG_PTR ProviderId;
  PVOID DataPath;
  ULONG BufferSize;
  PVOID Buffer;
  } WMI;

ProviderId is a pointer to the device object to which the request is directed. Buffer is the address of an input/output area where the first several bytes are mapped by the WNODE_HEADER structure. BufferSize gives the size of the buffer area. Your dispatch function will extract some information from this buffer and will also return results in the same memory area. For all the minor functions except IRP_MN_REGINFO, DataPath is the address of a 128-bit GUID that identifies a class of data block. The DataPath field is either WMIREGISTER or WMIUPDATE (0 or 1, respectively) for an IRP_MN_REGINFO request, depending on whether you're being told to provide initial registration information or just to update the information you supplied earlier.

When you design your driver, you must choose between two ways of handling system control IRPs. One method is relying on the facilities of the WMILIB support "driver." WMILIB is really a kernel-mode DLL that exports services you can call from your driver to handle some of the annoying mechanics of IRP processing. The other method is simply handling the IRPs yourself. If you use WMILIB, you'll end up writing less code but you won't be able to use every last feature of WMI to its fullest—you'll be limited to the subset supported by WMILIB. Furthermore, your driver won't run under the original retail release of Microsoft Windows 98 because WMILIB wasn't available then. Before you let the lack of WMILIB in original Windows 98 ruin your day, consult the compatibility notes at the end of this chapter.

WMILIB suffices for most drivers, so I'm going to limit my discussion to using WMILIB. The DDK documentation describes how to handle system control IRPs yourself if you absolutely have to.

Delegating IRPs to WMILIB

In your dispatch routine for system control IRPs, you delegate most of the work to WMILIB with code like the following:

1 

















2 




3 

4 





5 



6 

7 
WMIGUIDREGINFO guidlist[] = {
  {&GUID_WMI42_SCHEMA, 1, WMIREG_FLAG_INSTANCE_PDO},
  };

WMILIB_CONTEXT libinfo = {
  arraysize(guidlist),
  guidlist,
  QueryRegInfo,
  QueryDataBlock,
  SetDataBlock,
  SetDataItem,
  ExecuteMethod,
  FunctionControl,
  };

NTSTATUS DispatchWmi(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
  {
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  NTSTATUS status = IoAcquireRemoveLock(&pdx->RemoveLock, Irp);
  if (!NT_SUCCESS(status))
    return CompleteRequest(Irp, status, 0);

  SYSCTL_IRP_DISPOSITION disposition;
  status = WmiSystemControl(&libinfo, fdo, Irp, &disposition);

  switch (disposition)
    {

  case IrpProcessed:
    break;

  case IrpNotCompleted:
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    break;

  default:
  case IrpNotWmi:
  case IrpForward:
    IoSkipCurrentIrpStackLocation(Irp);
    status = IoCallDriver(pdx->LowerDeviceObject, Irp);
    break;
    }

  IoReleaseRemoveLock(&pdx->RemoveLock, Irp);
  return status;
  }

  1. The WMILIB_CONTEXT structure declared at file scope describes the class GUIDs your driver supports and lists several callback functions that WMILIB uses to handle WMI requests in the appropriate device-dependent and driver-dependent way.
  2. As with other dispatch routines, we acquire and release the remove lock while handling this IRP. The problem we prevent is having the device object underneath us disappear because of a Plug and Play (PnP) event. Our own device object cannot disappear because our call to IoWMIRegistrationControl acquired a reference to it.
  3. This statement calls WMILIB to handle the IRP. We pass the address of our WMILIB_CONTEXT structure. It's customary to use a static context structure, by the way, because the information in it is unlikely to change from one IRP to the next. WmiSystemControl returns two pieces of information: an NTSTATUS code and a SYSCTL_IRP_DISPOSITION value.
  4. Depending on the disposition code, we might have additional work to perform on this IRP. If the code is IrpProcessed, the IRP has already been completed and we need do nothing more with it. This case would be the normal one for minor functions other than IRP_MN_REGINFO.
  5. If the disposition code is IrpNotCompleted, completing the IRP is our responsibility. This case would be the normal one for IRP_MN_REGINFO. WMILIB has already filled in the IoStatus block of the IRP, so we need only call IoCompleteRequest.
  6. The default and IrpNotWmi cases shouldn't arise in Windows 2000. We'd get to the default label if we weren't handling all possible disposition codes; we'd get to the IrpNotWmi case label if we sent an IRP to WMILIB that didn't have one of the minor function codes that specifies WMI functionality.
  7. The IrpForward case occurs for system control IRPs that are intended for some other driver. Recall that the ProviderId parameter indicates the driver that is supposed to handle this IRP. WmiSystemControl compares that value to the device object pointer we supply as the second function argument. If they're not the same, it returns IrpForward so that we'll send the IRP down the stack to the next driver.

The way a WMI consumer matches up to your driver in your driver's role as a WMI provider is based on the GUID(s) you supply in the context structure. When a consumer wants to retrieve data, it (indirectly) accesses the data dictionary in the WMI repository to translate a symbolic object name into a GUID. The GUID is part of the MOF syntax I showed you earlier. You specify the same GUID in your context structure, and WMILIB takes care of the matching.

WMILIB will call routines in our driver to perform device-dependent or driver-dependent processing. Most of the time, the callback routines will perform the requested operation synchronously. However, except in the case of IRP_MN_REGINFO, we can defer processing by returning STATUS_PENDING and completing the request later. If a callback routine will pend the operation, it should call IoAcquireRemoveLock an extra time. Whoever completes the request should make the balancing call to IoReleaseRemoveLock.

The QueryRegInfo Callback

The first system control IRP we'll receive after making our registration call has the minor function code IRP_MN_REGINFO. When we pass this IRP to WmiSystemControl, it turns around and calls the QueryRegInfo function—it finds the function's address in our WMILIB_CONTEXT structure. Here's how WMI42.SYS handles this callback:

NTSTATUS QueryRegInfo(PDEVICE_OBJECT fdo, PULONG flags, 
  PUNICODE_STRING instname, PUNICODE_STRING* regpath,
  PUNICODE_STRING resname, PDEVICE_OBJECT* pdo)
  {
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  *flags = WMIREG_FLAG_INSTANCE_PDO;
  *regpath = &servkey;
  RtlInitUnicodeString(resname, L"MofResource");
  *pdo = pdx->Pdo;
  return STATUS_SUCCESS;
  }

We set regpath to the address of a UNICODE_STRING structure that contains the name of the service registry key describing our driver. This key is the one below …\System\CurrentControlSet\Services. Our DriverEntry routine received the name of this key as an argument and saved it in the global variable servkey. We set resname to the name we chose to give our schema in our resource script. Here's the resource file for WMI42.SYS so that you can see where this name comes from:

#include <windows.h>

LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
MofResource MOFDATA wmi42.bmf

WMI42.BMF is where our build script puts the compiled MOF file. You can name this resource anything you want to, but MofResource is traditional (in a tradition stretching back to, uh, last Tuesday). All that matters about the name is that you specify the same name when you service the QueryRegInfo call.

How we set the remaining values depends on how our driver wants to handle instance naming. I'll come back to the subject of instance naming later in the chapter (in "Instance Naming"). The simplest choice, and the one Microsoft strongly recommends, is the one I adopted in WMI42.SYS: have the system automatically generate names that are static based on the name the bus driver gave to the physical device object (PDO). When we make this choice of naming method, we do the following tasks in QueryRegInfo:

Apart from making your life easier, basing your instance names on the PDO allows viewer applications to automatically determine your device's friendly name and other properties without you doing anything more in your driver.

When you return a successful status from QueryRegInfo, WMILIB goes on to create a complicated structure called a WMIREGINFO that includes your GUID list, your registry key, your resource name, and information about your instance names. It returns to your dispatch function, which then completes the IRP and returns. Figure 10-3 diagrams this process.

The QueryDataBlock Callback

The information you provide in your answer to the initial registration query allows the system to route relevant data operations to you. User-mode code can use various COM interfaces to get and set data values at several levels of aggregation. Table 102 summarizes the four possibilities.

Click to view at full size.

Figure 10-3. Control flow for IRP_MN_REGINFO.

Table 10-2. Forms of data queries.

IRP Minor Function WMILIB Callback Description
IRP_MN_QUERY_ALL_DATA QueryDataBlock Get all items of all instances
IRP_MN_QUERY_SINGLE_INSTANCE QueryDataBlock Get all items of one instance
IRP_MN_CHANGE_SINGLE_INSTANCE SetDataBlock Set all items of one instance
IRP_MN_CHANGE_SINGLE_ITEM SetDataItem Set one item in one instance

When someone wants to learn the value(s) of the data you're keeping, they send you a system control IRP with one of the minor function codes IRP_MN_QUERY_ALL_DATA or IRP_MN_QUERY_SINGLE_INSTANCE. If you're using WMILIB, you'll delegate the IRP to WmiSystemControl, which will then call your QueryDataBlock callback routine. You'll provide the requested data, call another WMILIB routine named WmiCompleteRequest to complete the IRP, and then return to WMILIB to unwind the process. In this situation, WmiSystemControl will return the IrpProcessed disposition code because you've already completed the IRP. Refer to Figure 10-4 for a diagram of the overall control flow.

Click to view at full size.

Figure 10-4. Control flow for data queries.

Your QueryDataBlock callback can end up being a relatively complex function if your driver is maintaining multiple instances of a data block that varies in size from one instance to the next. I'll discuss the complications later in "Dealing with Multiple Instances." The WMI42 sample shows how to handle a simpler case in which your driver maintains only one instance of the WMI class:





1 





2 



3 
NTSTATUS QueryDataBlock(PDEVICE_OBJECT fdo, PIRP Irp, 
  ULONG guidindex, ULONG instindex, ULONG instcount, 
  PULONG instlength, ULONG bufsize, PUCHAR buffer)
  {
  if (!instlength || bufsize < sizeof(ULONG))
    return WmiCompleteRequest(fdo, Irp, STATUS_BUFFER_TOO_SMALL, 
      sizeof(ULONG), IO_NO_INCREMENT);

  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;

  PULONG pvalue = (PULONG) buffer;
  *pvalue = pdx->TheAnswer;
  instlength[0] = sizeof(ULONG);

  return WmiCompleteRequest(fdo, Irp, STATUS_SUCCESS, sizeof(ULONG), 
    IO_NO_INCREMENT);
  }

  1. We're obliged to make this check to verify that the buffer area is large enough to accommodate the data and data length values we're going to put there. The first part of the test—is there an instlength array?—is boilerplate. The second part of the test—is the buffer big enough for a ULONG?—is where we verify that all of our data values will fit. In this simple driver, we're providing only a single ULONG value.
  2. The buffer parameter points to a memory area where we can put our data. The instlength parameter points to an array where we're supposed to place the length of each data instance we're returning. Here, we install the single ULONG data value our schema calls for—the value of the TheAnswer property—and its length. Figuring out what TheAnswer actually is numerically is left as an exercise for the reader.
  3. The WMILIB specification requires us to complete the IRP by calling the WmiCompleteRequest helper routine. The fourth argument indicates how much of the buffer area we used for data values. By now, the other arguments should be self-explanatory.

You'll notice that I didn't discuss the purpose of the guidindex, instindex, and instcount arguments to QueryDataBlock. I'll come back to those a bit further on in "Dealing with Multiple Instances" when I discuss some of the more complicated features of WMI. In WMI42.SYS, you should expect these values to be 0, 0, and 1, respectively.

The SetDataBlock Callback

The system might ask you to change an entire instance of one of your classes by sending you an IRP_MN_CHANGE_SINGLE_INSTANCE request. WmiSystemControl processes this IRP by calling your SetDataBlock callback routine. A simple version of this routine might look like this:





1 







2 
NTSTATUS SetDataBlock(PDEVICE_OBJECT fdo, PIRP Irp, ULONG guidindex, 
  ULONG instindex, ULONG bufsize, PUCHAR buffer)
  {
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  if (bufsize == sizeof(ULONG)
    {
    pdx->TheAnswer = *(PULONG) buffer;
    status = STATUS_SUCCESS;
    info = sizeof(ULONG);
    }
  else
    status = STATUS_INFO_LENGTH_MISMATCH, info = 0;
  return WmiCompleteRequest(fdo, Irp, status, info, IO_NO_INCREMENT);
  }

  1. The system should already know—based on the MOF declaration—how big an instance of each class is and should give us a buffer that's exactly the right size. If it doesn't, we'll end up failing this IRP. Otherwise, we'll copy a new value for the data block into the place where we keep our copy of that value.
  2. We're responsible for completing the IRP by calling WmiCompleteRequest.

The SetDataItem Callback

Sometimes consumers want to change just one field in one of the WMI objects we support. Each field has an identifying number that appears in the WmiDataId property of the field's MOF declaration. (The Active and InstanceName properties are not changeable and don't have identifiers. Furthermore, they're implemented by the system and don't even appear in the data blocks we work with.) To change the one field, the consumer references the field's ID. We then receive an IRP_MN_CHANGE_SINGLE_ITEM request, which WmiSystemControl processes by calling our SetDataItem callback routine:

NTSTATUS SetDataItem(PDEVICE_OBJECT fdo, PIRP Irp, ULONG guidindex, 
  ULONG instindex, ULONG id, ULONG bufsize, PUCHAR buffer)
  {
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  NTSTATUS status;
  ULONG info;

  if (bufsize == sizeof(ULONG))
    {
    pdx->TheAnswer = *(PULONG) buffer;
    status = STATUS_SUCCESS;
    info = sizeof(ULONG);
    }
  else
    status = STATUS_INFO_LENGTH_MISMATCH, info = 0;

  return WmiCompleteRequest(fdo, Irp, status, info, IO_NO_INCREMENT);
  }

In my WMI42.SYS sample, you'll notice that this SetDataItem routine is identical to SetDataBlock because my class has only a single item.

NOTE
The WMI system code that generates calls to the SetDataItem routine was apparently not complete in the beta version of Windows 2000 with which I tested my sample drivers. The only way I was able to invoke this routine was by using an internal Microsoft testing tool, and I always ended up with an item ID of 0 instead of the 1 that's declared in the MOF schema. I don't know whether there was a bug in this internal tool, in the operating system, or in my own understanding of how this was supposed to work. I advise that you fail calls to this routine with STATUS_WMI_NOT_SUPPORTED until you're sure the item ID means what you think it should.

Advanced Features

The preceding discussion covers much of what you need to know to provide meaningful performance information for metering applications. Use your imagination here: instead of providing just a single statistic (TheAnswer), you could accumulate and return any number of performance measures that are relevant to your specific device. You can support, however, some additional WMI features for more specialized purposes. I'll discuss these features now.

Dealing with Multiple Instances

WMI allows you to create multiple instances of a particular class data block for a single device object. You might want to provide multiple instances if your device is a controller or some other device into which other devices plug; each instance might represent data about one of the child devices. Mechanically, you specify the number of instances of a class in the WMIGUIDREGINFO structure for the GUID associated with the class. If WMI42 had three different instances of its standard data block, for example, it would have used the following GUID list in its WMILIB_CONTEXT structure:

WMIGUIDREGINFO guidlist[] = {
  {&GUID_WMI42_SCHEMA, 3, WMIREG_FLAG_INSTANCE_PDO},
  };

The only difference between this GUID list and the one I showed you earlier is the instance count here is 3 instead of 1. This list declares that there will be three instances of the WMI42 data block, each with its own value for the three properties (that is, InstanceName, Active, and TheAnswer) that belong in that block.

If the number of instances changes over time, you can call IoWmiRegistrationControl with the action code WMIREG_ACTION_UPDATE_GUID to cause the system to send you another registration request, which you'll process using an updated copy of your WMILIB_CONTEXT structure. If you're going to be changing your registration information, you should probably allocate the WMILIB_CONTEXT structure and GUID list from the free pool rather than use static variables, by the way.

If user-mode code were to enumerate all instances of GUID_WMI42_SCHEMA, it would find three instances. This result might present a confusing picture to user-mode code, though. It's impossible to tell a priori that the three instances disclosed by the enumeration belong to a single device, as opposed to a situation in which three WMI42 devices each expose a single instance of the same class. To allow WMI clients to sort out the difference between the two situations, your schema should include a property (such as a device name or the like) that can function as a key.

Once you allow for the possibility of multiple instances, several of your WMILIB callbacks will require changes from the simple examples I showed you earlier. In particular:

Figure 10-5 illustrates how your QueryDataBlock function uses the output buffer when it's asked to provide more than one instance of a data block. Imagine that you were asked to provide data for two instances beginning at instance number 2. You'll copy the data values, which I've shown as being of different sizes, into the data buffer. You start each instance on an 8-byte boundary. You indicate the total number of bytes you consume when you complete the query, and you indicate the lengths of each individual instance by filling in the instlength array, as shown in the figure.

Instance Naming

Each instance of a WMI class has a unique name. Consumers that know the name of an instance can perform queries and invoke method routines. Consumers that don't know the names of the instance(s) you provide can learn them by enumerating the class. In any case, you're responsible for generating the names that consumers use or discover.

I showed you the simplest way—from the driver's perspective, that is—of naming instances of a custom data block, which is to request that WMI automatically generate a static, unique name based on the name of the PDO for your device. If your PDO has the name Root\*WCO0A01\0000, for example, a PDO-based name for a single instance of some data block would be Root\*WCO0A01\0000_0. The _0 at the end is what makes this name unique. The name is static in that it persists until you deregister or update your registration information.

Click to view at full size.

Figure 10-5. Getting multiple data block instances.

Basing instance names on the PDO name is obviously convenient because all you need to do in the driver is set the WMIREG_FLAG_INSTANCE_PDO flag in each WMIGUIDREGINFO structure and in the flags variable that WMILIB passes to your QueryRegInfo callback routine. The author of a consumer application can't know what this name will be, however, because the name will vary depending on how your device was installed. To make the instance names slightly more predictable, you can elect to use a constant base name for object instances instead. You indicate this choice by omitting the WMIREG_FLAG_INSTANCE_PDO flag from your WMIGUIDREGINFO structures and by responding in the following way to the registration query:

NTSTATUS QueryRegInfo(PDEVICE_OBJECT fdo, PULONG flags,
  PUNICODE_STRING instname, PUNICODE_STRING* regpath,
  PUNICODE_STRING resname, PDEVICE_OBJECT* pdo)
  {
  *flags = WMIREG_FLAG_INSTANCE_BASENAME; 
  *regpath = &servkey;
  RtlInitUnicodeString(resname, L"MofResource");
  static WCHAR basename[] = L"WMIEXTRA";
  instname->Buffer = (PWCHAR) ExAllocatePool(PagedPool,
    sizeof(basename));
  if (!instname->Buffer)
    return STATUS_INSUFFICIENT_RESOURCES;
  instname->MaximumLength = sizeof(basename);
  instname->Length = sizeof(basename) - 2;
  RtlCopyMemory(instname->Buffer, basename, sizeof(basename)); 
  }

The parts of this function that differ from the previous example of QueryRegInfo are in boldface. In the WMIEXTRA sample, only one instance of each data block exists, and each receives the instance name WMIEXTRA with no additional decoration.

If you elect to use a base name, try to avoid generic names such as Toaster because of the confusion that can ensue. The purpose of this feature is to let you use specific names like AcmeWaffleToaster.

In some circumstances, static instance names won't suit your needs. If you maintain a population of data blocks that changes frequently, using static names means that you have to request a registration update each time the population changes. The update is relatively expensive, and you should avoid requesting one often. You can assign dynamic instance names to the instances of your data blocks instead of static names. The instance names then become part of the queries and replies that you deal with in your driver. Unfortunately, WMILIB doesn't support the use of dynamic instance names. To use this feature, therefore, you'll have to fully implement support for the IRP_MJ_SYSTEM_CONTROL requests that WMILIB would otherwise interpret for you. Describing how to handle these IRPs yourself is beyond the scope of this book, but the DDK documentation contains detailed information about how to go about it.

Dealing with Multiple Classes

WMI42 deals with only one class of data block. If you want to support more than one class, you need to have a bigger array of GUID information structures, as WMIEXTRA does:

WMIGUIDREGINFO guidlist[] = {
  {&GUID_WMIEXTRA_EVENT, 1, 
    WMIREG_FLAG_INSTANCE_PDO | WMIREG_FLAG_EVENT_ONLY_GUID},
  {&GUID_WMIEXTRA_EXPENSIVE, 1, 
    WMIREG_FLAG_EXPENSIVE | WMIREG_FLAG_INSTANCE_PDO},
  {&GUID_WMIEXTRA_METHOD, 1, 
    WMIREG_FLAG_INSTANCE_PDO},
  };

Before calling one of your callback routines, WMILIB looks up the GUID accompanying the IRP in your list. If the GUID isn't in the list, WMILIB fails the IRP. If it's in the list, WMILIB calls your callback routine with the guidindex parameter set equal to the index of the GUID in your list. By inspecting this parameter, you can tell which data block you're being asked to work with.

You can use the special flag WMIREG_FLAG_REMOVE_GUID in a GUID information structure. The purpose of this flag is to remove a particular GUID from the list of supported GUIDs during a registration update. Using this flag also prevents WMILIB from calling you to perform an operation on a GUID that you're trying to remove.

Expensive Statistics

It can sometimes be burdensome to collect all of the statistics that are potentially useful to an end user or administrator. For example, it would be possible for a disk driver (or, more likely, a filter driver sitting in the same stack as a disk driver) to collect histogram data showing how often I/O requests reference a particular sector of the disk. This data would be useful to a disk-defragmenting program because it would allow the most frequently accessed sectors to be placed in the middle of a disk for optimal seek time. You wouldn't want to routinely collect this data, though, because of the amount of memory needed for the collection. That memory would have to be nonpaged, too, because of the possibility that a particular I/O request would be for page swapping.

WMI allows you to declare a particular data block as being expensive so that you don't need to collect it except on demand, as shown in this excerpt from the WMIEXTRA sample program:

WMIGUIDREGINFO guidlist[] = {
  ...
  {&GUID_WMIEXTRA_EXPENSIVE, 1,
    WMIREG_FLAG_EXPENSIVE},
  ...
};

The WMIREG_FLAG_EXPENSIVE flag indicates that the data block identified by GUID_WMIEXTRA_EXPENSIVE has this expensive characteristic.

When an application expresses interest in retrieving values from an expensive data block, WMI sends you a system control IRP with the minor function code IRP_MN_ENABLE_COLLECTION. When no applications are interested in an expensive data block anymore, WMI sends you another IRP with the minor function code IRP_MN_DISABLE_COLLECTION. If you delegate these IRPs to WMILIB, it will turn around and call your FunctionControl callback routine to either enable or disable collection of the values in the data block:

NTSTATUS FunctionControl(PDEVICE_OBJECT fdo, PIRP Irp,
  ULONG guidindex, WMIENABLEDISABLECONTROL fcn, BOOLEAN enable)
  {
  ...
  return WmiCompleteRequest(fdo, Irp, STATUS_SUCCESS, 0,
    IO_NO_INCREMENT);
  }

In these arguments, guidindex is the index of the GUID for the expensive data block in your list of GUIDs, fcn will equal the enumeration value WmiDataBlockControl to indicate that collection of an expensive statistic is being either enabled or disabled, and enable will be TRUE or FALSE to indicate whether you should or should not collect the statistic, respectively. As shown in this fragment, you call WmiCompleteRequest prior to returning from this function.

An application "expresses interest" in a data block, by the way, by retrieving an IWbemClassObject interface pointer bound to a particular instance of your data block's WMI class. Notwithstanding the fact that an application has to discover an instance of the class, no instance index appears in the call to your FunctionControl callback. The instruction to collect or not collect the expensive statistic therefore applies to all instances of your class.

WMI Events

WMI provides a way for providers to notify consumers of interesting or alarming events. A device driver might use this facility to alert a user to some facet of device operation that requires user intervention. For example, a disk driver might notice that an unusually large number of bad sectors have accumulated on a disk. Logging such an event as described in Chapter 9, "Specialized Topics," is one way to inform the human world of this fact, but an administrator has to actively look at the event log to see the entry. If someone were to write an event-monitoring applet, however, and if you were to fire a WMI event when you noticed the degradation, the event could be brought immediately to the user's attention.

WMI events are just regular WMI classes used in a special way. In MOF syntax, you must derive the data block from the abstract WMIEvent class, as illustrated in this excerpt from WMIEXTRA's MOF file:

[Dynamic, Provider("WMIProv"),
 WMI, 
 Description("Event Info from WMIExtra"),
 guid("c4b678f6-b6e9-11d2-bb87-00c04fa330a6"),
 locale("MS\\0x409")]

class wmiextra_event : WMIEvent
{
    [key, read] 
     string InstanceName;

    [read] boolean Active;

    [WmiDataId(1), read] uint32 EventInfo;

};

Although events can be normal data blocks, you might not want to allow applications to read and write them separately. If not, use the EVENT_ONLY flag in your declaration of the GUID:

WMIGUIDREGINFO guidlist[] = {
  ...
  {&GUID_WMIEXTRA_EVENT, 1, 
    WMIREG_FLAG_INSTANCE_PDO | WMIREG_FLAG_EVENT_ONLY_GUID},
  ...
  };

When an application expresses interest in knowing about a particular event, WMI sends your driver a system control IRP with the minor function code IRP_MN_ENABLE_EVENTS. When no application is interested in an event anymore, WMI sends you another IRP with the minor function code IRP_MN_DISABLE_EVENTS. If you delegate these IRPs to WMILIB, you'll receive a call in your FunctionControl callback to specify the GUID index in your list of GUIDs, a fcn code of WmiEventControl, and a Boolean enable flag.

To fire an event, construct an instance of the event class in nonpaged memory and call WmiFireEvent. For example:

PULONG junk = (PULONG) ExAllocatePool(NonPagedPool, sizeof(ULONG));
*junk = 42;
WmiFireEvent(fdo, (LPGUID) &GUID_WMIEXTRA_EVENT, 0, sizeof(ULONG), junk);

The WMI subsystem will release the memory that's occupied by the event object in due course.

WMI Method Routines

In addition to defining mechanisms for transferring data and signalling events, WMI prescribes a way for consumers to invoke method routines implemented by providers. WMIEXTRA defines the following class that includes a method routine:

[Dynamic, Provider("WMIProv"),
 WMI, 
 Description("WMIExtra class with method"),
 guid("cd7ec27d-b6e9-11d2-bb87-00c04fa330a6"),
 locale("MS\\0x409")]

class wmiextra_method
{
    [key, read] 
     string InstanceName;

    [read] boolean Active;

    [Implemented, WmiMethodId(1)] uint32 
     AnswerMethod([in,out] uint32 TheAnswer);

};

This declaration indicates that AnswerMethod accepts an input/output argument named TheAnswer (a 32-bit unsigned integer) and returns a 32-bit unsigned integer as its result.

When you delegate system control IRPs to WMILIB, a method routine call manifests itself in a call to your ExecuteMethod callback routine:

NTSTATUS ExecuteMethod(PDEVICE_OBJECT fdo, PIRP Irp,
  ULONG guidindex, ULONG instindex, ULONG id,
  ULONG cbInbuf, ULONG cbOutbuf, PUCHAR buffer)
  {
  NSTATUS status = STATUS_SUCCESS;
  ULONG bufused = 0;
  ...
  return WmiCompleteRequest(fdo, Irp, status, bufused,
    IO_NO_INCREMENT);
  }

The buffer area contains an image of the input class, whose length is cbInbuf. Your job is to perform the method and overstore the buffer area with an image of the output class. You complete the request with the byte size (bufused ) of the output class. In the WMIEXTRA case, I put the following code in place of the ellipsis. (I've omitted the error checking.)

switch (guidindex)
  {
case 2:
  bufused = sizeof(ULONG);
  (*(PULONG) buffer)++;
  break;
default:
  status = STATUS_WMI_GUID_NOT_FOUND;
  break;
  }

This particular method routine simply adds 1 to its input argument.

Some of the details surrounding method routine calls were still ambiguous when I was writing this chapter. Here are some issues for you to think about:

Standard Data Blocks

Microsoft has defined some standardized data blocks for various types of devices. If your device belongs to a class for which standardized data blocks are defined, you should support those blocks in your driver. Consult WMICORE.MOF in the DDK to see the class definitions, and see Table 10-3.

Table 10-3. Standard data blocks.

Device Type Standard Class Description
Keyboard MSKeyboard_PortInformation Configuration and performance information
Mouse MSMouse_PortInformation Configuration and performance information
Disk MSDiskDriver_Geometry
MSDiskDriver_Performance
Format information
Performance information
Storage MSStorageDriver_FailurePredictStatus Determine whether drive is predicting a failure
  MSStorageDriver_FailurePredictData Failure prediction data
  MSStorageDriver_FailurePredictEvent Event fired when failure is predicted
  MSStorageDriver_FailurePredictFunction Methods related to failure prediction
Serial MSSerial_PortName Name of port
MSSerial_CommInfo Communication parameters
MSSerial_HardwareConfiguration I/O resource information
MSSerial_PerformanceInformation Performance information
MSSerial_CommProperties Communication parameters
Parallel MSParallel_AllocFreeCounts Counts of allocation and free operations
  MSParallel_DeviceBytesTransferred Transfer counts

To implement your support for a standard data block, include the corresponding GUID in the list you report back from the registration query. Implement supporting code for getting and putting data, enabling and disabling events, and so on, using the techniques I've already discussed. Don't include definitions of the standard data blocks in your own schema; those class definitions are already in the repository, and you don't want to override them.

In many cases, by the way, a Microsoft class driver will be providing the actual WMI support for these standard classes—you might not have any work to do.

Standard Controls

Windows 2000 will someday employ WMI as a method of sending certain common commands to drivers. In particular, management applications will be able to send commands related to power management by means of WMI. At the present time, only two such commands are defined. See Table 10-4.

Table 10-4. Standard WMI commands.

WMI Class (WMICORE.MOF) GUID Name (WDMGUID.H) Purpose
MSPower_DeviceEnable GUID_POWER_DEVICE_ENABLE Should device dynamically power on and off while the system is working?
MSPower_DeviceWakeEnable GUID_POWER_DEVICE_WAKE_ENABLE Should device arm its wake-up feature?

If you refer to WMICORE.MOF, you'll see that the DeviceEnable and DeviceWakeEnable classes include only a Boolean member named Enable that a WMI client can either read or write. To support these two classes in your driver, include the two GUIDs in the list of GUIDs you pass to WMILIB and use code to get and set instances of this class. The code to handle these details is so similar to WMI42 that I won't show it to you here.

If you trace back through the beta releases of Windows 2000, it looks like Microsoft originally planned to implement another WMI class (probably with the name MSPower_DeviceTimeouts) that would query and set the two timeout values you use when you register with the Power Manager for idle detection. That plan appears to have fallen by the wayside. The GUID definition (GUID_POWER_DEVICE_TIMEOUTS) still appears in WDMGUID.H, though.