Scott and I were recently discussing features in Windows driver development that we thought were cool, but that few devs seemed to know about or use. We viewed them as sort of driver development “hidden gems.” We decided to do an occasional series to describe our favorite overlooked features. This is the first installment in that series. In this article and the accompanying video, I discuss one of my long favorite, but rarely used, features in Windows driver development: The use of name spaces in device drivers.
Device name spaces aren’t the sort of feature that you’ll need to use in every driver you write. But knowing the feature is there can really save you a lot of time because you can exploit it when you have just the right situation for it.
Let’s say you have a device that has multiple functions or multiple capabilities. I’ll call my example device FOO. It has two channels. Channel 1 and Channel 2.
Let’s also say you want your users to be able to choose to open either channel 1 or channel 2 of that FOO device, and you’d like to make it simple for them to make that choice. Understand this is just an example. Your device might be a satellite, where users could choose a transponder to access. Or a machine tool of some kind, where the user chooses which tool or cutter to access. Or perhaps you want your users to choose the type of access to use (raw or encrypted). But, regardless of the specific use, the idea is the same: You want the user to be able to choose a specific feature of the device to use in their subsequent I/O operations on the device.
If you want the user’s choice to last for the duration of a user’s session with the device, a really convenient way of allowing the user to make this choice is to allow them to choose as part of issuing the CreateFile call. And this is where device name spaces come in.
Once you’ve created your WDF Device Object, and given it a name, you can create a symbolic link for that name in the Global ?? Namespace to make your device easily accessible (by name) from user mode.
So, for our FOO device, we’re going to name it FOO and we have a symbolic link that points to that device in the \Global??\ namespace that’s also called FOO. To open the base device, users would say ordinarily issue a call like the following:
hFile = CreateFile(“\\.\FOO”,…);
Of course, in your program you’d have to escape all those backslashes. But I’ve left the escaping out to make the example easier to read.
If the user opens “\\.\FOO” they’re opening the base device itself. In the case where you we want the user to make a choice of a particular aspect of our device, such as choosing to open Channel 1 or Channel 2 in our example device, we could disallow CreateFile on the base device itself if we wanted to.
Now, this is where device name spaces enter the picture. If a user want’s to open Channel 1 of the FOO device, they would use something like the following name in their CreateFile call:
hFile = CreateFile(“\\.\FOO\C1“,…);
(Again, without the escaping for backslashes in that string)
The cool part of this is that the part of the name that comes after the base device name can be any arbitrary string that you decide will make sense to your driver. Just to be clear, in the above example, the base device name is “\\.\FOO”, and “\C1” is the part after the base device name.
To process this CreateFile operation and retrieve the part of the name that follows the base device name, you need to specify an EvtFileCreate Event Processing Callback in your driver. In our example this will allow us to differentiate between opens for “FOO” (the base device) or “FOO\C1” (Channel 1 on the FOO device) or “FOO\C2” (Channel 2 on the FOO device). You specify the EvtFileCreate callback as part of your EvtDriverDeviceAdd Event Processing Callback. Prior to calling WdfDeviceCreate, you allocate and initialize a WDF_FILE_OBJECT_CONFIG structure. You then fill in the EvtFileCreate field of that structure to point to the function in your driver that you want the Framework to call when a user issues a CreateFile for your device. At this time you will also almost certainly want to specify a Context structure for the Framework to associate with each WDF File Object that’s created. You’ll use that File Object Context area to store information for easy retrieval during subsequent I/O processing. After you’ve filled-in the WDF_FILE_OBJECT_CONFIG structure, you call WdfDeviceInitSetFileObjectConfig. You the proceed to create your WDF Device Object in the usual way.
Once you’ve set things up as I’ve just described, the Framework will call your driver at your EvtFileCreate Event Processing Callback each time a user attempts to open your device. One of the input parameters to EvtFileCreate is a handle to the WDF File Object that represents the new open instance being requested by the user. Within EvtFileCreate, you call WdfFileObjectGetFileName. This returns a pointer to a UNICODE_STRING containing the part of the name that’s NOT part of your base device name. So, in our example, you’ll be returned a UNICODE_STRING that contains just the”\C1″ or just “\C2. If you specified a File Object Context area, you can store into that Context the information that you’ve determined during open processing. In our example, I’d store which channel of the device the open instance is associated with.
Now, when you get Requests in your EvtIoRead, EvtIoWrite, and EvtIoDeviceControl Event Processing Callbacks you can get the handle to the WDF File Object associated with the Request by calling WdfRequestGetFileObject. You can then use your Accessor Function to get a pointer to your Context from the File Object Handle and determine whether the user who sent you that read, or that write, or that device control wants to operate on channel 1 or channel 2 or the base device or whatever.
Device Namespaces, they’re cool. Give them a try!