EternalWindows
ZIP / IShellDispatchによる展開

ハードディスクの増量が進む今日においても、ファイルを圧縮して運用することは日常的に行われています。 Windowsにおけるファイルの圧縮フォーマットはMicrosoftが開発したCAB形式が有名ですが、 Windows XPからはZIP形式に標準対応したこともあり、最近ではZIPファイルによる圧縮もよく見かけるようになりました。 ZIPファイルにおける圧縮や展開の機能はzipfldr.dllに実装されていますが、 開発者にとって残念なことに、このDLLの使い方はリファレンス化されていません。 つまり、ZIPファイルを扱うための関数やインターフェースが分からないため、 正規の方法で圧縮や展開を行うことができません。 今回から取り上げる内容は、既存のCOMインターフェースを応用した非正規の方法であるため、 実際に使用する場合は十分に注意してください。

今回使用するインターフェースはIShellDispatchです。 IShellDispatchはシェルが提供する機能へアクセスするためのメソッドを持っており、 たとえばFileRunというメソッドを呼び出せば、 シェルが提供している「ファイル名を指定して実行」というダイアログを表示することができます。 また、NameSpaceというメソッドを呼び出せば、 指定されたパスのアイテムをFolderインターフェースで識別することができます。

VARIANT var;
Folder  *pFolder;

VariantInit(&var);
var.vt = VT_BSTR;
var.bstrVal = SysAllocString(L"C:\\sample");
hr = pShellDispatch->NameSpace(var, &pFolder);

NameSpaceの第1引数は、VARIANT構造体のアドレスを指定します。 vtメンバにVT_BSTRを指定すればbstrValメンバが初期化できるようになるため、 SysAllocStringでBSTR型の文字列を指定しています。 メソッドが成功した場合は第2引数にFolder型のアドレスが返りますが、 これはインターフェースです。 このインターフェースにはCopyHereというメソッドがあるのですが、 これに対してZIPのアイテムを指定すると、 そのZIPが展開されてFolderのパスにコピーされることになります。 よって、このCopyHereを如何にして呼び出すかが今回の鍵になります。

今回のプログラムは、IShellDispatchを使用してZIPファイルを展開します。

#include <windows.h>
#include <shlobj.h>

