2019/03/09
Python : zip にアーカイブした pyz を直接実行できる専用 exe を作る
実行ファイルの置き換えに python を使用したときのメモです。Python 3 はプログラムコードを zip にアーカイブしたまま実行することができます。
●(1) Library として読み込む場合
テストコードを appmain.py, testmodule.py とします。
そのまままとめて zip 圧縮可能で、Libpath に追加すると読み込むことができます。
embeddable 版の python3x.zip と同じように事前にコンパイルしておくこともできます。
●(2) zipapp (pyz) に変換してから実行する場合
zip ではなく zipapp (pyz) に変換すると script のように直接実行できるようになります。
予めサブフォルダ app を作って、その中に appmain.py, testmodule.py を入れておきます。下記のコードを実行すると pyz にまとめることができます。create_archive() 最初の引数 'app' はフォルダ名です。
変換出力した app.pyz はただの zip ですが、エントリポイントとして __main__.py が挿入されています。この pyz ファイルはそのまま python で実行できます。
また unix 系 os の場合は実行属性をつけることで直接実行することができます。
Windows の場合そのまま実行することはできませんが、pyz 専用の exe を作る方法が zipapp のマニュアルで紹介されています。
●(3) pyz の起動用に exe を作る
・zipapp --- Manage executable Python zip archives
上記ページに載っている zastub.c をそのままコンパイルします。
コンパイル方法もマニュアル通りです。下記のコードを実行してコンパイルできます。
zastub.exe が作られるので、(2) で作った pyz とそのまま合成します。単純につなげるだけです。
これで pyz を実行可能な app.exe ができました。そのまま実行できます。(python3.dll が必要)
プログラムの内容によっては python の import でモジュールが見つからない場合があるかもしれません。その場合は予め検索パス設定しておいてください。環境変数 PYTHONPATH でも構いませんし、zastub.c の中で Py_SetPath() を使ってパスを与えることもできます。
zastub.c は python.exe 相当です。これが引数を何も加工していない状態になります。
zastub.c では 1番目の引数に自分自身のパスを複製して挿入します。例えば引数が "arg1" なら「app.exe arg1」→ 「app.exe app.exe arg1」となります。自分自身である app.exe には app.pyz の内容がそのまま含まれているので、app.exe を pyz とみなしてそのまま実行できるわけです。
なお 1番目の引数を正しく渡すことができるなら、必ずしも exe に pyz を合成する必要がありません。例えば下のように直接 "app.pyz" を渡すことができます。
ただし若干問題もあります。上のコードはカレントディレクトリでなければ app.pyz を読み込むことができません。例えば他のフォルダからフルパスで app.exe を実行しようとすると app.pyz が見つからずにエラーになります。もし app.pyz を実行ファイルと同じ場所に置くなら、__wargv[0] や GetModuleFileNameW() を利用して app.pyz のパスを求めることができます。
↓ __wargv[0] から app.pyz のパスを求める。
同じようにして exe の場所からライブラリの位置を特定し、Py_SetPath() することもできます。例えば embeddable 版を使うなら、Py_SetPath() に python3x.zip と dll/pyd フォルダの 2つを与えれば OK です。
●(1) Library として読み込む場合
テストコードを appmain.py, testmodule.py とします。
# appmain.py
import testmodule
def main():
testmodule.app()
if __name__ == '__main__':
main()
import testmodule
def main():
testmodule.app()
if __name__ == '__main__':
main()
# testmodule.py
def app():
print( 'testmodule' )
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()
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' )
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
$ ./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 <windows.h>
#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);
}
#define Py_LIMITED_API 1
#include "Python.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#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")
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 );
}
{
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);
}
{
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 <Python.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#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);
}
#include <Python.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#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 です。
iOS/OSX の API の多くは Objective-C の Interface となっています。
他のプラットフォームとのコード共有を考えるならば、
アプリケーション側はできるだけ普通の C/C++ で書きたいと思うかもしれません。
この場合 System の API 呼び出し部分を分離して、何らかの Wrapper 経由で
アクセスすることになります。
Wrapper Library のヘッダは C++ と Objective-C++ で共有されることになるので、
Objective-C の Object をそのまま記述することが出来ません。
ヘッダとソースで実装を分離して隠蔽すべきなのでしょうが、
扱う Class や Object が増えて結構手間がかかっていました。
おそらく一番簡単で確実な方法は、iOS/OSX の場合だけアプリケーション側の
.cpp (.cc) を Objective-C++ とみなしてコンパイルすることでしょう。
ですが、どうしても純粋な C++ としてコンパイルしたかったので、
C++ で Objective-C の Object を所有できるようにしてみました。
一見うまく動作するように見えますが、dealloc のタイミングが意図したものに
ならないことがあります。
上の例は (1) ではなく (2) のタイミングで TempClass が dealloc されます。
原因は GetID() が id を返しているためで、ARC により autoreleasepool への
登録が行われているようです。
weak なポインタを返す場合、所有者の生存期間をコンパイラが保証できないため
autoreleasepool に委ねているものと思われます。
実際には autoreleasepool に入って欲しくないケースもあります。
例えば上のコードは SetID() が即座に retain しているため、
生存期間を考慮する必要がないのですが autoreleasepool に入ります。
void* のまま受け取ってから受け取り側が __bridge cast するか、または
__attribute__((ns_returns_retained)) をつけると autoreleasepool に
含まれないことがわかりました。
↑の場合は GetID() しても autoreleasepool に入ることがありません。
これで C++ 側でも比較的安全に Objective-C Object の所有や受け渡しを
行うことが出来ます。
Objective-C の Object の instance は ARC で管理されていますが、
C++ からは smart pointer (shared_ptr) として見えることになります。
使用例
他のプラットフォームとのコード共有を考えるならば、
アプリケーション側はできるだけ普通の 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
};
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;
}
#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)
}
{
@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( 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
};
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;
}
#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 );
};
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()];
}
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()];
}
2014/03/11
C++11 alignas/alignof メモリアライメント
SSE 等の SIMD 命令やキャッシュを意識したプログラムではメモリのアライメントを
考慮する必要があります。
これまではコンパイラ毎の拡張命令に頼っていましたが、
C++11 では言語仕様に含まれるようになりました。
メモリアクセスの同期命令も含めて、低レベルなメモリ命令を積極的に取り込んでいる印象です。
alignas はメモリ配置時のアライメントを宣言します。
↑変数 array は 32byte 単位のアドレス境界に配置されます。
↑ position も 32byte 単位のアドレスに配置します。
VC と gcc/clang の attribute は若干書式に違いがあります。
alignas を VC のように struct の前に記述すると変数への修飾となり、
型宣言に含まれません。VC では含まれているようです↓。
static (global) に宣言した変数は静的にアドレスが求まるので、
コンパイル時 (link時) に解決します。
ただしプログラム (data segment) がロードされるアドレスが、
より大きなアドレス境界に配置していることが条件となります。
(1) 基準となるメモリアドレスのアライメント
(2) メモリを pack したときの整合性
つまりコンパイラ側が行うのは (2) と (1) の一部になります。
stack に宣言した変数は、実行するまでアドレスがわからないので
実行時に端数を切り捨てるコードが埋め込まれます。
heap の場合は確保したメモリが alignment に従っていない可能性があるため
利用時にアドレス整合を意識しなければなりません。
alignof を用いるとコンパイル時に型情報を判断できるので、alignment に
対応したアロケータを作ることができます。
この flNew() で作成した AVECT ↓は、宣言時に alignas(32) が指定されているため
32byte 単位で確保されます。(malloc_aligned が実装されていると仮定する)
関連エントリ
・VisualStudio と C++11 、コンパイラの違いなど
・C++11 Lambda function ラムダ関数
・C++11 Variadic Templates 可変長引数のテンプレート
・スレッド同期命令の比較 C++11 とコンパイラ
・C++11 Rvalue Reference 右辺値参照
考慮する必要があります。
これまではコンパイラ毎の拡張命令に頼っていましたが、
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 右辺値参照
2014/01/03
VisualStudio と C++11 、コンパイラの違いなど
Visual C++ Compiler Nov 2012 CTP では動いていたけど、
VisualStudio 2013 以降コンパイルできなくなったコード。
template を分解して単純化して回避。
↑ T0 は VS ではまだ POD 扱いにならないようです。
POD かどうかで、値渡し時に関数の呼び出し方が変わります。
sizeof が 8byte 未満でも register に入らず参照戻しになります。
Windows x64 は fastcall 相当なので引数はレジスタ ecx, edx, r8, r9 に入ります。
上の T1 ではそのまま引数が ecx に入っています。
↑ T2 の場合戻り値を受け取るために、呼び出し側が領域を確保して rcx で渡しています。
実際の引数は edx です。
↑ 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
下記は gcc 以外はコンパイルが通ります。
template 引数が可変長でなければコンパイル通ります。
関連エントリ
・C++11 Lambda function ラムダ関数
・C++11 Variadic Templates 可変長引数のテンプレート
・スレッド同期命令の比較 C++11 とコンパイラ
・C++11 Rvalue Reference 右辺値参照
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 右辺値参照
2013/10/07
zip ファイルの展開 (zlib で zip にアクセスする (2) )
zip のアクセスはファイル単位なので比較的容易で、
圧縮解凍も zlib を使うことが出来ます。
apk や ipa, jar など様々なところで利用されています。
前回に続き、実際に zip ファイルを展開してみます。
zip ヘッダやファイルの情報はリトルエンディアンで格納されていますが、
アライメントが考慮されていないのでバイトアクセスが必要になります。
今後多用するのでメモリアクセス命令を作っておきます。
これを用いて、unaligned access 用のデータタイプを定義します。
UI16LE, UI32LE はそれぞれ UI16 (unsigned short), UI32 (unsigned int) と
対になっており、2byte, 4byte の符号なし整数型です。
アライメントを考慮する必要がなく、かつプラットフォーム非依存で
LittleEndian で読み書きします。
これで zip のヘッダ構造を定義できるようになりました。
下記は読み出しに必要な最小限で、32bit ヘッダのみとなっています。
前回の手順通り、まずは EOR (End of Central Directory record) を検索します。
↑上のコードはファイルの終端から 512byte を読み込んで、EOR の Signature
"PK0506" (0x06054b50) を検索しています。
T_ZIP_CENTRALDIR32_EOR の CommentLength の分だけ追加情報を挿入できるので、
必ずしもこの範囲に存在するとは限りません。
見つからない場合にさらに読み進める処理が必要かもしれません。
EOR がわかれば CentralDirectoryOffset, CentralDirectorySize を使って
Central Directory を読み込むことが出来ます。
ファイル名検索に何度も利用するので、最初にメモリに読み込んでおきます。
EOR 読み込みと合わせて class 化します。
これで zip ファイル内のファイル情報にアクセスできるようになりました。
下記は ZipDirectory を使ってファイル名一覧を取り出すサンプルです。
T_ZIP_CENTRALDIR32 内のファイル名が 0 終端になっていない点に注意。
ZipDirectory の中に出てくる Buffer は汎用的なメモリ確保を行っています。
同様に File 型も必要に応じて作ります。
例えば stdio なら下記の通り。
ファイル情報がとれたので、あとはファイルの内容を読み出すだけです。
下記 UnzipFile() は、zip 内のファイルを解凍することが出来ます。
UnzipFile() では、Central Directory (T_ZIP_CENTRALDIR32) の情報を元に
まず Local Header (T_ZIP_LOCAL32) を読み込んでいます。
T_ZIP_LOCAL32 の NameLength と ExtraLength から実際のデータ位置が求まるので、
あらためてデータ本体を読み込みます。
データが圧縮されている場合は、前回解説したとおり zlib の raw format と
みなして展開しています。
ここまでのコードで zip 内ファイルを取り出すことが可能です。
ただし展開パスにディレクトリが含まれている場合は書き込みに失敗するので、
もう少しだけ手を加えてみます。
下記の MakePath() は階層に対応した mkdir() です。(終端に '/' が必要)
同じパスを何度も mkdir するのは非効率なので、前回と同じパスは省きます。
でもあまり効果が無いかもしれません。
zip の展開ができるようになりました。
下記 Unzip() は階層付きで zip に含まれる全ファイルを展開することが出来ます。
最終的なコードはこちら。Windows と Linux で確認しています。
・zip2.cpp
関連エントリ
・データ圧縮 zlib と gzip と zip (zlib で 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 にアクセスする)
| 次のページ(日付が古い方向)>>