プログラム全般」カテゴリーアーカイブ

Python : zip にアーカイブした pyz を直接実行できる専用 exe を作る

実行ファイルの置き換えに python を使用したときのメモです。Python 3 はプログラムコードを zip にアーカイブしたまま実行することができます。

●(1) Library として読み込む場合

テストコードを appmain.py, testmodule.py とします。

# appmain.py
import  testmodule

def main():
    testmodule.app()

if __name__ == '__main__':
    main()

# testmodule.py
def app():
    print( 'testmodule' )

そのまままとめて zip 圧縮可能で、Libpath に追加すると読み込むことができます。

### zip file を作る
import  zipfile

src_list= [
        'appmain.py',
        'testmodule.py',
    ]

with zipfile.ZipFile( 'applib.zip', 'w' ) as fo:
    for src in src_list:
        fo.write( src )


### zip file 内の code を実行する
import sys

sys.path.append( 'applib.zip' )

import  appmain
appmain.main()

embeddable 版の python3x.zip と同じように事前にコンパイルしておくこともできます。

●(2) zipapp (pyz) に変換してから実行する場合

zip ではなく zipapp (pyz) に変換すると script のように直接実行できるようになります。

予めサブフォルダ app を作って、その中に appmain.py, testmodule.py を入れておきます。下記のコードを実行すると pyz にまとめることができます。create_archive() 最初の引数 ‘app’ はフォルダ名です。

import	zipapp

zipapp.create_archive( 'app', 'app.pyz', '/usr/bin/env python3', 'appmain:main' )

変換出力した app.pyz はただの zip ですが、エントリポイントとして __main__.py が挿入されています。この pyz ファイルはそのまま python で実行できます。

python app.pyz

また unix 系 os の場合は実行属性をつけることで直接実行することができます。

$ chmod 755 app.pyz
$ ./app.pyz

Windows の場合そのまま実行することはできませんが、pyz 専用の exe を作る方法が zipapp のマニュアルで紹介されています。

●(3) pyz の起動用に exe を作る

zipapp — Manage executable Python zip archives

上記ページに載っている zastub.c をそのままコンパイルします。

// zastub.c
#define Py_LIMITED_API 1
#include "Python.h"

#define WIN32_LEAN_AND_MEAN
#include 

#ifdef WINDOWS
int WINAPI wWinMain(
    HINSTANCE hInstance,      /* handle to current instance */
    HINSTANCE hPrevInstance,  /* handle to previous instance */
    LPWSTR lpCmdLine,         /* pointer to command line */
    int nCmdShow              /* show state of window */
)
#else
int wmain()
#endif
{
    wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
    myargv[0] = __wargv[0];
    memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
    return Py_Main(__argc+1, myargv);
}

コンパイル方法もマニュアル通りです。下記のコードを実行してコンパイルできます。

# compile.py
from distutils.ccompiler import new_compiler
import distutils.sysconfig
import sys
import os
from pathlib import Path

def compile(src):
    src = Path(src)
    cc = new_compiler()
    exe = src.stem
    cc.add_include_dir(distutils.sysconfig.get_python_inc())
    cc.add_library_dir(os.path.join(sys.base_exec_prefix, 'libs'))
    # First the CLI executable
    objs = cc.compile([str(src)])
    cc.link_executable(objs, exe)
    # Now the GUI executable
    #cc.define_macro('WINDOWS')
    #objs = cc.compile([str(src)])
    #cc.link_executable(objs, exe + 'w')

if __name__ == "__main__":
    compile("zastub.c")

zastub.exe が作られるので、(2) で作った pyz とそのまま合成します。単純につなげるだけです。

copy /b zastub.exe + app.pyz app.exe

これで pyz を実行可能な app.exe ができました。そのまま実行できます。(python3.dll が必要)

app.exe

プログラムの内容によっては python の import でモジュールが見つからない場合があるかもしれません。その場合は予め検索パス設定しておいてください。環境変数 PYTHONPATH でも構いませんし、zastub.c の中で Py_SetPath() を使ってパスを与えることもできます。

zastub.c は python.exe 相当です。これが引数を何も加工していない状態になります。

int wmain()
{
    return  Py_Main( __argc, __wargv );
}

zastub.c では 1番目の引数に自分自身のパスを複製して挿入します。例えば引数が “arg1” なら「app.exe arg1」→ 「app.exe app.exe arg1」となります。自分自身である app.exe には app.pyz の内容がそのまま含まれているので、app.exe を pyz とみなしてそのまま実行できるわけです。

なお 1番目の引数を正しく渡すことができるなら、必ずしも exe に pyz を合成する必要がありません。例えば下のように直接 “app.pyz” を渡すことができます。

int wmain()
{
    wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
    memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
    myargv[0] = __wargv[0];
    myargv[1] = L"app.pyz";
    return Py_Main(__argc+1, myargv);
}

