前説
LZMA SDKには、
typedef struct ISeqInStream ISeqInStream;
struct ISeqInStream
{
SRes (*Read)(const ISeqInStream *p, void *buf, size_t *size);
};
などの構造体が存在し、このポインタを引数に渡す関数が大半である。
なぜstruct内の関数ポインタがコールバックなのにわざわざstructのポインタを受け取らなければならないのか。
これはインターフェースを実現するためである。
方法
- 呼び出し側はこのインターフェースを先頭に持った構造体を作成する。
- 構造体の先頭要素のポインタを渡す。
- コールバックは インターフェースポインタをもとの構造体のポインタにキャスト し、内部にアクセスする。
- これはインターフェースが先頭にあることによりアドレスが同じになるから可能なことである。
typedef struct{
ISeqInStream p;
FILE *f;
} Treader;
static SRes reader(const ISeqInStream *p, void *buf, size_t *size){
Treader *r=(Treader*)p;
*size=fread(buf,1,*size,r->f);
return 0;
}
Treader Sreader={{reader},stdin};
XzDecMt_Decode(h,...,&Sreader.p,...);
感想
- 「インターフェースポインタをもとの構造体のポインタにキャスト」って発想、斬新ですね、こういうことをしているライブラリを私は他に知らないというか。
- LZMA SDKの使い方はやはり難しい…C++側も難解なのにC言語側にもその難しいのを持ってこようとしているので輪をかけて厳しくなっている…
例(XZ圧縮展開)
XZの圧縮展開プログラムを示す。展開部はCXzUnpackerによりzlib-likeな展開もできるが、圧縮は上記のインターフェースを用いることが必須である。
# compat.hは7bgzfとかから拾ってください、全部同じですので…
// gcc -O2 -DSTANDALONE -D_7ZIP_ST src/unxz.c lib/lzma/C/{7zCrc,7zCrcOpt,Sha256,Alloc,LzmaDec,LzmaEnc,LzFind,Lzma2Dec,Lzma2Enc,Xz,XzDec,XzEnc,XzCrc64,XzCrc64Opt,Bra,Bra86,BraIA64,Delta,CpuArch}.c
#include "../lib/lzma/C/Alloc.h"
#include "../lib/lzma/C/Xz.h"
#include "../lib/lzma/C/XzEnc.h"
#include "../lib/lzma/C/XzCrc64.h"
#include "../lib/lzma/C/7zCrc.h"
#include "../compat.h"
#include <stdio.h>
/// stream interface ///
// I really think this is abuse of pointer, but I also think this is a limitation of C.
// Sreader={{reader},fin}
typedef struct{
ISeqInStream p;
FILE *f;
} Treader;
static SRes reader(const ISeqInStream *p, void *buf, size_t *size){
Treader *r=(Treader*)p;
*size=fread(buf,1,*size,r->f);
return 0;
}
// Swriter={{writer},fout}
typedef struct{
ISeqOutStream p;
FILE *f;
} Twriter;
static size_t writer(const ISeqOutStream *p, const void *buf, size_t size){
Twriter *r=(Twriter*)p;
return fwrite(buf,1,size,r->f);
}
typedef struct{
ICompressProgress p;
} Tprogress;
static SRes progress(const ICompressProgress *p, UInt64 inSize, UInt64 outSize){
return 0;
}
int zunxz(const int argc, const char **argv){
CrcGenerateTable();//Crc64GenerateTable(); //why the fuck do I need to initialize crc table?
Treader Sreader={{reader},stdin};
Twriter Swriter={{writer},stdout};
Tprogress Sprogress={{progress}};
CXzDecMtProps props;
CXzStatInfo xzstat;
CXzDecMtHandle h=XzDecMt_Create(&g_Alloc, &g_MidAlloc);
XzDecMtProps_Init(&props);
XzStatInfo_Clear(&xzstat);
int isMT=0;
XzDecMt_Decode(h,&props,NULL,0,&Swriter.p,&Sreader.p,&xzstat,&isMT,&Sprogress.p);
XzDecMt_Destroy(h);
return 0;
}
static int encode(FILE *fin,FILE *fout,int level){
CrcGenerateTable();//Crc64GenerateTable(); //why the fuck do I need to initialize crc table?
Treader Sreader={{reader},fin};
Twriter Swriter={{writer},fout};
Tprogress Sprogress={{progress}};
CXzProps props;
CXzEncHandle h=XzEnc_Create(&g_Alloc, &g_BigAlloc);
XzProps_Init(&props);
props.lzma2Props.lzmaProps.level = level;
XzEnc_SetProps(h,&props);
XzEnc_Encode(h,&Swriter.p,&Sreader.p,&Sprogress.p);
XzEnc_Destroy(h);
return 0;
}
#ifdef STANDALONE
int main(const int argc, const char **argv){
initstdio();
#else
int zxz(const int argc, const char **argv){
#endif
char mode,level;
if(argc<2)goto argerror;
mode=argv[1][0],level=argv[1][1];
if(!mode)goto argerror;
if(mode=='-')mode=argv[1][1],level=argv[1][2];
if(mode!='e'&&mode!='c'&&mode!='d')goto argerror;
if(isatty(fileno(stdin))&&isatty(fileno(stdout)))goto argerror;
return mode=='d'?zunxz(argc,argv):encode(stdin,stdout,level?level-'0':9);
argerror:
fprintf(stderr,"zxz e/d < in > out\nYou can also use -e,-c,-d.\n");
return -1;
}