c++でzip解凍 – IShellDispatch編 –

なんでこんな令和の時代に、こんなレトロなこと、やっとんじゃい。
当初Windows XP向けに開発したWin32アプリケーションで、zipファイルを指定されたら、同じ場所にTmp_yyyymmddhhmmssフォルダ掘って解凍して、とあるcsvファイルを探したい。
zlibを使う方法も考えているけど、簡単そうなこちらを参考にさせていただきました。

ヘッダファイル

#pragma once

#include "resource.h"

#include <Commdlg.h>
#include <iostream>
#include <string.h>
#include <string>
#include <shlobj.h>
#include <shellapi.h>
#include <tchar.h>
#include <Uxtheme.h>
#include <functional>

using namespace std;

bool ExtractZip();
bool ExtractZip(IShellDispatch*, TCHAR*, TCHAR*);
bool FindCSVFile(TCHAR*);

// メンバ変数
TCHAR m_strFilePath[MAX_PATH];	// 選択ファイルパス
TCHAR m_strCSVPath[MAX_PATH];	// CSVファイルパス
TCHAR m_strTempPath[MAX_PATH];	// 一時解凍フォルダパス

ソースコード

m_strFilePathには選択したzipファイルパスが入っているとして、Tmpフォルダ掘るところ。

#include "stdafx.h"
#include "上のヘッダファイル名.h"
#include <time.h>
#include <experimental/filesystem>

using namespace std::experimental::filesystem;

bool ExtractZip() {
	int nStartPos = 0;			// ファイル名称開始位置
	int nEndPos = 0;			// ファイル名称終了位置
	int nTryCount = 0;			// 試行回数
	TCHAR OutputPath[MAX_PATH];	// 出力先
	int idx = 0;
	TCHAR TempFolderName[] = _T("Tmp_");
	bool bIsCreate = false;
	bool ret = false;

	for (int i = 0; i < MAX_PATH; i++) {
		// 終端まできたら手前がファイル名称終了位置
		if (m_strFilePath[i] == NULL) {
			nEndPos = i - 1;
			break;
		}
		// 最後の\マークの次がファイル名称開始位置
		if (m_strFilePath[i] == '\\') {
			nStartPos = i + 1;
		}
	}

	// 選択ファイルと同じ場所に作る
	for (int i = 0; i < nStartPos; i++, idx++)
	{
		OutputPath[idx] = m_strFilePath[i];
	}
	for (int i = 0; i < _tcslen(TempFolderName); i++, idx++) {
		OutputPath[idx] = TempFolderName[i];
	}
	while (true)
	{
		// 初期化
		for (int i = idx; i < MAX_PATH; ++i) {
			OutputPath[i] = NULL;
		}

		time_t tnow = time(NULL);
		struct tm pnow;
		localtime_s(&pnow, &tnow);
		char charnow[MAX_PATH];
		strftime(charnow, sizeof(charnow), "%Y%m%d%H%M%S", &pnow);
		for (int i = 0; i < strlen(charnow); i++)
		{
			OutputPath[idx + i] = charnow[i];
		}
		bIsCreate = std::experimental::filesystem::create_directory(OutputPath);
		if (bIsCreate) {
			idx += strlen(charnow);
			_tcscpy_s(m_strTempPath, OutputPath);
			break;
		}

		// 解凍先の生成に失敗しても3回はリトライ
		nTryCount++;
		if (3 < nTryCount) {
			return false;
		}
		// フォルダ名が変化するよう1秒待機
		Sleep(1000);
	}

	nTryCount = 0;
	HRESULT hr;
	IShellDispatch *pShellDisp;
	// UNICODE文字を標準出力に正しく表示させるためにロケールを設定
	_tsetlocale(LC_ALL, _TEXT(""));

	CoInitializeEx(0, COINIT_MULTITHREADED);
	hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShellDisp));
	if (FAILED(hr)) {
		CoUninitialize();
		return false;
	}
	while (true)
	{
		ret = ExtractZip(pShellDisp, m_strFilePath, OutputPath);
		if (ret) {
			break;
		}

		// 解凍先の生成に失敗しても3回はリトライ
		nTryCount++;
		if (3 < nTryCount) {
			// 作成したフォルダは削除
			if (bIsCreate) {
				std::experimental::filesystem::remove_all(OutputPath);
			}
			return false;
		}
		// 1秒待機
		Sleep(1000);
	}

	return ret;
}

上記から呼び出す、zipをTmpフォルダに解凍するところ。

