USB UMDF(클라이언트 드라이버 코드 구조) 이해
이 항목에서는 UMDF 기반 USB 클라이언트 드라이버의 소스 코드에 대해 알아봅니다. 코드 예제는 Microsoft Visual Studio에 포함된 USB 사용자 모드 드라이버 템플릿에 의해 생성됩니다. 템플릿 코드는 ATL(활성 템플릿 라이브러리)을 사용하여 COM 인프라를 생성합니다. 클라이언트 드라이버의 COM 구현에 대한 ATL 및 세부 정보는 여기에 설명되어 있지 않습니다.
UMDF 템플릿 코드를 생성하는 방법에 대한 지침은 첫 번째 UMDF(USB 클라이언트 드라이버)를 작성하는 방법을 참조하세요. 템플릿 코드는 다음 섹션에서 설명합니다.
템플릿 코드의 세부 정보를 논의하기 전에 UMDF 드라이버 개발과 관련된 헤더 파일(Internal.h)의 일부 선언을 살펴보겠습니다.
Internal.h에는 WDK(Windows 드라이버 키트)에 포함된 다음 파일이 포함되어 있습니다.
#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에는 프레임워크에서 제공하는 USB I/O 대상 개체와 통신하는 데 필요한 UMDF 구조 및 메서드의 선언 및 정의가 포함되어 있습니다.
Internal.h의 다음 블록은 디바이스 인터페이스에 대한 GUID 상수를 선언합니다. 애플리케이션은 이 GUID를 사용하여 SetupDiXxx API를 사용하여 디바이스에 대한 핸들을 열 수 있습니다. 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는 호스트 프로세스의 instance 클라이언트 드라이버와 프레임워크를 로드합니다. 여기에서 프레임워크는 클라이언트 드라이버를 로드하고 초기화합니다. 프레임워크는 다음 작업을 수행합니다.
- 프레임워크에서 클라이언트 드라이버를 나타내는 드라이버 개체 를 만듭니다.
- 클래스 팩터리에서 IDriverEntry 인터페이스 포인터를 요청합니다.
- 프레임워크에서 디바이스 개체 를 만듭니다.
- PnP Manager가 디바이스를 시작한 후 디바이스 개체를 초기화합니다.
드라이버가 로드 및 초기화하는 동안 여러 이벤트가 발생하고 프레임워크를 통해 클라이언트 드라이버가 해당 이벤트를 처리하는 데 참여할 수 있습니다. 클라이언트 드라이버 쪽에서 드라이버는 다음 작업을 수행합니다.
- 프레임워크가 드라이버에 대한 참조를 가져올 수 있도록 클라이언트 드라이버 모듈에서 DllGetClassObject 함수를 구현하고 내보냅니다.
- IDriverEntry 인터페이스를 구현하는 콜백 클래스를 제공합니다.
- IPnpCallbackXxx 인터페이스를 구현하는 콜백 클래스를 제공합니다.
- 디바이스 개체에 대한 참조를 가져오고 클라이언트 드라이버의 요구 사항에 따라 구성합니다.
드라이버 콜백 소스 코드
프레임워크는 Windows에서 로드한 클라이언트 드라이버의 instance 나타내는 드라이버 개체를 만듭니다. 클라이언트 드라이버는 프레임워크에 드라이버를 등록하는 하나 이상의 드라이버 콜백을 제공합니다.
드라이버 콜백에 대한 전체 소스 코드는 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에서 호스트 프로세스를 인스턴스화한 후 프레임워크는 드라이버 개체를 만듭니다. 이를 위해 프레임워크는 드라이버 콜백 클래스의 instance 만들고 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
);
};
디바이스 콜백 소스 코드
프레임워크 디바이스 개체는 클라이언트 드라이버의 디바이스 스택에 로드되는 디바이스 개체를 나타내는 프레임워크 클래스의 instance. 디바이스 개체의 기능에 대한 자세한 내용은 디바이스 노드 및 디바이스 스택을 참조하세요.
디바이스 개체에 대한 전체 소스 코드는 Device.h 및 Device.c에 있습니다.
프레임워크 디바이스 클래스는 IWDFDevice 인터페이스를 구현합니다. 클라이언트 드라이버는 드라이버의 IDriverEntry::OnDeviceAdd 구현에서 해당 클래스의 instance 만들어야 합니다. 개체를 만든 후 클라이언트 드라이버는 새 개체에 대한 IWDFDevice 포인터를 가져오고 해당 인터페이스에서 메서드를 호출하여 디바이스 개체의 작업을 관리합니다.
IDriverEntry::OnDeviceAdd 구현
이전 섹션에서는 클라이언트 드라이버가 IDriverEntry::OnDeviceAdd에서 수행하는 작업을 간략하게 확인했습니다. 이러한 작업에 대한 자세한 내용은 다음과 같습니다. 클라이언트 드라이버:
만들 디바이스 개체에 대한 구성 정보를 지정합니다.
IDriverEntry::OnDeviceAdd 메서드의 클라이언트 드라이버 구현에 대한 프레임워크 호출에서 프레임워크는 IWDFDeviceInitialize 포인터를 전달합니다. 클라이언트 드라이버는 이 포인터를 사용하여 만들 디바이스 개체에 대한 구성 정보를 지정합니다. 예를 들어 클라이언트 드라이버는 클라이언트 드라이버가 필터인지 함수 드라이버인지를 지정합니다. 클라이언트 드라이버를 필터 드라이버로 식별하기 위해 IWDFDeviceInitialize::SetFilter를 호출합니다. 이 경우 프레임워크는 FiDO(필터 디바이스 개체)를 만듭니다. 그렇지 않으면 FDO(함수 디바이스 개체)가 만들어집니다. 설정할 수 있는 또 다른 옵션은 IWDFDeviceInitialize::SetLockingConstraint를 호출하여 동기화 모드입니다.
IWDFDeviceInitialize 인터페이스 포인터, 디바이스 콜백 개체의 IUnknown 참조 및 포인터 대 포인터 IWDFDevice 변수를 전달하여 IWDFDriver::CreateDevice 메서드를 호출합니다.
IWDFDriver::CreateDevice 호출이 성공한 경우:
프레임워크는 디바이스 개체를 만듭니다.
프레임워크는 프레임워크에 디바이스 콜백을 등록합니다.
디바이스 콜백이 프레임워크 디바이스 개체와 쌍을 이루는 후 프레임워크와 클라이언트 드라이버는 PnP 상태 및 전원 상태 변경과 같은 특정 이벤트를 처리합니다. 예를 들어 PnP 관리자가 디바이스를 시작하면 프레임워크에 알림이 표시됩니다. 그런 다음 프레임워크는 디바이스 콜백의 IPnpCallbackHardware::OnPrepareHardware 구현을 호출합니다. 모든 클라이언트 드라이버는 하나 이상의 디바이스 콜백 개체를 등록해야 합니다.
클라이언트 드라이버는 IWDFDevice 변수에서 새 디바이스 개체의 주소를 받습니다. 프레임워크 디바이스 개체에 대한 포인터를 받으면 클라이언트 드라이버는 I/O 흐름에 대한 큐 설정 및 디바이스 인터페이스 GUID 등록과 같은 초기화 작업을 진행할 수 있습니다.
IWDFDevice::CreateDeviceInterface를 호출하여 클라이언트 드라이버의 디바이스 인터페이스 GUID를 등록합니다. 애플리케이션은 GUID를 사용하여 클라이언트 드라이버에 요청을 보낼 수 있습니다. GUID 상수는 Internal.h에서 선언됩니다.
디바이스를 오가는 I/O 전송에 대한 큐를 초기화합니다.
템플릿 코드는 구성 정보를 지정하고 디바이스 개체를 만드는 도우미 메서드 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 에 보내고, 반대로 시스템이 S0 의 작동 상태가 되면 디바이스를 D0으로 다시 가져옵니다. 자세한 내용은 UMDF의 전원 정책 소유권을 참조하세요.
또 다른 구성 옵션은 클라이언트 드라이버가 디바이스의 필터 드라이버인지 또는 함수 드라이버인지를 지정하는 것입니다. 코드 예제에서 클라이언트 드라이버는 명시적으로 기본 설정을 지정하지 않습니다. 즉, 클라이언트 드라이버는 함수 드라이버이며 프레임워크는 디바이스 스택에 FDO를 만들어야 합니다. 클라이언트 드라이버가 필터 드라이버가 되려면 드라이버가 IWDFDeviceInitialize::SetFilter 메서드를 호출해야 합니다. 이 경우 프레임워크는 디바이스 스택에 FiDO를 만듭니다.
또한 클라이언트 드라이버는 클라이언트 드라이버의 콜백에 대한 프레임워크의 호출이 동기화되지 않음을 지정합니다. 클라이언트 드라이버는 모든 동기화 작업을 처리합니다. 이 기본 설정을 지정하기 위해 클라이언트 드라이버는 IWDFDeviceInitialize::SetLockingConstraint 메서드를 호출합니다.
다음으로 클라이언트 드라이버는 IUnknown::QueryInterface를 호출하여 디바이스 콜백 클래스에 대한 IUnknown 포인터를 가져옵니다. 그런 다음 클라이언트 드라이버는 IWDFDriver::CreateDevice를 호출하여 프레임워크 디바이스 개체를 만들고 IUnknown 포인터를 사용하여 클라이언트 드라이버의 디바이스 콜백을 등록합니다.
클라이언트 드라이버는 디바이스 콜백 클래스의 프라이빗 데이터 멤버에 디바이스 개체의 주소( IWDFDriver::CreateDevice 호출을 통해 수신됨)를 저장한 다음 DriverSafeRelease(Internal.h에 정의된 인라인 함수)를 호출하여 해당 참조를 해제합니다. 이는 디바이스 개체의 수명이 프레임워크에서 추적되기 때문입니다. 따라서 클라이언트 드라이버는 디바이스 개체의 추가 참조 수를 유지할 필요가 없습니다.
템플릿 코드는 디바이스 인터페이스 GUID를 등록하고 큐를 설정하는 공용 메서드 Configure를 정의합니다. 다음 코드 예제에서는 디바이스 콜백 클래스인 CMyDevice에서 Configure 메서드의 정의를 보여줍니다. 프레임워크 디바이스 개체를 만든 후 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;
}
앞의 코드 예제에서 클라이언트 드라이버는 I/O 흐름에 대한 큐를 초기화하고 디바이스 인터페이스 GUID를 등록하는 두 가지 기본 작업을 수행합니다.
큐는 CMyIoQueue 클래스에서 만들어지고 구성됩니다. 첫 번째 작업은 CreateInstanceAndInitialize라는 정적 메서드를 호출하여 해당 클래스를 인스턴스화하는 것입니다. 클라이언트 드라이버는 Configure를 호출하여 큐를 초기화합니다. CreateInstanceAndInitialize 및 Configure는 이 항목의 뒷부분에서 설명하는 CMyIoQueue에서 선언됩니다.
또한 클라이언트 드라이버는 IWDFDevice::CreateDeviceInterface 를 호출하여 클라이언트 드라이버의 디바이스 인터페이스 GUID를 등록합니다. 애플리케이션은 GUID를 사용하여 클라이언트 드라이버에 요청을 보낼 수 있습니다. GUID 상수는 Internal.h에서 선언됩니다.
IPnpCallbackHardware 구현 및 USB 관련 작업
다음으로 Device.cpp에서 IPnpCallbackHardware 인터페이스의 구현을 살펴보겠습니다.
모든 디바이스 콜백 클래스는 IPnpCallbackHardware 인터페이스를 구현해야 합니다. 이 인터페이스에는 IPnpCallbackHardware::OnPrepareHardware 및 IPnpCallbackHardware::OnReleaseHardware의 두 가지 메서드가 있습니다. 프레임워크는 PnP Manager가 디바이스를 시작할 때와 디바이스를 제거할 때의 두 가지 이벤트에 대한 응답으로 이러한 메서드를 호출합니다. 디바이스가 시작되면 하드웨어에 대한 통신이 설정되지만 디바이스가 작업 상태(D0)에 들어가지 않았습니다. 따라서 IPnpCallbackHardware::OnPrepareHardware 에서 클라이언트 드라이버는 하드웨어에서 디바이스 정보를 얻고, 리소스를 할당하고, 드라이버 수명 동안 필요한 프레임워크 개체를 초기화할 수 있습니다. PnP 관리자가 디바이스를 제거하면 드라이버가 시스템에서 언로드됩니다. 프레임워크는 드라이버가 해당 리소스 및 프레임워크 개체를 해제할 수 있는 클라이언트 드라이버의 IPnpCallbackHardware::OnReleaseHardware 구현을 호출합니다.
PnP 관리자는 PnP 상태 변경으로 인해 발생하는 다른 유형의 이벤트를 생성할 수 있습니다. 프레임워크는 해당 이벤트에 대한 기본 처리를 제공합니다. 클라이언트 드라이버는 해당 이벤트 처리에 참여하도록 선택할 수 있습니다. USB 디바이스가 호스트에서 분리되는 시나리오를 고려합니다. PnP 관리자는 해당 이벤트를 인식하고 프레임워크에 알린다. 클라이언트 드라이버가 이벤트에 대한 응답으로 추가 작업을 수행하려는 경우 드라이버는 디바이스 콜백 클래스에서 IPnpCallback 인터페이스 및 관련 IPnpCallback::OnSurpriseRemoval 메서드를 구현해야 합니다. 그렇지 않으면 프레임워크는 이벤트의 기본 처리를 진행합니다.
USB 클라이언트 드라이버는 지원되는 인터페이스, 대체 설정 및 엔드포인트에 대한 정보를 검색하고 데이터 전송을 위한 I/O 요청을 보내기 전에 구성해야 합니다. UMDF는 클라이언트 드라이버에 대한 많은 구성 작업을 간소화하는 특수 I/O 대상 개체를 제공합니다. USB 디바이스를 구성하려면 클라이언트 드라이버에 PnP Manager가 디바이스를 시작한 후에만 사용할 수 있는 디바이스 정보가 필요합니다.
이 템플릿 코드는 IPnpCallbackHardware::OnPrepareHardware 메서드에 해당 개체를 만듭니다.
일반적으로 클라이언트 드라이버는 디바이스 디자인에 따라 이러한 구성 작업 중 하나 이상을 수행합니다.
- 인터페이스 수와 같은 현재 구성에 대한 정보를 검색합니다. 프레임워크는 USB 디바이스에서 첫 번째 구성을 선택합니다. 클라이언트 드라이버는 다중 구성 디바이스의 경우 다른 구성을 선택할 수 없습니다.
- 엔드포인트 수와 같은 인터페이스에 대한 정보를 검색합니다.
- 인터페이스가 둘 이상의 설정을 지원하는 경우 각 인터페이스 내에서 대체 설정을 변경합니다. 기본적으로 프레임워크는 USB 디바이스의 첫 번째 구성에서 각 인터페이스의 첫 번째 대체 설정을 선택합니다. 클라이언트 드라이버는 대체 설정을 선택하도록 선택할 수 있습니다.
- 각 인터페이스 내의 엔드포인트에 대한 정보를 검색합니다.
이러한 작업을 수행하기 위해 클라이언트 드라이버는 WDF에서 제공하는 이러한 유형의 특수 USB I/O 대상 개체를 사용할 수 있습니다.
USB I/O 대상 개체 | Description | 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 I/O 대상 개체를 사용하려면 클라이언트 드라이버가 먼저 USB 대상 디바이스 개체를 만들어야 합니다. 프레임워크 개체 모델에서 USB 대상 디바이스 개체는 USB 디바이스를 나타내는 디바이스 개체의 자식입니다. USB 대상 디바이스 개체는 프레임워크에서 구현되며 구성 선택과 같은 USB 디바이스의 모든 디바이스 수준 작업을 수행합니다.
앞의 코드 예제에서 클라이언트 드라이버는 프레임워크 디바이스 개체를 쿼리하고 USB 대상 디바이스 개체를 만드는 클래스 팩터리에 대한 IWDFUsbTargetFactory 포인터를 가져옵니다. 클라이언트 드라이버는 해당 포인터를 사용하여 IWDFUsbTargetDevice::CreateUsbTargetDevice 메서드를 호출합니다. 메서드는 USB 대상 디바이스 개체를 만들고 IWDFUsbTargetDevice 인터페이스에 대한 포인터를 반환합니다. 또한 메서드는 해당 구성의 각 인터페이스에 대해 기본(첫 번째) 구성 및 대체 설정 0을 선택합니다.
템플릿 코드는 디바이스 콜백 클래스의 프라이빗 데이터 멤버에 USB 대상 디바이스 개체( IWDFDriver::CreateDevice 호출을 통해 수신됨)의 주소를 저장한 다음 DriverSafeRelease를 호출하여 해당 참조를 해제합니다. USB 대상 디바이스 개체의 참조 수는 프레임워크에서 유지 관리됩니다. 개체는 디바이스 개체가 활성 상태인 한 활성 상태입니다. 클라이언트 드라이버는 IPnpCallbackHardware::OnReleaseHardware에서 참조를 해제해야 합니다.
클라이언트 드라이버가 USB 대상 디바이스 개체를 만든 후 드라이버는 IWDFUsbTargetDevice 메서드를 호출하여 다음 작업을 수행합니다.
- 디바이스, 구성, 인터페이스 설명자 및 기타 정보(예: 디바이스 속도)를 검색합니다.
- I/O 컨트롤 요청의 형식을 지정하고 기본 엔드포인트로 보냅니다.
- 전체 USB 디바이스에 대한 전원 정책을 설정합니다.
자세한 내용은 UMDF에서 USB 디바이스 작업을 참조하세요. 다음 코드 예제에서는 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;
}
큐 소스 코드
프레임워크 큐 개체는 특정 프레임워크 디바이스 개체에 대한 I/O 큐를 나타냅니다. 큐 개체에 대한 전체 소스 코드는 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;
}
앞의 코드 예제에서 클라이언트 드라이버는 프레임워크 큐 개체를 만듭니다. 프레임워크는 클라이언트 드라이버에 대한 요청 흐름을 처리하는 큐 개체를 제공합니다.
개체를 만들기 위해 클라이언트 드라이버는 IWDFDriver::CreateDevice 에 대한 이전 호출에서 가져온 IWDFDevice 참조에서 IWDFDevice::CreateIoQueue를 호출합니다.
IWDFDevice::CreateIoQueue 호출에서 클라이언트 드라이버는 프레임워크가 큐를 만들기 전에 특정 구성 옵션을 지정합니다. 이러한 옵션은 큐가 전원 관리되는지, 길이가 0인 요청을 허용하는지, 드라이버의 기본 큐 역할을 하는지 여부를 결정합니다. 클라이언트 드라이버는 다음 정보 집합을 제공합니다.
큐 콜백 클래스에 대한 참조
해당 큐 콜백 클래스에 대한 IUnknown 포인터를 지정합니다. 이렇게 하면 프레임워크 큐 개체와 클라이언트 드라이버의 큐 콜백 개체 간에 파트너 관계가 만들어집니다. I/O 관리자가 애플리케이션에서 새 요청을 받으면 프레임워크에 알 수 있습니다. 그런 다음, 프레임워크는 IUnknown 포인터를 사용하여 큐 콜백 개체에 의해 노출되는 공용 메서드를 호출합니다.
기본 또는 보조 큐
큐는 기본 큐 또는 보조 큐여야 합니다. 프레임워크 큐 개체가 기본 큐 역할을 하는 경우 모든 요청이 큐에 추가됩니다. 보조 큐는 특정 유형의 요청에만 적용됩니다. 클라이언트 드라이버가 보조 큐를 요청하는 경우 드라이버는 IWDFDevice::ConfigureRequestDispatching 메서드를 호출하여 프레임워크가 지정된 큐에 배치해야 하는 요청 유형을 나타내야 합니다. 템플릿 코드에서 클라이언트 드라이버는 bDefaultQueue 매개 변수에 FALSE를 전달합니다. 기본 큐가 아닌 보조 큐를 만들도록 메서드에 지시합니다. 나중에 IWDFDevice::ConfigureRequestDispatching을 호출하여 큐에 디바이스 I/O 제어 요청만 있어야 함을 나타냅니다(이 섹션의 예제 코드 참조).
디스패치 유형
큐 개체의 디스패치 유형은 프레임워크가 클라이언트 드라이버에 요청을 전달하는 방법을 결정합니다. 배달 메커니즘은 순차적, 병렬 또는 클라이언트 드라이버에서 정의한 사용자 지정 메커니즘에 의해 될 수 있습니다. 순차 큐의 경우 클라이언트 드라이버가 이전 요청을 완료할 때까지 요청이 전달되지 않습니다. 병렬 디스패치 모드에서 프레임워크는 I/O 관리자에서 도착하는 즉시 요청을 전달합니다. 즉, 클라이언트 드라이버는 다른 드라이버를 처리하는 동안 요청을 받을 수 있습니다. 사용자 지정 메커니즘에서 클라이언트는 드라이버가 처리할 준비가 되면 프레임워크 큐 개체에서 다음 요청을 수동으로 끌어냅니다. 템플릿 코드에서 클라이언트 드라이버는 병렬 디스패치 모드를 요청합니다.
전원 관리형 큐
프레임워크 큐 개체는 디바이스의 PnP 및 전원 상태와 동기화되어야 합니다. 디바이스가 작업 상태가 아닌 경우 프레임워크 큐 개체는 모든 요청의 디스패치를 중지합니다. 디바이스가 작업 상태이면 큐 개체가 디스패치를 다시 시작합니다. 전원 관리형 큐에서 동기화는 프레임워크에서 수행됩니다. 그렇지 않으면 클라이언트 드라이브가 해당 작업을 처리해야 합니다. 템플릿 코드에서 클라이언트는 전원 관리형 큐를 요청합니다.
허용되는 길이가 0인 요청
클라이언트 드라이버는 큐에 버퍼를 배치하는 대신 길이가 0인 버퍼로 I/O 요청을 완료하도록 프레임워크에 지시할 수 있습니다. 템플릿 코드에서 클라이언트는 프레임워크에 이러한 요청을 완료하도록 요청합니다.
단일 프레임워크 큐 개체는 읽기, 쓰기 및 디바이스 I/O 컨트롤 등과 같은 여러 유형의 요청을 처리할 수 있습니다. 템플릿 코드를 기반으로 하는 클라이언트 드라이버는 디바이스 I/O 제어 요청만 처리할 수 있습니다. 이를 위해 클라이언트 드라이버의 큐 콜백 클래스는 IQueueCallbackDeviceIoControl 인터페이스 및 IQueueCallbackDeviceIoControl::OnDeviceIoControl 메서드를 구현합니다. 이렇게 하면 프레임워크가 디바이스 I/O 제어 요청을 처리할 때 프레임워크에서 IQueueCallbackDeviceIoControl::OnDeviceIoControl 의 클라이언트 드라이버 구현을 호출할 수 있습니다.
다른 유형의 요청의 경우 클라이언트 드라이버는 해당 IQueueCallbackXxx 인터페이스를 구현해야 합니다. 예를 들어 클라이언트 드라이버가 읽기 요청을 처리하려는 경우 큐 콜백 클래스는 IQueueCallbackRead 인터페이스 및 IQueueCallbackRead::OnRead 메서드를 구현해야 합니다. 요청 및 콜백 인터페이스 유형에 대한 자세한 내용은 I/O 큐 이벤트 콜백 함수를 참조하세요.
다음 코드 예제에서는 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 함수를 호출하여 디바이스 I/O 제어 요청을 보냅니다. 제어 코드의 유형에 따라 애플리케이션은 해당 호출에서 입력 및 출력 버퍼를 지정할 수 있습니다. 호출은 결국 I/O 관리자에 의해 수신되며 프레임워크에 알 수 있습니다. 프레임워크는 프레임워크 요청 개체를 만들고 프레임워크 큐 개체에 추가합니다. 템플릿 코드에서 큐 개체가 WdfIoQueueDispatchParallel 플래그를 사용하여 만들어졌기 때문에 요청이 큐에 추가되는 즉시 콜백이 호출됩니다.
프레임워크는 클라이언트 드라이버의 이벤트 콜백을 호출할 때 애플리케이션에서 보낸 요청(및 해당 입력 및 출력 버퍼)을 보유하는 프레임워크 요청 개체에 핸들을 전달합니다. 또한 해당 요청을 포함하는 프레임워크 큐 개체에 핸들을 보냅니다. 이벤트 콜백에서 클라이언트 드라이버는 필요에 따라 요청을 처리합니다. 템플릿 코드는 요청을 완료하기만 하면 됩니다. 클라이언트 드라이버는 더 많은 관련 작업을 수행할 수 있습니다. instance 경우 애플리케이션이 특정 디바이스 정보를 요청하는 경우 이벤트 콜백에서 클라이언트 드라이버는 USB 제어 요청을 만들고 USB 드라이버 스택으로 보내 요청된 디바이스 정보를 검색할 수 있습니다. USB 제어 요청은 USB 제어 전송에서 설명합니다.
드라이버 항목 소스 코드
템플릿 코드에서 드라이버 항목은 Dllsup.cpp에 구현됩니다.
Dllsup.cpp
include 섹션 후에는 클라이언트 드라이버에 대한 GUID 상수가 선언됩니다. 해당 GUID는 드라이버의 INF(설치 파일)에 있는 GUID와 일치해야 합니다.
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는 WUDFHost.exe 클라이언트 드라이버 모듈을 로드한 후 DllMain 을 호출합니다. Windows가 클라이언트 드라이버를 메모리에 언로드하기 직전에 Windows에서 DllMain 을 다시 호출합니다. DllMain은 드라이버 수준에서 전역 변수를 할당하고 해제할 수 있습니다. 템플릿 코드에서 클라이언트 드라이버는 WPP 추적에 필요한 리소스를 초기화 및 해제하고 ATL 클래스의 DllMain 구현을 호출합니다.
다음 코드 조각은 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인지 확인합니다. 프레임워크는 드라이버의 INF 파일에서 클라이언트 드라이버에 대한 CLSID를 검색합니다. 유효성을 검사하는 동안 지정된 GUID가 INF에서 제공한 GUID와 일치하는지 확인합니다.
- 클라이언트 드라이버에서 구현한 클래스 팩터리를 인스턴스화합니다. 템플릿 코드에서 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 아래에 제공합니다. 자세한 내용은 DEF 파일을 사용하여 DLL에서 내보내기 를 참조하세요.