ただし若干問題もあります。上のコードはカレントディレクトリでなければ app.pyz を読み込むことができません。例えば他のフォルダからフルパスで app.exe を実行しようとすると app.pyz が見つからずにエラーになります。もし app.pyz を実行ファイルと同じ場所に置くなら、__wargv[0] や GetModuleFileNameW() を利用して app.pyz のパスを求めることができます。

↓ __wargv[0] から app.pyz のパスを求める。

#define Py_LIMITED_API 1
#include    

#define WIN32_LEAN_AND_MEAN
#include    

#ifdef WINDOWS
int WINAPI wWinMain(
    HINSTANCE hInstance,      /* handle to current instance */
    HINSTANCE hPrevInstance,  /* handle to previous instance */
    LPWSTR lpCmdLine,         /* pointer to command line */
    int nCmdShow              /* show state of window */
)
#else
int wmain()
#endif
{
    wchar_t *PYZ_NAME= L"app.pyz";      
    size_t  NAME_LENGTH= wcslen( PYZ_NAME ) + 1;
    size_t  length= wcslen( __wargv[0] ) + NAME_LENGTH;
    wchar_t *pyz_path= _alloca( (length + 2) * sizeof(wchar_t) );
    wcscpy_s( pyz_path, length, __wargv[0] );

    wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
    myargv[0] = __wargv[0];
    memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));

    wchar_t *eptr= pyz_path;
    for( wchar_t *ptr= pyz_path ; *ptr ; ptr++ ){
        if( *ptr == L'/' || *ptr == L'\\' ){
            eptr= ptr + 1;
        }
    }
    wcscpy_s( eptr, NAME_LENGTH, PYZ_NAME );

    myargv[1]= pyz_path;
    return Py_Main(__argc+1, myargv);
}

同じようにして exe の場所からライブラリの位置を特定し、Py_SetPath() することもできます。例えば embeddable 版を使うなら、Py_SetPath() に python3x.zip と dll/pyd フォルダの 2つを与えれば OK です。

Objective-C の Object を C++ で扱う (smart pointer)

iOS/OSX の API の多くは Objective-C の Interface となっています。
他のプラットフォームとのコード共有を考えるならば、
アプリケーション側はできるだけ普通の C/C++ で書きたいと思うかもしれません。
この場合 System の API 呼び出し部分を分離して、何らかの Wrapper 経由で
アクセスすることになります。

Applicaton     *.cpp : C++
Library header *.h   : C++ / Objective-C++
Library source *.mm  : Objective-C++

Wrapper Library のヘッダは C++ と Objective-C++ で共有されることになるので、
Objective-C の Object をそのまま記述することが出来ません。
ヘッダとソースで実装を分離して隠蔽すべきなのでしょうが、
扱う Class や Object が増えて結構手間がかかっていました。

おそらく一番簡単で確実な方法は、iOS/OSX の場合だけアプリケーション側の
.cpp (.cc) を Objective-C++ とみなしてコンパイルすることでしょう。

ですが、どうしても純粋な C++ としてコンパイルしたかったので、
C++ で Objective-C の Object を所有できるようにしてみました。

// IDPointer.h
class IDPointer {
    void*  iPointer;
public:
    IDPointer() : iPointer( NULL )
    {
    }
    ~IDPointer()
    {
        Clear();
    }
    void Clear();
#if __OBJC__
    void SetID( id obj )
    {
        Clear();
        iPointer= (__bridge_retained void*)obj;
    }
    id GetID() const
    {
        return  (__bridge id)iPointer;
    }
#endif
};
// IDPointer.mm
#import  "IDPointer.h"

void IDPointer::Clear()
{
    id  obj= (__brdige_transfer id)iPointer;
    obj= NULL;
    iPointer= NULL;
}

一見うまく動作するように見えますが、dealloc のタイミングが意図したものに
ならないことがあります。

void idpointer_test()
{
    @autoreleasepool {
      TempClass*  t0= [[TempClass alloc] init];
      IDPointer  ptr;
      ptr.SetID( t0 );

      TempClass*  p0= ptr.GetID();

      t0= NULL;
      p0= NULL;
      ptr.Clear();
      // --- (1)
    }
    // --- (2)
}

上の例は (1) ではなく (2) のタイミングで TempClass が dealloc されます。
原因は GetID() が id を返しているためで、ARC により autoreleasepool への
登録が行われているようです。
weak なポインタを返す場合、所有者の生存期間をコンパイラが保証できないため
autoreleasepool に委ねているものと思われます。
実際には autoreleasepool に入って欲しくないケースもあります。

IDPointer& operator=( const IDPointer& src )
{
    SetID( src.GetID() );
    return  *this;
}

例えば上のコードは SetID() が即座に retain しているため、
生存期間を考慮する必要がないのですが autoreleasepool に入ります。

