COMとして最低限必要なインターフェイスメソッド
さて、インターフェイスが具体的にどのようなものであるのかを把握できたところで、COMとして最低限必要なメソッドQueryInterface、AddRef、Releaseについて説明します。これらはIUnknownが持つメソッドであり、COMインターフェイスのすべてはIUnknownを実装することを義務付けられています。
Dim IID_IUnknown = [&H00000000, &H0000, &H0000, _ [&HC0, &H00, &H00, &H00, &H00, &H00, &H00, &H46]] As GUID Interface IUnknown Function QueryInterface(ByRef riid As IID, _ ByRef pvObj As Any) As HRESULT Function AddRef() As DWord Function Release() As DWord End Interface TypeDef LPUNKNOWN = *IUnknown
既にC++などの言語でCOMに触れている方は、おなじみです。反面、COMが初めてだという方はこれらのメソッドがどのような働きをするのか、まったく検討が付かないものと思います。
特に複雑な処理をするわけではなく、これら3つのメソッドは下記のような役割を果たします。
- QueryInterface
コンポーネントがサポートするインターフェイスのインスタンスを取得するためのメソッド。
- AddRef
参照カウンタを1だけ増加させるためのメソッド(主に、
QueryInterface内部から呼ばれる)。 - Release
参照カウンタを1だけ減算し、0になったらインスタンスの解放を行うためのメソッド。
では、早速IUnknownを実装すべく、サンプルプログラムを見ていきましょう。いきなりすべてを実装するのは大変なので、まずはインスタンスを取得するためのQueryInterfaceの実装を行います。後ほど、完璧なIUnknown実装コードに仕立て上げて、COM対応を図っていきたいと思います。
QueryInterfaceメソッドを実装する
まず、QueryInterfaceメソッドを実装するに当たって、インターフェイス固有のCLSIDが必要になってきます。あらかじめGUID生成ツールで適当な値を取得するなどしておきましょう。もちろん、このサンプルについている値を利用しても支障はありません。
ITestインターフェイスの定義文では、IUnknownを継承しているところにも注目しておきましょう。QueryInterfaceメソッド内では、パラメータに指定されたGUIDがIID_ITestまたはIIT_IUnknownと等しかったらインスタンスへの参照をriidにコピーし、S_OKを返します。それ以外は、E_NOINTERFACEを返します。
AddRefメソッドは後ほどコーディングするので、ここでは何も記述しないことにします。ただし、AddRefについて何も記述がないとCTestが抽象クラスのまま(インスタンス化不可能)になるため、空のコードで実装しておきましょう。
Releaseメソッドでは、自身のインスタンスを解放(Delete)するための処理を記述しておきます。
#N88BASIC
'インターフェイスを定義
Dim IID_ITest=[&H13203E62,&H5D18,&H4226, _
[&HB8,&H19,&H94,&HA3,&HF4,&H3C,&HAF,&H83]] As GUID
Interface ITest
Inherits IUnknown
Sub ShowMsg()
End Interface
'クラスを定義
Class CTest
Inherits ITest
Public
Sub CTest()
Print "CTestのインスタンスを生成"
End Sub
Sub ~CTest()
Print "CTestのインスタンスを破棄"
End Sub
Override Function QueryInterface(ByRef riid As IID, _
ppvObj As *VoidPtr) As HRESULT
If IsEqualIID(riid,IID_ITest)<>0 or _
IsEqualIID(riid,IID_IUnknown)<>0 Then
Set_LONG_PTR(ppvObj,VarPtr(This) As LONG_PTR)
Return S_OK
End If
Set_LONG_PTR(ppvObj,0)
Return E_NOINTERFACE
End Function
Override Sub AddRef()
End Sub
Override Sub Release()
Delete VarPtr(This)
End Sub
Override Sub ShowMsg()
Print "ShowMsgメソッドが呼ばれました"
End Sub
End Class
' インターフェイスの生成
Function CreateInstance() As *ITest
Dim pObj As *CTest
pObj=New CTest()
pObj->QueryInterface(IID_ITest,VarPtr(CreateInstance))
End Function
'-----------------------------------------------------
' インターフェイスを経由したオブジェクトへのアクセス
'-----------------------------------------------------
Dim lpTest As *ITest
lpTest=CreateInstance()
lpTest->ShowMsg()
lpTest->Release()
実行結果は以下の通りです。
参照カウンタ(インスタンスは生かされるのか、それとも殺されるのか!?)
先ほどのサンプルでは、インターフェイスの取得と解放が行えましたが、インターフェイスへの参照が複数あり、どのタイミングで解放を行えばよいのかを考慮する必要性がでてきます。IUnknownにはそのような機能として参照カウンタが採用されており、AddRef、Releaseがそれらの役割を果たします。ここでは、先ほどのサンプルコードに参照カウンタを採用し、完璧なIUnknownの実装を行いたいと思います。
まず、CTestのメンバとしてm_refが定義されている点に注目してみましょう。m_refは参照カウンタそのものであり、参照数が格納されます。コンストラクタで0に初期化され、AddRefで1つ増加、Releaseで1つ減少します。Releaseメソッド内で0になった場合(どこからも参照されなくなった場合)に、インスタンスを解放するようにします。
CreateInstance関数内で参照を増やすから、AddRefを呼ばなくてもいいのだろうか? といった疑問を持たれるかと思いますが、実はインターフェイスを取得するQueryInterfaceメソッド内でAddRefが呼ばれています。要するに、インターフェイスの取得に成功したら自動的にAddRefが呼ばれる処理にしておけば、スマートに収まるというわけです。
#N88BASIC
'インターフェイスを定義
Dim IID_ITest=[&H13203E62,&H5D18,&H4226, _
[&HB8,&H19,&H94,&HA3,&HF4,&H3C,&HAF,&H83]] As GUID
Interface ITest
Inherits IUnknown
Sub ShowMsg()
End Interface
'クラスを定義
Class CTest
Inherits ITest
'参照カウンタ
m_ref As Long
Public
Sub CTest()
'参照カウンタを初期化
m_ref=0
Print "CTestのインスタンスを生成"
End Sub
Sub ~CTest()
Print "CTestのインスタンスを破棄"
End Sub
Override Function QueryInterface(ByRef riid As IID, _
ppvObj As *VoidPtr) As HRESULT
If IsEqualIID(riid,IID_ITest)<>0 or _
IsEqualIID(riid,IID_IUnknown)<>0 Then
Set_LONG_PTR(ppvObj,VarPtr(This) As LONG_PTR)
'参照を増やす
AddRef()
Return S_OK
End If
Set_LONG_PTR(ppvObj,0)
Return E_NOINTERFACE
End Function
Override Sub AddRef()
m_ref++
End Sub
Override Sub Release()
m_ref--
If m_ref=0 Then
'参照されなくなったら解放
Delete VarPtr(This)
End If
End Sub
Override Sub ShowMsg()
Print "ShowMsgメソッドが呼ばれました"
End Sub
End Class
' インターフェイスの生成
Function CreateInstance() As *ITest
Dim pObj As *CTest
pObj=New CTest()
pObj->QueryInterface(IID_ITest,VarPtr(CreateInstance))
End Function
'-----------------------------------------------------
' インターフェイスを経由したオブジェクトへのアクセス
'-----------------------------------------------------
Dim lpTest As *ITest
lpTest=CreateInstance()
lpTest->ShowMsg()
lpTest->Release()
このプログラムの実行結果は、2番目のサンプルコードと同等のものになります。
まとめ
COMの仕組みを含め、IUnknownの存在をある程度理解していただけたでしょうか? お世辞にもコンポーネントとは呼べないような地味なサンプルでしたが、これでIUnknownの実装が確実なものになりました。今回紹介したIUnknownの実装は、QueryInterfaceメソッドのGUIDを差し替えれば使いまわしができる内容であり、COMコンポーネント開発者においては今後の利用価値も一段と高くなることと思われます。
次回は、COMコンポーネント化されたLuxMenuの仕組みについてお届けします。ABユーザー、必見です!
