処理を一時中断できるライブラリを組もう

C++での美しいライブラリの組み方あれこれの前にC言語でのライブラリの組み方で私がいままで行ってきた組み方での失敗例をお見せしよう。


DKC_EXTERN int WINAPI dkcLZWEncode(DKC_LZW *ptr,DKC_LZW_HEADER *ph,
BYTE *dest,size_t dsize,const BYTE *src,size_t ssize,
size_t CloseProcessSize,ULONG sig,ULONG option);
この関数はLZWで圧縮する関数だが、処理がこの関数ひとつで完結してしまうように設計されている。すなわち、srcバッファで指定したサイズのものしか圧縮しないのだ。(見ればあたりまえだが)バッファに収まりきらないサイズのファイルを処理するときには非効率になってしまう。(私は詳しく説明できないが、どうも動的辞書?を毎処理毎に作成するから上手く利用できないためらしい?でしょうか?ねぇ?)
以下のような設計が望ましい

///SHA1の生ハッシュ値(binary)に必要なバッファサイズ
#define SHA1_BIN_BUFFER_SIZE (SHA_HASH * 4)
///SHA1ハッシュ値文字列に必要なバッファサイズ
#define SHA1_STR_BUFFER_SIZE (SHA_HASH * 8 + 1)

#define SHA160_BIN_BUFFER_SIZE SHA1_BIN_BUFFER_SIZE

#define SHA160_STR_BUFFER_SIZE SHA1_STR_BUFFER_SIZE


typedef struct dkc_SHA1{
DWORD m_dwH[SHA_HASH];
DWORD m_dwLNumBits;
DWORD m_dwHNumBits;
DWORD m_aBlock[SHA_BLOCK];
int m_nNumChr;
BYTE mFinalized;
}DKC_SHA1,DKC_SHA160;


/*!
@return DKC_SHA1構造体への確保したメモリ領域
@note
すでにdkcSHA1Init()は呼ばれています。
*/

DKC_EXTERN DKC_SHA1 *WINAPI dkcAllocSHA1();
///@return dkcAllocSHA1()から取得した領域を初期化する。
DKC_EXTERN void WINAPI dkcSHA1Init(DKC_SHA1 *);
/*!
@param p[in][out] dkcAllocSHA1()で取得したポインタ
@param pBuffer[in] 読み取るバッファへのポインタ
@param dwSize[in] バッファにアクセスしてOKなサイズ
*/

DKC_EXTERN void WINAPI dkcSHA1Load(DKC_SHA1 *p,
const BYTE *pBuffer,DWORD dwSize);


/*!
@param p[in][out] dkcAllocSHA1()で取得したポインタ
@note
dkcSHA1FinalDigest()を使用する事を奨励します。
*/

DKC_EXTERN void WINAPI dkcSHA1Final(DKC_SHA1 *p);
/*!
@param p[in][out] dkcAllocSHA1()で取得したポインタ
@param buff[out] 書き込むバッファへのポインタ
@param size[in] buffのサイズ
@return 成功したらedk_SUCCEEDEDが返る
*/

DKC_EXTERN int WINAPI dkcSHA1DigestStr(DKC_SHA1 *p,
char *buff,size_t size);
///binaryハッシュ値 版 @see dkcSHA1DigestStr()
DKC_EXTERN int WINAPI dkcSHA1Digest(DKC_SHA1 *p,
BYTE *buff,size_t size);

/*!
@see 引数、戻り値についてはdkcSHA1Digest()と同じです。
@note
dkcSHA1Final()とdkcSHA1Digest()を使用するより、この関数の使用を奨励します。
*/

DKC_EXTERN int WINAPI dkcSHA1FinalDigestStr(DKC_SHA1 *p,
char *buff,size_t size);

///binaryハッシュ値 版 @see dkcSHA1FinalDigestStr()
DKC_EXTERN int WINAPI dkcSHA1FinalDigest(DKC_SHA1 *p,
BYTE *buff,size_t size);

/*!
@param p[in][out] dkcAllocSHA1()で取得したポインタへのポインタ
@return 上手く開放できたらedk_SUCCEEDED
*/

DKC_EXTERN int WINAPI dkcFreeSHA1(DKC_SHA1 **p);

特に注目すべきところはdkcSHA1Load()関数である。
これは初期化の後に何回も呼び出せるところにある。
dkcSHA1Load()でデータをSHA1機構にロード(計算)した後、salt値をdkcSHA1Load()でロードすることが出来る。そう。これのような扱いの出来るLZW圧縮機構を作るべきだった。
なので、今設計を見直して組みなおしているところだ。このSHA1機構のように組みなおせば今までの関数の処理互換で組みなおすことが出来る。このSHA1機構はラップされて前述のLZW圧縮関数インターフェイスのような

DKC_EXTERN int WINAPI dkcSecureHashCalculateBinaryDigest(
UINT hash_option,BYTE *digest_dest,size_t destsize,
const BYTE *data,size_t data_size
);

を組むことが出来るのですから。
よってC言語でライブラリを作る際どのような順番でラップしていくかを以下に示そうと思う。
  • ステート保存用変数の把握、それによる構造体の宣言。*1
  • 1ステップ毎( 先ほどの例ではdkcSHA1Load()のような事 )の処理の把握、それによる関数の宣言、それらの関数は*1で示した構造体にステートを保存できること。( 保存できることによって一時処理を中断したものでも後から処理を再開できるような機構にしておくこと )
  • 1ステップ毎の関数を一まとめにしたラッパー関数を組むこと。( 先ほどの例ではdkcSecureHashCalculateBinaryDigest() のような関数のこと Microsoftが提供するAPIはこのライブラリデザインの類が多い。 )