確認環境: Windows 7 SP1 64bit, Visual Studio Community 2015 Update 3
IFileOperation関係の前の記事: IFileOperationで異なる種類の操作を登録した時のUI表示の話 (以前はC#のCOM相互運用を使っていました)
IFileOperation関係の次の記事: IFileOperation::NewItem()の使用例
背景
ExplorerでZIPファイルを展開出来るのならIFileOperationでも出来るのでは?
{展開, 解凍, decompress, extract, unzip}などでググっても全くヒットしないけど出来るのでは?
といろいろ試していたら出来ました。
注意点
IShellItem::BindToHandler method 中のBHID_EnumItemsの説明には"If the item is a folder"とフォルダの場合だけ記述されており、ZIPファイルの場合に取得できるかどうかは記述されていません。
そのため環境によっては上手くいかない可能性があります。
実際の処理
COMを扱うならC++から使う方が楽だと思ったので、今回からC++です。CComPtrによりリソースが解放されます。
ZIPファイルのパスからIShellItemを作成し、IShellItem::BindToHandlerでIEnumShellItemsを取得しているのがポイントです。
なお、BHID_StorageEnumを使った場合に違いがあるかは分かりませんでした。隠しフォルダーはどちらの場合でも展開されました。
#include<winsdkver.h> #define WINVER _WIN32_WINNT_WIN7 #define _WIN32_WINNT _WIN32_WINNT_WIN7 #include<ShObjIdl.h> #include<ShlGuid.h> #include<atlbase.h> void ExtractZip(LPCWSTR zipPath, LPCWSTR destFolderPath) { int hr; CComPtr<IShellItem> pShellItemZip; hr = SHCreateItemFromParsingName(zipPath, nullptr, IID_PPV_ARGS(&pShellItemZip)); if (FAILED(hr)) { return; } CComPtr<IEnumShellItems> pEnumShellItems; hr = pShellItemZip->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&pEnumShellItems)); if (FAILED(hr)) { return; } CComPtr<IShellItem> pShellItemDestFolder; hr = SHCreateItemFromParsingName(destFolderPath, nullptr, IID_PPV_ARGS(&pShellItemDestFolder)); if (FAILED(hr)) { return; } CComPtr<IFileOperation> pFileOperation; hr = pFileOperation.CoCreateInstance(CLSID_FileOperation); if (FAILED(hr)) { return; } hr = pFileOperation->CopyItems(pEnumShellItems, pShellItemDestFolder); if (FAILED(hr)) { return; } pFileOperation->PerformOperations(); } int main() { if (SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) { ExtractZip(L"c:\\test\\test.zip", L"c:\\test\\target\\"); CoUninitialize(); } }
展開例
例1: フォルダ1つを圧縮したZIPファイル
before: c:\test ├test.zip │└foo │ ├bar │ └baz └target after: c:\test ├test.zip(内容は同じなので子要素省略) └target └foo ├bar └baz
例2: 直下に複数のエントリーを含むZIPファイル
before: c:\test ├test.zip │├[Content_Types].xml │├customXml(子要素略) │├docProps(子要素略) │├word(子要素略) │└_rels(子要素略) └target after: c:\test ├test.zip(内容は同じなので子要素省略) └target ├[Content_Types].xml ├customXml(子要素略) ├docProps(子要素略) ├word(子要素略) └_rels(子要素略)
パスワードがかかったZIPを展開する場合 (2017/01/14 追記)
ZIPにパスワードがかかってる場合、自動的にプロンプトが表示されます。
正しいパスワードを入力した場合は正常に展開されます。キャンセルした場合はコピー全体がキャンセルされます。
ただしスキップした場合は次のダイアログが表示されます。
再試行を押すと同一ファイルのパスワード入力へ戻り、スキップを押すと次のファイルのパスワード入力に進みます。
ここで表示されているHRESULTの 0x800704DE は ERROR_CONTINUE の数値です。
邪魔でしか無いこのダイアログは IFileOperation::SetOperationFlags method で FOF_NOERRORUI を指定すると表示されなくなります。