MediaFoundationを使う (0) (というより番外編) [IUnknownの実装]

MediaFoundationの資料はいろいろなところで言われていますが、日本語の資料がとても少なく大変です。

サンプルをみても説明が英語だとうまく読めない人もいるだろうな~と思ってDirectShowを置き換えられるか調べてみて、というところです。

それは置いておきまして、まずIUnknownを実装してみるところです。

IUnknownが持っている機能は

  • 継承している、もしくは所有しているインターフェイスの公開(QueryInterface)
  • 参照カウントの安全な管理(AddRef,Release)
  • 自身の安全な解放(Release)

となります。MicrosoftはIUnknownをすべてのCOMオブジェクトの元にしているのでこれを実装しておくとCOMで自分で実装が必要になったときに手間を省くことができます。

WindowsでCOMなどを踏み込んで実装するときにはこのコードを持っておくと非常に便利です。

参照元はDirectShowが持っているCUnknown

今から紹介するコードは元をたどればDirectShowのライブラリであるbaseclasses内に実装されているCUnknownです。(ファイルはcombase.cpp/h)

これを依存性をできる限りなくして自前で使えるようにしています。

まずはヘッダー部

一応ファイル名はunknownbase.hとしておいてください。

#ifndef __unknownbase_h__
#define __unknownbase_h__
//仮想関数テーブルを初期化しないようにする宣言
#ifndef AM_NOVTABLE
#ifdef  _MSC_VER
#if _MSC_VER >= 1100
#define AM_NOVTABLE __declspec(novtable)
#else
#define AM_NOVTABLE
#endif
#endif //MSC_VER
#endif //AM_NOVTABLE
#ifndef INONDELEGATINGUNKNOWN_DEFINED
DECLARE_INTERFACE(INonDelegatingUnknown)
{
	STDMETHOD(NonDelegatingQueryInterface) (THIS_ REFIID, LPVOID *) PURE;
	STDMETHOD_(ULONG, NonDelegatingAddRef)(THIS) PURE;
	STDMETHOD_(ULONG, NonDelegatingRelease)(THIS) PURE;
};
#define INONDELEGATINGUNKNOWN_DEFINED
#endif
//IUnknownの機能を実装する
class AM_NOVTABLE CUnknownBase : public INonDelegatingUnknown
{
public:
	CUnknownBase(IUnknown *lpUnknown); //コンストラクタ
	virtual ~CUnknownBase(); //デストラクタ
	STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,void **ppv); //インターフェイスを要求する
	STDMETHODIMP_(ULONG) NonDelegatingAddRef(void); //参照カウントの増加
	STDMETHODIMP_(ULONG) NonDelegatingRelease(void); //参照カウントの減少
	//オーナーオブジェクトを取得する
	IUnknown *GetOwner(void) const { return const_cast<IUnknown *>(m_lpUnknown); }
protected:
	volatile long m_lRefCount; //参照カウント
private:
	//オブジェクトのコピーの禁止
	CUnknownBase(const CUnknownBase &unk); //コピーコンストラクタ
	CUnknownBase & operator = (const CUnknownBase &unk); //コピーオペレータ
private:
	const IUnknown *m_lpUnknown; //IUnknownインターフェイス
};
//インターフェイスの変換時に使用する補助関数
STDAPI GetInterface(IUnknown *lpUnknown,void **ppv);
STDAPI GetInterfaceEx(INonDelegatingUnknown *lpUnknown,void **ppv);
//デバッグチェックマクロ
#define CheckPointer(p,ret)			{ if((p) == NULL){ return (ret); } }
#define ValidateReadPtr(p,cb)		0
#define ValidateWritePtr(p,cb)		0
#define ValidateReadWritePtr(p,cb)	0
//IUnknownの機能を実装するときに行うマクロ
#define DECLARE_IUNKNOWN										\
	STDMETHODIMP QueryInterface(REFIID riid, __deref_out void **ppv) {	  \
		return GetOwner()->QueryInterface(riid,ppv);			\
	};														  \
	STDMETHODIMP_(ULONG) AddRef() {							 \
		return GetOwner()->AddRef();							\
	};														  \
	STDMETHODIMP_(ULONG) Release() {							\
		return GetOwner()->Release();						   \
	};
#endif //__unknownbase_h__

ほとんどCUnknownの実装をそのまま持ってきている

ということで、詳しい説明は余りしません。いくつか追加実装を行っています。要点としては

  • IUnknownをそのまま実装するのではなくINonDelegatingInterfaceという別のインターフェイスを宣言して使ってる
  • オブジェクトのコピーを禁止するためにprivateにコピーコンストラクタおよびコピーオペレータを宣言している
  • 補助マクロおよび補助関数としてGetInterface、CheckPointerなどを宣言している

です。ちなみに、本来なら何も考えずにIUnknownをベースにすればいいですが、そのまま持ってきてしまったため宣言になってしまいました。