void* のまま受け取ってから受け取り側が __bridge cast するか、または
__attribute__((ns_returns_retained)) をつけると autoreleasepool に
含まれないことがわかりました。

// IDPointer.h
class IDPointer {
    void*  iPointer;
public:
    IDPointer() : iPointer( NULL )
    {
    }
    ~IDPointer()
    {
        Clear();
    }
    IDPointer( const IDPointer& src );
    IDPointer& operator=( const IDPointer& src );
    void Clear();
#if __OBJC__
    explicit IDPointer( id obj )
    {
        iPointer= (__bridge_retained void*)obj;
    }
    void SetID( id obj )
    {
        Clear();
        iPointer= (__bridge_retained void*)obj;
    }
    __attribute__((ns_returns_retained)) id GetID() const
    {
        return  (__bridge id)iPointer;
    }
    IDPointer& operator=( id obj )
    {
        SetID( obj );
	return  *this;
    }
#endif
};
// IDPointer.mm
#import  "IDPointer.h"

IDPointer::IDPointer( const IDPointer& src )
{
    iPointer= (__bridge_retained void*)src.GetID();
}
IDPointer& IDPointer::operator=( const IDPointer& src )
{
    SetID( src.GetID() );
    return  *this;
}
void IDPointer::Clear()
{
    id  obj= (__brdige_transfer id)iPointer;
    obj= NULL;
    iPointer= NULL;
}

↑の場合は GetID() しても autoreleasepool に入ることがありません。

これで C++ 側でも比較的安全に Objective-C Object の所有や受け渡しを
行うことが出来ます。
Objective-C の Object の instance は ARC で管理されていますが、
C++ からは smart pointer (shared_ptr) として見えることになります。

使用例

// SampleAPI.h
class PlayerData : public IDPointer {
};
class Player {
    IDPointer  PlayerInstance;
public:
    void Init();
    void CreateData( PlayerData& data );
    void Play( PlayerData& data );
};
// SampleAPI.mm
void Player::Init()
{
    PlayerInstance= [MYPlayer newPlayer];
}

void Player::CreateData( PlayerData& data )
{
    data.SetID( [[MYPlayerData alloc] init] );
}

void Player::Play( PlayerData& data )
{
    id player= PlayerInstance.GetID();
    [player stop];
    [player playWithData:data.GetID()];
}

C++11 alignas/alignof メモリアライメント

SSE 等の SIMD 命令やキャッシュを意識したプログラムではメモリのアライメントを
考慮する必要があります。
これまではコンパイラ毎の拡張命令に頼っていましたが、
C++11 では言語仕様に含まれるようになりました。
メモリアクセスの同期命令も含めて、低レベルなメモリ命令を積極的に取り込んでいる印象です。