BOOL Unzip(IShellDispatch *pShellDispatch, LPWSTR lpszZipPath, LPWSTR lpszOutPath);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HRESULT        hr;
	IShellDispatch *pShellDispatch;

	CoInitialize(NULL);

	hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShellDispatch));
	if (FAILED(hr)) {
		CoUninitialize();
		return 0;
	}

	if (Unzip(pShellDispatch, L"C:\\sample.zip", L"C:\\sample"))
		MessageBox(NULL, TEXT("ZIPファイルを展開しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("ZIPファイルの展開に失敗しました。"), NULL, MB_ICONWARNING);

	CoUninitialize();

	return 0;
}

BOOL Unzip(IShellDispatch *pShellDispatch, LPWSTR lpszZipPath, LPWSTR lpszOutPath)
{
	HRESULT     hr;
	VARIANT     varOutDir, varZip, varItem, varOptions;
	Folder      *pOutFolder, *pZipFile;
	FolderItems *pZipFileItems;
	
	VariantInit(&varOutDir);
	varOutDir.vt = VT_BSTR;
	varOutDir.bstrVal = SysAllocString(lpszOutPath);
	hr = pShellDispatch->NameSpace(varOutDir, &pOutFolder);
	VariantClear(&varOutDir);
	if (hr != S_OK)
		return FALSE;

	VariantInit(&varZip);
	varZip.vt = VT_BSTR;
	varZip.bstrVal = SysAllocString(lpszZipPath);
	hr = pShellDispatch->NameSpace(varZip, &pZipFile);
	VariantClear(&varZip);
	if (hr != S_OK) {
		pOutFolder->Release();
		return FALSE;
	}

	hr = pZipFile->Items(&pZipFileItems);
	if (hr != S_OK) {
		pZipFile->Release();
		pOutFolder->Release();
		return FALSE;
	}

	VariantInit(&varItem);
	varItem.vt = VT_DISPATCH;
	varItem.pdispVal = pZipFileItems;
	VariantInit(&varOptions);
	varOptions.vt = VT_I4;
	varOptions.lVal = 4;
	hr = pOutFolder->CopyHere(varItem, varOptions);
	if (hr != S_OK) {
		pZipFileItems->Release();
		pZipFile->Release();
		pOutFolder->Release();
		return FALSE;
	}

	pZipFileItems->Release();
	pZipFile->Release();
	pOutFolder->Release();

	return TRUE;
}

IShellDispatchを取得するには、CoCreateInstanceにCLSID_Shellを指定します。 これが成功した場合はUnzipという自作関数で、第2引数のZIPファイルを第3引数のフォルダに展開します。 Unzipの処理を順に見ていきます。

VariantInit(&varOutDir);
varOutDir.vt = VT_BSTR;
varOutDir.bstrVal = SysAllocString(lpszOutPath);
hr = pShellDispatch->NameSpace(varOutDir, &pOutFolder);

このコードは展開先フォルダのFolderを取得する部分です。 最終的な目標はpOutFolder->CopyHereによって、ZIPファイルを自分に対してコピーすることです。 VariantClearによって、SysAllocStringで確保されたメモリは開放されます。

VariantInit(&varZip);
varZip.vt = VT_BSTR;
varZip.bstrVal = SysAllocString(lpszZipPath);
hr = pShellDispatch->NameSpace(varZip, &pZipFile);
VariantClear(&varZip);

このコードはZIPファイルのFolderを取得する部分です。 ただし、このFolderをCopyHereに指定してもZIPファイルの中身ではなく、ZIPファイル自体がコピーされることになるため、 中身のアイテムを取得するためにItemsを呼び出します。

hr = pZipFile->Items(&pZipFileItems);

これで、ZIPファイルの中身をpZipFileItemsで識別できるようになります。 後はCopyHereを呼び出すだけです。

VariantInit(&varItem);
varItem.vt = VT_DISPATCH;
varItem.pdispVal = pZipFileItems;
VariantInit(&varOptions);
varOptions.vt = VT_I4;
varOptions.lVal = 4;
hr = pOutFolder->CopyHere(varItem, varOptions);

第1引数にはpZipFileItemsを格納したVARIANT構造体を指定します。 このとき、vtにVT_DISPATCHを指定してpdispValにpZipFileItemsを指定します。 第2引数はオプションを格納したVARIANT構造体であり、 lValに4を指定した場合は展開時におけるダイアログが表示されなくなります。 lValを初期化する場合はvtにVT_I4を指定します。

ZIPファイルへの追加

既存のZIPファイルにアイテムを追加する例を次に示します。

#include <windows.h>
#include <shlobj.hgt;

BOOL AddItem(IShellDispatch *pShellDispatch, LPWSTR lpszZipPath, LPWSTR lpszInPath);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HRESULT        hr;
	IShellDispatch *pShellDispatch;

	CoInitialize(NULL);

	hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShellDispatch));
	if (FAILED(hr)) {
		CoUninitialize();
		return 0;
	}

	if (AddItem(pShellDispatch, L"C:\\sample.zip", L"C:\\sample"))
		MessageBox(NULL, TEXT("アイテムを追加しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("アイテムの追加に失敗しました。"), NULL, MB_ICONWARNING);

	CoUninitialize();

	return 0;
}

BOOL AddItem(IShellDispatch *pShellDispatch, LPWSTR lpszZipPath, LPWSTR lpszInPath)
{
	HRESULT hr;
	VARIANT varInDir, varZip, varItem, varOptions;
	Folder  *pInFolder, *pZipFile;
	
	VariantInit(&varInDir);
	varInDir.vt = VT_BSTR;
	varInDir.bstrVal = SysAllocString(lpszInPath);
	hr = pShellDispatch->NameSpace(varInDir, &pInFolder);
	VariantClear(&varInDir);
	if (hr != S_OK)
		return FALSE;

	VariantInit(&varZip);
	varZip.vt = VT_BSTR;
	varZip.bstrVal = SysAllocString(lpszZipPath);
	hr = pShellDispatch->NameSpace(varZip, &pZipFile);
	VariantClear(&varZip);
	if (hr != S_OK) {
		pInFolder->Release();
		return FALSE;
	}
	
	VariantInit(&varItem);
	varItem.vt = VT_DISPATCH;
	varItem.pdispVal = pInFolder;
	VariantInit(&varOptions);
	varOptions.vt = VT_I4;
	varOptions.lVal = 4;
	hr = pZipFile->CopyHere(varItem, varOptions);
	if (hr != S_OK) {
		pZipFile->Release();
		pInFolder->Release();
		return FALSE;
	}

	pZipFile->Release();
	pInFolder->Release();

	return TRUE;
}

pZipFile->CopyHereによって、第1引数のアイテムがZIPファイルにコピーされることになります。 今回のアイテムはフォルダになっているため、フォルダが圧縮されてZIPファイルに追加されます。



戻る