zlibでPDF内の圧縮データを解凍するDLLを作成



# はじめに 
PDFの内部にあるDEFLATE(圧縮)された文章を解凍するにあたり適切なWindows APIが見当たらなかったので作ってみました。過去に特許や法律絡みでゴタゴタがあった分野です。

# DLLとは？
ステートレス(状態を持たない)な関数の、開発言語に依存せす、様々なアプリケーションから使用され、動作が長期に渡り保障されるＣ関数の集合体です。

# DLLの定義ヘッダ (PDFDeflate.h)
DLLEXPORTのdefineはいつものやつです。DLL側とAPP側ではDLLEXPORTの定義が変わります。PDFDCompressは圧縮、PDFDDeflateは解凍です。DLL内部の情報を外には出さないように、HANDLEだけを返しています。HANDLEはvoid*とtypedefされています。DLL内部でメモリを確保してAPPへ返す形なのでメモリの解放はPDFDCloseで行います。処理されたデータはPDFGet、その長さはPDFGetLengthで取得できます。

    #ifdef _WINDLL 
    #define DLLEXPORT     extern "C" __declspec( dllexport )
    #else
    #define DLLEXPORT     extern "C"
    #endif

    DLLEXPORT HANDLE WINAPI PDFDCompress(const BYTE* p, DWORD len);
    DLLEXPORT HANDLE WINAPI PDFDDeflate(const BYTE* p, DWORD len);
    DLLEXPORT bool  WINAPI PDFDClose(HANDLE h);
    DLLEXPORT BYTE* WINAPI PDFDGet(HANDLE h);
    DLLEXPORT DWORD WINAPI PDFDLength(HANDLE h);

# DLLの内部1 (Entry.cpp)
　_XのStructは内部でだけ有効で、外には出さないのでhでなくCPP内で定義します。HANDLEはコンパイラの型CHECKが効かないのため、簡易なエラー防止機構としてmagicをつけました。

    //Entry.cpp
    #include "pch.h"
    #include "PDFDeflate.h"
    #include "s1.h"

    struct _X
    {
        int magic;
        BYTE* memory;
        DWORD len;
    };

    #define MAGICNUM 20201002

    bool zlib(bool isCompress, BYTE* in_mem, DWORD in_mem_len, ministream* sm);

    HANDLE PDFDZlib(bool isCompress, const BYTE* p, DWORD len)
    {
        ministream sm;
        
        if (zlib(isCompress, (BYTE*)p,len,&sm))
        {
            _X* x = new _X();
            x->magic = MAGICNUM;
            x->len = sm.length();
            x->memory = new BYTE[x->len];

            memcpy(x->memory, sm.get(), sm.length() );

            return x;
        }
        return nullptr;
    }

    DLLEXPORT HANDLE WINAPI PDFDCompress(const BYTE* p, DWORD len)
    { 
        return PDFDZlib(true,p,len); 
    }
    DLLEXPORT HANDLE WINAPI PDFDDeflate(const BYTE* p, DWORD len)
    { 
        return PDFDZlib(false,p,len); 
    }

    DLLEXPORT bool WINAPI PDFDClose(HANDLE h)
    {
        if (h)
        {
            _X* px = (_X*)h;
            if (px->magic == MAGICNUM)
            {
                delete [] px->memory;
                delete px;
                return true;
            }
        }
        return false;
    }
    DLLEXPORT BYTE* WINAPI PDFDGet(HANDLE h)
    {
        if (h)
        {
            _X& x = *(_X*)h;
            if (x.magic == MAGICNUM)
                return x.memory;
        }
        return nullptr;
    }
    DLLEXPORT DWORD WINAPI PDFDLength(HANDLE h)
    {
        if (h)
        {
            _X& x = *(_X*)h;
            if (x.magic == MAGICNUM)
                return x.len;
        }
        return 0;
    }


