なんでこんな令和の時代に、こんなレトロなこと、やっとんじゃい。
当初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なんかな?
他にもニッチなIT関連要素をまとめていますので、よければ一覧記事もご覧ください。