bool ExtractZip(IShellDispatch *pShellDisp, TCHAR* strZipPath, TCHAR* strOutPath) {
	bool ret = false;
	HRESULT hr;
	Folder  *pOutDtc = NULL;
	Folder *pZipFile = NULL;
	FolderItems *pZipItems = NULL;

	// 展開先Folderオブジェクトを作成
	VARIANT vDtcDir;
	VariantInit(&vDtcDir);
	vDtcDir.vt = VT_BSTR;
	vDtcDir.bstrVal = SysAllocString(strOutPath);
	hr = pShellDisp->NameSpace(vDtcDir, &pOutDtc);
	VariantClear(&vDtcDir);
	if (hr != S_OK) {
		goto EXIT;
	}

	// ZIPファイルのFolderオブジェクトを作成
	VARIANT varZip;
	VariantInit(&varZip);
	varZip.vt = VT_BSTR;
	varZip.bstrVal = SysAllocString(strZipPath);
	hr = pShellDisp->NameSpace(varZip, &pZipFile);
	VariantClear(&varZip);
	if (hr != S_OK) {
		goto EXIT;
	}
	// ZIPファイルの中身を取得
	hr = pZipFile->Items(&pZipItems);
	if (hr != S_OK) {
		goto EXIT;
	}
	VARIANT vDisp;
	VariantInit(&vDisp);
	vDisp.vt = VT_DISPATCH;
	vDisp.pdispVal = pZipItems;
	VARIANT vOpt;
	VariantInit(&vOpt);
	vOpt.vt = VT_I4;
	vOpt.lVal = 0;
	// 解凍
	hr = pOutDtc->CopyHere(vDisp, vOpt);
	VariantClear(&vDisp);
	VariantClear(&vOpt);
	if (hr != S_OK) {
		goto EXIT;
	}

	// 解凍先にcsvファイルはあるか
	ret = FindCSVFile(strOutPath);

EXIT:;
	if (pZipItems != NULL) {
		pZipItems->Release();
	}
	if (pZipFile != NULL) {
		pZipFile->Release();
	}
	if (pOutDtc != NULL) {
		pOutDtc->Release();
	}

	return ret;
}

解凍した結果、csvファイルを探すところ。
csvかどうかだけじゃなくて諸々調べているので、ここでは割愛しています。

bool FindCSVFile(TCHAR* strOutPath)
{
	WIN32_FIND_DATA tFindFileData;
	int nEndIdx;
	TCHAR strWC[] = _T("\\*.*");
	bool ret = false;

	// すべてのファイルを検索するためのワイルドカードを付与
	for (int i = 0; i < MAX_PATH; i++) {
		if (strOutPath[i] == NULL) {
			nEndIdx = i;
			break;
		}
	}
	for (int i = 0; i < _tcslen(strWC); i++) {
		strOutPath[nEndIdx + i] = strWC[i];
	}

	// 最初に一致するファイルを取得
	HANDLE hFile = ::FindFirstFile(strOutPath, &tFindFileData);
	if (INVALID_HANDLE_VALUE == hFile) return false;

	// L"??*.*"を削除
	for (int i = 0; i < _tcslen(strWC); i++) {
		strOutPath[nEndIdx + i] = NULL;
	}

	do {
		TCHAR* wpFileName = tFindFileData.cFileName;
		// フルパスの生成
		TCHAR strFullPath[MAX_PATH];
		memset(&strFullPath, NULL, sizeof(strFullPath));
		_tcscpy_s(strFullPath, strOutPath);
		strFullPath[nEndIdx] = L'\\';
		for (int i = 0; i < _tcslen(wpFileName); i++) {
			strFullPath[nEndIdx + 1 + i] = wpFileName[i];
		}
		strFullPath[nEndIdx + 1 + _tcslen(wpFileName)] = NULL;

		// フォルダかどうかを判定
		if (tFindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			// L"."とL".."はスキップ
			if (L'.' == wpFileName[0]) {
				if (1 == _tcslen(wpFileName) || (L'.' == wpFileName[1] && 2 == _tcslen(wpFileName))) {
					continue;
				}
			}
			// 再起してサブフォルダを巡回する
			FindCSVFile(strFullPath);
		}
		else if (/* strFullPathが条件に当てはまるか */)) {
			_tcscpy_s(m_strCSVPath, strFullPath);
			ret = true;
			break;
		}
		// 次に一致するファイルの検索
	} while (::FindNextFile(hFile, &tFindFileData));

	// 検索ハンドルを閉じる
	::FindClose(hFile);
	return ret;
}

あとはファイルの再選択や終了時にTmpフォルダの削除も忘れずに。

	// 解凍フォルダがあれば削除
	if (_tcslen(m_strTempPath) != 0) {
		std::experimental::filesystem::remove_all(m_strTempPath);
		memset(&m_strTempPath, NULL, sizeof(m_strTempPath));
	}

これで果たしていいのか?

Windows10 64bitでも動きましたけれど、良くはない。
エクスプローラーで操作する以外にCopyHereで解凍するのは非推奨だよってMicrosoft様も仰ってる。

http://support.microsoft.com/kb/2679832/ja

やっぱzlibなんかな?

返信を残す

メールアドレスが公開されることはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)