# DLLの内部2 (s1.cpp)
zlibの呼び出し、ほとんどコピペですが動的にメモリを拡張するように改造しています。

    #include "pch.h"
    #include "zlib.h"

    #include "s1.h"

    bool decompress(BYTE* in_mem, DWORD in_mem_len, ministream* sm)
    {
        char outbuf[OUTBUFSIZ];
        int count, status;
        z_stream z = { 0 };    

        if (inflateInit(&z) != Z_OK) {
            return false;
        }

        z.next_out = (BYTE*)outbuf;
        z.avail_out = OUTBUFSIZ; 
        status = Z_OK;
        DWORD readoff = 0;
        while (status != Z_STREAM_END)
        {
            if (z.avail_in == 0) {
                z.next_in = in_mem + readoff;           
                z.avail_in = min(INBUFSIZ,in_mem_len);
                readoff += z.avail_in;
                in_mem_len -= z.avail_in;
            }
            status = inflate(&z, Z_NO_FLUSH); 
            if (status == Z_STREAM_END) break;
            if (status != Z_OK) { 
                return false;
            }
            if (z.avail_out == 0) {

                sm->write((BYTE*)outbuf, OUTBUFSIZ);

                z.next_out = (BYTE*)outbuf;
                z.avail_out = OUTBUFSIZ;
            }
        }

        if ((count = OUTBUFSIZ - z.avail_out) != 0)
            sm->write((BYTE*)outbuf, count);

        if (inflateEnd(&z) != Z_OK) {
            return false;
        }

        return true;
    }
    bool compress(BYTE* in_mem, DWORD in_mem_len, ministream* sm)
    {
        char outbuf[OUTBUFSIZ];         
        int count, status;
        z_stream z = { 0 };

        int lank_compress = Z_DEFAULT_COMPRESSION;

        if (deflateInit(&z, lank_compress) != Z_OK) {
            return false;
        }

        z.next_out = (BYTE*)outbuf;
        z.avail_out = OUTBUFSIZ;
        status = Z_OK;

        int flush = Z_NO_FLUSH;

        DWORD readoff = 0;
        while (status != Z_STREAM_END)
        {
            if (z.avail_in == 0) {
                z.next_in = in_mem + readoff;
                z.avail_in = min(INBUFSIZ,in_mem_len);            
                readoff += z.avail_in;
                in_mem_len -= z.avail_in;

                if (z.avail_in < INBUFSIZ) flush = Z_FINISH;
            }
            status = deflate(&z, flush);
            if (status == Z_STREAM_END) break;
            if (status != Z_OK) {
                return false;
            }
            if (z.avail_out == 0) {

                sm->write((BYTE*)outbuf, OUTBUFSIZ);

                z.next_out = (BYTE*)outbuf; 
                z.avail_out = OUTBUFSIZ;
            }
        }

        if ((count = OUTBUFSIZ - z.avail_out) != 0)
            sm->write((BYTE*)outbuf, count);

        if (deflateEnd(&z) != Z_OK) {
            return false;
        }

        return true;
    }

    bool zlib(bool isCompress, BYTE* in_mem, DWORD in_mem_len, ministream* sm)
    {
        return (isCompress ? compress(in_mem, in_mem_len, sm) : decompress(in_mem, in_mem_len, sm));
    }

# zlib
zlibからVSのソースファイルとして追加するのは以下です。すべてで「プリコンパイル済みヘッダを使用しない」を設定します。ソースは無修正です。

    // zlib 1.2.11
    adler32.c
    crc32.c
    deflate.c
    infback.c
    inffast.c
    inflate.c
    inftrees.c
    trees.c
    zutil.c

# DLLの内部3 (s1.h)
自動でメモリを広げる簡易な動的バッファーです。

    #pragma once
    #define INBUFSIZ   4096                 // 入力バッファサイズ（任意）
    #define OUTBUFSIZ  4096                 // 出力バッファサイズ（任意）

    class ministream
    {
    public:
        ministream() :mem(0), buflen(0), wlen(0) {}

        void write(BYTE* cb, DWORD cblen)
        {
            if (mem == 0)
            {
                buflen = OUTBUFSIZ;
                mem = new BYTE[buflen];
            }

            while (1)
            {
                if (wlen + cblen < buflen)
                {
                    memcpy(mem + wlen, cb, cblen);
                    wlen += cblen;
                    break;
                }
                else
                {
                    expand();
                }
            }
        }

        ~ministream() { close(); }

        DWORD length() const { return wlen; }

        void copy(BYTE* dst) { memcpy(dst, mem, wlen); }

        void close()
        {
            if (mem)
            {
                delete[] mem;
                wlen = buflen = 0;
                mem = nullptr;
            }
        }
        const BYTE* get() const { return mem; }
    private:
        void expand()
        {
            buflen = buflen * 2;
            BYTE* mem2 = new BYTE[buflen];
            memcpy(mem2, mem, wlen);
            delete[] mem;
            mem = mem2;
        }
    private:
        BYTE* mem;
        DWORD wlen, buflen;
    };


#　その他のソース

pch.h

    #ifndef PCH_H
    #define PCH_H
    #include "framework.h"
    #include <Windows.h>
    #endif 

pch.cpp

    #include "pch.h"


dllmain.cpp

    #include "pch.h"

    BOOL APIENTRY DllMain( HMODULE hModule,
                        DWORD  ul_reason_for_call,
                        LPVOID lpReserved
                        )
    {
        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
        }
        return TRUE;
    }

# ビルド
VisualStudio2019のテンプレートからダイナミックリンクライブラリ(DLL)選択して、上記のソースを追加してビルドします。「追加のインクルードディレクトリ」として"zlib-1.2.11"を追加。Cファイルのプリコンパイルヘッダを使用しない、これでno-errorでビルドできました。

# 使用例
PDFの場合、FlateDecodeとしてあるstreamに使用。

    20 0 obj 
        /Filter /FlateDecode
        /Length 256
        /Type /Stream
        stream

        ...compreesed data1

        endstream
        endobj

上のstreamからendstreamまでの256バイトを読み込んで、DLLへ投げるだけ。

    HANDLE hm = PDFDDeflate((BYTE*)data1,256);
	if ( hm )
	{
		LPCSTR data = (LPCSTR)PDFDGet(hm);
		DWORD dlen = PDFDLength(hm);
        ...
		PDFDClose(hm);
	}


#　参照

    https://zlib.net/
    http://dencha.ojaru.jp/programs/pg_filer_04.html
    https://oku.edu.mie-u.ac.jp/~okumura/compression/comptest.c