技术的乐趣在于分享,欢迎多多交流,多多沟通。
全部博文(877)
分类: Windows平台
2015-05-14 14:10:15
This article will provide an overview of what it takes to implement a UMDF driver. It assumes familiarity with the UMDF architecture, as described in . Please keep in mind that the UMDF architecture is still changing. Therefore, the information presented may not reflect how the released version looks. With that in mind, let's start at the beginning.
It's a DLL
A UMDF driver is a Dynamic Link Library (DLL) and a COM object wrapped into one. Since it's a DLL, it should support DllMain, which is a well-known entry point for a DLL. Figure 1 contains the code for DllMain.
BOOL Figure 1 - DllMain |
As you can see, DLLMain is pretty simple. Now we just need to look at the reason code passed to us, and then decide what processing needs to be done. If a process is being attached to this DLL for the first time, we initialize WPP tracing for the driver. We also save the ModuleHandle passed in to us for later use when registering with COM. If this DLL is being detached from, then all we do is end WPP tracing by calling WPP_CLEANUP().
Besides being a DLL, the UMDF driver is also a COM-based object. This DLL has to provide additional routines that get the COM-based object registered. In addition, the DLL provides routines that COM will use to get the IWDFDriver-based object created. In order to provide these routines, the following entry points must be implemented:
Most of these routines are complicated, especially if you're not familiar with the basics of COM. I could kill a lot of trees by explaining these routines, but I'll refrain. Why? Well 99.9% of the code to do this registration is boilerplate that can be copied from one project to another. The one exception is the DllGetClassObject routine, which is responsible for creating an instance of the IWDFDriver-based driver by doing the necessary COM stuff. In the sample UMDF projects contained in the UMDF kit I used, each project contained dllsup.cpp and comsup.cpp that did all the work. Since we're more concerned about our driver than the COM related matters, we're going to skip a discussion of the COM material and move right into the guts of the driver.
The Driver
The driver we're going to talk about in this article is a sample written for the OSR USB-FX2 Learning Kit (See ). This driver is part of the UMDF version distributed in the WDF Pre-Release, handed out at the 2005 Windows Driver DevCon. Again, UMDF is still undergoing development, so its architecture and the sample driver we're going to talk about may be quite different in the initial release. Please keep this in mind when reading this article.
Creating an Instance of IWDFDriver Derived Class
Since we're creating a driver, the driver "object" has to first get created somehow. If this was a kernel mode driver, the I/O Manager would create a Driver Object, and then load the driver. In the case of a UMDF driver, the DLL gets loaded and eventually a routine in comsup.cpp called CClassFactory::CreateInstance would be called. This routine is shown in Figure 2.
HRESULT STDMETHODCALLTYPE CClassFactory::CreateInstance( PCMyDriver driver; hr = CMyDriver::CreateInstance(&driver);
if (S_OK == hr)
return hr; Figure 2 - CClassFactory::CreateInstance |
CClassFactory::CreateInstance is responsible for creating an instance of the COM object that supports IWDFDriver interfaces, which in our case is part of the CMyDriverClass shown in Figure 3.It does this by calling the CMyDriver::CreateInstance routine, which is a static function in the CMyDriver class.
class CMyDriver : public CUnknown, public IDriverEntry
HRESULT Initialize(VOID);
//
virtual HRESULT STDMETHODCALLTYPE
virtual HRESULTSTDMETHODCALLTYPE
virtual VOID STDMETHODCALLTYPE
// IUnknown methods.
virtual ULONG STDMETHODCALLTYPE Release(VOID){
virtual HRESULTSTDMETHODCALLTYPE QueryInterface( Figure 3 - CMyDriver Class Definition |
The CMyDriver::CreateInstance shown in Figure 4 creates an instance of the CMyDriver class and does any initialization of this object's data members that may be required. Once the instance is created, the CClassFactory::CreateInstance routine in Figure 2 calls the QueryInterface method of the newly created instance of CMyDriver.
HRESULT
// // Do driver object initialization ..........Rest of the code intentionally omitted..... Figure 4 - CMyDriver::CreateInstance |
Why? Well, since a UDMF driver has to support an IWDFDriver-based class supporting the IDriverEntry interface, the call to QueryInterface is used to see if the newly created object supports that interface. Since our object does support that interface, we return a pointer to the objects IDriverEntry interface. Remember that by supporting an interface, we mean we support the callbacks for that interface. In the case of IDriverEntry, this means we support the OnInitialize, OnDeviceAdd, and OnDeinitialize callbacks.
The QueryInterface routine in Figure 5 shows how this routine is implemented. (The QueryIDriverEntry routine is in Figure 3.)
HRESULT Figure 5 - CMyDriver::QueryInterface |
Whew! We have gone through a lot just to get to the point where our IWDFDriver-based object is created and ready to be used by UMDF. These steps really aren't as complicated as they sound since most of the work is done for you.
Creating Instance of a IWDFDevice Derived Class
Now that our IWDFDriver object instance is created, we're at the point where the driver can handle the addition of devices. In a KMDF driver this occurs when the EvtDriverDeviceAdd routine is called. In the case of UMDF, however, this occurs when the CMyDriver::OnDeviceAdd callback, shown in Figure 6, is called. As with a KMDF driver, the goal of OnDeviceAdd is to create an instance of an IWDFDevice-based class and perform any initialization that might be necessary for that class.
HHRESULT CMyDriver::OnDeviceAdd(
HRESULT hr;
//
// .................Code intentionally omitted. Figure 6 - CMyDriver::OnDeviceAdd |
The creation of an IWDFDevice instance is similar to what was done in the CMyDriver::CreateInstance, so we don't need to show that implementation. However, it is important to show what you might do for device initialization. As in KMDF, you have the option of setting attributes for the device. These attributes could be:
Figure 7 shows the CMyDevice class definition that will be discussed shortly. Figure 8 shows the initialization of the CMyDevice class by the CMyDevice::Initialize routine. The routine begins by setting the locking constraints used by this device to WdfDeviceLevel via the call to SetLockingConstraint. WdfDeviceLevel indicates all callbacks into the driver are associated with this device are serialized. This serialization includes all callbacks associated with I/O queues. Finally, we create a new UMDF IWDFDevice object and assign the new callback object to handle any device level events that occur.
class CMyDevice : public CUnknown, public IDevicePnpHardware Figure 7 - CMyDevice Class Definition |
HRESULT
// If that succeeded, then set the FxDevice member variable. Figure 8 - CMyDevice::Initialize |
With the IWDFDevice-based object constructed, it's time to create any queues or other structures the driver needs. In this sample driver this work is done by the CMyDevice::Configure routine that creates an IWDFQueue object in much the same way the IWDFDevice object was created. In addition, a device interface for this device will be created by calling IWDFDevice::SetDeviceInterface, which makes the device's services visible to user mode.
But we're getting ahead of ourselves. Having queues are nice, but how do you talk to the hardware? That's next.
Connecting to Hardware via IDevicePnHardware::OnPrepareHardware
The CMyDevice class derives interfaces from both CUnknown and IDevicePnPHardware. Since the USB OSR FX2 device is a PnP device and we're creating a driver for that piece of hardware, the driver needs to be notified of the resources that will be used to communicate with the USB device. As mentioned in the UMDF Architecture article, communication with the device is done via the WinUSB driver. Figure 9 shows how this is accomplished. Note that we have intentionally shortened the routine in Figure 9 for clarity. OnPrepareHardware gets the device name for the device by calling IWDFDevice::GetDeviceName, which retrieves the name of the underlying kernel mode device. We do a Win32 CreateFile to open it up, and then call WinUSB_Initialize to make a connection to WinUSB. Once that is done successfully, we can call all the other WinUSB APIs in order to interact with the device. It's pretty slick, if I do say so.
HRESULT CMyDevice::OnPrepareHardware(__in IWDFDevice * /* FxDevice */) Figure 9 - CMyDevice::OnPrepareHardware |
Handling I/O
As in KMDF, our driver will have one or more queues from which it will receive I/O requests from the user. The IWDFQueue derived class for this driver will need to implement callbacks for interfaces such as IQueueCallbackRead and IQueueCallbackWrite. These routines will process the received IWDFIoRequest and will perform the corresponding I/O to the USB device via the WinUSB API.
There You Have It