Defining a Device Class
Let s suppose you have a device that doesn t fit into one of the device classes Microsoft has already defined. When you re initially testing your device and your driver, you can get away with using the Unknown class in your INF file. Production devices are not supposed to be in the Unknown class, however. You should instead place your custom device in a new device class that you define in the INF file. I ll explain how to create a custom class in this section.
The INF example I showed you earlier relied on a custom device class:
[Version] Signature=$CHICAGO$ Class=Sample ClassGuid={894A7460-A033-11d2-821E-444553540000}
In fact, all of the samples in this book use the Sample class.
When you want to define a new class of device, you need to run GUIDGEN to create a unique GUID for the class. You can add polish to the user interface for your device class by writing a property page provider for use with the Device Manager and putting some special entries in the registry key your class uses. You can also provide filter drivers and parameter overrides that will be used for every device of your class. You control each of these additional features by statements in your INF file. For example, each of the INF files for the samples in the companion content includes these boilerplate entries:
[ClassInstall32] AddReg=ClassInstall32AddReg CopyFiles=ClassInstall32CopyFiles [ClassInstall32AddReg] HKR,,,,"WDM Book Samples" HKR,,Installer32,,"samclass.dll,SampleClassInstaller" HKR,,EnumPropPages32,,samclass.dll HKR,,Icon,,101 [ClassInstall32CopyFiles] samclass.dll,,,2
Whenever you use a nonstandard setup class in your INF file, you should have a [ClassInstall32] section to define the class. Don t do what I did in the first edition and depend on some other setup procedure to define the class. Note that the system uses this section the very first time it installs a device belonging to your custom class.
In the AddReg section for a [ClassInstall32] section, HKR refers to the class key. The default (unnamed) value is the friendly name of the class that will appear in Device Manager and hardware install wizard dialog boxes.
Installer32 names a class installer DLL. For the SAMPLE class, I combined the property page provider and the installer into one DLL named SAMCLASS.DLL. I included the installer is so I could provide a custom icon for the class.
EnumPropPages32 names a Device Manager property page provider.
Icon specifies the icon resource in the property page provider DLL.
This statement is how we make sure that the property page and installer DLL gets copied during installation of the first SAMPLE device. A statement in the [DestinationDirs] section of the INF file directs this file to the system directory.
A Property Page Provider
Way back in the introduction to this book, I showed you a screen shot of the property page I invented for use with the Sample device class. The SAMCLASS sample in the companion content is the source code for the property page provider that produced that page, and I m now going to explain how it works.
TIP
Microsoft recommends that you use an installer or co-installer DLL as a vehicle for adding property pages to the Device Manager s property sheet. The property-page provider concept described here is still supported, and you can put the property-page function in the same DLL as an installer or a co-installer. In addition, you ll need a (16-bit) property page provider DLL to get the same functionality in Windows 98/Me, and the code in one of those is nearly identical to that in a 32-bit DLL for Windows 2000 and later systems. It s your choice how you proceed. If you want to follow the Microsoft recommendation, I suggest that you consult the CLASSINSTALLER portion of the TOASTER sample in the DDK or the COINSTALLER sample in the companion content for this book.
A property page provider for a device class is a 32-bit DLL with the following contents:
An exported entry point for each class for which the DLL supplies property pages
Dialog resources for each property page
A dialog procedure for each property page
In general, a single DLL can provide property pages for several device classes. Microsoft supplies some DLLs with the operating system that do this, for example. SAMCLASS, however, provides only a single page for a single class of device. Its only exported entry point is the following function:
extern "C" BOOL CALLBACK EnumPropPages (PSP_PROPSHEETPAGE_REQUEST p, LPFNADDPROPSHEETPAGE AddPage, LPARAM lParam) { PROPSHEETPAGE page; HPROPSHEETPAGE hpage; memset(&page, 0, sizeof(page)); page.dwSize = sizeof(PROPSHEETPAGE); page.hInstance = hInst; page.pszTemplate = MAKEINTRESOURCE(IDD_SAMPAGE); page.pfnDlgProc = PageDlgProc; <some more stuff> hpage = CreatePropertySheetPage(&page); if (!hpage) return TRUE; if (!(*AddPage)(hpage, lParam)) DestroyPropertySheetPage(hpage); return TRUE; }
When the Device Manager is about to construct the property sheet for a device, it consults the class registry key to see whether there s a property page provider. It loads the DLL you specify (SAMCLASS.DLL in the case of SAMPLE) and calls the designated entry point (EnumPropPages). If the function returns TRUE, the Device Manager will display the property page; otherwise, it won t. The function can add zero or more pages by calling the AddPage function as shown in the preceding example.
Inside the SP_PROPSHEETPAGE_REQUEST structure your enumeration function receives as an argument, you ll find two useful pieces of information: a handle to a device information set and the address of an SP_DEVINFO_DATA structure that pertains to the device you re concerned with. These data items (but not, unfortunately, the SP_PROPSHEETPAGE_REQUEST structure that contains them) remain valid for as long as the property page is visible, and it would be useful for you to be able to access them inside the dialog procedure you write for your property page. Windows SDK Programming 101 (well, maybe 102 because this process is a little obscure) taught you how to do this. First create an auxiliary structure whose address you pass to CreatePropertySheetPage as the lParam member of the PROPSHEETPAGE structure:
struct SETUPSTUFF { HDEVINFO info; PSP_DEVINFO_DATA did; char infopath[MAX_PATH]; }; BOOL EnumPropPages(...) { PROPSHEETPAGE page; SETUPSTUFF* stuff = new SETUPSTUFF; stuff->info = p->DeviceInfoSet; stuff->did = p->DeviceInfoData; page.lParam = (LPARAM) stuff; page.pfnCallback = PageCallbackProc; page.dwFlags = PSP_USECALLBACK; } UINT CALLBACK PageCallbackProc(HWND junk, UINT msg, LPPROPSHEETPAGE p) { if (msg == PSPCB_RELEASE && p->lParam) delete (SETUPSTUFF*) p->lParam; return TRUE; }
The WM_INITDIALOG message that Windows sends to your dialog procedure gets an lParam value that s a pointer to the same PROPSHEETPAGE structure, so you can retrieve the stuff pointer there. You can then use SetWindowLong and GetWindowLong to save the stuff pointer.
You also need to provide a way to delete the SETUPSTUFF structure when it s no longer needed. The easiest way, which works whether or not you ever get a WM_INITDIALOG message (you won t if there s an error constructing your property page), is to use a property page callback function as shown in the preceding fragment.
You can do all sorts of things in a custom property page. For the sample class, I wanted to provide a button that would bring up an explanation for each sample device. To keep things as general as possible, I decided to put a Sample Info value naming the explanation file in the device s hardware registry key. To invoke a viewer for the explanation file, it suffices to call ShellExecute, which will interpret the file extension and locate an appropriate viewer application. For my book samples, the explanation files are HTML files, so the viewer in question will be your Web browser.
Most of the work in SAMCLASS occurs in the WM_INITDIALOG handler. (Error checking is again omitted.)
case WM_INITDIALOG: { SETUPSTUFF* stuff = (SETUPSTUFF*) ((LPPROPSHEETPAGE) lParam)->lParam; SetWindowLong(hdlg, DWL_USER, (LONG) stuff); TCHAR name[256]; SetupDiGetDeviceRegistryProperty(stuff->info, stuff->did, SPDRP_FRIENDLYNAME, NULL, (PBYTE) name, sizeof(name), NULL); SetDlgItemText(hdlg, IDC_SAMNAME, name); HWND hClassIcon = GetDlgItem(hdlg, IDC_CLASSICON); HICON hIcon; SetupDiLoadClassIcon(&stuff->did->ClassGuid, &hIcon, NULL); SendMessage(hClassIcon, STM_SETICON, (WPARAM) (HANDLE) hIcon, 0); HKEY hkey = SetupDiOpenDevRegKey(stuff->info, stuff->did, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); DWORD length = sizeof(name); RegQueryValueEx(hkey, "SampleInfo", NULL, NULL, (LPBYTE) name, &length); DoEnvironmentSubst(name, sizeof(name)); strcpy(stuff->infopath, name); RegCloseKey(hkey); break; }
This statement saves the SETUPSTUFF pointer where the dialog procedure can find it to handle later window messages.
Here we determine the friendly name for the device and put it in a static text control. The actual code sample obtains the device description if there s no friendly name. In the dialog template, I positioned the control quite carefully to match the position where the Device Manager puts a similar control on other pages of the property sheet. Taking this bit of extra care means that the text (which appears on every page) doesn t appear to hop around as you tab from one page to the next.
These statements determine the class icon. In this particular sample, I could have hard-coded the class icon in the dialog template since SAMCLASS is used only with SAMPLE class devices. I preferred to show you this more general way of getting the same icon that the Device Manager uses on other pages of the property sheet. Just as with the static control containing the friendly name, I had to position the icon control carefully in the dialog template.
The next few statements determine the SampleInfo filename from the hardware key s parameter subkey. The strings I put in the registry are of the form %wdmbook%\chap15\devprop\devprop.htm, in which %wdmbook% indicates substitution by the value of the WDMBOOK environment variable. The call to DoEnvironmentSubst, a standard Win32 API, expands the environment variable. (Trust me that I don t just do a blind strcpy in the actual code I check the length first.)
When the end user that would be you in this particular situation, I think presses the More Information button on the property page, the dialog procedure receives a WM_COMMAND message, which it processes as shown here:
SETUPSTUFF* stuff = (SETUPSTUFF*) GetWindowLong(hdlg, DWL_USER); case WM_COMMAND: switch (LOWORD(wParam)) { case IDB_MOREINFO: { ShellExecute(hdlg, NULL, stuff->infopath, NULL, NULL, SW_SHOWNORMAL); return TRUE; } } break;
ShellExecute will launch the application associated with the SampleInfo file namely, your Web browser whereupon you can view the file and find all sorts of interesting information.