DirectShowとの実装の兼ね合いですね。

で、コード部

ファイル名はunknownbase.cppです。

#include "stdafx.h"
#pragma warning(disable:4355)
//コンストラクタ
CUnknownBase::CUnknownBase(IUnknown *lpUnknown)
	: m_lRefCount(0), m_lpUnknown(lpUnknown != NULL ? lpUnknown : reinterpret_cast<IUnknown *>(static_cast<INonDelegatingUnknown *>(this)))
{
}
//デストラクタ
CUnknownBase::~CUnknownBase()
{
}
//インターフェイスを要求する
STDMETHODIMP CUnknownBase::NonDelegatingQueryInterface(REFIID riid,void **ppv)
{
	//ポインタチェック
	CheckPointer(ppv,E_POINTER);
	ValidateReadWritePtr(ppv,sizeof(PVOID));
	//IUnknownのみインターフェイスの受け継ぎを行う
	if(IsEqualIID(riid,IID_IUnknown)){
		GetInterface(reinterpret_cast<IUnknown *>(static_cast<INonDelegatingUnknown *>(this)),ppv);
		return S_OK;
	}
	else{
		*ppv = NULL;
		return E_NOINTERFACE;
	}
}
//参照カウント用の大きい方の値を返すテンプレート関数
template <class T> inline static T ourmax(const T &a,const T &b)
{
	return a > b ? a : b;
}
//参照カウントの増加
STDMETHODIMP_(ULONG) CUnknownBase::NonDelegatingAddRef(void)
{
	long lRef = InterlockedIncrement(&m_lRefCount);
	return ourmax((ULONG)lRef,1ul);
}
//参照カウントの減少
STDMETHODIMP_(ULONG) CUnknownBase::NonDelegatingRelease(void)
{
	long lRef = InterlockedDecrement(&m_lRefCount);
	if(lRef == 0){ m_lRefCount++; delete this; return (ULONG)0; }
	else return ourmax((ULONG)lRef,1ul);
}
//インターフェイスの変換時に使用する補助関数
STDAPI GetInterface(IUnknown *lpUnknown,void **ppv)
{
	CheckPointer(ppv,E_POINTER);
	*ppv = lpUnknown; lpUnknown->AddRef();
	return S_OK;
}
STDAPI GetInterfaceEx(INonDelegatingUnknown *lpUnknown,void **ppv)
{
	CheckPointer(ppv,E_POINTER);
	*ppv = lpUnknown; lpUnknown->NonDelegatingAddRef();
	return S_OK;
}

なんかいろいろとすさまじしい実装だと思った

まず初めにpragmaを使って警告を消さないとうるさいです。

この警告は「コンストラクタの初期変数初期化でthisを使うべきではない」というものです。

普通ならコンストラクタが始まるまでは変数すら初期化されていないので参照自体がおかしくなる、という意味なのですがここではthisのアドレスだけ必要なので無視です。

また、IUnknownとは等しくはないが同型のインターフェイスを使うので変換時にstatic_castとreinterpret_castを両方使うという豪華仕様だったりします。

assertやらもばっさりとカットして必要な部分だけ残っているコードです。

注意点としては

  • QueryInterfaceでは必ずIUnknownを返すことができなければならないのでその実装を行うこと
  • Releaseでは参照カウントが0になったとき、delete thisにより自身を削除するのでそのコード以降はメンバ変数を使ってはならない
  • 継承したオブジェクトをnewで作成すると、作成された時点では参照カウントが0になるので使用側に返す前にAddRefを行い有効にすること
  • インターフェイスの変換時にGetInterfaceを使うことができるが、いくつかの例外では間違ったポインタを返すのでその場合はGetInterfaceExを使うこと

一応いくつかの例外を書いておきますと、DirectShowのライブラリが持っているCUnknownでも起こりますが、

class IObject : public IUnknown { }
class CObject : public CUnknownBase, IObject { }
class CObjectEx : public CObject { }
・・・
IObject *obj = new CObjectEx();
・・・
CObject *obj2
hr = obj->QueryInterface(IID_CObject,(void **)&obj2);

のように、QueryInterfaceでCUnknownBaseを継承しているオブジェクトを取得する場合はポインタの状態の関係でGetInterfaceExで取得したものを返さないと正しく動作しません。

通常は上記の例ではIObjectを取得することがほとんどなので気にはされませんが。

というわけで前哨戦

何でこんなものがいるかというとMediaFoundationは状態の管理にインターフェイスを使ったコールバックを多用していて、そのインターフェイスはIUnknownが親になるから、です。

ちなみにただ単に再生させるだけならDirectShowの場合はこんなものは必要ありません。

新しくなったのはいいとしても、最低でも使用側がインターフェイスを実装しないと使えない、というのはいただけないと思いますが・・・

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この記事のトラックバック用URL