ほっしーの技術ネタ備忘録

技術ネタの備忘録です。基本的に私が忘れないためのものです。他の人の役にも立つといいなぁ。

UMDF開発のすゝめ - 2

こないだの続きを執筆中。
断定口調で書いてたけどやっぱりですます調に変更。こっちのほうが個人的に読みやすいw

バイスオブジェクト・ドライバオブジェクトが、
フレームワーク側とドライバ側と二種類あるのがややこしいかも…
後で考えることにしてとりあえずガリガリ書こう。

ClassFactory

ClassFactoryを返す関数を作ったので、次はそのClassFactoryが必要になります。
実装する前にIClassFactoryの定義を見てみましょう。

interface IClassFactory : IUnknown
{
	HRESULT CreateInstance(
		[in, unique] IUnknown* pUnkOuter,
		[in] REFIID riid,
		[out, iid_is(riid)] void** ppvObject);
	HRESULT LockServer(
		[in] BOOL fLock);
};

実際には他にもいろいろごちゃごちゃと書いてありますが、本質的な部分ではないので省きます。
CUnknownから派生してCreateInstanceとLockServerの2つを実装すると大体下のようなコードになります。

class CClassFactory : public CUnknown, public IClassFactory
{
public: // IUnknown インターフェース
	virtual ULONG STDMETHODCALLTYPE AddRef( VOID ) { return __super::AddRef(); }
	virtual ULONG STDMETHODCALLTYPE Release( VOID ) { return __super::Release(); }
	virtual HRESULT STDMETHODCALLTYPE QueryInterface( __in REFIID IId, __out PVOID *Object ) {
		if( IsEqualIID( IId, __uuidof(IClassFactory) ) ) {
			AddRef();
			*Object = static_cast<IClassFactory*>( this );
			return S_OK;
		} else {
			return CUnknown::QueryInterface( IId, Object );
		}
	}
public: // IClassFactory インターフェース
	virtual HRESULT STDMETHODCALLTYPE CreateInstance( __in_opt IUnknown* Object, __in REFIID IId, __out PVOID* Interface ) {
		// ドライバオブジェクトを生成する
		CDriver* Driver = new CDriver();
		if( Driver == NULL ) return E_OUTOFMEMORY;

		// 要求されたインターフェースを取得する
		HRESULT hr = Driver->QueryInterface( IId, Interface );
		Driver->Release();

		return hr;
	}
	virtual HRESULT STDMETHODCALLTYPE LockServer( __in BOOL Lock ) {
		if( Lock ) {
			InterlockedIncrement( &s_LockCount );
		} else {
			InterlockedDecrement( &s_LockCount );
		}
		return S_OK;
	}
private:
	static LONG s_LockCount;
};
LONG CClassFactory::s_LockCount = 0;

IUnknownの実装に関しては基本通りですので特に説明の必要はないでしょう。IClassFactoryインターフェースに応えるのを忘れない程度です。
LockServerの実装はstaticなカウンタを用意し、Win32 APIのInterlockedIncrement/Decrementを用いています。このように通常のAPIがそのまま使えるのがUMDFの利点の1つです。
さて、メインの仕事であるCreateInstanceの実装ですが、大体DllGetClassObject関数とほぼ同じになります。どちらも「COMインスタンスを生成する」という点で本質的に同じことを行うからです。return前にDriver変数の所有権をReleaseしなければならない点も同じです。

DriverEntry

さて、ここまではUMDFのフレームワークが私たちの実装するCOMオブジェクトを取得するための手順でした。ここからは本題ともいえるDriverクラスに手をつけましょう。
Driverクラスは仕様でIDriverEntryインターフェースを継承することになっているので、まずはその定義から。

interface IDriverEntry : IUnknown
{
	HRESULT OnInitialize(
		[in, annotation("__in")] IWDFDriver* pWdfDriver
	);

	HRESULT OnDeviceAdd(
		[in, annotation("__in")] IWDFDriver* pWdfDriver,
		[in, annotation("__in")] IWDFDeviceInitialize* pWdfDeviceInit
	);

	void OnDeinitialize(
		[in, annotation("__in")] IWDFDriver* pWdfDriver
	);
};

