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()];
}



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<typename T, typename... A0>
inline T* flNew( A0&&... a0 )
{
    return  new( malloc_aligned( sizeof(T), alignof(T) ) ) T( std::forward<A0>(a0)... );
}

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

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

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


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


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<typename T, typename ...A0>
    T* Alloc( A0... a0 )
    {
        return  new T( a0... );
    }
};

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

...

Compiler compiler;
compiler.Alloc<List>( 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<T0>
-----------------------------------
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<typename ...A0>
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 を使うことが出来ます。
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<unsigned char>( value );
        ptr[1]= static_cast<unsigned char>( value >> 8);
    }
    inline void Store4( unsigned char* ptr, UI32 value )
    {
        ptr[0]= static_cast<unsigned char>( value );
        ptr[1]= static_cast<unsigned char>( value >> 8 );
        ptr[2]= static_cast<unsigned char>( value >>16 );
        ptr[3]= static_cast<unsigned char>( 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<const char*>( reinterpret_cast<uintptr_t>( 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<const T_ZIP_CENTRALDIR32*>( reinterpret_cast<uintptr_t>( 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<T_ZIP_CENTRALDIR32>();
    }

    bool IsEnd( const T_ZIP_CENTRALDIR32* dir_ptr ) const
    {
        return  reinterpret_cast<uintptr_t>(dir_ptr) >= reinterpret_cast<uintptr_t>(DirectoryImage.Address<void>()) + 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<const T_ZIP_CENTRALDIR32_EOR*>( 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<void>(), 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<unsigned long>( MemorySize );
    }
    template<typename T>
    T* Address() const
    {
        return  reinterpret_cast<T*>( 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<void>(), 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<void>(), src_buffer.Address<void>(), 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<Bytef>(), uncompressed_size ) != entry->CRC32.Get() ){
        return  false;
    }

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

    file.Write( dest_buffer.Address<void>(), 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<Bytef>();
    stream.avail_in= source.SizeLong();
    stream.next_out= dest.Address<Bytef>();
    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<char>();
    const char* ptr= extract_file_name;
    for(; *ptr ;){
        if( *ptr == '/' ){
            *str= '\0';
            MakeDirectory( path_buffer.Address<char>() );
        }
        *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<char>(), path, size ) == 0 ){
                    return  false;
                }
            }
            PrevPath.Alloc( size + 1 );
            memcpy( PrevPath.Address<char>(), path, size );
            PrevPath.Address<char>()[size]= '\0';
            return  true;
        }
        return  false;
    }
    const char* GetPath() const
    {
        return  PrevPath.Address<char>();
    }
};

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 にアクセスする)


Windows を除いてほとんどのプラットフォームには zlib が入っており、
データの圧縮に利用することが出来ます。

zlib

メモリ上のイメージを圧縮するだけなら下記のとおり。

// [1]
// 圧縮データが入るバッファの確保
uLong  buffer_size= compressBound( src_size );
Bytef* buffer= memory_alloc( buffer_size );

// 圧縮
compress( buffer, &buffer_size, src_memory, src_size );

// 圧縮後のサイズ
compressed_size= buffer_size;

圧縮率を指定するなら compress() の代わりに compress2() を使います。
展開も同様。

// [2]
uLong buffer_size= uncompressed_size;
uncompress( buffer, &buffer_size, src_memory, src_size );

解凍時は展開後の元のサイズ (src_size) が必要になるので、
圧縮時に保存しておく必要があります。


●ヘッダと動作モード

圧縮されたデータには、先頭にフォーマットを表す最小限のヘッダ、
最後にチェックサム Adler-32 が付加されます。
このヘッダ形式は zlib 独自のもので、gzip や zip 等とは互換性がありません。
チェックサムも一般的な CRC とは異なっています。

ただし圧縮アルゴリズム自体は広く使われている Deflate で、
ヘッダ&フッタが違うだけでデータ本体は gzip/zip などと同じです。
そのため zlib には、gzip のヘッダを用いて読み書きを行うモードがあります。

[1]/[2] の compress()/uncompress() 相当を低レベルな API で書きなおしたのが
下記の例です。

// [3] Compress (zlib)
size_t Compress_zlib( void* dest_memory, size_t dest_size, const void* src_memory, size_t src_size )
{
   z_stream  stream;
   memset( &stream, 0, sizeof(z_stream) );
   stream.next_in= (Bytef*)src_memory;
   stream.avail_in= src_size;
   stream.next_out= (Bytef*)dest_memory;
   stream.avail_out= dest_size;

   deflateInit2( &stream, COMPRESS_LEVEL, Z_DEFLATED, MAX_WBITS, 5, 8, Z_DEFAULT_STRATEGY );
   int result= deflate( &stream, Z_FINISH )
   if( result != ZSTREAM_END && result != Z_OK ){
      // error
      return  0;
   }
   deflateEnd( &stream );

   return  stream.total_out;  // compressed_size
}


// [4] Uncompress (zlib)
size_t Uncompress_zlib( void* dest_memory, size_t dest_size, const void* src_memory, size_t src_size )
{
   z_stream  stream;
   memset( &stream, 0, sizeof(z_stream) );
   stream.next_in= (Bytef*)src_memory;
   stream.avail_in= src_size;
   stream.next_out= (Bytef*)dest_memory;
   stream.avail_out= dest_size;

   inflateInit2( &stream, MAX_WBITS );
   int result= inflate( &stream, Z_FINISH )
   if( result != ZSTREAM_END && result != Z_OK ){
      // error
      return  0;
   }
   inflateEnd( &stream );

   assert( dest_size == stream.total_out );

   return  stream.total_out;  // uncompressed_size
}

deflateInit2() / inflateInit2() の引数のうち、上の [3]/[4] で
MAX_WBITS が入っている所が windowBits の指定場所になります。
windowBits には下記の特殊な用法があります。

(1) windowBits       =   8~15    zlib format
(2) windowBits + 16  =  24~31    gzip format
(3) windowBits + 32  =  40~47    zlib/gzip format (inflate のみ)
(4) -windowBits      =  -8~-15   raw format

(2) のようにパラメータに 16 を加えると gzip フォーマットになります。
チェックサムも CRC-32 です。
例えば上の例 [3]/[4] だと MAX_WBITS の代わりに (MAX_WBITS+16) を与える
ことになります。

(3) inflateInit2() の場合 windowBits に 32 を加えると zlib/gzip の
自動判別ができるようになります。

(4) のように負数を入れると余計なデータが出力されません。
ヘッダも付かずにチェックサム計算も省かれます。
zip のように互換性のないファイル構造でも、データ部分の圧縮と解凍に
zlib を利用出来るようになります。( Method 8 )

まとめると下記の通り。zlib には 3種類の動作モードがあります。

               windowBits      header       checksum
-----------------------------------------------------
zlib format    MAX_WBITS       zlib 形式    Adler-32
gzip format   (MAX_WBITS|16)   gzip 形式    CRC-32
raw format    -MAX_WBITS       無し         無し


● Windows

zlib は Windows でも利用できますが、64bit (x64) では少々問題があります。

zlib のサイズ値に uLong (unsigned long) 型が用いられているので、
同じ 64bit OS でも Windows とそれ以外で型のサイズが変わっています。
Windows は LLP64 なので 32bit、Linux x64, OSX, IOS arm64 は LP64 なので
64bit になります。

例えば上の [3]/[4] のコードは size_t を使用しています。
size_t は x64 で 64bit なので、Windows x64 のみ zlib API に渡すときに
32bit への変換が必要になってしまいます。
互換性の意味でも long はできるだけ避けたいのですが、
Windows x64 では諦めてキャストを使っています。


● zip の構造とデータアクセス

zip はデータ圧縮だけでなくアーカイバの機能も含まれています。
tar + gzip の組み合わせはアーカイブした後に一度に圧縮しますが、
zip は個々のファイルを個別に圧縮しています。

また圧縮はファイルの中身だけで、ヘッダや含まれるファイル情報
(Local Header, Central Directory など) は含まれません。
そのため小さいファイルが大量に含まれるケースでは zip の効率は落ちます。

反面、ファイル単位でランダムアクセスが可能で、必要なファイルだけを
取り出すことができます。
アクセスするためには下記の手順が必要になります。

(1) EOR を検索して、Central Directory の位置を特定する
(2) Central Directory を読み込んで必要なファイルを検索する
(3) (2) からファイルの Local Header の位置が求まるので読み込む
(4) Local Header からデータの位置を求めて特定する。

End of Central Directory record (EOR) の位置は不定なので、
ファイルの終端から逆方向に読み込んで SIGNATURE の検索が必要。
EOR が特定できれば Central Directory の位置とサイズがわかるので
まとめて読み込むことが出来ます。

Central Directory 内の個々の情報は不定長なので、一度先頭から
読み進める必要があります。
Local Header の位置、ファイル名、圧縮前後のファイルサイズ、CRC、
圧縮メソッドなどはここに含まれます。
ディレクトリも個別エントリとして含まれており、サイズは 0 で
ファイル名の最後に '/' が付きます。

Local Header はファイル本体の前に挿入されています。
こちらも長さ不定で、読み込んでみるまでデータの位置が厳密に求まりません。
適当なサイズを指定してとりあえず読み込みます。

Local Header からデータの位置が特定できるので、
実際のデータ部分を zip ファイルからロードできます。
Method が 8 なら圧縮されているため zlib の raw format として解凍できます。
解凍後に zlib の crc32() 等を使って CRC チェックを行います。

デフォルトで用いられている圧縮 Method は 0 と 8 です。
Method 8 が Deflate で Method 0 は非圧縮となっています。

下記の資料を参考にしました。

APPNOTE.TXT - .ZIP File Format Specification

簡単にデータ位置を特定できない点は少々やっかいですが、
Central Directory は先キャッシュ出来ますし、Local header も
データと同時に多めにサイズを指定してまとめて読み込んでしまうことも出来ます。
解凍も zlib が使えますし、広く使われているだけあって zip の
データアクセスは比較的容易にできます。


| 次のページ(日付が古い方向)>>