- 概要
- 指定したファイル/フォルダを ZIP 圧縮する。
ZIP 圧縮は Folder インターフェースを利用して行う。
Folder インターフェースによる ZIP 圧縮は圧縮完了を検知する仕組みが用意されていないため、本コードでは ZIP ファイルの書き込みオープンを定期的に行いオープンできた事を圧縮完了とみなすようにしている。
他サイトで圧縮完了を検知するために圧縮前と圧縮後のスレッドを比べ、圧縮前にはなかったスレッドが全て終了することを完了とみなしているものがあるが、これでは完了とみなすことはできないため注意。(このスレッド終了による検知ではフリーズすることがある)
- 注意
- 圧縮に時間を要する場合は以下の進捗ダイアログを表示する。(FOF_NO_GUI を指定しても表示される。Windows 11/10 で確認)
- C++ ソース(Windows 用)
- #include <tchar.h>
#include <locale.h>
#include <Windows.h>
#include <shlwapi.h>
#include <shlobj.h>
#if defined(UNICODE) || defined(_UNICODE)
#define ZipArchiver ZipArchiverW
#else
#define ZipArchiver ZipArchiverA
#endif
template <typename T>
inline void SafeRelease(T*& p)
{
if (p)
{
p->Release();
p = nullptr;
}
}
static HRESULT FolderItemCounter(LONG* lpItemCount, Folder* lpFolder)
{
HRESULT hr;
FolderItems* lpItems;
hr = lpFolder->Items(&lpItems);
if FAILED(hr)
{
return hr;
}
hr = lpItems->get_Count(lpItemCount);
lpItems->Release();
if FAILED(hr)
{
return hr;
}
return hr;
}
static BOOL CreateEmptyZip(const wchar_t* lpszPath)
{
const BYTE nZipHeader[] =
{
0x50, 0x4B, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
BOOL bResultCode = FALSE;
HANDLE hFile = ::CreateFileW(lpszPath, GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD nDataLength;
BOOL bResult = ::WriteFile(hFile, nZipHeader, sizeof(nZipHeader), &nDataLength, nullptr);
if (bResult)
{
if (sizeof(nZipHeader) == nDataLength)
{
bResultCode = TRUE;
}
}
::CloseHandle(hFile);
}
return bResultCode;
}
// @func
// ZIP 圧縮。UNICODE 版
// @param
// [I/-] lpszDstPath
// 出力先 ZIP ファイルのパス。
// [I/-] lpszSrcPath
// 圧縮対象のファイル/フォルダのパス。フォルダを指定するとフォルダをZIPへ格納する。
// @note
// CoInitialize / CoUninitialize の呼び出しが必要。
HRESULT WINAPI ZipArchiverW(const wchar_t* lpszDstPath, const wchar_t* lpszSrcPath)
{
BOOL bResult = CreateEmptyZip(lpszDstPath);
if (!bResult)
{
return E_FAIL;
}
IShellDispatch* lpShellDispatch = nullptr;
Folder* lpFolder = nullptr;
Folder* lpZipFolder = nullptr;
HRESULT hr;
hr = ::CoCreateInstance(CLSID_Shell, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&lpShellDispatch));
if FAILED(hr)
{
goto Finish;
}
VARIANT v;
VariantInit(&v);
v.vt = VT_BSTR;
v.bstrVal = SysAllocString(lpszSrcPath);
hr = lpShellDispatch->NameSpace(v, &lpFolder);
VariantClear(&v);
if FAILED(hr)
{
goto Finish;
}
VariantInit(&v);
v.vt = VT_BSTR;
v.bstrVal = SysAllocString(lpszDstPath);
hr = lpShellDispatch->NameSpace(v, &lpZipFolder);
VariantClear(&v);
if FAILED(hr)
{
goto Finish;
}
VariantInit(&v);
v.vt = VT_DISPATCH;
v.pdispVal = lpFolder;
VARIANT vOption;
VariantInit(&vOption);
vOption.vt = VT_I4;
vOption.lVal = FOF_NOERRORUI;
hr = lpZipFolder->CopyHere(v, vOption);
if FAILED(hr)
{
goto Finish;
}
// 圧縮完了待ち。
// ZIPファイルへの書き込みアクセスが可能になったことをもって圧縮完了とする。
for(;;)
{
Sleep(1500);
HANDLE hFile = ::CreateFileW(lpszDstPath, GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile != INVALID_HANDLE_VALUE)
{
::CloseHandle(hFile);
break;
}
}
// ZIP内のアイテムを確認。
// アイテムが存在しなければキャンセルと見なし、ZIPファイルを削除する。
LONG nItemCount;
hr = FolderItemCounter(&nItemCount, lpZipFolder);
if FAILED(hr)
{
goto Finish;
}
if (nItemCount == 0)
{
::DeleteFileW(lpszDstPath);
hr = S_FALSE;
}
Finish:
SafeRelease(lpZipFolder);
SafeRelease(lpFolder);
SafeRelease(lpShellDispatch);
return hr;
}
// @func
// ZIP圧縮。ANSI/MBCS 版
// @param
// [I/-] lpszDstPath
// 出力先 ZIP ファイルのパス。
// [I/-] lpszSrcPath
// 圧縮対象のファイル/フォルダのパス。フォルダを指定するとフォルダをZIPへ格納する。
// @note
// CoInitialize / CoUninitialize の呼び出しが必要。
HRESULT WINAPI ZipArchiverA(const char* lpszDstPath, const char* lpszSrcPath)
{
wchar_t wszDstPath[MAX_PATH];
wchar_t wszSrcPath[MAX_PATH];
int nResult;
nResult = ::MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, lpszDstPath, -1, wszDstPath, _countof(wszDstPath));
if (!nResult)
{
return E_INVALIDARG;
}
nResult = ::MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, lpszSrcPath, -1, wszSrcPath, _countof(wszSrcPath));
if (!nResult)
{
return E_INVALIDARG;
}
return ZipArchiverW(wszDstPath, wszSrcPath);
}
// @func
// ZIP 圧縮のサンプル。
// @note
// CoInitialize / CoUninitialize を呼び出して ZipArchiverA / ZipArchiverW を呼び出す。
int _tmain(int argc, _TCHAR* argv[])
{
HRESULT hr;
_tsetlocale(LC_ALL, _T("Japanese"));
::CoInitialize(nullptr);
hr = ZipArchiver(argv[1], argv[2]);
::CoUninitialize();
if (hr != S_OK)
{
hr = 1;
}
return hr;
}