IUnknown
IUnknown
インターフェイスは、Component Object Model (COM) において基盤となる抽象型である。COMの仕様では、すべてのCOMオブジェクトはIUnknown
インターフェイスを実装していなければならないとされている。すなわち、IUnknown
は、COMにおけるルートクラスである。また、すべてのCOMインターフェイスはIUnknown
の派生型である。
メソッド
[編集]IUnknown
には、QueryInterface
、AddRef
、Release
の3つのメソッドがある[1]。QueryInterface
は、ほかに実装しているインターフェイスを問い合わせ、取得するメソッドである。AddRef
とRelease
は、それぞれ参照カウントの増減を行う。
QueryInterface
はインターフェイスを識別するGUID(通常インターフェイスIDと呼び、IIDとも略される)に基づいて、他のインターフェイスへのポインタを得るために用いる。COMインターフェイスは任意のインターフェイスを継承することができ、COMオブジェクトは1つ以上の任意のインターフェイスを実装することができるが、対象のCOMオブジェクトが実装していないインターフェイスを要求された場合には、エラー値としてE_NOINTERFACE
を返す。- なお、すべてのCOMオブジェクトは
IUnknown
を実装しているという性質上、IUnknown
のIIDを第1引数に指定したQueryInterface
は必ず成功する。
- なお、すべてのCOMオブジェクトは
AddRef
はクライアントがこのCOMオブジェクトの参照を始めることを伝えるために用いる。Release
はクライアントがこのCOMオブジェクトの使用を終了することを伝えるために用いる。
interface IUnknown
{
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;
virtual ULONG STDMETHODCALLTYPE Release(void) = 0;
};
AddRef
とRelease
の戻り値は現在の参照カウントの値であるが、あくまでテスト用を目的としている。
QueryInterface
のようないくつかのメソッドの内部では、成功した場合にAddRef
を呼び出して参照カウントを増加させる。参照カウントを正しく把握してオブジェクトのライフサイクルを管理することはCOMクライアント側の責任でもある。RAIIをサポートするC++では、COMインターフェイスをラップして解放処理を自動化するためのスマートポインタが、ATLを始めとするいくつかのテンプレートライブラリによって定義されている[2][3]。
QueryInterface
はC++のdynamic_cast
に相当する機能を言語非依存で実現するものである[4]。つまり、安全なダウンキャストやクロスキャストに使うことができる。
なお、IUnknown
のインターフェイスIDは、{00000000-0000-0000-C000-000000000046}
と定義されている。
C++
[編集]COMの仕様にはないが、Windows SDK(旧Platform SDK)に用意されているヘッダーファイルをC++で用いると、テンプレートによって型安全性を向上させた次のようなQueryInterface
のオーバーロードが定義される[注釈 1]。
template<class Q>
HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)
{
return QueryInterface(__uuidof(Q), (void **)pp);
}
なお、__uuidof(Q)
は、型Q
に関連付けられたGUIDを取得するVisual C++の拡張機能である[5]。こちらを用いると、第2引数で指定するインターフェイスへのポインタ(実際にはポインタ変数のアドレスを渡す)と異なるインターフェイスIDを第1引数に指定してしまうという誤りを避けることが可能である。
役割
[編集]前述のように、IUnknown
は実装しているほかのインターフェイスの問い合わせ・取得(QueryInterface
)と参照カウントの管理を行うメソッドを持っているのみである。そして、すべてのCOMインターフェイスはIUnknown
から派生(直接に、またはIUnknown
から派生したインターフェイスから派生することによって間接的に)しているため、すべてのCOMインターフェイスで必ずこれらのメソッドが呼び出し可能であることが保証される[注釈 2]。
ただし、IUnknown
を直接扱う有用な例は少ない。
2つの異なるインターフェイスポインタが同じCOMオブジェクトを指すものであるか否かを調べるには、IUnknown
のIIDを指定したQueryInterface
の呼び出しを双方で行い、得られたインターフェイスへのポインタが一致するか否かを調べるとよい。これは、あるCOMオブジェクトに対し、IUnknown
のIIDを指定したQueryInterface
の呼び出しで得られるポインタは、かならず(どのインターフェイスでQueryInterface
を呼んでも)同じポインタ値となる保証があるためである(逆に言えば、実装者はそのように実装する必要がある)。
bool EqualObject(IUnknown* lhs, IUnknown* rhs)
{
bool ret = false;
IUnknown* lhsUnk;
if (SUCCEEDED(lhs->QueryInterface(&lhsUnk)))
{
IUnknown* rhsUnk;
if (SUCCEEDED(rhs->QueryInterface(&rhsUnk)))
{
ret = (lhsUnk == rhsUnk);
rhsUnk->Release();
}
lhsUnk->Release();
}
return ret;
}
C++でCOMクライアントを実装する場合、たとえインターフェイスIBase
とインターフェイスIDerived
の間に継承関係があったとしても、IUnknown::QueryInterface()
の代わりにdynamic_cast
を使ってダウンキャストを実行してはいけない。COMサーバーはC++で実装されているとは限らないからである。ATLには、引数付きコンストラクタや代入演算子オーバーロードにおいてQueryInterface
を暗黙的に実行し、IUnknown
または派生インターフェイスへのポインタから暗黙的に変換するスマートポインタCComQIPtr
も用意されている(オブジェクトがインターフェイスを実装しておらず、変換に失敗した場合、CComQIPtr
はヌルポインタ相当の空オブジェクトとなる)[7]。
なお、.NET Frameworkおよび.NETにはCOM相互運用機能が用意されている。System.Runtime.InteropServices.Marshal
クラス[8]を使うことで、ローレベルな相互運用コードを手動で記述することも可能だが、COMコンポーネントのタイプライブラリをインポートして.NET言語から利用できるようにするユーティリティツール(TlbImp.exe)を使うのが簡単である。Microsoft Visual Studioにも統合されており、タイプライブラリを参照設定に追加するだけで、C#やVisual Basic .NETといったマネージ言語で書かれたアプリケーションソフトウェアからCOMコンポーネントを利用することができるようになる。マネージ言語ではIUnknown::QueryInterface()
の代わりにキャスト構文を使用することができ[9]、COMコクラスが指定のインターフェイスを実装していなかった場合はSystem.InvalidCastException
がスローされる[10]。
脚注
[編集]注釈
[編集]- ^ Microsoft Visual Studio 2005付属のWindows SDK 5.0 <Unknwn.h> 121–125行目から引用した。
- ^ XAudio2はCOMプログラミングのスタイルをとっていながらも、
IXAudio2
を除く大半のインターフェイスはIUnknown
から派生しておらず、オブジェクトのライフサイクル管理に独自の手法を採用している[6]。
出典
[編集]- ^ “IUnknown - Win32 apps” (英語). Microsoft Learn. マイクロソフト. 2024年12月14日閲覧。
- ^ CComPtr Class | Microsoft Learn
- ^ ComPtr Class | Microsoft Learn
- ^ Asking an Object for an Interface - Win32 apps | Microsoft Learn
- ^ __uuidof Operator | Microsoft Learn
- ^ IXAudio2 (xaudio2.h) - Win32 apps | Microsoft Learn
- ^ How to: Create and use CComPtr and CComQIPtr instances | Microsoft Learn
- ^ Marshal Class (System.Runtime.InteropServices) | Microsoft Learn
- ^ Marshal.QueryInterface(IntPtr, Guid, IntPtr) Method (System.Runtime.InteropServices) | Microsoft Learn
- ^ COM 相互運用性 - 第 1 部 : C# クライアント チュートリアル, Internet Archive