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

BREW 開発者は、仮想テーブルの恩恵にあずかっている。


例えば、IAStreamVtbl, IMemAStream, IFileVtbl の定義を見てみよう。

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 );
};

struct IMemAStreamVtbl{
    uint32  (*AddRef)  ( IMemAStream* po );
    uint32  (*Release) ( IMemAStream* po );
    void    (*Readable)( IMemAStream* po , PFNNOTIFY pfn , void* pUser );
    int32   (*Read)    ( IMemAStream* po , void* pDest , uint32 nWant );
    void    (*Cancel)  ( IMemAStream* po , PFNNOTIFY pfn , void * pUser );
    
    void    (*Set)     ( IMemAStream* po , byte* pBuff , uint32 dwSize , uint32 dwOffset ,
        boolean bSysMem );
    void    (*SetEx)   ( IMemAStream* po , byte* pBuff , uint32 dwSize , uint32 nOffset ,
        PFNNOTIFY pUserFreeFn , void* pUserFeeData );
};

struct IFileVtbl{
    uint32  (*AddRef)  ( IFile* po );
    uint32  (*Release) ( IFile* po );
    void    (*Readable)( IFile* po , PFNNOTIFY pfn , void* pUser );
    int32   (*Read)    ( IFile* po , void* pDest , uint32 nWant );
    void    (*Cancel)  ( IFile* po , PFNNOTIFY pfn , void * pUser );
    
    uint32  (*Write)       ( IFile* pIFile , const void * pBuffer , uint32 dwCount );
    int     (*GetInfo)     ( IFile* pIFile , FileInfo* pInfo );
    int32   (*Seek)        ( IFile* pIFile , FileSeekType seek , int32 position );
    int     (*Truncate)    ( IFile* pIFile , uint32 truncate_pos );
    int     (*GetInfoEx)   ( IFile* po , AEEFileInfoEx * pi );
    int32   (*SetCacheSize)( IFile* po , int nSize );
};

注目すべき点は、IMemAStreamVtbl と IFileVtbl の先頭の5つの関数へのポインタが、IAStreamVtbl と同じということだ。BREW インターフェースオブジェクトを作った人は、こうなるように意図的に仮想テーブルの配置を決めているのだ。
これが何を意味するかというと、

IFILE_Read( pIFile , buf , size );
IASTREAM_Read( pIFile , buf , size );

この2つのマクロ呼び出しは、同じ関数を呼び出すことになるということだ。
また、

IASTREAM_Read( pIMemAStream , buf , size );
IASTREAM_Read( pFile , buf , size );

これは、異なる関数を呼び出すことになる。


これにどんな利点があるかというと、例えば IUNZIPASTREAM_SetStream() の定義は次のようになっている。

void IUNZIPASTREAM_SetStream( IUnzipAStream* pIUnzipAStream , IAStream* pInIAStream );

2番目の引数に、IAStream へのポインタを渡す必要がある。
この IUNZIPASTREAM_SetStream() の内部ではきっと、

IASTREAM_Read( pInIAStream , buf , size );

とか、そういう記述が書かれているに違いない。
ということは、この IAStream へのポインタというのは、仮想テーブルの配置が同じ IMemAStream や IFile でも構わないということなのだ。
pIMemAStream を渡しても、IASTREAM_Read() は IMEMASTREAM_Read() と同じ意味なので大丈夫だろうし、pIFile を渡しても、IASTREAM_Read() は IFILE_Read() と同じ意味なので大丈夫だろう。
IASTREAM_Read() は、中の関数がどんな実装になっているかは知らないけれども、とにかく buf の中に size バイト入れてくれる関数なのだ。
それがメモリからなのかファイルからなのか、それとも外部メモリからなのかネットワーク上なのか知らないが、とにかく buf の中に size バイトセットする関数なのだ。
本来であれば、メモリ上から読み込んで unzip する IUNZIPASTREAM_SetMemory() やら、ファイル上から読み込む IUNZIPSTREAM_SetFile() やらを作ったりする必要があるのだけれども、これだと IUNZIPASTREAM_SetStream() だけで出来るようになるのだ。