Click Here to Download: Code Associated With This Article Zip Archive, 45KB
In this article, I am going to show you how to create a custom property page for your driver that displays in Device Manager. In case you don?t know which ?property page? I?m talking about, check out the picture in Figure 1. The example driver I?ll use is for a device that uses a custom setup class.
Figure 1 ? WDM Nothing Driver Property Page
First of all you might ask: Why would I want a Device Property Page? According to the Windows 2000 DDK, ?A co installer or class installer should supply a custom device property page if its device or class has any individual properties that a user can set?. But even if you don?t create a co-installer or class installer, you still might want to have custom properties for your device or driver. Maybe you need to set the speed of your device, a buffer size for incoming data, or provide a license or security key. Or, you might want to provide means to selectively set some debug parameters. Whatever your reasons, creating a property page can make setting changeable device parameters easy, and creates a nice, finished, look to your driver. And it?s actually pretty easy, once you know the tricks I?ll describe in this article.
We?ll start with OSR?s implementation of the WDM NOTHING Driver. What does the driver do? Well, exactly what the name states, Nothing! Using this driver, we will take create an ?.INF? file for it, specifying a new setup class for the device (So, we?ll be talking about the CLASS32INSTALL section). Then, we?ll talk about what is needed in the DLL and how to implement it. Finally, we?ll talk about what is needed in the WDM NOTHING Driver so that the DLL can find it.
Getting Started
In the example to be used for this article, we create an INF file for the WDM NOTHING driver. As a part of creating this INF file, we decide that the device is a member of a unique setup class.. In order to define this new setup class, we add some information to the INF file. In the [Version] section of the file, we add the following 2 lines:
Class=OsrNothingClass
ClassGuid={0c833dae-8679-11d3-b19b-0060b0efd4aa}
The first line declares that the WDM NOTHING driver is part of the setup class named ?OsrNothingClass? (The currently support list of Device Setup Classes in located in ?Chapter 8 Device Setup Classes? of the DDK). Since this class is not a currently known class, we must create a Globally Unique Identifier (GUID) using the GUIDGEN.EXE utility, available with VC++ or the SDK. This will uniquely identify our new setup class to Win2K/XP. Once this is done, we must create a new section in our INF file, the [ClassInstall32] section, which will add a new setup device class (and possibly a class installer) for some number of devices of the same type. For our example, we add the following lines to our INF file:
[ClassInstall32]
Addreg=OsrNothingClass
[OsrNothingClass]
HKR,,,,%ClassName%
HKR,,Icon,,103 ; Empty
HKR,,EnumPropPages32,,"nothingproppage.dll,NOTHINGPropPageProvider"
In the [ClassInstall32] section, the ?AddReg? directive references another named section in our INF file, which contains class-specific value entries to be written into the registry when our INF file is processed. In our case, the [OsrNothingClass] is the only named section that will be processed.
Our [OsrNothingClass] section contains 3 lines. The Registry values created by these lines will be created under the ?HKLM\System\CurrentControlSet\Control\Class\{X} key in the registry (where X is the GUID, expressed in hex digits, that we generated for our new class). The first line:
HKR,,,,%ClassName%
creates an unnamed value in the Registry which defines the device-class friendly name that will be displayed by the Device Manager (See Figure 2), in our Example %ClassName% is defined in the [Strings] as ClassName = "OSR Nothing Devices?.
Figure 2 ? WDM Nothing Driver Shown in Device Manager
The second line in our INF file
HKR,,Icon,,103
creates a named value Icon which contains the string 103. What is 103? Well, that happens to correspond to a numbered resource in our property page DLL that will represent the icon displayed by the Device Manager. In our case this translates into a bucket icon (no smirks allowed! I can program, I never said I could draw). Of course, you could use one of the ?standard? icons for your device. But what fun is that? By the way, the icon should be something that represents your device to the user. It shouldn?t be a little miniature version of you company?s logo (which doesn?t necessary denote anything about your device other than who made it). The third and final line
HKR,,EnumPropPages32,,"nothingproppage.dll,NOTHINGPropPageProvider"
creates a named value EnumPropPages32. This value indicates that the property page provider for this class is called nothingproppage.dll (which must be located in %SystemRoot%\System32) and its main entry point is NOTHINGPropPageProvider.
Whew! Now that we?ve got this all out of the way, let?s get to the good stuff?.
The Property Page DLL and the NothingPropPageProvider Interface
A property page DLL is a standard Win32 DLL. It contains a ?DllMain? entry point that gets called when the DLL gets loaded. As you can see below our DllMain, named NOTHINGPropPageProvider, is pretty simple. It just saves away the Handle to the DLL module, which is the base address of the DLL. The HINSTANCE of a DLL is the same as the HMODULE of the DLL, so hinstDLL can be used in calls to functions that require a module handle. After the DLL is loaded the first routine called by the Device Manager is the entry point NOTHINGPropPageProvider that we defined in our EnumPropPages32 key. The definition of the NothingPropPageProvider entry point (as shown in Figure 3)is as follows:
· pPropPageRequest ? Which is used to retrieve a handle (or, potentially, multiple handles) to property pages for a specified property page type
· fAddFunc ? Is the address of a callback routine to be called when any pages defined in the pPropPageRequest structure (defined above) are needed.
· lParam ? Is the add sheet functions private data handle
The purpose of this routine is to respond to any calls for additional property sheets (other than the property sheets that the Device Manager handles) that our class of device might handle. In our case, we handle the request for the SPPSR_ENUM_ADV_DEVICE_PROPERTIES property sheet for our device. In our implementation, once we receive this type of request, we need to determine which of our devices information is being requested. We do this by calling our routine GetDeviceInterfaceDetail.
The GetDeviceInterfaceDetail routines? responsibility is to retrieve Device Interface Details. In other words, it must get the information necessary to open a handle to the correct instance of a WDM NOTHING device so that we may communicate with it. To do this, we use the SetupDiXxx interface routines. Our first call to SetupDiGetDevice InstanceId retrieves the device instance ID (a string) associated with a device information element, contained within the PSP_PROPSHEETPAGE_REQUEST that was passed into GetDeviceInterfaceDetail. As you can see in the code in Figure 3, we make two calls to this routine.
BOOL APIENTRY NOTHINGPropPageProvider(PSP_PROPSHEETPAGE_REQUEST pPropPageRequest, LPFNADDPROPSHEETPAGE fAddFunc, LPARAM lParam)
{
PSP_DEVICE_INTERFACE_DETAIL_DATA pDeviceInterfaceDetailData;
PNOTHING_PROP_INTF pNothingFeatures;
PROPSHEETPAGE PropSheetPage;
HPROPSHEETPAGE hPropSheetPage;
NothingOutputDebugString("NOTHINGPropPageProvider Enter\n");
// Check page requested
if( pPropPageRequest->PageRequested != SPPSR_ENUM_ADV_DEVICE_PROPERTIES ) {
return FALSE;
}
// Check device info set and data
if ((!pPropPageRequest->DeviceInfoSet) || (!pPropPageRequest->DeviceInfoData)) {
return FALSE;
}
// Allocate the memory for the Nothing features.
pNothingFeatures = (PNOTHING_PROP_INTF) LocalAlloc (LPTR, sizeof (NOTHING_PROP_INTF));
if (!pNothingFeatures) {
DbgError ("NOTHINGPropPageProvider: LocalAlloc: No Features");
return FALSE;
}
memset(pNothingFeatures,0,sizeof(NOTHING_PROP_INTF));
// Get the device interface detail which return a path to the device
// driver that we need to open the device.
if (!GetDeviceInterfaceDetail (pPropPageRequest, &pDeviceInterfaceDetailData)) {
return FALSE;
}
// Get the Nothing features through the private property call.
if (!GetNothingFeatures (pDeviceInterfaceDetailData, pNothingFeatures)) {
return FALSE;
}
pNothingFeatures->PIntfDetail = pDeviceInterfaceDetailData;
// We don't need the device interface details any more, get rid of it now!
// LocalFree (pDeviceInterfaceDetailData);
// initialize the property page
PropSheetPage.dwSize = sizeof(PROPSHEETPAGE);
PropSheetPage.dwFlags = PSP_USECALLBACK | PSP_HASHELP;
PropSheetPage.hInstance = ghInstance;
PropSheetPage.pszTemplate = MAKEINTRESOURCE(DLG_NOTHINGFEATURES);
PropSheetPage.pfnDlgProc = NothingDlgProc;
PropSheetPage.lParam = (LPARAM) pNothingFeatures;
PropSheetPage.pfnCallback = NothingSettingsDlgCallback;
// create the page and get back a handle
hPropSheetPage = CreatePropertySheetPage( &PropSheetPage );
if( !hPropSheetPage ) {
return FALSE;
}
// add the property page
if( !(*fAddFunc)(hPropSheetPage, lParam)) {
DestroyPropertySheetPage (hPropSheetPage);
return FALSE;
}
NothingOutputDebugString("NOTHINGPropPageProvider Exit\n");
return TRUE;
}
Figure 3
The first call is expected to fail, since all we really want here is to figure out the number of bytes that will have to be allocated in order to retrieve the device instance ID. Our second call to SetupDiGetDeviceInstanceId will pass this allocated buffer as a parameter, so that we can retrieve the string. You could eliminate the 2 calls and just allocate a buffer of fixed length, but that is a waste of memory (perhaps a topic on which Peter can pontificate!).
Now that we have the device instance ID, we want to verify that the device we?re going to talk to supports the Device Interface that the WDM NOTHING driver supports (technically, we already know the answer to this, given we have correctly implemented IoRegisterDeviceInterface as described in the DDK). To do this we call SetupDiGetClassDevs passing in the Device Interface GUID that we?re interested in, the device instance ID that we got previously, and finally the DIGCF_DEVICEINTERFACE flag which tells SetupDiGetClassDevs that it should only examine this particular device (specified by the device instance ID) for the requested interface class. Once we have this information, we must call SetupDiEnumDevice Interfaces that returns a context structure for a device interface element of a device information set. In other words it returns us information about the NOTHINGID_PRIVATE interface, so that we may retrieve the interface information required to do a CreateFile to our device. Finally, we call SetupDiGetInterfaceDetail that will return us all the information necessary to communicate with our device (Note: we actually call this function twice to ensure that we allocate the correct amount of memory for the returned information).
While painful, this is the Microsoft preferred way to get a handle to the device you want to communicate with. Using pre-defined device names i.e. COM1, COM2?COM99, is a thing of the past. With plug and play, devices may not be named in numerical order; it is much simpler to just say that your device supports the GUID_CLASS_COMPORT interface.
Once we successfully return from GetDeviceInterfaceDetail we have enough information to be able to communicate with the correct device instance. So, the next thing that our NothingPropPageProvider routine does is call GetNothingFeatures. This is just a simple routine which opens our WDM NOTHING device instance, and issues a private IOCTL we define (IOCTL_GET_NOTHING_ PROPERTY) in order to receive our NOTHING_ FEATURES information (used to update our property sheet).
Once we have the NOTHING_FEATURES information we must define our property sheet to the Device Manager. To do this we must fill in a PROPSHEETPAGE structure with information about our sheet and then add it to the Device Manager?s list of sheets. The code that does this (as shown in Figure 2) has fields defined as follows:
· dwSize ? Receives the size of the PROPSHEETPAGE structure
· dwFlags ? This field instructs the property page manager that this property page supports help (PSP_HASHELP) and wants to be called back (using PSP_USECALLBACK) at the routine specified in PfnCallBack.
· hInstance ? This field is where we store the location of ghInstance (the filed that we set in DLLMain).
· pszTemplate ? This field is the storage location for the resourceID for our property page (which is how the device manager knows what information to display).
· pfnDlgProc ? This field receives a pointer to our NothingDlgProc routine that is the dialog box procedure for our property page (i.e., the routine that processes Windows messages targeted at our dialog box).
· lParam ? This field receives a pointer to our NOTHING_PROP_INTF structure, which contains context for our WDM NOTHING driver features, and information on which device instance to communicate with.
· pfnCallBack ? This field contains the address of NothingSettingsDlgCallback, which gets called when the property sheet is created or destroyed (if destroyed, we clean up any memory we allocated).
Once this structure is initialized, we call the Win32 API CreatePropertSheetPage routine, which creates the property page (for clarity, property sheets contain property pages) and then, if successful, we call the AddPropertySheet routine whose address was passed in to our NothingPropPage Provider routine.
NothingDlgProc
The NothingDlgProc routine handles Windows messages targeted at our Property Page Dialog box. As you can see in the code shown in Figure 4, we handle very few messages, only WM_INITDIALOG and WM_COMMAND.
int APIENTRY NothingDlgProc (HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
switch (uMessage) {
// We don't do anything for these messages.
case WM_COMMAND:
NothingOutputDebugString("NothingDlgProc WM_COMMAND\n");
switch(LOWORD(wParam)) {
case IDC_CHECK_DO_NOTHING: {
PNOTHING_PROP_INTF pNothingFeatures = (PNOTHING_PROP_INTF)GetWindowLong(hDlg, DWL_USER);
UINT val = IsDlgButtonChecked(hDlg,IDC_CHECK_DO_NOTHING);
//
// Something has happened to our control. See if the state of the button
// has changed.
//
switch(val) {
case BST_CHECKED:
pNothingFeatures->NothingFeatures.DoNothing = TRUE;
NothingOutputDebugString("NothingDlgProc Checked\n");
break;
case BST_UNCHECKED:
pNothingFeatures->NothingFeatures.DoNothing = FALSE;
NothingOutputDebugString("NothingDlgProc Unchecked\n");
break;
default:
NothingOutputDebugString("NothingDlgProc Indeterminate\n");
break;
}
SetNothingFeatures (pNothingFeatures->PIntfDetail,pNothingFeatures);
}
break;
default:
break;
}
NothingOutputDebugString("NothingDlgProc WM_COMMAND exit\n");
break;
case WM_CONTEXTMENU:
case WM_HELP:
case WM_NOTIFY:
case WM_DESTROY:
break;
case WM_INITDIALOG:
return NothingPropPage_OnInitDialog (hDlg, (HWND) wParam, lParam);
}
return FALSE;
}
Figure 4 ? NothingDlgProc
Let?s start with WM_INITDIALOG, when we get this message, Windows is asking us to initialize our dialog box, so we call our NothingPropPage_OnInitDialog routine (oh, don?t start up with me about these long routine names, OK? I happen to have to support the code that I write). This routine is simple; It just initializes the dialog box fields. In our case, it calls the WDM NOTHING device instance to get information about its features, and initializes the Check Box in the dialog with the correct information. The one interesting thing is that we save a pointer to our NOTHING_PROP_INTF structure in the window memory of the dialog (using SetWindowLong) so that we have a context value to work with when subsequent WM_COMMANDs come to this routine.
When the NothingDlgProc routine receives WM_ COMMAND messages, these messages are targeted at controls in the window (in our case the Check Box) so it is this routines responsibility to determine which control is affected by this message and how it is affected. In order to figure out which control is affected, the code calls LOWORD(wParam). This macro returns the resource ID of the control affected by the message. We only have one control IDC_CHECK_DO_NOTHING and the only thing that could have happened to the control is that it was either checked or unchecked. Once we determine which one it was, we call the call our SetNothingFeatures routine which calls the driver with the IOCTL we defined to set features (IOCTL_SET_NOTHING_PROPERTY).
Summary
As you can see writing a Property Page DLL is not that difficult. It can be time consuming, and you do have to know something about Windows GUI programming (I only know enough to be dangerous). You also need to understand how your driver works, but if you?re reading The NT Insider, I sure hope you already know that. In fact probably the hardest thing to get correct is the SetupDi? calls and using IoRegisterDeviceInterface and IoSetDeviceInterfaceState.
转自: http://www.osronline.com/custom.cfm?name=articlePrint.cfm&id=27