OnInitialize/OnDeinitializeメソッドはドライバ自体の初期化、破棄時に呼ばれるメソッドで、OnDeviceAddメソッドは初期化完了後にデバイスオブジェクトをシステムに追加するためのメソッドです。
OnInitializeでたとえばシステムにフックを仕掛けたり、ドライバのスコープとなるバッファを用意して初期化したりしておいて、OnDeinitializeで解放します。
ドライバの初期化が終わるとシステムから「デバイスオブジェクトを作ってシステムに登録しろ」と要求がきます。これに対応するメソッドがOnDeviceAddです。
また、各関数に渡されるIWDFDriverインターフェースは、このドライバクラスと関連付けられたフレームワーク側のオブジェクトです。

class CDriver : public CUnknown, public IDriverEntry
{
public:
	virtual ULONG STDMETHODCALLTYPE AddRef( VOID ) { return __super::AddRef(); }
	virtual ULONG STDMETHODCALLTYPE Release( VOID ) { return __super::Release(); }
	virtual HRESULT STDMETHODCALLTYPE QueryInterface( __in REFIID IId, __out PVOID* Object ) {
		if( IsEqualIID( IId, __uuidof(IDriverEntry) ) ) {
			AddRef();
			*Object = static_cast<IDriverEntry*>( this );
			return S_OK;
		} else {
			return CUnknown::QueryInterface( IId, Object );
		}
	}
public:
	virtual HRESULT STDMETHODCALLTYPE OnInitialize( __in IWDFDriver* FxWdfDriver ) {
		return S_OK;
	}
	virtual HRESULT STDMETHODCALLTYPE OnDeviceAdd( __in IWDFDriver* FxWdfDriver, __in IWDFDeviceInitialize* FxDeviceInit ) {
		// 同期処理はフレームワークにお任せ
		FxDeviceInit->SetLockingConstraint( WdfDeviceLevel );

		// デバイスオブジェクトの生成
		CDevice* Device = new CDevice();
		if( Device == NULL ) return E_OUTOFMEMORY;

		// デバイスオブジェクトをシステムに登録
		IWDFDevice* fxDevice;
		IUnknown* unk = Device->QueryIUnknown();
		HRESULT hr = FxWdfDriver->CreateDevice( FxDeviceInit, unk, &fxDevice );
		unk->Release();
		if( FAILED( hr ) ) {
			Device->Release();
			return hr;
		}

		// 生成したデバイスの設定
		hr = Device->Configure( fxDevice );
		if( FAILED( hr ) ) {
			fxDevice->Release();
			Device->Release();
			return hr;
		}

		fxDevice->Release();
		Device->Release();

		return hr;
	}
	virtual VOID STDMETHODCALLTYPE OnDeinitialize( __in IWDFDriver* FxWdfDriver ) {
		return;
	}
};

今回は特に初期化することもないのでそのままS_OK(成功)を返します。OnDeinitializeも特にすることもないでしょう。
OnDeviceAddではデバイスオブジェクトを生成してフレームワークに登録します。後で実装するCDeviceのインスタンスを生成してIUnknownインターフェースをCreateDeviceに渡すと、システムにデバイスが追加されます。
そのあとで呼んでいるConfigureは何かのメソッドというわけではなく、デバイスオブジェクトの初期化のために用意した関数です。詳しくは次で説明しましょう。

Deviceクラス

最後にデバイスクラスを実装しましょう。
とは言っても今回は特に何もしないデバイスですので実装する内容もありませんし、仕様で実装しなければならないインターフェースもありませんので、先ほど初期化用に用意すると書いたConfigureを実装して終わりです。もちろん、COMなのでIUnknownは継承します。

class CDevice : public CUnknown
{
public:
	virtual ULONG STDMETHODCALLTYPE AddRef( VOID ) { return __super::AddRef(); }
	virtual ULONG STDMETHODCALLTYPE Release( VOID ) { return __super::Release(); }
	virtual HRESULT STDMETHODCALLTYPE QueryInterface( __in REFIID IId, __out PVOID* Object ) {
		return CUnknown::QueryInterface( IId, Object );
	}
public:
	// デバイスの初期化
	HRESULT Configure( IWDFDevice* fxDevice ) {
		OutputDebugString( L"Hello World!!" ); // お約束 :-)

		return S_OK;
	}
};

お遊びのつもりでデバッグ出力APIを使ってHello World!!なんて表示させています。