自前のストリームクラスに unzip ストリームを実装する
BREW には IUnzipAStream というのがあるのだけれども、これは IUNZIPASTREAM_SetStream() で IAStream を継承したクラスを渡す必要がある。
しかし、もし何らかの理由(C++ の継承関係を持っている同期的なストリームクラスが欲しい!)で自前のストリームクラスを作っていた場合には IAStream を継承していないので IUNZIPASTREAM_SetStream() に渡すことができない。
こういう場合には以下みたいにするとよかったりする。
// 自前の入力ストリーム class istream : noncopyable { public: virtual uint32 read(void* buf, uint32 size) = 0; }; class unzip_stream : public istream { private: // istream を IAStream 互換のインターフェースに変換するためクラス class StreamProxy : public IAStream { public: StreamProxy(istream& is) : is_(is) { vtbl_.AddRef = &AddRef; vtbl_.Release = &Release; vtbl_.Read = &Read; vtbl_.Readable = &Readable; vtbl_.Cancel = &Cancel; this->pvt = &vtbl_; } public: // override in IBase static uint32 AddRef(IAStream*) { return 1; } static uint32 Release(IAStream*) { return 2; } // override in IAStream static int32 Read(IAStream* pIAStream, void* pBuffer, uint32 dwCount) { return static_cast<StreamProxy*>(pIAStream)->is_.read(pBuffer, dwCount); } // Read() で AEE_STREAM_WOULDBLOCK を返さない限りこれらは使用されないはず。 static void Readable(IAStream*, PFNNOTIFY, void*) { } static void Cancel(IAStream*, PFNNOTIFY, void*) { } private: AEEVTBL(IAStream) vtbl_; // IAStream の仮想テーブル istream& is_; }; private: StreamProxy is_; brew_ptr<IUnzipAStream> unzip_; public: unzip_stream(IShell* sh, istream& is) : is_(is) , unzip_(static_pointer_cast<IUnzipAStream>(create_instance(sh, AEECLSID_UNZIPSTREAM))) { IUNZIPASTREAM_SetStream(unzip_.get(), &is_); } virtual uint32 read(void* buf, uint32 size) { uint32 count = 0; while (true) { int n = IUNZIPASTREAM_Read(unzip_.get(), static_cast<byte*>(buf) + count, size - count); count += n; if (n <= 0 || count >= size) break; } return count; } };
StreamProxy を仲介役にすることによって IUNZIPASTREAM_SetStream() を可能にしてる。
うん、単なる Proxy パターン。串さんは結構便利。
ちょっとしたコツとして、AddRef と Release の実装で定数を返す。こうすることによって参照数のための余分な領域と動的なメモリ確保が必要なくなってリソースの節約になる。
ただ、これは COM だとよく使われていて合法なコードなんだけど、BREW として合法なのかどうかは知らない。でも動くからいいじゃん!