Основные сведения о структуре кода драйвера USB-клиента (UMDF)
В этом разделе вы узнаете об исходном коде драйвера USB-клиента на основе UMDF. Примеры кода создаются с помощью шаблона драйвера пользовательского режима USB , который входит в состав Microsoft Visual Studio. Код шаблона использует библиотеку активных шаблонов (ATL) для создания инфраструктуры COM. AtL и сведения о реализации COM в драйвере клиента здесь не рассматриваются.
Инструкции по созданию кода шаблона UMDF см. в статье Создание первого драйвера USB-клиента (UMDF). Код шаблона рассматривается в следующих разделах:
- Исходный код обратного вызова драйвера
- Исходный код обратного вызова устройства
- Исходный код очереди
- Исходный код записи драйвера
Прежде чем обсуждать сведения о коде шаблона, давайте рассмотрим некоторые объявления в файле заголовка (Internal.h), которые относятся к разработке драйверов UMDF.
Internal.h содержит следующие файлы, включенные в комплект драйверов Windows (WDK):
#include "atlbase.h"
#include "atlcom.h"
#include "wudfddi.h"
#include "wudfusb.h"
Atlbase.h и atlcom.h включают объявления для поддержки ATL. Каждый класс, реализованный драйвером клиента, реализует открытый класс ATL CComObjectRootEx.
Wudfddi.h всегда включается для разработки драйверов UMDF. Файл заголовка содержит различные объявления и определения методов и структур, необходимых для компиляции драйвера UMDF.
Wudfusb.h включает объявления и определения структур и методов UMDF, необходимых для взаимодействия с целевыми объектами USB-ввода-вывода, предоставляемыми платформой.
Следующий блок в Internal.h объявляет константу GUID для интерфейса устройства. Приложения могут использовать этот GUID для открытия дескриптора для устройства с помощью API SetupDiXxx . GUID регистрируется после создания платформой объекта устройства.
// Device Interface GUID
// f74570e5-ed0c-4230-a7a5-a56264465548
DEFINE_GUID(GUID_DEVINTERFACE_MyUSBDriver_UMDF_,
0xf74570e5,0xed0c,0x4230,0xa7,0xa5,0xa5,0x62,0x64,0x46,0x55,0x48);
В следующей части объявляется макрос трассировки и GUID трассировки. Обратите внимание на GUID трассировки; Он понадобится для включения трассировки.
#define WPP_CONTROL_GUIDS \
WPP_DEFINE_CONTROL_GUID( \
MyDriver1TraceGuid, (f0261b19,c295,4a92,aa8e,c6316c82cdf0), \
\
WPP_DEFINE_BIT(MYDRIVER_ALL_INFO) \
WPP_DEFINE_BIT(TRACE_DRIVER) \
WPP_DEFINE_BIT(TRACE_DEVICE) \
WPP_DEFINE_BIT(TRACE_QUEUE) \
)
#define WPP_FLAG_LEVEL_LOGGER(flag, level) \
WPP_LEVEL_LOGGER(flag)
#define WPP_FLAG_LEVEL_ENABLED(flag, level) \
(WPP_LEVEL_ENABLED(flag) && \
WPP_CONTROL(WPP_BIT_ ## flag).Level >= level)
#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) \
WPP_LEVEL_LOGGER(flags)
#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \
(WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl)
В следующей строке internal.h forward объявляется класс, реализованный драйвером клиента для объекта обратного вызова очереди. Он также включает другие файлы проекта, созданные шаблоном. Файлы реализации и заголовка проекта рассматриваются далее в этом разделе.
// Forward definition of queue.
typedef class CMyIoQueue *PCMyIoQueue;
// Include the type specific headers.
#include "Driver.h"
#include "Device.h"
#include "IoQueue.h"
После установки драйвера клиента Windows загружает драйвер клиента и платформу в экземпляре хост-процесса. Отсюда платформа загружает и инициализирует драйвер клиента. Платформа выполняет следующие задачи:
- Создает объект драйвера на платформе, который представляет драйвер клиента.
- Запрашивает указатель интерфейса IDriverEntry из фабрики классов.
- Создает объект устройства на платформе.
- Инициализирует объект устройства после запуска устройства диспетчером PnP.
Во время загрузки и инициализации драйвера происходит несколько событий, и платформа позволяет клиентскому драйверу участвовать в их обработке. На стороне клиентского драйвера драйвер выполняет следующие задачи:
- Реализует и экспортирует функцию DllGetClassObject из клиентского модуля драйвера, чтобы платформа получите ссылку на драйвер.
- Предоставляет класс обратного вызова, реализующий интерфейс IDriverEntry .
- Предоставляет класс обратного вызова, реализующий интерфейсы IPnpCallbackXxx .
- Получает ссылку на объект устройства и настраивает его в соответствии с требованиями драйвера клиента.
Исходный код обратного вызова драйвера
Платформа создает объект драйвера, который представляет экземпляр клиентского драйвера, загруженного Windows. Драйвер клиента предоставляет по крайней мере один обратный вызов драйвера, который регистрирует драйвер в платформе.
Полный исходный код для обратного вызова драйвера находится в файлах Driver.h и Driver.c.
Драйвер клиента должен определить класс обратного вызова драйвера, который реализует интерфейсы IUnknown и IDriverEntry . Файл заголовка Driver.h объявляет класс CMyDriver, который определяет обратный вызов драйвера.
EXTERN_C const CLSID CLSID_Driver;
class CMyDriver :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CMyDriver, &CLSID_Driver>,
public IDriverEntry
{
public:
CMyDriver()
{
}
DECLARE_NO_REGISTRY()
DECLARE_NOT_AGGREGATABLE(CMyDriver)
BEGIN_COM_MAP(CMyDriver)
COM_INTERFACE_ENTRY(IDriverEntry)
END_COM_MAP()
public:
// IDriverEntry methods
virtual
HRESULT
STDMETHODCALLTYPE
OnInitialize(
__in IWDFDriver *FxWdfDriver
)
{
UNREFERENCED_PARAMETER(FxWdfDriver);
return S_OK;
}
virtual
HRESULT
STDMETHODCALLTYPE
OnDeviceAdd(
__in IWDFDriver *FxWdfDriver,
__in IWDFDeviceInitialize *FxDeviceInit
);
virtual
VOID
STDMETHODCALLTYPE
OnDeinitialize(
__in IWDFDriver *FxWdfDriver
)
{
UNREFERENCED_PARAMETER(FxWdfDriver);
return;
}
};
OBJECT_ENTRY_AUTO(CLSID_Driver, CMyDriver)
Обратный вызов драйвера должен быть COM-классом, то есть он должен реализовывать IUnknown и связанные методы. В коде шаблона классы ATL CComObjectRootEx и CComCoClass содержат методы IUnknown .
После того как Windows создаст экземпляр хост-процесса, платформа создает объект драйвера. Для этого платформа создает экземпляр класса обратного вызова драйвера и вызывает реализацию драйверов DllGetClassObject (рассматривается в разделе Исходный код драйвера) и для получения указателя интерфейса IDriverEntry клиентского драйвера. Этот вызов регистрирует объект обратного вызова драйвера с объектом драйвера платформы. После успешной регистрации платформа вызывает реализацию клиентского драйвера при возникновении определенных событий, относящихся к драйверу. Первый метод, вызываемый платформой, — это метод IDriverEntry::OnInitialize . В реализации IDriverEntry::OnInitialize драйвер клиента может выделять глобальные ресурсы драйверов. Эти ресурсы должны быть освобождены в IDriverEntry::OnDeinitialize , который вызывается платформой непосредственно перед подготовкой к выгрузке драйвера клиента. Код шаблона обеспечивает минимальную реализацию методов OnInitialize и OnDeinitialize .
Наиболее важным методом IDriverEntry является IDriverEntry::OnDeviceAdd. Прежде чем платформа создаст объект устройства платформы (рассматривается в следующем разделе), она вызывает реализацию драйвера IDriverEntry::OnDeviceAdd . При вызове метода платформа передает указатель IWDFDriver на объект драйвера и указатель IWDFDeviceInitialize . Драйвер клиента может вызывать методы IWDFDeviceInitialize , чтобы указать определенные параметры конфигурации.
Как правило, драйвер клиента выполняет следующие задачи в реализации IDriverEntry::OnDeviceAdd :
- Указывает сведения о конфигурации создаваемого объекта устройства.
- Создает экземпляр класса обратного вызова устройства драйвера.
- Создает объект устройства платформы и регистрирует его объект обратного вызова устройства в платформе.
- Инициализирует объект устройства платформы.
- Регистрирует GUID интерфейса устройства драйвера клиента.
В коде шаблона IDriverEntry::OnDeviceAdd вызывает статический метод CMyDevice::CreateInstanceAndInitialize, определенный в классе обратного вызова устройства. Статический метод сначала создает экземпляр класса обратного вызова устройства драйвера клиента, а затем создает объект устройства платформы. Класс обратного вызова устройства также определяет открытый метод с именем Configure, который выполняет оставшиеся задачи, упомянутые в предыдущем списке. Реализация класса обратного вызова устройства рассматривается в следующем разделе. В следующем примере кода показана реализация IDriverEntry::OnDeviceAdd в коде шаблона.
HRESULT
CMyDriver::OnDeviceAdd(
__in IWDFDriver *FxWdfDriver,
__in IWDFDeviceInitialize *FxDeviceInit
)
{
HRESULT hr = S_OK;
CMyDevice *device = NULL;
hr = CMyDevice::CreateInstanceAndInitialize(FxWdfDriver,
FxDeviceInit,
&device);
if (SUCCEEDED(hr))
{
hr = device->Configure();
}
return hr;
}
В следующем примере кода показано объявление класса устройства в Device.h.
class CMyDevice :
public CComObjectRootEx<CComMultiThreadModel>,
public IPnpCallbackHardware
{
public:
DECLARE_NOT_AGGREGATABLE(CMyDevice)
BEGIN_COM_MAP(CMyDevice)
COM_INTERFACE_ENTRY(IPnpCallbackHardware)
END_COM_MAP()
CMyDevice() :
m_FxDevice(NULL),
m_IoQueue(NULL),
m_FxUsbDevice(NULL)
{
}
~CMyDevice()
{
}
private:
IWDFDevice * m_FxDevice;
CMyIoQueue * m_IoQueue;
IWDFUsbTargetDevice * m_FxUsbDevice;
private:
HRESULT
Initialize(
__in IWDFDriver *FxDriver,
__in IWDFDeviceInitialize *FxDeviceInit
);
public:
static
HRESULT
CreateInstanceAndInitialize(
__in IWDFDriver *FxDriver,
__in IWDFDeviceInitialize *FxDeviceInit,
__out CMyDevice **Device
);
HRESULT
Configure(
VOID
);
public:
// IPnpCallbackHardware methods
virtual
HRESULT
STDMETHODCALLTYPE
OnPrepareHardware(
__in IWDFDevice *FxDevice
);
virtual
HRESULT
STDMETHODCALLTYPE
OnReleaseHardware(
__in IWDFDevice *FxDevice
);
};
Исходный код обратного вызова устройства
Объект устройства платформы — это экземпляр класса платформы, представляющий объект устройства, загруженный в стек устройств клиентского драйвера. Сведения о функциональных возможностях объекта устройства см. в разделе Узлы устройств и стеки устройств.
Полный исходный код объекта устройства находится в device.h и Device.c.
Класс устройств платформы реализует интерфейс IWDFDevice . Драйвер клиента отвечает за создание экземпляра этого класса в реализации драйвера IDriverEntry::OnDeviceAdd. После создания объекта драйвер клиента получает указатель IWDFDevice на новый объект и вызывает методы в этом интерфейсе для управления операциями объекта устройства.
Реализация IDriverEntry::OnDeviceAdd
В предыдущем разделе вы кратко узнали о задачах, выполняемых драйвером клиента в IDriverEntry::OnDeviceAdd. Ниже приведены дополнительные сведения об этих задачах. Драйвер клиента:
Указывает сведения о конфигурации создаваемого объекта устройства.
В вызове платформы для реализации драйвера клиента метода IDriverEntry::OnDeviceAdd платформа передает указатель IWDFDeviceInitialize . Драйвер клиента использует этот указатель для указания сведений о конфигурации создаваемого объекта устройства. Например, драйвер клиента указывает, является ли драйвер клиента фильтром или драйвером функции. Чтобы определить драйвер клиента в качестве драйвера фильтра, он вызывает IWDFDeviceInitialize::SetFilter. В этом случае платформа создает объект устройства фильтра (FiDO); в противном случае создается объект устройства-функции (FDO). Другой вариант, который можно задать, — режим синхронизации, вызвав метод IWDFDeviceInitialize::SetLockingConstraint.
Вызывает метод IWDFDriver::CreateDevice путем передачи указателя интерфейса IWDFDeviceInitialize , ссылки IUnknown на объект обратного вызова устройства и переменной IWDFDevice указателя на указатель.
Если вызов IWDFDriver::CreateDevice выполнен успешно:
Платформа создает объект устройства.
Платформа регистрирует обратный вызов устройства в платформе .
После связывания обратного вызова устройства с объектом устройства платформы платформа и драйвер клиента обрабатывают определенные события, такие как изменение состояния PnP и состояния питания. Например, когда диспетчер PnP запускает устройство, платформа получает уведомление. Затем платформа вызывает реализацию обратного вызова устройства IPnpCallbackHardware::OnPrepareHardware . Каждый драйвер клиента должен зарегистрировать по крайней мере один объект обратного вызова устройства.
Драйвер клиента получает адрес нового объекта устройства в переменной IWDFDevice . Получив указатель на объект устройства платформы, драйвер клиента может приступить к выполнению задач инициализации, таких как настройка очередей для потока ввода-вывода и регистрация GUID интерфейса устройства.
Вызывает IWDFDevice::CreateDeviceInterface для регистрации GUID интерфейса устройства драйвера клиента. Приложения могут использовать GUID для отправки запросов драйверу клиента. Константа GUID объявлена в Internal.h.
Инициализирует очереди для передачи операций ввода-вывода на устройство и с устройства.
Код шаблона определяет вспомогательный метод Initialize, который задает сведения о конфигурации и создает объект устройства.
В следующем примере кода показаны реализации для Initialize.
HRESULT
CMyDevice::Initialize(
__in IWDFDriver * FxDriver,
__in IWDFDeviceInitialize * FxDeviceInit
)
{
IWDFDevice *fxDevice = NULL;
HRESULT hr = S_OK;
IUnknown *unknown = NULL;
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
FxDeviceInit->SetLockingConstraint(None);
FxDeviceInit->SetPowerPolicyOwnership(TRUE);
hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_DEVICE,
"%!FUNC! Failed to get IUnknown %!hresult!",
hr);
goto Exit;
}
hr = FxDriver->CreateDevice(FxDeviceInit, unknown, &fxDevice);
DriverSafeRelease(unknown);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_DEVICE,
"%!FUNC! Failed to create a framework device %!hresult!",
hr);
goto Exit;
}
m_FxDevice = fxDevice;
DriverSafeRelease(fxDevice);
Exit:
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");
return hr;
}
В предыдущем примере кода драйвер клиента создает объект устройства и регистрирует его обратный вызов устройства. Перед созданием объекта устройства драйвер задает параметры конфигурации, вызывая методы в указателе интерфейса IWDFDeviceInitialize . Это тот же указатель, переданный платформой в предыдущем вызове метода IDriverEntry::OnDeviceAdd клиентского драйвера.
Драйвер клиента указывает, что он будет владельцем политики управления питанием для объекта устройства. Как владелец политики управления питанием, драйвер клиента определяет соответствующее состояние питания, которое устройство должно входить при изменении состояния питания системы. Драйвер также отвечает за отправку соответствующих запросов на устройство для перехода состояния питания. По умолчанию драйвер клиента на основе UMDF не является владельцем политики управления питанием; платформа обрабатывает все переходы состояния питания. Платформа автоматически отправляет устройство в D3 , когда система переходит в спящий режим, и, наоборот, возвращает устройство в D0 , когда система переходит в рабочее состояние S0. Дополнительные сведения см. в разделе Power Policy Ownership in UMDF.
Другой вариант конфигурации — указать, является ли драйвер клиента драйвером фильтра или драйвером функции для устройства. Обратите внимание, что в примере кода драйвер клиента явно не указывает свои предпочтения. Это означает, что драйвер клиента является драйвером функции, и платформа должна создать FDO в стеке устройств. Если драйвер клиента хочет быть драйвером фильтра, он должен вызвать метод IWDFDeviceInitialize::SetFilter . В этом случае платформа создает FiDO в стеке устройств.
Драйвер клиента также указывает, что ни один из вызовов платформы для обратных вызовов драйвера клиента не синхронизируется. Драйвер клиента обрабатывает все задачи синхронизации. Чтобы указать этот параметр, драйвер клиента вызывает метод IWDFDeviceInitialize::SetLockingConstraint .
Затем драйвер клиента получает указатель IUnknown на свой класс обратного вызова устройства путем вызова IUnknown::QueryInterface. Затем драйвер клиента вызывает IWDFDriver::CreateDevice, который создает объект устройства платформы и регистрирует обратный вызов устройства клиентского драйвера с помощью указателя IUnknown .
Обратите внимание, что драйвер клиента хранит адрес объекта устройства (полученный через вызов IWDFDriver::CreateDevice ) в члене частных данных класса обратного вызова устройства, а затем освобождает ссылку, вызвав DriverSafeRelease (встроенная функция, определенная в Internal.h). Это связано с тем, что время существования объекта устройства отслеживается платформой. Поэтому драйвер клиента не требуется для хранения дополнительного количества ссылок объекта устройства.
Код шаблона определяет открытый метод Configure, который регистрирует GUID интерфейса устройства и настраивает очереди. В следующем примере кода показано определение метода Configure в классе обратного вызова устройства CMyDevice. Настройка вызывается методом IDriverEntry::OnDeviceAdd после создания объекта устройства платформы.
CMyDevice::Configure(
VOID
)
{
HRESULT hr = S_OK;
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
hr = CMyIoQueue::CreateInstanceAndInitialize(m_FxDevice, this, &m_IoQueue);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_DEVICE,
"%!FUNC! Failed to create and initialize queue %!hresult!",
hr);
goto Exit;
}
hr = m_IoQueue->Configure();
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_DEVICE,
"%!FUNC! Failed to configure queue %!hresult!",
hr);
goto Exit;
}
hr = m_FxDevice->CreateDeviceInterface(&GUID_DEVINTERFACE_MyUSBDriver_UMDF_,NULL);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_DEVICE,
"%!FUNC! Failed to create device interface %!hresult!",
hr);
goto Exit;
}
Exit:
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");
return hr;
}
В предыдущем примере кода драйвер клиента выполняет две задачи main: инициализацию очередей для потока ввода-вывода и регистрацию GUID интерфейса устройства.
Очереди создаются и настраиваются в классе CMyIoQueue. Первая задача — создать экземпляр этого класса, вызвав статический метод с именем CreateInstanceAndInitialize. Драйвер клиента вызывает Configure для инициализации очередей. CreateInstanceAndInitialize и Configure объявляются в CMyIoQueue, который рассматривается далее в этом разделе.
Драйвер клиента также вызывает IWDFDevice::CreateDeviceInterface для регистрации GUID интерфейса устройства драйвера клиента. Приложения могут использовать GUID для отправки запросов драйверу клиента. Константа GUID объявлена в Internal.h.
Реализация IPnpCallbackHardware и задачи, связанные с USB
Теперь рассмотрим реализацию интерфейса IPnpCallbackHardware в Device.cpp.
Каждый класс обратного вызова устройства должен реализовывать интерфейс IPnpCallbackHardware . Этот интерфейс имеет два метода: IPnpCallbackHardware::OnPrepareHardware и IPnpCallbackHardware::OnReleaseHardware. Платформа вызывает эти методы в ответ на два события: когда диспетчер PnP запускает устройство и удаляет устройство. При запуске устройства устанавливается связь с оборудованием, но устройство не перешло в рабочее состояние (D0). Таким образом, в IPnpCallbackHardware::OnPrepareHardware драйвер клиента может получать сведения об устройстве из оборудования, выделять ресурсы и инициализировать объекты платформы, необходимые в течение времени существования драйвера. Когда диспетчер PnP удаляет устройство, драйвер выгружается из системы. Платформа вызывает реализацию IPnpCallbackHardware::OnReleaseHardware клиентского драйвера, в которой драйвер может освободить эти ресурсы и объекты платформы.
Диспетчер PnP может создавать события других типов, которые возникают в результате изменений состояния PnP. Платформа обеспечивает обработку этих событий по умолчанию. Драйвер клиента может принять участие в обработке этих событий. Рассмотрим сценарий, в котором USB-устройство отсоединяется от узла. Диспетчер PnP распознает это событие и уведомляет платформу. Если драйвер клиента хочет выполнить дополнительные задачи в ответ на событие, драйвер должен реализовать интерфейс IPnpCallback и связанный метод IPnpCallback::OnSurpriseRemoval в классе обратного вызова устройства. В противном случае платформа продолжает обработку события по умолчанию.
Драйвер USB-клиента должен получить сведения о поддерживаемых интерфейсах, альтернативных параметрах и конечных точках и настроить их перед отправкой любых запросов ввода-вывода для передачи данных. UMDF предоставляет специализированные целевые объекты ввода-вывода, которые упрощают многие задачи настройки для клиентского драйвера. Чтобы настроить USB-устройство, драйверу клиента требуются сведения об устройстве, доступные только после запуска устройства диспетчером PnP.
Этот код шаблона создает эти объекты в методе IPnpCallbackHardware::OnPrepareHardware .
Как правило, драйвер клиента выполняет одну или несколько из следующих задач конфигурации (в зависимости от структуры устройства):
- Извлекает сведения о текущей конфигурации, например количество интерфейсов. Платформа выбирает первую конфигурацию на USB-устройстве. Драйвер клиента не может выбрать другую конфигурацию в случае устройств с несколькими конфигурациями.
- Извлекает сведения об интерфейсах, например о количестве конечных точек.
- Изменяет альтернативный параметр в каждом интерфейсе, если интерфейс поддерживает несколько параметров. По умолчанию платформа выбирает первый альтернативный параметр каждого интерфейса в первой конфигурации на USB-устройстве. Драйвер клиента может выбрать альтернативный параметр.
- Извлекает сведения о конечных точках в каждом интерфейсе.
Для выполнения этих задач драйвер клиента может использовать эти типы специализированных целевых объектов USB-ввода-вывода, предоставляемых WDF.
Целевой объект USB-ввода-вывода | Описание | Интерфейс UMDF |
---|---|---|
Объект целевого устройства | Представляет USB-устройство и предоставляет методы для получения дескриптора устройства и отправки управляющих запросов на устройство. | IWDFUsbTargetDevice |
Объект целевого интерфейса | Представляет отдельный интерфейс и предоставляет методы, которые драйвер клиента может вызывать для выбора альтернативного параметра и получения сведений о параметре. | IWDFUsbInterface |
Целевой объект канала | Представляет отдельный канал для конечной точки, настроенной в текущем альтернативном параметре для интерфейса. Драйвер USB-шины выбирает каждый интерфейс в выбранной конфигурации и настраивает канал связи для каждой конечной точки в интерфейсе. В терминологии USB этот коммуникационный канал называется каналом. | IWDFUsbTargetPipe |
В следующем примере кода показана реализация IPnpCallbackHardware::OnPrepareHardware.
HRESULT
CMyDevice::OnPrepareHardware(
__in IWDFDevice * /* FxDevice */
)
{
HRESULT hr;
IWDFUsbTargetFactory *usbFactory = NULL;
IWDFUsbTargetDevice *usbDevice = NULL;
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
hr = m_FxDevice->QueryInterface(IID_PPV_ARGS(&usbFactory));
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_DEVICE,
"%!FUNC! Failed to get USB target factory %!hresult!",
hr);
goto Exit;
}
hr = usbFactory->CreateUsbTargetDevice(&usbDevice);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_DEVICE,
"%!FUNC! Failed to create USB target device %!hresult!",
hr);
goto Exit;
}
m_FxUsbDevice = usbDevice;
Exit:
DriverSafeRelease(usbDevice);
DriverSafeRelease(usbFactory);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");
return hr;
}
Чтобы использовать целевые объекты USB-ввода-вывода платформы, драйвер клиента должен сначала создать объект целевого устройства USB. В объектной модели платформы целевой объект USB-устройства является дочерним элементом объекта устройства, представляющего USB-устройство. Объект целевого устройства USB реализуется платформой и выполняет все задачи usb-устройства на уровне устройства, например выбор конфигурации.
В предыдущем примере кода драйвер клиента запрашивает объект устройства платформы и получает указатель IWDFUsbTargetFactory на фабрику классов, создающую объект целевого устройства USB. С помощью этого указателя драйвер клиента вызывает метод IWDFUsbTargetDevice::CreateUsbTargetDevice . Метод создает объект целевого устройства USB и возвращает указатель на интерфейс IWDFUsbTargetDevice . Метод также выбирает конфигурацию по умолчанию (первую) и альтернативный параметр 0 для каждого интерфейса в этой конфигурации.
Код шаблона сохраняет адрес объекта целевого устройства USB (полученного через вызов IWDFDriver::CreateDevice ) в члене частных данных класса обратного вызова устройства, а затем освобождает ссылку, вызвав DriverSafeRelease. Число ссылок объекта целевого устройства USB поддерживается платформой. Объект активен, пока объект устройства находится в активном виде. Драйвер клиента должен освободить ссылку в IPnpCallbackHardware::OnReleaseHardware.
После создания клиентского драйвера целевого устройства USB драйвер вызывает методы IWDFUsbTargetDevice для выполнения следующих задач:
- Получение устройства, конфигурации, дескрипторов интерфейса и других сведений, таких как скорость устройства.
- Форматирование и отправка запросов элементов управления вводом-выводом в конечную точку по умолчанию.
- Задайте политику питания для всего USB-устройства.
Дополнительные сведения см. в статье Работа с USB-устройствами в UMDF. В следующем примере кода показана реализация IPnpCallbackHardware::OnReleaseHardware.
HRESULT
CMyDevice::OnReleaseHardware(
__in IWDFDevice * /* FxDevice */
)
{
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
if (m_FxUsbDevice != NULL) {
m_FxUsbDevice->DeleteWdfObject();
m_FxUsbDevice = NULL;
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");
return S_OK;
}
Исходный код очереди
Объект очереди платформы представляет очередь ввода-вывода для определенного объекта устройства платформы. Полный исходный код объекта очереди находится в IoQueue.h и IoQueue.c.
IoQueue.h
Файл заголовка IoQueue.h объявляет класс обратного вызова очереди.
class CMyIoQueue :
public CComObjectRootEx<CComMultiThreadModel>,
public IQueueCallbackDeviceIoControl
{
public:
DECLARE_NOT_AGGREGATABLE(CMyIoQueue)
BEGIN_COM_MAP(CMyIoQueue)
COM_INTERFACE_ENTRY(IQueueCallbackDeviceIoControl)
END_COM_MAP()
CMyIoQueue() :
m_FxQueue(NULL),
m_Device(NULL)
{
}
~CMyIoQueue()
{
// empty
}
HRESULT
Initialize(
__in IWDFDevice *FxDevice,
__in CMyDevice *MyDevice
);
static
HRESULT
CreateInstanceAndInitialize(
__in IWDFDevice *FxDevice,
__in CMyDevice *MyDevice,
__out CMyIoQueue** Queue
);
HRESULT
Configure(
VOID
)
{
return S_OK;
}
// IQueueCallbackDeviceIoControl
virtual
VOID
STDMETHODCALLTYPE
OnDeviceIoControl(
__in IWDFIoQueue *pWdfQueue,
__in IWDFIoRequest *pWdfRequest,
__in ULONG ControlCode,
__in SIZE_T InputBufferSizeInBytes,
__in SIZE_T OutputBufferSizeInBytes
);
private:
IWDFIoQueue * m_FxQueue;
CMyDevice * m_Device;
};
В предыдущем примере кода драйвер клиента объявляет класс обратного вызова очереди. При создании экземпляра объект сотрудничает с объектом очереди платформы, который обрабатывает способ отправки запросов в драйвер клиента. Класс определяет два метода, которые создают и инициализируют объект очереди платформы. Статический метод CreateInstanceAndInitialize создает экземпляр класса обратного вызова очереди, а затем вызывает метод Initialize, который создает и инициализирует объект очереди платформы. Он также задает параметры диспетчеризации для объекта очереди.
HRESULT
CMyIoQueue::CreateInstanceAndInitialize(
__in IWDFDevice *FxDevice,
__in CMyDevice *MyDevice,
__out CMyIoQueue** Queue
)
{
CComObject<CMyIoQueue> *pMyQueue = NULL;
HRESULT hr = S_OK;
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");
hr = CComObject<CMyIoQueue>::CreateInstance( &pMyQueue );
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_QUEUE,
"%!FUNC! Failed to create instance %!hresult!",
hr);
goto Exit;
}
hr = pMyQueue->Initialize(FxDevice, MyDevice);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_QUEUE,
"%!FUNC! Failed to initialize %!hresult!",
hr);
goto Exit;
}
*Queue = pMyQueue;
Exit:
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");
return hr;
}
В следующем примере кода показана реализация метода Initialize.
HRESULT
CMyIoQueue::Initialize(
__in IWDFDevice *FxDevice,
__in CMyDevice *MyDevice
)
{
IWDFIoQueue *fxQueue = NULL;
HRESULT hr = S_OK;
IUnknown *unknown = NULL;
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");
assert(FxDevice != NULL);
assert(MyDevice != NULL);
hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_QUEUE,
"%!FUNC! Failed to query IUnknown interface %!hresult!",
hr);
goto Exit;
}
hr = FxDevice->CreateIoQueue(unknown,
FALSE, // Default Queue?
WdfIoQueueDispatchParallel, // Dispatch type
TRUE, // Power managed?
FALSE, // Allow zero-length requests?
&fxQueue); // I/O queue
DriverSafeRelease(unknown);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_QUEUE,
"%!FUNC! Failed to create framework queue.");
goto Exit;
}
hr = FxDevice->ConfigureRequestDispatching(fxQueue,
WdfRequestDeviceIoControl,
TRUE);
if (FAILED(hr))
{
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_QUEUE,
"%!FUNC! Failed to configure request dispatching %!hresult!.",
hr);
goto Exit;
}
m_FxQueue = fxQueue;
m_Device= MyDevice;
Exit:
DriverSafeRelease(fxQueue);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");
return hr;
}
В предыдущем примере кода драйвер клиента создает объект очереди платформы. Платформа предоставляет объект очереди для обработки потока запроса к драйверу клиента.
Чтобы создать объект, драйвер клиента вызывает IWDFDevice::CreateIoQueue по ссылке IWDFDevice , полученной при предыдущем вызове IWDFDriver::CreateDevice.
В вызове IWDFDevice::CreateIoQueue драйвер клиента указывает определенные параметры конфигурации перед созданием очередей платформой. Эти параметры определяют, является ли очередь управляемой питанием, разрешает ли запросы нулевой длины и выступает в качестве очереди по умолчанию для драйвера. Драйвер клиента предоставляет следующий набор сведений:
Ссылка на класс обратного вызова очереди
Указывает указатель IUnknown на класс обратного вызова очереди. Это создает связь между объектом очереди платформы и объектом обратного вызова очереди драйвера клиента. Когда диспетчер ввода-вывода получает новый запрос от приложения, он уведомляет платформу. Затем платформа использует указатель IUnknown для вызова открытых методов, предоставляемых объектом обратного вызова очереди.
Очередь по умолчанию или вторичная очередь
Очередь должна быть либо очередью по умолчанию, либо вторичной очередью. Если объект очереди платформы выступает в качестве очереди по умолчанию, все запросы добавляются в очередь. Вторичная очередь выделяется для определенного типа запроса. Если драйвер клиента запрашивает вторичную очередь, драйвер также должен вызвать метод IWDFDevice::ConfigureRequestDispatching , чтобы указать тип запроса, который платформа должна поместить в указанную очередь. В коде шаблона драйвер клиента передает значение FALSE в параметре bDefaultQueue . Это указывает методу создать вторичную очередь, а не очередь по умолчанию. Позже он вызывает IWDFDevice::ConfigureRequestDispatching , чтобы указать, что в очереди должны быть только запросы управления вводом-выводом устройства (см. пример кода в этом разделе).
Тип диспетчеризации
Тип диспетчеризации объекта очереди определяет, как платформа доставляет запросы драйверу клиента. Механизм доставки может быть последовательным, параллельным или настраиваемым механизмом, определенным драйвером клиента. Для последовательной очереди запрос не доставляется, пока драйвер клиента не завершит предыдущий запрос. В режиме параллельной диспетчеризации платформа пересылает запросы сразу же, как только они поступают из диспетчера ввода-вывода. Это означает, что драйвер клиента может получить запрос при обработке другого. В пользовательском механизме клиент вручную извлекает следующий запрос из объекта очереди платформы, когда драйвер будет готов к его обработке. В коде шаблона драйвер клиента запрашивает режим параллельной диспетчеризации.
Очередь, управляемая питанием
Объект очереди платформы должен быть синхронизирован с PnP и состоянием питания устройства. Если устройство не находится в рабочем состоянии, объект очереди платформы перестает отправлять все запросы. Когда устройство находится в рабочем состоянии, объект очереди возобновляет отправку. В очереди, управляемой питанием, синхронизация выполняется платформой; В противном случае диск клиента должен обрабатывать такую задачу. В коде шаблона клиент запрашивает управляемую питанием очередь.
Разрешены запросы нулевой длины
Драйвер клиента может указать платформе выполнять запросы ввода-вывода с буферами нулевой длины, а не помещать их в очередь. В коде шаблона клиент запрашивает платформу для выполнения таких запросов.
Один объект очереди платформы может обрабатывать несколько типов запросов, таких как чтение, запись, управление вводом-выводом устройства и т. д. Драйвер клиента, основанный на коде шаблона, может обрабатывать только запросы управления вводом-выводом устройства. Для этого класс обратного вызова очереди драйвера клиента реализует интерфейс IQueueCallbackDeviceIoControl и его метод IQueueCallbackDeviceIoControl::OnDeviceIoControl . Это позволяет платформе вызывать реализацию драйвера клиента IQueueCallbackDeviceIoControl::OnDeviceIoControl , когда платформа обрабатывает запрос управления вводом-выводом устройства.
Для других типов запросов драйвер клиента должен реализовать соответствующий интерфейс IQueueCallbackXxx . Например, если драйвер клиента хочет обрабатывать запросы на чтение, класс обратного вызова очереди должен реализовывать интерфейс IQueueCallbackRead и его метод IQueueCallbackRead::OnRead . Сведения о типах запросов и интерфейсов обратного вызова см. в разделе Функции обратного вызова событий очереди ввода-вывода.
В следующем примере кода показана реализация IQueueCallbackDeviceIoControl::OnDeviceIoControl .
VOID
STDMETHODCALLTYPE
CMyIoQueue::OnDeviceIoControl(
__in IWDFIoQueue *FxQueue,
__in IWDFIoRequest *FxRequest,
__in ULONG ControlCode,
__in SIZE_T InputBufferSizeInBytes,
__in SIZE_T OutputBufferSizeInBytes
)
{
UNREFERENCED_PARAMETER(FxQueue);
UNREFERENCED_PARAMETER(ControlCode);
UNREFERENCED_PARAMETER(InputBufferSizeInBytes);
UNREFERENCED_PARAMETER(OutputBufferSizeInBytes);
HRESULT hr = S_OK;
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");
if (m_Device == NULL) {
// We don't have pointer to device object
TraceEvents(TRACE_LEVEL_ERROR,
TRACE_QUEUE,
"%!FUNC!NULL pointer to device object.");
hr = E_POINTER;
goto Exit;
}
//
// Process the IOCTLs
//
Exit:
FxRequest->Complete(hr);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");
return;
}
Давайте посмотрим, как работает механизм очереди. Для взаимодействия с USB-устройством приложение сначала открывает дескриптор устройства и отправляет запрос на управление вводом-выводом устройства, вызывая функцию DeviceIoControl с определенным кодом элемента управления. В зависимости от типа кода элемента управления приложение может указать входные и выходные буферы в этом вызове. В конечном итоге вызов будет получен диспетчером ввода-вывода, который уведомляет платформу. Платформа создает объект запроса платформы и добавляет его в объект очереди платформы. В коде шаблона, так как объект очереди был создан с флагом WdfIoQueueDispatchParallel, обратный вызов вызывается сразу после добавления запроса в очередь.
Когда платформа вызывает обратный вызов события драйвера клиента, она передает дескриптор объекту запроса платформы, который содержит запрос (и его входные и выходные буферы), отправленные приложением. Кроме того, он отправляет дескриптор объекту очереди платформы, который содержит этот запрос. При обратном вызове события драйвер клиента обрабатывает запрос по мере необходимости. Код шаблона просто завершает запрос. Драйвер клиента может выполнять более задействованные задачи. Например, если приложение запрашивает определенные сведения об устройстве, при обратном вызове события драйвер клиента может создать запрос управления USB и отправить его в стек usb-драйвера для получения запрошенных сведений об устройстве. Запросы на управление USB обсуждаются в разделе Передача элементов управления USB.
Исходный код записи драйвера
В коде шаблона запись драйвера реализована в файле Dllsup.cpp.
Dllsup.cpp
После раздела include объявляется константа GUID для драйвера клиента. Этот GUID должен соответствовать ИДЕНТИФИКАТОРу GUID в файле установки драйвера (INF).
const CLSID CLSID_Driver =
{0x079e211c,0x8a82,0x4c16,{0x96,0xe2,0x2d,0x28,0xcf,0x23,0xb7,0xff}};
В следующем блоке кода объявляется фабрика классов для драйвера клиента.
class CMyDriverModule :
public CAtlDllModuleT< CMyDriverModule >
{
};
CMyDriverModule _AtlModule;
Код шаблона использует поддержку ATL для инкапсуляции сложного COM-кода. Фабрика класса наследует класс шаблона CAtlDllModuleT, содержащий весь необходимый код для создания драйвера клиента.
В следующем фрагменте кода показана реализация DllMain
extern "C"
BOOL
WINAPI
DllMain(
HINSTANCE hInstance,
DWORD dwReason,
LPVOID lpReserved
)
{
if (dwReason == DLL_PROCESS_ATTACH) {
WPP_INIT_TRACING(MYDRIVER_TRACING_ID);
g_hInstance = hInstance;
DisableThreadLibraryCalls(hInstance);
} else if (dwReason == DLL_PROCESS_DETACH) {
WPP_CLEANUP();
}
return _AtlModule.DllMain(dwReason, lpReserved);
}
Если драйвер клиента реализует функцию DllMain , Windows считает DllMain точкой входа для модуля драйвера клиента. Windows вызывает DllMain после загрузки модуля драйвера клиента в WUDFHost.exe. Windows снова вызывает DllMain непосредственно перед тем, как Windows выгрузит драйвер клиента в памяти. DllMain может выделять и освобождать глобальные переменные на уровне драйвера. В коде шаблона драйвер клиента инициализирует и освобождает ресурсы, необходимые для трассировки WPP, и вызывает реализацию DllMain класса ATL.
В следующем фрагменте кода показана реализация DllGetClassObject.
STDAPI
DllGetClassObject(
__in REFCLSID rclsid,
__in REFIID riid,
__deref_out LPVOID FAR* ppv
)
{
return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
}
В коде шаблона фабрика классов и DllGetClassObject реализованы в ATL. Предыдущий фрагмент кода просто вызывает реализацию ATL DllGetClassObject . Как правило, DllGetClassObject должен выполнять следующие задачи:
- Убедитесь, что ИДЕНТИФИКАТОР CLSID, передаваемый платформой, является GUID для драйвера клиента. Платформа извлекает ИДЕНТИФИКАТОР CLSID для драйвера клиента из INF-файла драйвера. При проверке убедитесь, что указанный GUID совпадает с указанным в INF-файле.
- Создайте экземпляр фабрики классов, реализованной драйвером клиента. В коде шаблона это инкапсулируется классом ATL.
- Получите указатель на интерфейс IClassFactory фабрики классов и верните полученный указатель на платформу.
После загрузки модуля драйвера клиента в память платформа вызывает предоставленную драйвером функцию DllGetClassObject . При вызове платформы к DllGetClassObject платформа передает идентификатор CLSID, который идентифицирует драйвер клиента, и запрашивает указатель на интерфейс IClassFactory фабрики классов. Драйвер клиента реализует фабрику классов, которая упрощает создание обратного вызова драйвера. Таким образом, драйвер клиента должен содержать по крайней мере одну фабрику классов. Затем платформа вызывает IClassFactory::CreateInstance и запрашивает указатель IDriverEntry на класс обратного вызова драйвера.
Exports.def
Чтобы платформа вызывала DllGetClassObject, драйвер клиента должен экспортировать функцию из ФАЙЛА DEF. Файл уже включен в проект Visual Studio.
; Exports.def : Declares the module parameters.
LIBRARY "MyUSBDriver_UMDF_.DLL"
EXPORTS
DllGetClassObject PRIVATE
В приведенном выше фрагменте кода из export.def, включенном в проект драйвера, клиент предоставляет имя модуля драйвера как LIBRARY и DllGetClassObject в разделе EXPORTS. Дополнительные сведения см. в статье Экспорт из библиотеки DLL с помощью DEF-файлов.