前回、バッファのインターフェースとその簡単な実装を一つ定義しましたが、考えてみると実際にバッファを使う場面では「オブジェクト内部のデータ」としてオブジェクトに包含される形で使われることも多いでしょう。そんな時、前回のような「外部からリサイズやポインタ操作ができる」バッファでは不都合です。そこで今回は、外部からリサイズやポインタ操作が不可能な固定長バッファインターフェースD2IFixedBufferと、その簡単な実装D2CFixedBufferクラスを定義してみることにしましょう。前回のようなリサイズやポインタ操作が可能なバッファインターフェースは、後で改めて定義し直すことにします。
また、バッファを1バイトずつ、あるいは指定した単位で線形に読み書きできるストリームとしても扱えるようストリームインターフェースD2IStreamも定義しておきます。
今回の固定長バッファインターフェースD2IFixedBufferクラスは、以下のようにストリームインターフェースとバッファの読み書きを行うインターフェースを多重継承するものとして定義します。
class D2IFixedBuffer:public D2IStream,public D2IBufferReader,public D2IBufferWriter;
このうちストリームインターフェースD2IStreamクラスは、バッファの読み込みを行うD2IBufferReaderインターフェースとD2IBufferWriterインタフェースをあわせたものです。
class D2IStreamReader { public: virtual UI8 readStreamByte()=0; virtual I32 readStream(BUF,I32)=0; virtual ~D2IStreamReader() {} }; class D2IStreamWriter { public: virtual bool writeStreamByte(UI8)=0; virtual I32 writeStream(const BUF,I32)=0; virtual ~D2IStreamWriter() {}; }; class D2IStream:public D2IStreamReader,public D2IStreamWriter { public: ~D2IStream() {} };
ストリームインターフェースでは、ストリームのサイズは直接見えずひたすら読み込み/書き込みを行う機能のみを定義しています。これは、ファイルやバッファなどあらかじめ大きさが想定できる領域の読み書きだけではなく、ネットワークなどストリームで読み書きできる領域を特定できない場面でも使用できるようにするためです。
固定長バッファインターフェースは、前回同様バッファの読み書きを行うインターフェース(D2IBufferReader/D2IBufferWriter)を持ちますが、リサイズやポインタ操作は除いてストリームインターフェースも継承するようにしました。
class D2IBufferInterface { public: virtual I32 getBufferSize() const=0; virtual ~D2IBufferInterface() {} }; class D2IBufferReader:public D2IBufferInterface { public: virtual BUF copyBuffer() const=0; virtual UI8 readBufferByte(I32) const=0; virtual I32 readBuffer(I32,BUF,I32) const=0; virtual bool equalBuffer(const BUF,I32) const=0; virtual bool equalBuffer(const D2IBufferReader *) const=0; ~D2IBufferReader() {} }; class D2IBufferWriter:public D2IBufferInterface { public: virtual bool writeBufferByte(I32,UI8)=0; virtual I32 writeBuffer(I32,const BUF,I32)=0; virtual bool fillBuffer(I32,UI8,I32)=0; ~D2IBufferWriter() {} }; class D2IFixedBuffer:public D2IStream,public D2IBufferReader,public D2IBufferWriter { public: virtual ~D2IFixedBuffer() {} };
以上で「固定長の領域をバッファ/ストリームとしてアクセスするインターフェースクラス」D2IFixedBufferができたので、その基本的な機能を以下のようなD2CFixedBufferクラスで実装してみましょう。
class D2CFixedBuffer:public D2IObject,public D2IFixedBuffer { public: D2CFixedBuffer(); D2CFixedBuffer(I32); D2CFixedBuffer(const BUF,I32); D2CFixedBuffer(const D2IBufferReader *); D2CFixedBuffer(const D2CFixedBuffer&); D2CFixedBuffer& operator=(const D2CFixedBuffer&); virtual ~D2CFixedBuffer(); //D2IObject virtual UI32 getObjectType() const; virtual UI8 getObjectVersion() const; virtual UI8 getDataType() const; virtual UI16 getEncoding() const; virtual void * getInterface(UI32); virtual CSTR getCSTR(); // D2IStreamReader virtual UI8 readStreamByte() throw(std::out_of_range); virtual I32 readStream(BUF,I32); // D2IStreamWriter virtual bool writeStreamByte(UI8); virtual I32 writeStream(const BUF,I32); // D2IBufferInterface virtual I32 getBufferSize() const; // D2IBufferReader virtual BUF copyBuffer() const; virtual UI8 readBufferByte(I32) const throw(std::out_of_range); virtual I32 readBuffer(I32,BUF,I32) const; virtual bool equalBuffer(const BUF,I32) const; virtual bool equalBuffer(const D2IBufferReader *) const; // D2IBufferWriter virtual bool writeBufferByte(I32,UI8); virtual I32 writeBuffer(I32,const BUF,I32); virtual bool fillBuffer(I32,UI8,I32); protected: virtual bool resetBuffer(I32); virtual I32 getStreamIndex() const; virtual I32 getStreamIndex(I32); virtual bool setStreamIndex(I32); virtual bool addStreamIndex(I32); virtual bool setBufferSize(I32); virtual BUF getBufferPointer() const; virtual bool setBufferPointer(const BUF); private: I32 m_i32StreamIndex,m_i32BufferSize; BUF m_bfBuffer,m_bfCSTR; };
バッファのサイズはコンストラクタで直接、あるいはコンストラクタで参照する領域を指定して間接的に設定し、以後このサイズは(代入演算子による他のバッファ代入を除いて)変更できないようにします。
D2CFixedBufferクラスの内部では、ポインタ操作にバッファのポインタm_bfBufferのアクセス関数getBufferPointer()/setBufferPointer()を使用しています。この関数で、最も基本的な機能であるバッファの読み書きを以下のように定義し、後はこの関数とバッファのサイズを取得するgetBufferSize()を使ってバッファやストリームの機能を実現してみました。
UI8 D2CFixedBuffer::readBufferByte(I32 i32Arg) const throw(out_of_range) { // 指定されたインデックスがバッファの領域外なら例外 if (i32Arg<0 || i32Arg>=getBufferSize()) throw out_of_range("無効なインデックス"); return getBufferPointer()[i32Arg]; } I32 D2CFixedBuffer::readBuffer(I32 i32Arg1,BUF bfArg,I32 i32Arg2) const { if (i32Arg1<0 || i32Arg1>=getBufferSize() || bfArg==NULL || i32Arg2<1) return 0; I32 i32Read=i32Arg2; // 実際に読み込めるサイズを検証・設定 if (i32Arg1+i32Read>getBufferSize()) i32Read=getBufferSize()-i32Arg1; memcpy(bfArg,getBufferPointer()+i32Arg1,i32Read); // 実際に読み込んだバイト数を返す return i32Read; } bool D2CFixedBuffer::writeBufferByte(I32 i32Arg,UI8 u8Arg) { if (i32Arg<0 || i32Arg>=getBufferSize()) return false; getBufferPointer()[i32Arg]=u8Arg; return true; } I32 D2CFixedBuffer::writeBuffer(I32 i32Arg1,const BUF bfArg,I32 i32Arg2) { if (i32Arg1<0 || i32Arg1>=getBufferSize() || bfArg==NULL || i32Arg2<1) return 0; I32 i32Write=i32Arg2; // 実際に書き込めるサイズを検証・設定 if (i32Arg1+i32Write>getBufferSize()) i32Write=getBufferSize()-i32Arg1; memcpy(getBufferPointer()+i32Arg1,bfArg,i32Write); // 実際に書き込んだバイト数を返す return i32Write; }
ストリームの読み書きは、バッファに「現在の読み書き位置」を設定し、この読み書き位置から読み書きを行う機能です。読み書きの位置は任意に設定することはできず、読み書きを行うとその分だけ読み書き位置が移動し、次に行う読み書きは今読み書きをした次の位置から行われます。
今回は、この読み書き位置をあらわすメンバ変数m_i32StreamIndexを用意し、アクセス関数getStreamIndex()/addStreamIndex()で操作しながらストリームの機能を実装しました。このうち、getStreamIndex(I32 i32Arg)は、現在のm_i32StreamIndexを返すと同時にi32Argだけm_i32StreamIndexを加算するものです。
UI8 D2CFixedBuffer::readStreamByte() throw(out_of_range) { if (getStreamIndex()>=getBufferSize()) throw out_of_range("無効なストリームインデックス"); return readBufferByte(getStreamIndex(1)); } I32 D2CFixedBuffer::readStream(BUF bfArg,I32 i32Arg) { I32 i32Read=readBuffer(getStreamIndex(),bfArg,i32Arg); addStreamIndex(i32Read); return i32Read; } bool D2CFixedBuffer::writeStreamByte(UI8 u8Arg) { return writeBufferByte(getStreamIndex(1),u8Arg); } I32 D2CFixedBuffer::writeStream(const BUF bfArg,I32 i32Arg) { I32 i32Write=writeBuffer(getStreamIndex(),bfArg,i32Arg); addStreamIndex(i32Write); return i32Write; }
インターフェースの取得やオブジェクト情報の取得などD2IObjectの実装は、基本的に前回と同様に行います。
UI32 D2CFixedBuffer::getObjectType() const { I32 i32ID=VER_D2_0 << 24; i32ID+=TYPE_BUF << 16; i32ID+=ENCODE_NONE; return i32ID; } UI8 D2CFixedBuffer::getObjectVersion() const { return VER_D2_0; } UI8 D2CFixedBuffer::getDataType() const { return TYPE_BUF; } UI16 D2CFixedBuffer::getEncoding() const { return ENCODE_NONE; } void * D2CFixedBuffer::getInterface(UI32 u32Arg) { switch(u32Arg) { case ID_D2IObject: return (D2IObject *)this; case ID_D2IStreamReader: return (D2IStreamReader *)this; case ID_D2IStreamWriter: return (D2IStreamWriter *)this; case ID_D2IStream: return (D2IStream *)this; case ID_D2IBufferReader: return (D2IBufferReader *)this; case ID_D2IBufferWriter: return (D2IBufferWriter *)this; case ID_D2IFixedBuffer: return (D2IFixedBuffer *)this; } return NULL; } CSTR D2CFixedBuffer::getCSTR() { if (m_bfCSTR!=NULL) { delete [] m_bfCSTR; m_bfCSTR=NULL; } if (getBufferSize()<1) return NULL; m_bfCSTR=new UI8[getBufferSize()+1]; memcpy(m_bfCSTR,getBufferPointer(),getBufferSize()); m_bfCSTR[getBufferSize()]=0; return (CSTR)m_bfCSTR; }
今回のプログラムは、D2CFixedBufferとインターフェースのテストプログラムです。最初にストリームで5文字書き込み、さらに6文字目と7文字目にバッファから書き込みます。続いて、D2IStreamWriterインターフェースを取得してストリームに一文字書き込んで表示してみると、6文字目がストリームの結果で上書きされていますので、ストリームは正常に機能しているようです。