CC           alignas                         alignof
-----------------------------------------------------------------
VisualC++    __declspec(align(byte))         __alignof(type)
gcc/clang    __attribute__((aligned(byte))   __alignof__(type)
C++11        alignas(byte)                   alignof(type)

alignas はメモリ配置時のアライメントを宣言します。

// 変数宣言
__declspec(align(32)) int  array[8];      // VC
int  array[8] __attribute((aligned(32));  // gcc/clang
alignas(32) int  array[8];                // C++11

↑変数 array は 32byte 単位のアドレス境界に配置されます。

// 型宣言

// VC
__declspec(align(32)) struct AVECT { float pos[4]; };
struct __declspec(align(32)) AVECT { float pos[4]; };

// gcc/clang
struct AVECT { float pos[4]; } __attribute((aligned(32));
struct __attribute((aligned(32)) AVECT { float pos[4]; };

// C++11
struct alignas(32) AVECT { float pos[4]; };

// 使用時
AVECT position;

↑ position も 32byte 単位のアドレスに配置します。

VC と gcc/clang の attribute は若干書式に違いがあります。
alignas を VC のように struct の前に記述すると変数への修飾となり、
型宣言に含まれません。VC では含まれているようです↓。

alignas(32) struct AVECT2 { float pos[4]; }  va;

// VC Nov 2013 CTP : alignof(AVECT2) == 32
// gcc4.8/clang3.4 : alignof(AVECT2) == 4

static (global) に宣言した変数は静的にアドレスが求まるので、
コンパイル時 (link時) に解決します。
ただしプログラム (data segment) がロードされるアドレスが、
より大きなアドレス境界に配置していることが条件となります。

(1) 基準となるメモリアドレスのアライメント
(2) メモリを pack したときの整合性

つまりコンパイラ側が行うのは (2) と (1) の一部になります。

基準アドレス(1)の生成
------------------------------------------------
data    OS が行う
stack   実行時に行う (コンパイラが自動的に生成)
heap    ライブラリまたはユーザー任せ

stack に宣言した変数は、実行するまでアドレスがわからないので
実行時に端数を切り捨てるコードが埋め込まれます。

; clang x64 alignas(32)
    pushq   %rbps
    andq    $-32, %rsp  ; alignas(32)
    subq    $32, %rsp   ; int array[8]
; clang armv7l alignas(32)
    add    r11, sp, #8
    sub    sp, sp, #80
    bic    sp, sp, #31  ; alignas(32)

heap の場合は確保したメモリが alignment に従っていない可能性があるため
利用時にアドレス整合を意識しなければなりません。

alignof を用いるとコンパイル時に型情報を判断できるので、alignment に
対応したアロケータを作ることができます。

template
inline T* flNew( A0&&... a0 )
{
    return  new( malloc_aligned( sizeof(T), alignof(T) ) ) T( std::forward(a0)... );
}

template
inline void flDelete( T* ptr )
{
    ptr->~T();
    free_aligned( ptr );
}

この flNew() で作成した AVECT ↓は、宣言時に alignas(32) が指定されているため
32byte 単位で確保されます。(malloc_aligned が実装されていると仮定する)

AVECT* ap= flNew(); // alignas(32)

関連エントリ
VisualStudio と C++11 、コンパイラの違いなど
C++11 Lambda function ラムダ関数
C++11 Variadic Templates 可変長引数のテンプレート
スレッド同期命令の比較 C++11 とコンパイラ
C++11 Rvalue Reference 右辺値参照

VisualStudio と C++11 、コンパイラの違いなど

Visual C++ Compiler Nov 2012 CTP では動いていたけど、
VisualStudio 2013 以降コンパイルできなくなったコード。

struct List {
    int var_a, var_b;
    List( int a, int b ) : var_a( a ), var_b( b )
    {
    }
};

class Tree {
public:
    template
    T* Alloc( A0... a0 )
    {
        return  new T( a0... );
    }
};

class Compiler {
    Tree    tree;
public:
    template
    T* Alloc( A0... a0 )
    {
        return  tree.Alloc( a0... );
    }
};

...

Compiler compiler;
compiler.Alloc( 1, 2 );
VS2012 + Nov 2012 CTP : OK
VS2013                : internal error
VS2013 + Nov 2013 CTP : internal error
gcc 4.8.1             : OK
clang 3.2             : OK

template を分解して単純化して回避。

struct T0 {
    T0()= default;
    T0( const T0& )= default;
    T0( int a ){};
};

↑ T0 は VS ではまだ POD 扱いにならないようです。

                 std::is_pod
-----------------------------------
VS2013                 false
VS2013 + Nov 2013 CTP  false
gcc 4.8.1 (Linux)      true
clang 3.2 (Linux)      true

POD かどうかで、値渡し時に関数の呼び出し方が変わります。
sizeof が 8byte 未満でも register に入らず参照戻しになります。

struct T1 {
    T1()= default;
    T1( const T1& )= default;
    int var;
};
struct T2 {
    T2()= default;
    T2( const T2& )= default;
    T2( int a ){};
    int var;
};

T1 pod_test1( T1 a )
{
    return  a;
}
T2 pod_test2( T2 a )
{
    return  a;
}

void pod_test()
{
   T1 a1;
   T1 ret1= pod_test1( a1 );
   T2 a2;
   T2 ret2= pod_test2( a2 );
}
// Windows x64
// T1
    xor ecx, ecx
    call    ?pod_test1@@YA?AUT1@@U1@@Z      ; pod_test1
// T2
    lea rcx, QWORD PTR $T2[rsp]
    xor edx, edx
    call    ?pod_test2@@YA?AUT2@@U1@@Z      ; pod_test2

Windows x64 は fastcall 相当なので引数はレジスタ ecx, edx, r8, r9 に入ります。
上の T1 ではそのまま引数が ecx に入っています。

↑ T2 の場合戻り値を受け取るために、呼び出し側が領域を確保して rcx で渡しています。
実際の引数は edx です。

// Windows x64
// T1
?pod_test1@@YA?AUT1@@U1@@Z PROC             ; pod_test1, COMDAT
    mov eax, ecx
    ret 0

// T2
?pod_test2@@YA?AUT2@@U1@@Z PROC             ; pod_test2, COMDAT
    mov DWORD PTR [rcx], edx
    mov rax, rcx
    ret 0

↑ T1 では呼ばれた側は引数を戻り値 rax にコピーしているだけ。
↑ T2 は rcx のバッファに結果を格納しつつ rax にもアドレスを返しています。

gcc 4.8/clang 3.2 ではどちらも POD 扱いなので T1/T2 共に同じコードとなっています。
Linux では呼び出し規約 (calling convention) が異なるのでレジスタは別です。
(rdi, rsi, rdx, rcx, r8, r9 の順)

Function Calling convention

// Linux x86_x64
// T1
    xorl    %edi, %edi
    callq   _Z9pod_test12T1
// T2
    xorl    %edi, %edi
    callq   _Z9pod_test22T2
// Linux x86_x64
// T1
_Z9pod_test12T1:                        # @_Z9pod_test12T1
    movl    %edi, %eax
    ret
// T2
_Z9pod_test22T2:                        # @_Z9pod_test22T2
    movl    %edi, %eax
    ret

下記は gcc 以外はコンパイルが通ります。

template
void CallScript_UIEvent( A0... a0 )
{
    slice::Add( "Move",
        [=]( slice::SliceBase* slice ){
            CallScript_Direct( a0... );
            slice->Delete();
        }
    );
}
VS2012 + Nov 2012 CTP : OK
VS2013                : OK
VS2013 + Nov 2013 CTP : OK
gcc 4.8.1 (Linux)     : error
clang 3.2 (Linux)     : OK

template 引数が可変長でなければコンパイル通ります。

関連エントリ
C++11 Lambda function ラムダ関数
C++11 Variadic Templates 可変長引数のテンプレート
スレッド同期命令の比較 C++11 とコンパイラ
C++11 Rvalue Reference 右辺値参照

zip ファイルの展開 (zlib で zip にアクセスする (2) )

zip のアクセスはファイル単位なので比較的容易で、
圧縮解凍も zlib を使うことが出来ます。
apk や ipa, jar など様々なところで利用されています。

前回に続き、実際に zip ファイルを展開してみます。

zip ヘッダやファイルの情報はリトルエンディアンで格納されていますが、
アライメントが考慮されていないのでバイトアクセスが必要になります。

今後多用するのでメモリアクセス命令を作っておきます。

typedef unsigned short  UI16;
typedef unsigned int    UI32;

namespace le {
    inline UI32 Load2( const unsigned char* ptr )
    {
        return  (ptr[0]) | (ptr[1]<< 8);
    }
    inline UI32 Load4( const unsigned char* ptr )
    {
        return  (ptr[0]) | (ptr[1]<< 8) | (ptr[2]<<16) | (ptr[3]<<24);
    }
    inline void Store2( unsigned char* ptr, UI32 value )
    {
        ptr[0]= static_cast( value );
        ptr[1]= static_cast( value >> 8);
    }
    inline void Store4( unsigned char* ptr, UI32 value )
    {
        ptr[0]= static_cast( value );
        ptr[1]= static_cast( value >> 8 );
        ptr[2]= static_cast( value >>16 );
        ptr[3]= static_cast( value >>24 );
    }
}

これを用いて、unaligned access 用のデータタイプを定義します。

struct UI16LE {
    unsigned char   Memory[2];
    UI32 Get() const
    {
        return  le::Load2( Memory );
    }
    void Set( UI32 value )
    {
        le::Store2( Memory, value );
    }
};

struct UI32LE {
    unsigned char   Memory[4];
    UI32 Get() const
    {
        return  le::Load4( Memory );
    }
    void Set( UI32 value )
    {
        le::Store4( Memory, value );
    }
};

UI16LE, UI32LE はそれぞれ UI16 (unsigned short), UI32 (unsigned int) と
対になっており、2byte, 4byte の符号なし整数型です。
アライメントを考慮する必要がなく、かつプラットフォーム非依存で
LittleEndian で読み書きします。

これで zip のヘッダ構造を定義できるようになりました。
下記は読み出しに必要な最小限で、32bit ヘッダのみとなっています。

enum {
    ZIP_SIGNATURE_LOCAL32           = 0x04034b50,
    ZIP_SIGNATURE_CENTRALDIR32      = 0x02014b50,
    ZIP_SIGNATURE_CENTRALDIR32_EOR  = 0x06054b50,
};

struct T_ZIP_LOCAL32 {
    UI32LE  Signature;  // PK0304 = 0x04034b50
    UI16LE  Extract;
    UI16LE  BitFlag;
    UI16LE  Method;
    UI16LE  Time;
    UI16LE  Date;
    UI32LE  CRC32;
    UI32LE  CompressedSize;
    UI32LE  UncompressedSize;
    UI16LE  NameLength;
    UI16LE  ExtraLength;
};

struct T_ZIP_CENTRALDIR32 {
    UI32LE  Signature;  // PK0102 = 0x02014b50
    UI16LE  Version;
    UI16LE  Extract;
    UI16LE  BitFlag;
    UI16LE  Method;
    UI16LE  Time;
    UI16LE  Date;
    UI32LE  CRC32;
    UI32LE  CompressedSize;
    UI32LE  UncompressedSize;
    UI16LE  NameLength;
    UI16LE  ExtraLength;
    UI16LE  CommentLength;
    UI16LE  DiskNumberStart;
    UI16LE  InternalAttributes;
    UI32LE  ExternalAttributes;
    UI32LE  LocalHeaderOffset;
public:
    const char* GetNamePointer() const
    {
        return  reinterpret_cast( reinterpret_cast( this ) + sizeof(T_ZIP_CENTRALDIR32) );
    }
    const bool IsDirectory() const
    {
        unsigned int    name_length= NameLength.Get();
        return  name_length && GetNamePointer()[ name_length-1 ] == '/';
    }
    const T_ZIP_CENTRALDIR32* Next() const
    {
        return  reinterpret_cast( reinterpret_cast( this ) + sizeof(T_ZIP_CENTRALDIR32) + NameLength.Get() + ExtraLength.Get() + CommentLength.Get() );
    }
};

struct T_ZIP_CENTRALDIR32_EOR {
    UI32LE  Signature;          // PK0506 = 0x06054b50
    UI16LE  NumberOfThisDisk;
    UI16LE  StartDisk;
    UI16LE  TotalEntriesDisk;
    UI16LE  TotalEntries;
    UI32LE  CentralDirectorySize;
    UI32LE  CentralDirectoryOffset;
    UI16LE  CommentLength;
};

前回の手順通り、まずは EOR (End of Central Directory record) を検索します。

const int   EOR_LOCATOR_SIZE= 512;
unsigned char   Locator[EOR_LOCATOR_SIZE];

file.Seek( -EOR_LOCATOR_SIZE, SEEK_END );
file.Read( Locator, EOR_LOCATOR_SIZE );

const unsigned char*  ptr= Locator;
const unsigned char*  end_ptr= ptr +  EOR_LOCATOR_SIZE - sizeof(T_ZIP_CENTRALDIR32_EOR) + sizeof(UI32LE);

for(; ptr < end_ptr ; ptr++ ){
    if( *ptr == 'P' && le::Load4( ptr ) == ZIP_SIGNATURE_CENTRALDIR32_EOR ){
	// found
    }
}

↑上のコードはファイルの終端から 512byte を読み込んで、EOR の Signature
"PK0506" (0x06054b50) を検索しています。
T_ZIP_CENTRALDIR32_EOR の CommentLength の分だけ追加情報を挿入できるので、
必ずしもこの範囲に存在するとは限りません。
見つからない場合にさらに読み進める処理が必要かもしれません。

EOR がわかれば CentralDirectoryOffset, CentralDirectorySize を使って
Central Directory を読み込むことが出来ます。
ファイル名検索に何度も利用するので、最初にメモリに読み込んでおきます。
EOR 読み込みと合わせて class 化します。

class ZipDirectory {
    unsigned int    DirectoryEntries;
    Buffer          DirectoryImage;
public:
    const T_ZIP_CENTRALDIR32*   Begin() const
    {
        return  DirectoryImage.Address();
    }

    bool IsEnd( const T_ZIP_CENTRALDIR32* dir_ptr ) const
    {
        return  reinterpret_cast(dir_ptr) >= reinterpret_cast(DirectoryImage.Address()) + DirectoryImage.Size();
    }

    static const unsigned char* FindEOR( const unsigned char* ptr, size_t size )
    {
        const unsigned char*    end_ptr= ptr + size - sizeof(T_ZIP_CENTRALDIR32_EOR) + sizeof(UI32LE);
        for(; ptr < end_ptr ; ptr++ ){
            if( *ptr == 'P' && le::Load4( ptr ) == ZIP_SIGNATURE_CENTRALDIR32_EOR ){
                return  ptr;
            }
        }
        return  NULL;
    }

    bool Load( const char* zip_file_name )
    {
        File    file;
        if( !file.Open( zip_file_name ) ){
            return  false;
        }
        const int   EOR_LOCATOR_SIZE= 512;
        unsigned char   Locator[EOR_LOCATOR_SIZE];
        file.Seek( -EOR_LOCATOR_SIZE, SEEK_END );
        size_t  read_size= file.Read( Locator, EOR_LOCATOR_SIZE );

        const unsigned char*    ptr= FindEOR( Locator, read_size );
        if( !ptr ){
            file.Close();
            return  false;
        }
        const T_ZIP_CENTRALDIR32_EOR*   eor= reinterpret_cast( ptr );
        unsigned int    dir_size= eor->CentralDirectorySize.Get();
        DirectoryEntries= eor->TotalEntries.Get();
        DirectoryImage.Alloc( dir_size );

        file.Seek( eor->CentralDirectoryOffset.Get(), SEEK_SET );
        file.Read( DirectoryImage.Address(), dir_size );
        file.Close();
        return  true;
    }
};

これで zip ファイル内のファイル情報にアクセスできるようになりました。
下記は ZipDirectory を使ってファイル名一覧を取り出すサンプルです。
T_ZIP_CENTRALDIR32 内のファイル名が 0 終端になっていない点に注意。

void file_lsit( const char* zip_file_name )
{
    ZipDirectory    directory;
    directory.Load( zip_file_name );

    const int   NAME_BUFFER_SIZE= 512;
    char    name_buffer[ NAME_BUFFER_SIZE ];

    const T_ZIP_CENTRALDIR32*   dir_ptr= directory.Begin();
    for(; !directory.IsEnd( dir_ptr ) ; dir_ptr= dir_ptr->Next() ){

        unsigned int    name_length= dir_ptr->NameLength.Get();
        assert( name_length < NAME_BUFFER_SIZE );

        // ファイル名の取り出し
        memcpy( name_buffer, dir_ptr->GetNamePointer(), name_length );
        name_buffer[name_length]= '\0';
        printf( "%8d %8d %s\n", uncompressed_size, compressed_size, name_buffer );
    }
}

ZipDirectory の中に出てくる Buffer は汎用的なメモリ確保を行っています。

class Buffer {
    void*   Memory;
    size_t  MemorySize;
    Buffer( const Buffer& ){}
    Buffer& operator=( const Buffer& src ){ return *this; }
public:
    Buffer() : Memory( NULL ), MemorySize( 0 ){}
    ~Buffer()
    {
        Release();
    }
    void Release()
    {
        if( Memory ){
            free( Memory );
            Memory= NULL;
        }
    }
    void Alloc( size_t size )
    {
        Release();
        Memory= malloc( size );
        MemorySize= size;
    }
    void Shrink( size_t size )
    {
        assert( size <= MemorySize );
        MemorySize= size;
    }
    size_t Size() const
    {
        return  MemorySize;
    }
    unsigned long SizeLong() const
    {
        return  static_cast( MemorySize );
    }
    template
    T* Address() const
    {
        return  reinterpret_cast( Memory );
    }
};

同様に File 型も必要に応じて作ります。
例えば stdio なら下記の通り。

class File {
    FILE*   Fp;
public:
    File() : Fp( NULL ){}
    ~File()
    {
        Close();
    }
    bool Open( const char* file_name )
    {
#if _WINDOWS
        fopen_s( &Fp, file_name, "rb" );
#else
        Fp= fopen( file_name, "rb" );
#endif
        return  Fp != NULL;
    }
    bool Create( const char* file_name )
    {
#if _WINDOWS
        fopen_s( &Fp, file_name, "wb" );
#else
        Fp= fopen( file_name, "wb" );
#endif
        return  Fp != NULL;
    }
    void Close()
    {
        if( Fp ){
            fclose( Fp );
            Fp= NULL;
        }
    }
    size_t Read( void* buffer, size_t size )
    {
        return  fread( buffer, 1, size, Fp );
    }
    size_t Write( void* buffer, size_t size )
    {
        return  fwrite( buffer, 1, size, Fp );
    }
    void Seek( long long offset, int origin )
    {
#if _WINDOWS
        _fseeki64( Fp, offset, origin );
#else
        fseek( Fp, offset, origin );
#endif
    }
};

ファイル情報がとれたので、あとはファイルの内容を読み出すだけです。
下記 UnzipFile() は、zip 内のファイルを解凍することが出来ます。

bool UnzipFile( const T_ZIP_CENTRALDIR32* entry, const char* zip_file_name, const char* extract_file_name )
{
    File    zip_file;
    if( !zip_file.Open( zip_file_name ) ){
        return  false;
    }

    // Local Header の読み込み
    T_ZIP_LOCAL32   LocalHeader;
    zip_file.Seek( entry->LocalHeaderOffset.Get(), SEEK_SET );
    zip_file.Read( &LocalHeader, sizeof(T_ZIP_LOCAL32) );
    assert( LocalHeader.Signature.Get() == ZIP_SIGNATURE_LOCAL32 );

    // Offset の算出
    unsigned int    local_offset= sizeof(T_ZIP_LOCAL32) + LocalHeader.NameLength.Get() + LocalHeader.ExtraLength.Get();
    unsigned int    uncompressed_size= entry->UncompressedSize.Get();
    unsigned int    compressed_size= entry->CompressedSize.Get();

    // データ本体の読み込み
    Buffer  src_buffer;
    src_buffer.Alloc( compressed_size );

    zip_file.Seek( entry->LocalHeaderOffset.Get() + local_offset, SEEK_SET );
    zip_file.Read( src_buffer.Address(), compressed_size );

    Buffer  dest_buffer;
    dest_buffer.Alloc( uncompressed_size );

    switch( entry->Method.Get() ){
    case 0: // 非圧縮
        assert( uncompressed_size == compressed_size );
        memcpy( dest_buffer.Address(), src_buffer.Address(), uncompressed_size );
        break;
    case 8: // 圧縮されている場合
        if( !zlib_uncompress_raw( dest_buffer, src_buffer ) ){
            return  false;
        }
        break;
    default:
        assert( 0 );
        break;
    }

    // CRC の確認
    if( crc32( 0, dest_buffer.Address(), uncompressed_size ) != entry->CRC32.Get() ){
        return  false;
    }

    // 書き込み
    File    file;
    if( !file.Create( extract_file_name ) ){
        return  false;
    }

    file.Write( dest_buffer.Address(), dest_buffer.Size() );
    file.Close();

    return  true;
}

UnzipFile() では、Central Directory (T_ZIP_CENTRALDIR32) の情報を元に
まず Local Header (T_ZIP_LOCAL32) を読み込んでいます。
T_ZIP_LOCAL32 の NameLength と ExtraLength から実際のデータ位置が求まるので、
あらためてデータ本体を読み込みます。

データが圧縮されている場合は、前回解説したとおり zlib の raw format と
みなして展開しています。

size_t zlib_uncompress_raw( Buffer& dest, const Buffer& source )
{
    z_stream    stream;
    memset( &stream, 0, sizeof(z_stream) );
    stream.next_in= source.Address();
    stream.avail_in= source.SizeLong();
    stream.next_out= dest.Address();
    stream.avail_out= dest.SizeLong();

    // raw format の指定
    if( inflateInit2( &stream, -MAX_WBITS ) != Z_OK ){
        return  0;
    }

    int err= inflate( &stream, Z_FINISH );
    if( err != Z_STREAM_END && err != Z_OK ){
        return  0;
    }
    err= inflateEnd( &stream );
    assert( dest.Size() == stream.total_out );
    return  stream.total_out;
}

ここまでのコードで zip 内ファイルを取り出すことが可能です。

ただし展開パスにディレクトリが含まれている場合は書き込みに失敗するので、
もう少しだけ手を加えてみます。
下記の MakePath() は階層に対応した mkdir() です。(終端に '/' が必要)

static bool MakeDirectory( const char* path )
{
#if _WINDOWS
    return  _mkdir( path ) == 0;
#else
    return  mkdir( path, 0755 ) == 0;
#endif
}

static void MakePath( const char* extract_file_name )
{
    size_t  length= strlen( extract_file_name );
    Buffer  path_buffer;
    path_buffer.Alloc( length + 1 );
    char*       str= path_buffer.Address();
    const char* ptr= extract_file_name;
    for(; *ptr ;){
        if( *ptr == '/' ){
            *str= '\0';
            MakeDirectory( path_buffer.Address() );
        }
        *str++= *ptr++;
    }
}

同じパスを何度も mkdir するのは非効率なので、前回と同じパスは省きます。
でもあまり効果が無いかもしれません。

struct PathCache {
    Buffer  PrevPath;

    bool IsNewPath( const char* path )
    {
        const char* last_path= NULL;
        for( const char* ptr= path ; *ptr ; ptr++ ){
            if( *ptr == '/' ){
                last_path= ptr + 1;
            }
        }
        if( last_path ){
            size_t  size= last_path - path;
            if( PrevPath.Size() == size + 1 ){
                if( memcmp( PrevPath.Address(), path, size ) == 0 ){
                    return  false;
                }
            }
            PrevPath.Alloc( size + 1 );
            memcpy( PrevPath.Address(), path, size );
            PrevPath.Address()[size]= '\0';
            return  true;
        }
        return  false;
    }
    const char* GetPath() const
    {
        return  PrevPath.Address();
    }
};

zip の展開ができるようになりました。
下記 Unzip() は階層付きで zip に含まれる全ファイルを展開することが出来ます。

void Unzip( const char* zip_file_name )
{
    ZipDirectory    directory;
    directory.Load( zip_file_name );

    PathCache   path_cache;

    const T_ZIP_CENTRALDIR32*   dir_ptr= directory.Begin();
    for(; !directory.IsEnd( dir_ptr ) ; dir_ptr= dir_ptr->Next() ){
        assert( dir_ptr->Signature.Get() == ZIP_SIGNATURE_CENTRALDIR32 );

        unsigned int    name_length= dir_ptr->NameLength.Get();
        const int   NAME_BUFFER_SIZE= 512;
        char    name_buffer[ NAME_BUFFER_SIZE ];
        assert( name_length < NAME_BUFFER_SIZE );
        memcpy( name_buffer, dir_ptr->GetNamePointer(), name_length );
        name_buffer[name_length]= '\0';

        if( !dir_ptr->IsDirectory() ){
            if( path_cache.IsNewPath( name_buffer ) ){
                MakePath( path_cache.GetPath() );
            }
            UnzipFile( dir_ptr, zip_file_name, name_buffer );
        }
    }
}
int main( int argc, char** argv )
{
    for(; --argc ; Unzip( *++argv ) );
    return  0;
}

最終的なコードはこちら。Windows と Linux で確認しています。

zip2.cpp

関連エントリ
データ圧縮 zlib と gzip と zip (zlib で zip にアクセスする)