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 開発者はこの恩恵にあずかっている。