Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом разделе вы узнаете о исходном коде драйвера 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 на новый объект и вызывает методы в этом интерфейсе для управления операциями объекта устройства.
Реализация метода OnDeviceAdd интерфейса IDriverEntry
В предыдущем разделе вы кратко узнали о задачах, выполняемых драйвером клиента в 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, который задает сведения о конфигурации и создает объект устройства.
В следующем примере кода показаны реализации для инициализации.
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. Дополнительные сведения см. в разделе Владение политикой питания в 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;
}
В приведенном выше примере кода драйвер клиента выполняет две основные задачи: инициализация очередей для потока ввода-вывода и регистрация 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 соответствует указанному идентификатору 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, включенном в проект драйвера, клиент предоставляет имя модуля драйвера в качестве библиотеки и DllGetClassObject в разделе EXPORTS. Дополнительные сведения см. в статье "Экспорт из библиотеки DLL с помощью DEF-файлов".