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.
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; } |
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 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 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.
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.
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); } |
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 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); } |
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.
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.
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.
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.
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.
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.
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 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.
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:
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.
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.