Help us understand the problem. What is going on with this article?

C言語でインターフェースを実現する

More than 1 year has passed since last update.

前説

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;
}
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away