C++ による BREW 開発(5)

BREW の仮想テーブルの実体は、関数へのポインタを列挙した構造体だ。


例えば、IApplet と IAStream の仮想テーブルは次のようになっている。

struct IAppletVtbl{
    uint32  (*AddRef)     ( IApplet* po );
    uint32  (*Release)    ( IApplet* po );
    boolean (*HandleEvent)( IApplet* po , AEEEvent evt , uint16 wp , uint32 dwp );
};

struct IAStreamVtbl{
    uint32  (*AddRef)  ( IAStream* po );
    uint32  (*Release) ( IAStream* po );
    void    (*Readable)( IAStream* po , PFNNOTIFY pfn , void* pUser );
    int32   (*Read)    ( IAStream* po , void* pDest , uint32 nWant );
    void    (*Cancel)  ( IAStream* po , PFNNOTIFY pfn , void * pUser );
};

この構造体はただの構造体で、これだけを見てもなぜ仮想テーブルと呼ばれているのかさっぱりわからない。
この構造体が一体どんな意味を持つのか、それを考えてみる。


この構造体を使うと、どんな効果があるのか。
まず、IApplet 構造体や IAStream 構造体の定義を見てみる。

struct IApplet{
    IAppletVtbl*    pvt;
};

struct IAStream{
    IAStreamVtbl*   pvt;
};

これだけだ。
BREW インターフェースオブジェクトと呼ばれているものは全て、このように仮想テーブルへのポインタを、構造体の先頭に持っている。
そして、IAPPLET_AddRef() や、IASTREAM_AddRef()、あと IBASE_AddRef() は次のように定義されている。

#define IAPPLET_AddRef(p)        ((IApplet*)p)->pvt->AddRef(p)
#define IASTREAM_AddRef(p)       ((IAStream*)p)->pvt->AddRef(p)
#define IBASE_AddRef(p)          ((IBase*)p)->pvt->AddRef(p)

3つとも、仮想テーブルの AddRef() を呼び出しているだけだ。


情報がバラバラでよくわからないかもしれない。
ところが、ここである事実を利用すると、おもしろいことが出来るようになる。
全ての AddRef() という関数が、仮想テーブルの先頭で定義されていることだ。
また、その次には必ず Release() という関数が定義されている。
次のコードを考えてみよう。

IApplet* pIApplet;
IAPPLET_AddRef( pIApplet );
IBASE_AddRef( pIApplet );

この2つのマクロ呼び出しの違いは何だろうか。
実は、違いは何もない。
上記の定義を見てみれば分かると思うが、両方とも IAppletVtbl の先頭の関数、AddRef() を呼び出しているのだ。
IAStream だろうが IBitmap だろうが IFile だろうが ITextCtl だろうが全て同じ。これらの仮想テーブル IAStreamVtbl, IBitmapVtbl, IFileVtbl, ITextCtlVtbl の先頭には、AddRef(), その次には Release() が定義されているのだ。
つまり、IASTREAM_AddRef(), IBITMAP_AddRef(), IFILE_AddRef(), ITEXTCTL_AddRef() は、全て IBASE_AddRef() に置き換えることが出来るのだ。


そしてこれがどうおもしろいかというと、全て同じ IBASE_AddRef() を使っているのに、呼び出している関数は異なる、という点だ。
昨日の AEEApplet_New() の関数内に、次の記述があった。

    appFuncs->AddRef      = AEEApplet_AddRef;

これは、IAppletVtbl の AddRef に AEEApplet_AddRef 関数へのポインタをセットしているところだ。
こうすると、IAPPLET_AddRef( pIApplet ) は、AEEApplet_AddRef() 関数を呼び出すようになる。
他の BREW インターフェースオブジェクトも同じで、これらは ISHELL_CreateInstance() の中で、インターフェースオブジェクト毎に異なる関数へのポインタを、AddRef にセットしているのだ。
こうすることにより、同じ IBASE_AddRef() を使っても、実際に呼び出す関数を別にすることが出来るのだ。


仮想テーブルというのは、「同じ IBASE_AddRef() を使っても、実際に呼び出す関数を別にする」ための仕組みで、これを使えば、抽象的な処理を行うことが出来るようになる。
実際に、BREW 開発者はこの恩恵にあずかっている。