SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

デザイン性に優れた拡張メニューコントロール

LuxMenuにおいてCOMはどうやって実現されているのか

デザイン性に優れた拡張メニューコントロール 第2回


COMとして最低限必要なインターフェイスメソッド

 さて、インターフェイスが具体的にどのようなものであるのかを把握できたところで、COMとして最低限必要なメソッドQueryInterfaceAddRefReleaseについて説明します。これらはIUnknownが持つメソッドであり、COMインターフェイスのすべてはIUnknownを実装することを義務付けられています。

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)するための処理を記述しておきます。

IID_ITestの定義
#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にはそのような機能として参照カウンタが採用されており、AddRefReleaseがそれらの役割を果たします。ここでは、先ほどのサンプルコードに参照カウンタを採用し、完璧なIUnknownの実装を行いたいと思います。

 まず、CTestのメンバとしてm_refが定義されている点に注目してみましょう。m_refは参照カウンタそのものであり、参照数が格納されます。コンストラクタで0に初期化され、AddRefで1つ増加、Releaseで1つ減少します。Releaseメソッド内で0になった場合(どこからも参照されなくなった場合)に、インスタンスを解放するようにします。

 CreateInstance関数内で参照を増やすから、AddRefを呼ばなくてもいいのだろうか? といった疑問を持たれるかと思いますが、実はインターフェイスを取得するQueryInterfaceメソッド内でAddRefが呼ばれています。要するに、インターフェイスの取得に成功したら自動的にAddRefが呼ばれる処理にしておけば、スマートに収まるというわけです。

IID_ITestの定義
#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ユーザー、必見です!

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
デザイン性に優れた拡張メニューコントロール連載記事一覧

もっと読む

この記事の著者

山本 大祐(ヤマモト ダイスケ)

普段はActiveBasicと周辺ツールの開発を行っています。最近は諸先輩方を見習いながら勉強中の身。AB開発日記

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/513 2008/08/26 13:40

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー