diff --git a/source/ShellExt/MultiParShlExt.ini b/source/ShellExt/MultiParShlExt.ini new file mode 100644 index 0000000..5381249 Binary files /dev/null and b/source/ShellExt/MultiParShlExt.ini differ diff --git a/source/ShellExt/ShellExt.cpp b/source/ShellExt/ShellExt.cpp new file mode 100644 index 0000000..68c53ad --- /dev/null +++ b/source/ShellExt/ShellExt.cpp @@ -0,0 +1,1568 @@ +// Copyright : 2021-12-02 Yutaka Sawada +// License : The MIT license + +// ShellExt.cpp : DLL アプリケーション用のエントリ ポイントを定義します。 +// + +#ifndef _UNICODE +#define _UNICODE +#endif +#ifndef UNICODE +#define UNICODE +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 // Windows Vista or later +#endif + +#include +#include +#include + +#pragma comment(lib, "uxtheme.lib") + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// クラスファクトリの作成 (IClassFactoryインターフェイスを継承する) +class CShellExtClassFactory : public IClassFactory +{ +protected: + // 参照カウント + volatile long m_cRef; +public: + // コンストラクタ・デストラクタ + CShellExtClassFactory(); + ~CShellExtClassFactory(); + + //IUnknown インターフェイスのメソッド + STDMETHODIMP QueryInterface(REFIID, LPVOID FAR *); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + //IClassFactory インターフェイスのメソッド + STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, LPVOID FAR *); + STDMETHODIMP LockServer(BOOL); +}; + +// ポインタ型を宣言 +typedef CShellExtClassFactory *LPCSHELLEXTCLASSFACTORY; + + +class CShellExtension : public IShellExtInit, + public IContextMenu +{ +protected: + volatile long m_cRef; // オブジェクトの参照カウント + LPDATAOBJECT m_pDataObj; // エクスプローラから受け取るデータオブジェクト + int single_file; + int CheckData(void); // 選択したファイルの検査 + int DoCommand(UINT idCmd); // 選択した拡張メニューのコマンド実行 + int DoCommand7zip(void); + +public: + CShellExtension(); + ~CShellExtension(); + + // IUnknown インターフェイスのメソッド + STDMETHODIMP QueryInterface(REFIID, LPVOID FAR *); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IShellExtInit インターフェイスのメソッド + STDMETHODIMP Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hKeyID); + + // IContextMenu インターフェイスのメソッド + STDMETHODIMP QueryContextMenu( + HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags); + STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi); + STDMETHODIMP GetCommandString( + UINT_PTR idCmd, UINT uFlags, UINT FAR *reserved, LPSTR pszName, UINT cchMax); +}; + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// {333EFDA5-A74E-4df4-A225-92A7AF81F29A} +static const GUID CLSID_ShellExt = +{ 0x333efda5, 0xa74e, 0x4df4, { 0xa2, 0x25, 0x92, 0xa7, 0xaf, 0x81, 0xf2, 0x9a } }; + +HINSTANCE g_inst = NULL; +HBITMAP g_bmp, g_bmp2; +volatile long g_cRefDll = 0; + +#define TOTAL_LENGTH 256 +#define MIN_LENGTH 2 + +// 動作設定 (MultiPar.ini から読み込む) +int menu_behavior; + +// 7-Zip のパスが正しいか確認してキーを閉じる +static int check_path_7zip(HKEY hKey, wchar_t *buf) +{ + unsigned long ret, type, size; + + size = (MAX_PATH - 7) * 2; + ret = RegQueryValueEx(hKey, L"Path", NULL, &type, (LPBYTE)buf, &size); + if (ret == ERROR_SUCCESS){ + if ((type == REG_SZ) && (size >= 6) && (buf[size / 2 - 2] == '\\')){ + // 7-Zip の実行ファイルが存在するか確かめる + wcscat(buf, L"7zG.exe"); + type = GetFileAttributes(buf); + if ((type == INVALID_FILE_ATTRIBUTES) || (type & FILE_ATTRIBUTE_DIRECTORY)){ + buf[0] = 0; + ret = 100002; // ファイルが存在しない + } + } else { + buf[0] = 0; + ret = 100001; // key は存在するけど形式が違う + } + } + RegCloseKey(hKey); + return ret; +} + +// 7-Zip のディレクトリを取得する +static void get_path_7zip(wchar_t *buf) +{ + int ret; + HKEY hKey; + + buf[0] = 0; // 正常に取得できた場合は、ここに値が入る + ret = RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\7-Zip", 0, KEY_READ, &hKey); + if (ret == ERROR_SUCCESS) + ret = check_path_7zip(hKey, buf); + + if (ret != ERROR_SUCCESS){ + // 関連付けやインストール時の選択肢によっては HKLM に値が記録される?32-bit 版の 7-Zip + ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\7-Zip", 0, KEY_READ, &hKey); + if (ret == ERROR_SUCCESS) + ret = check_path_7zip(hKey, buf); + } + + if (ret != ERROR_SUCCESS){ + // 64-bit 版の 7-Zip のキーは 32-bit 用のレジストリには書き込まれてない。 + ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\7-Zip", 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + if (ret == ERROR_SUCCESS) + check_path_7zip(hKey, buf); + } +} + +static void InitBitmapInfo(BITMAPINFO *pbmi, LONG cx, LONG cy) +{ + ZeroMemory(pbmi, sizeof(BITMAPINFO)); + pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + pbmi->bmiHeader.biWidth = cx; + pbmi->bmiHeader.biHeight = cy; + pbmi->bmiHeader.biPlanes = 1; + pbmi->bmiHeader.biBitCount = 32; + pbmi->bmiHeader.biCompression = BI_RGB; +} + +// メニュー・アイコン用のビットマップを読み込む +static int load_menu_icon(void) +{ + int size_w, size_h; + HICON hIcon; + LPVOID lpBits; + BITMAPINFO bmi; + + if (g_bmp != NULL) + return 0; + + size_w = GetSystemMetrics(SM_CXSMICON); + size_h = GetSystemMetrics(SM_CYSMICON); + + // ARGB 形式のビットマップを作成する + InitBitmapInfo(&bmi, size_w, size_h); + g_bmp = CreateDIBSection(NULL, (BITMAPINFO*)&bmi, DIB_RGB_COLORS, &lpBits, NULL, 0); + if (g_bmp == NULL) + return 1; + + // アイコンをロードする + hIcon = (HICON)LoadImage(g_inst, MAKEINTRESOURCE(100), IMAGE_ICON, size_w, size_h, LR_DEFAULTCOLOR); + if (hIcon == NULL){ + DeleteObject(g_bmp); + g_bmp = NULL; + return 2; + } + + // ビットマップに ARGB 形式のアイコンをコピーする(アルファ値が無いと見た目がおかしくなる) + HDC hDC; + HBITMAP prev_bmp; + hDC = CreateCompatibleDC(NULL); + if (hDC == NULL){ + DestroyIcon(hIcon); + DeleteObject(g_bmp); + g_bmp = NULL; + return 3; + } + prev_bmp = (HBITMAP)SelectObject(hDC, g_bmp); + if (DrawIconEx(hDC, 0, 0, hIcon, size_w, size_h, 0, NULL, DI_NORMAL) == 0){ + SelectObject(hDC, prev_bmp); + DeleteDC(hDC); + DestroyIcon(hIcon); + DeleteObject(g_bmp); + g_bmp = NULL; + return 4; + } + SelectObject(hDC, prev_bmp); + DeleteDC(hDC); + DestroyIcon(hIcon); + + return 0; +} + +// Uxtheme.h を参照すること。Windows Vista 以降なら対応してる +typedef DWORD ARGB; + +static int ConvertToPARGB32(HDC hdc, __inout ARGB *pargb, HBITMAP hbmp, int size_x, int size_y, int cxRow) +{ + BITMAPINFO bmi; + InitBitmapInfo(&bmi, size_x, size_y); + + HRESULT hr = E_OUTOFMEMORY; + HANDLE hHeap = GetProcessHeap(); + void *pvBits = HeapAlloc(hHeap, 0, bmi.bmiHeader.biWidth * 4 * bmi.bmiHeader.biHeight); + if (pvBits){ + hr = E_UNEXPECTED; + if (GetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, pvBits, &bmi, DIB_RGB_COLORS) == bmi.bmiHeader.biHeight){ + ULONG cxDelta = cxRow - bmi.bmiHeader.biWidth; + ARGB *pargbMask = static_cast(pvBits); + + for (ULONG y = bmi.bmiHeader.biHeight; y; --y){ + for (ULONG x = bmi.bmiHeader.biWidth; x; --x){ + if (*pargbMask++){ // transparent pixel + *pargb++ = 0; + } else { // opaque pixel + *pargb++ |= 0xFF000000; + } + } + + pargb += cxDelta; + } + + hr = S_OK; + } + + HeapFree(hHeap, 0, pvBits); + } + + return hr; +} + +static bool HasAlpha(__in ARGB *pargb, int size_x, int size_y, int cxRow) +{ + int cxDelta = cxRow - size_x; + for (int y = size_y; y; --y){ + for (int x = size_x; x; --x){ + if (*pargb++ & 0xFF000000){ + return true; + } + } + + pargb += cxDelta; + } + + return false; +} + +// 実行ファイルからメニュー・アイコン用のビットマップを読み込む +static int load_menu_icon2(wchar_t *file_path) +{ + int size_w, size_h, rv; + HICON hIcon; + LPVOID lpBits; + BITMAPINFO bmi; + + if (g_bmp2 != NULL) + return 0; + + size_w = GetSystemMetrics(SM_CXSMICON); + size_h = GetSystemMetrics(SM_CYSMICON); + + // ARGB 形式のビットマップを作成する + InitBitmapInfo(&bmi, size_w, size_h); + g_bmp2 = CreateDIBSection(NULL, (BITMAPINFO*)&bmi, DIB_RGB_COLORS, &lpBits, NULL, 0); + if (g_bmp2 == NULL){ + return 3; + } + + // アイコンを実行ファイルから読み取る + rv = ExtractIconEx(file_path, 0, NULL, &hIcon, 1); + if ((hIcon == NULL) || (rv != 1)){ + DeleteObject(g_bmp2); + g_bmp2 = NULL; + return 4; + } + + // ビットマップにアイコンをコピーする + HDC hDC; + HBITMAP prev_bmp; + hDC = CreateCompatibleDC(NULL); + if (hDC == NULL){ + DeleteObject(g_bmp2); + g_bmp2 = NULL; + DestroyIcon(hIcon); + return 5; + } + prev_bmp = (HBITMAP)SelectObject(hDC, g_bmp2); + // ARGB形式に変換する作業バッファーを用意する + BLENDFUNCTION bfAlpha = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + BP_PAINTPARAMS paintParams = {0}; + paintParams.cbSize = sizeof(paintParams); + paintParams.dwFlags = BPPF_ERASE; + paintParams.pBlendFunction = &bfAlpha; + HDC hdcBuffer; + RECT rcIcon; + SetRect(&rcIcon, 0, 0, size_w, size_h); + HPAINTBUFFER hPaintBuffer = BeginBufferedPaint(hDC, &rcIcon, BPBF_DIB, &paintParams, &hdcBuffer); + if (hPaintBuffer){ + if (DrawIconEx(hdcBuffer, 0, 0, hIcon, size_w, size_h, 0, NULL, DI_NORMAL) == 0){ + EndBufferedPaint(hPaintBuffer, FALSE); + SelectObject(hDC, prev_bmp); + DeleteDC(hDC); + DestroyIcon(hIcon); + DeleteObject(g_bmp2); + g_bmp2 = NULL; + return 6; + } + // ConvertBufferToPARGB32 の間はエラーになってもアルファ無しのアイコンを表示する + RGBQUAD *prgbQuad; + int cxRow; + if (GetBufferedPaintBits(hPaintBuffer, &prgbQuad, &cxRow) == S_OK){ + ARGB *pargb = reinterpret_cast(prgbQuad); + if (!HasAlpha(pargb, size_w, size_h, cxRow)){ // アルファ値が無ければ + ICONINFO info; + if (GetIconInfo(hIcon, &info)){ + if (info.hbmMask) + ConvertToPARGB32(hdcBuffer, pargb, info.hbmMask, size_w, size_h, cxRow); + DeleteObject(info.hbmColor); + DeleteObject(info.hbmMask); + } + } + } + EndBufferedPaint(hPaintBuffer, TRUE); + } + SelectObject(hDC, prev_bmp); + DeleteDC(hDC); + DestroyIcon(hIcon); + + return 0; +} + +// 言語別のテキスト (MultiParShlExt.ini から読み込む) +int offset_c, offset_v, offset_a; +wchar_t menu_item[TOTAL_LENGTH]; + +static int load_setting(void) +{ + wchar_t path[MAX_PATH], path2[MAX_PATH], lang_num[8]; + int rv, lang_id; + + // 設定ファイルが存在するディレクトリ + // DLL と呼び出す EXE ファイルは同じディレクトリに置くこと + rv = GetModuleFileName(g_inst, path, MAX_PATH); + if ((rv == 0) || (rv >= MAX_PATH)) + return 1; + for (rv--; rv > 0; rv--){ + if (path[rv] == '\\'){ + path[rv] = 0; + break; + } + } + + // アプリケーション・データのディレクトリを決める + rv = 0; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, SHGFP_TYPE_CURRENT, path2))){ + //MessageBox(NULL, path2, L"program files path", MB_OK); + // ProgramFiles の位置と比較する + int i, max = 0; + while (path2[max] != 0) + max++; + for (i = 0; i < max; i++){ + if (path2[i] != path[i]) + break; + } + if (i == max){ + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path2))){ + //MessageBox(NULL, path2, L"application data path", MB_OK); + i = 0; + while (path2[i] != 0) + i++; + if (i < MAX_PATH - 10 - 18){ + wcscat(path2, L"\\MultiPar"); + rv = 2; // AppData を使う + } + } + } + } + if (rv != 2) + wcscpy(path2, path); // DLL が存在するディレクトリを使う + wcscat(path2, L"\\MultiPar.ini"); + //MessageBox(NULL, path2, L"MultiPar.ini path", MB_OK); + + // 動作設定を読み込む + menu_behavior = GetPrivateProfileInt(L"Option", L"ShellExtension", 0, path2); + lang_id = GetPrivateProfileInt(L"Option", L"Language", -1, path2); + + if ((menu_behavior & 16) == 0){ // メニューにアイコンを付ける + if (load_menu_icon() != 0) + menu_behavior |= 16; // アイコン作成失敗 + } + + if ((menu_behavior & 32) == 0){ // 7-Zip がインストールされてなければ使わない + get_path_7zip(path2); + if (path2[0] == 0){ + menu_behavior |= 32; // メニューを表示しない + } else if ((menu_behavior & 16) == 0){ + load_menu_icon2(path2); // アイコン取得に失敗してもメニューは表示する + } + } + + // 言語ごとのメニュー項目を取得する + wcscat(path, L"\\MultiParShlExt.ini"); + if (GetFileAttributes(path) == INVALID_FILE_ATTRIBUTES) + return 1; // 言語別テキストが無い + // 指定された言語のテキストを読み込む + if (lang_id < 0) + lang_id = GetUserDefaultLangID(); + wsprintf(lang_num, L"0x%04x", lang_id); + rv = GetPrivateProfileString(lang_num, L"MenuTitle", L"", menu_item, TOTAL_LENGTH, path); + if (rv < MIN_LENGTH){ // その言語の設定項目が無いので、読み込みに失敗した + // 同じ言語に複数の地域が割り振られてる場合は別ので代用する + unsigned int pri_id, sub_id; + pri_id = lang_id & 0x03FF; // PRIMARYLANGID だけにする + for (sub_id = 1; sub_id < 64; sub_id++){ // SUBLANGID を変更する + lang_id = pri_id | (sub_id << 10); + wsprintf(lang_num, L"0x%04x", lang_id); + rv = GetPrivateProfileString(lang_num, L"MenuTitle", L"", menu_item, TOTAL_LENGTH, path); + if (rv >= MIN_LENGTH) + break; + } + if (rv < MIN_LENGTH){ // 英語のリソースが存在すればそれで代用する + lang_id = 0x409; + wsprintf(lang_num, L"0x%04x", lang_id); + rv = GetPrivateProfileString(lang_num, L"MenuTitle", L"", menu_item, TOTAL_LENGTH, path); + } + } + if (rv >= MIN_LENGTH){ // 設定項目を読み込み成功した + offset_c = rv + 1; + if (offset_c >= TOTAL_LENGTH - MIN_LENGTH) + return 1; + rv = GetPrivateProfileString(lang_num, L"Create", L"", menu_item + offset_c, TOTAL_LENGTH - offset_c, path); + if (rv < MIN_LENGTH) + return 1; + offset_v = offset_c + rv + 1; + if (offset_v >= TOTAL_LENGTH - MIN_LENGTH){ + menu_behavior |= 8; // disable sub-menu for Verify + menu_behavior |= 32; // disable archive + return 0; + } + if ((menu_behavior & 8) == 0){ + rv = GetPrivateProfileString(lang_num, L"Verify", L"", menu_item + offset_v, TOTAL_LENGTH - offset_v, path); + offset_a = offset_v + rv + 1; + if (rv < MIN_LENGTH) + menu_behavior |= 8; // disable sub-menu for Verify + if (offset_a >= TOTAL_LENGTH - MIN_LENGTH){ + menu_behavior |= 32; // disable archive + return 0; + } + } else { + offset_a = offset_v; + } + if ((menu_behavior & 32) == 0){ + rv = GetPrivateProfileString(lang_num, L"Archive", L"", menu_item + offset_a, TOTAL_LENGTH - offset_a, path); + if (rv < MIN_LENGTH){ + menu_behavior |= 32; // disable archive + return 0; + } + } + } else { + return 1; + } + + return 0; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + if (dwReason == DLL_PROCESS_ATTACH){ + g_inst = hInstance; + g_bmp = NULL; + g_bmp2 = NULL; + menu_behavior = -1; // 設定はまだ読み込まれてない + } else if (dwReason == DLL_PROCESS_DETACH){ + if (g_bmp != NULL){ + DeleteObject(g_bmp); + g_bmp = NULL; + } + if (g_bmp2 != NULL){ + DeleteObject(g_bmp2); + g_bmp2 = NULL; + } + } + return 1; +} + +STDAPI DllCanUnloadNow(void) +{ + //参照カウントが 0 ならS_OK + return (g_cRefDll == 0 ? S_OK : S_FALSE); +} + + +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut) +{ + *ppvOut = NULL; + + if (IsEqualIID(rclsid, CLSID_ShellExt)){ + // クラスファクトリの作成。 + CShellExtClassFactory *pcf = new CShellExtClassFactory; + return pcf->QueryInterface(riid, ppvOut); + } + //失敗時はCLASS_E_CLASSNOTAVAILABLEを返す + return CLASS_E_CLASSNOTAVAILABLE; +} + +// レジストリ登録 regsvr32.exe ShellExt.dll +STDAPI DllRegisterServer(void) +{ + wchar_t CLSID_str[] = L"{333EFDA5-A74E-4df4-A225-92A7AF81F29A}"; + wchar_t ext_name[] = L"MultiPar Shell Extension"; + wchar_t path[MAX_PATH], buf[128]; + unsigned int len; + HKEY hKey = NULL; + +/*{ // for debug + len = load_setting(); + wsprintf(buf, L"return value = %d", len); + MessageBox(NULL, buf, NULL, 0); + + return E_FAIL; +}*/ + + len = GetModuleFileName(g_inst, path, MAX_PATH); + if ((len == 0) || (len >= MAX_PATH)) + return E_FAIL; + + // CLSID に COM オブジェクトを登録する + wsprintf(buf, L"Software\\Classes\\CLSID\\%s", CLSID_str); + if (RegCreateKeyEx(HKEY_CURRENT_USER, buf, 0, NULL, 0, KEY_SET_VALUE, NULL, &hKey, NULL) != ERROR_SUCCESS) + goto error_end; + RegCloseKey(hKey); + hKey = NULL; + wsprintf(buf, L"Software\\Classes\\CLSID\\%s\\InprocServer32", CLSID_str); + if (RegCreateKeyEx(HKEY_CURRENT_USER, buf, 0, NULL, 0, KEY_SET_VALUE, NULL, &hKey, NULL) != ERROR_SUCCESS) + goto error_end; + if (RegSetValueEx(hKey, NULL, 0, REG_SZ, (const BYTE*)path, (len+1)*2) != ERROR_SUCCESS) + goto error_end; + lstrcpy(buf, L"Apartment"); + len = lstrlen(buf); + if (RegSetValueEx(hKey, L"ThreadingModel", 0, REG_SZ, (const BYTE*)buf, (len+1)*2) != ERROR_SUCCESS) + goto error_end; + RegCloseKey(hKey); + hKey = NULL; + + // 全てのファイルとフォルダに Shell Extension を追加する + wsprintf(buf, L"Software\\Classes\\*\\shellex\\ContextMenuHandlers\\%s", ext_name); + if (RegCreateKeyEx(HKEY_CURRENT_USER, buf, 0, NULL, 0, KEY_SET_VALUE, NULL, &hKey, NULL) != ERROR_SUCCESS) + goto error_end; + len = lstrlen(CLSID_str); + if (RegSetValueEx(hKey, NULL, 0, REG_SZ, (const BYTE*)CLSID_str, (len+1)*2) != ERROR_SUCCESS) + goto error_end; + RegCloseKey(hKey); + hKey = NULL; + wsprintf(buf, L"Software\\Classes\\Directory\\shellex\\ContextMenuHandlers\\%s", ext_name); + if (RegCreateKeyEx(HKEY_CURRENT_USER, buf, 0, NULL, 0, KEY_SET_VALUE, NULL, &hKey, NULL) != ERROR_SUCCESS) + goto error_end; + if (RegSetValueEx(hKey, NULL, 0, REG_SZ, (const BYTE*)CLSID_str, (len+1)*2) != ERROR_SUCCESS) + goto error_end; + RegCloseKey(hKey); + hKey = NULL; + + // 許可を登録する + if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", 0, KEY_WRITE, &hKey) == ERROR_SUCCESS){ + len = lstrlen(ext_name); + RegSetValueEx(hKey, CLSID_str, 0, REG_SZ, (const BYTE*)ext_name, (len+1)*2); // 登録できなくてもエラーにしない + RegCloseKey(hKey); + } + hKey = NULL; + + return S_OK; +error_end: + if (hKey) + RegCloseKey(hKey); + return E_FAIL; +} + +// レジストリ登録解除 regsvr32.exe /u ShellExt.dll +STDAPI DllUnregisterServer(void) +{ + wchar_t CLSID_str[] = L"{333EFDA5-A74E-4df4-A225-92A7AF81F29A}"; + wchar_t ext_name[] = L"MultiPar Shell Extension"; + wchar_t buf[128]; + unsigned long err = 0, empty, len, ret; + HKEY hKey; + FILETIME ft; + + // CLSID の COM オブジェクトを削除する + wsprintf(buf, L"Software\\Classes\\CLSID\\%s\\InprocServer32", CLSID_str); + ret = RegDeleteKey(HKEY_CURRENT_USER, buf); + if ((ret != ERROR_SUCCESS) && (ret != ERROR_FILE_NOT_FOUND)) + err++; + wsprintf(buf, L"Software\\Classes\\CLSID\\%s", CLSID_str); + ret = RegDeleteKey(HKEY_CURRENT_USER, buf); + if ((ret != ERROR_SUCCESS) && (ret != ERROR_FILE_NOT_FOUND)) + err++; + + // 全てのファイルとフォルダの Shell Extension を削除する + wsprintf(buf, L"Software\\Classes\\*\\shellex\\ContextMenuHandlers\\%s", ext_name); + ret = RegDeleteKey(HKEY_CURRENT_USER, buf); + if ((ret != ERROR_SUCCESS) && (ret != ERROR_FILE_NOT_FOUND)) + err++; + wsprintf(buf, L"Software\\Classes\\Directory\\shellex\\ContextMenuHandlers\\%s", ext_name); + ret = RegDeleteKey(HKEY_CURRENT_USER, buf); + if ((ret != ERROR_SUCCESS) && (ret != ERROR_FILE_NOT_FOUND)) + err++; + + // 空のエントリーを削除する + empty = 1; + if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Classes\\*\\shellex\\ContextMenuHandlers", 0, KEY_READ, &hKey) == ERROR_SUCCESS){ + // ContextMenuHandlers 内に他のデータが無ければ項目自体を削除する。 + len = sizeof(buf) / 2; + if (RegEnumKeyEx(hKey, 0, buf, &len, NULL, NULL, NULL, &ft) != ERROR_NO_MORE_ITEMS){ + empty = 0; + } else { + len = sizeof(buf) / 2; + if (RegEnumValue(hKey, 0, buf, &len, NULL, NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS) + empty = 0; + } + RegCloseKey(hKey); + if (empty && (RegDeleteKey(HKEY_CURRENT_USER, L"Software\\Classes\\*\\shellex\\ContextMenuHandlers") != ERROR_SUCCESS)) + err++; + } + if (empty){ + if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Classes\\*\\shellex", 0, KEY_READ, &hKey) == ERROR_SUCCESS){ + // shellex 内に他のデータが無ければ項目自体を削除する。 + len = sizeof(buf) / 2; + if (RegEnumKeyEx(hKey, 0, buf, &len, NULL, NULL, NULL, &ft) != ERROR_NO_MORE_ITEMS){ + empty = 0; + } else { + len = sizeof(buf) / 2; + if (RegEnumValue(hKey, 0, buf, &len, NULL, NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS) + empty = 0; + } + RegCloseKey(hKey); + if (empty && (RegDeleteKey(HKEY_CURRENT_USER, L"Software\\Classes\\*\\shellex") != ERROR_SUCCESS)) + err++; + } + if (empty){ + if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Classes\\*", 0, KEY_READ, &hKey) == ERROR_SUCCESS){ + // * 内に他のデータが無ければ項目自体を削除する。 + len = sizeof(buf) / 2; + if (RegEnumKeyEx(hKey, 0, buf, &len, NULL, NULL, NULL, &ft) != ERROR_NO_MORE_ITEMS){ + empty = 0; + } else { + len = sizeof(buf) / 2; + if (RegEnumValue(hKey, 0, buf, &len, NULL, NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS) + empty = 0; + } + RegCloseKey(hKey); + if (empty && (RegDeleteKey(HKEY_CURRENT_USER, L"Software\\Classes\\*") != ERROR_SUCCESS)) + err++; + } + } + } + empty = 1; + if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\shellex\\ContextMenuHandlers", 0, KEY_READ, &hKey) == ERROR_SUCCESS){ + // ContextMenuHandlers 内に他のデータが無ければ項目自体を削除する。 + len = sizeof(buf) / 2; + if (RegEnumKeyEx(hKey, 0, buf, &len, NULL, NULL, NULL, &ft) != ERROR_NO_MORE_ITEMS){ + empty = 0; + } else { + len = sizeof(buf) / 2; + if (RegEnumValue(hKey, 0, buf, &len, NULL, NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS) + empty = 0; + } + RegCloseKey(hKey); + if (empty && (RegDeleteKey(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\shellex\\ContextMenuHandlers") != ERROR_SUCCESS)) + err++; + } + if (empty){ + if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\shellex", 0, KEY_READ, &hKey) == ERROR_SUCCESS){ + // shellex 内に他のデータが無ければ項目自体を削除する。 + len = sizeof(buf) / 2; + if (RegEnumKeyEx(hKey, 0, buf, &len, NULL, NULL, NULL, &ft) != ERROR_NO_MORE_ITEMS){ + empty = 0; + } else { + len = sizeof(buf) / 2; + if (RegEnumValue(hKey, 0, buf, &len, NULL, NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS) + empty = 0; + } + RegCloseKey(hKey); + if (empty && (RegDeleteKey(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\shellex") != ERROR_SUCCESS)) + err++; + } + if (empty){ + if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Classes\\Directory", 0, KEY_READ, &hKey) == ERROR_SUCCESS){ + // * 内に他のデータが無ければ項目自体を削除する。 + len = sizeof(buf) / 2; + if (RegEnumKeyEx(hKey, 0, buf, &len, NULL, NULL, NULL, &ft) != ERROR_NO_MORE_ITEMS){ + empty = 0; + } else { + len = sizeof(buf) / 2; + if (RegEnumValue(hKey, 0, buf, &len, NULL, NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS) + empty = 0; + } + RegCloseKey(hKey); + if (empty && (RegDeleteKey(HKEY_CURRENT_USER, L"Software\\Classes\\Directory") != ERROR_SUCCESS)) + err++; + } + } + } + + // 許可を削除する + if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", 0, KEY_WRITE, &hKey) == ERROR_SUCCESS){ + ret = RegDeleteValue(hKey, CLSID_str); + if ((ret != ERROR_SUCCESS) && (ret != ERROR_FILE_NOT_FOUND)) // 項目自体が存在しない場合はエラーにしない + err++; + RegCloseKey(hKey); + } + + // バージョン 1.1.8.6 までは HKEY_LOCAL_MACHINE に書き込んでたので、存在すれば削除する。 + // 存在しなくて削除できなくてもエラーにしない +/* + // CLSID の COM オブジェクトを削除する + wsprintf(buf, L"Software\\Classes\\CLSID\\%s\\InprocServer32", CLSID_str); + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, buf, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS){ + RegCloseKey(hKey); + if (RegDeleteKey(HKEY_LOCAL_MACHINE, buf) != ERROR_SUCCESS) + err++; + wsprintf(buf, L"Software\\Classes\\CLSID\\%s", CLSID_str); + if (RegDeleteKey(HKEY_LOCAL_MACHINE, buf) != ERROR_SUCCESS) + err++; + } + + // 全てのファイルとフォルダの Shell Extension を削除する + wsprintf(buf, L"Software\\Classes\\*\\shellex\\ContextMenuHandlers\\%s", ext_name); + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, buf, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS){ + RegCloseKey(hKey); + if (RegDeleteKey(HKEY_LOCAL_MACHINE, buf) != ERROR_SUCCESS) + err++; + } + wsprintf(buf, L"Software\\Classes\\Directory\\shellex\\ContextMenuHandlers\\%s", ext_name); + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, buf, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS){ + RegCloseKey(hKey); + if (RegDeleteKey(HKEY_LOCAL_MACHINE, buf) != ERROR_SUCCESS) + err++; + } + + // 許可を削除する + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS){ + empty = REG_SZ; + if (RegQueryValueEx(hKey, CLSID_str, NULL, &empty, NULL, NULL) == ERROR_SUCCESS){ + RegCloseKey(hKey); + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS){ + if (RegDeleteValue(hKey, CLSID_str) != ERROR_SUCCESS) + err++; + RegCloseKey(hKey); + } else { + err++; + } + } else { + RegCloseKey(hKey); + } + } +*/ +/* + // バージョン 1.1.4.0 では Directory ではなく Folder を使ってたので存在してればついでに削除する + wsprintf(buf, L"Software\\Classes\\Folder\\shellex\\ContextMenuHandlers\\%s", ext_name); + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, buf, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS){ + RegCloseKey(hKey); + if (RegDeleteKey(HKEY_LOCAL_MACHINE, buf) != ERROR_SUCCESS) + err++; + } +*/ + + if (err == 0) + return S_OK; + return E_FAIL; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +CShellExtClassFactory::CShellExtClassFactory() +{ + m_cRef = 0L; + + InterlockedIncrement(&g_cRefDll); +} + +CShellExtClassFactory::~CShellExtClassFactory() +{ + InterlockedDecrement(&g_cRefDll); +} + +STDMETHODIMP CShellExtClassFactory::QueryInterface(REFIID riid, LPVOID FAR *ppv) +{ + *ppv = NULL; + + // Any interface on this object is the object pointer + if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory)){ + *ppv = (LPCLASSFACTORY)this; + + AddRef(); + + return NOERROR; + } + + return E_NOINTERFACE; +} + +STDMETHODIMP_(ULONG) CShellExtClassFactory::AddRef() +{ + InterlockedIncrement(&m_cRef); + return m_cRef; +} + +STDMETHODIMP_(ULONG) CShellExtClassFactory::Release() +{ + ULONG theRef = InterlockedDecrement(&m_cRef); + if (theRef == 0L){ + delete this; + } + return theRef; // ローカル変数を使っているのは delete 後でも値を返せるように +} + +// IClassFactory::CreateInstance() +STDMETHODIMP CShellExtClassFactory::CreateInstance( + LPUNKNOWN pUnkOuter, REFIID riid, LPVOID *ppvObj) +{ + *ppvObj = NULL; + + // 集合をサポートしないので却下(?)。SDKサンプルより + if (pUnkOuter) + return CLASS_E_NOAGGREGATION; + + // シェル拡張オブジェクトを作成する。 + // そのあとシェルはppvObjのIID_IShellExtInitを引数に + // QueryInterfaceメソッドを呼び出し、初期化します + CShellExtension *pShellExt = new CShellExtension(); + + if (NULL == pShellExt) + return E_OUTOFMEMORY; + // 目的のインターフェイスのポインタを取得 + return pShellExt->QueryInterface(riid, ppvObj); +} + +// IClassFactory::LockServer() +STDMETHODIMP CShellExtClassFactory::LockServer(BOOL fLock) +{ + // ただ返るだけ... + return NOERROR; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +CShellExtension::CShellExtension() +{ + m_cRef = 0L; + m_pDataObj = NULL; + + InterlockedIncrement(&g_cRefDll); +} + +CShellExtension::~CShellExtension() +{ + if (m_pDataObj) + m_pDataObj->Release(); + + InterlockedDecrement(&g_cRefDll); +} + +STDMETHODIMP CShellExtension::QueryInterface(REFIID riid, LPVOID FAR *ppv) +{ + *ppv = NULL; + + if (IsEqualIID(riid, IID_IShellExtInit) || IsEqualIID(riid, IID_IUnknown)){ + *ppv = (LPSHELLEXTINIT)this; + } + else if (IsEqualIID(riid, IID_IContextMenu)){ + *ppv = (LPCONTEXTMENU)this; + } + + if (*ppv){ + AddRef(); + + return NOERROR; + } + + return E_NOINTERFACE; +} + +STDMETHODIMP_(ULONG) CShellExtension::AddRef() +{ + InterlockedIncrement(&m_cRef); + return m_cRef; +} + +STDMETHODIMP_(ULONG) CShellExtension::Release() +{ + ULONG theRef = InterlockedDecrement(&m_cRef); + if (theRef == 0L){ + delete this; + } + return theRef; // ローカル変数を使っているのは delete 後でも値を返せるように +} + +STDMETHODIMP CShellExtension::Initialize( + LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hRegKey) +{ + // 何回も呼ばれるので、二回目以降は、オブジェクトを解放 + if (m_pDataObj){ + m_pDataObj->Release(); + m_pDataObj = NULL; + } + if (pDataObj){ + m_pDataObj = pDataObj; + pDataObj->AddRef(); + return CheckData(); + } + + return NOERROR; +} + +STDMETHODIMP CShellExtension::QueryContextMenu( + HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) +{ + // uFlags に CMF_DEFAULTONLY が含まれる場合は何もしない + if (uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY | CMF_NOVERBS)) + return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0); + + UINT idCmdMax; + HMENU hSubmenu = NULL; + MENUITEMINFO mii; + + //wsprintf(menu_item + 128, L"First %d, Last %d", idCmdFirst, idCmdLast); + //MessageBox(NULL, NULL, menu_item + 128, MB_OK); + + if ((menu_behavior < 0) && (load_setting() != 0)){ // 読み込むのは一回だけ + //if (load_setting() != 0){ // read setting everytime for debug + wcscpy(menu_item, L"&MultiPar"); + offset_c = (int)wcslen(menu_item) + 1; + wcscpy(menu_item + offset_c, L"Create Recovery Files"); + if ((menu_behavior & 8) == 0){ + offset_v = offset_c + (int)wcslen(menu_item + offset_c) + 1; + wcscpy(menu_item + offset_v, L"Verify Recovery File"); + } else { + offset_a = offset_v; + } + if ((menu_behavior & 32) == 0){ + offset_a = offset_v + (int)wcslen(menu_item + offset_v) + 1; + wcscpy(menu_item + offset_a, L"Archive and Create Recovery Files"); + } + } + + if (menu_behavior & 1){ // 上に区切りを追加する + UINT menu_state; + + if (menu_behavior & 2){ // 上下を区切る場合は QuickPar と同じ独立配置にする + do { + menu_state = GetMenuState(hMenu, indexMenu, MF_BYPOSITION); + indexMenu++; + if (menu_state & MF_SEPARATOR) + break; + } while (menu_state != -1); + } else { // 上にだけ区切りを付ける + menu_state = GetMenuState(hMenu, indexMenu, MF_BYPOSITION); + if ((menu_state & MF_SEPARATOR) == 0) // まだ区切りがない場合だけ + InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, NULL); + } + } + + // 右クリック・メニューに項目を追加する + // ID = 0: Create or Verify + // ID = 1: Create + // ID = 2: Verify + // ID = 3: Archive and Create (and Append) + ZeroMemory(&mii, sizeof(MENUITEMINFO)); + mii.cbSize = sizeof(MENUITEMINFO); + if (idCmdLast - idCmdFirst >= 2){ // サブ・メニューを追加できるか + hSubmenu = CreatePopupMenu(); + if (idCmdLast - idCmdFirst < 4) + menu_behavior |= 32; + } + if (hSubmenu == NULL){ // 選択肢無しでトップ・メニューだけにする + mii.fMask = MIIM_ID | MIIM_STRING; + } else { + mii.fMask = MIIM_ID | MIIM_STRING | MIIM_SUBMENU; + mii.hSubMenu = hSubmenu; + } + mii.wID = idCmdFirst; + mii.dwTypeData = menu_item; + if (((menu_behavior & 16) == 0) && (g_bmp != NULL)){ // メニューにアイコンを付ける + mii.fMask |= MIIM_BITMAP; + mii.hbmpItem = g_bmp; + } + idCmdMax = 0; + if (hSubmenu != NULL){ // サブ・メニューを作る + InsertMenu(hSubmenu, -1, MF_STRING | MF_BYPOSITION, idCmdFirst + 1, menu_item + offset_c); + idCmdMax = 1; + if ((menu_behavior & 32) == 0){ + if (menu_behavior & 4) + InsertMenu(hSubmenu, -1, MF_SEPARATOR | MF_BYPOSITION, 0, NULL); + if ((menu_behavior & 16) || (g_bmp2 == NULL)){ // アイコンを表示しないなら + InsertMenu(hSubmenu, -1, MF_STRING | MF_BYPOSITION, idCmdFirst + 3, menu_item + offset_a); + } else { + MENUITEMINFO mii2; + ZeroMemory(&mii2, sizeof(MENUITEMINFO)); + mii2.cbSize = sizeof(MENUITEMINFO); + mii2.fMask = MIIM_ID | MIIM_STRING | MIIM_BITMAP; + mii2.wID = idCmdFirst + 3; + mii2.dwTypeData = menu_item + offset_a; + mii2.hbmpItem = g_bmp2; + InsertMenuItem(hSubmenu, -1, TRUE, &mii2); + } + idCmdMax = 3; + } + if ((single_file) && ((menu_behavior & 8) == 0)){ + if (menu_behavior & 4) + InsertMenu(hSubmenu, -1, MF_SEPARATOR | MF_BYPOSITION, 0, NULL); + InsertMenu(hSubmenu, -1, MF_STRING | MF_BYPOSITION, idCmdFirst + 2, menu_item + offset_v); + if (idCmdMax < 2) + idCmdMax = 2; + } + } + InsertMenuItem(hMenu, indexMenu++, TRUE, &mii); + + if (menu_behavior & 2) + InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, NULL); + + // Microsoft のページによって説明が異なる・・・でも、サンプル・コードは最大 offset +1 を返す + + // How to Implement the IContextMenu Interface + // https://docs.microsoft.com/en-us/windows/win32/shell/how-to-implement-the-icontextmenu-interface + // 追加したコマンド ID の最大番号 + 1 を返す(ID は連番で無くてもいい) + //return MAKE_HRESULT(SEVERITY_SUCCESS, 0, (USHORT)(idCmdFirst + idCmdMax + 1)); + + // Shobjidl.h (Windows XP or later) + // https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-icontextmenu-querycontextmenu + // 追加したコマンド ID の最大番号 - idCmdFirst + 1 を返す(ID は連番で無くてもいい) + return MAKE_HRESULT(SEVERITY_SUCCESS, 0, (USHORT)(idCmdMax + 1)); +} + +STDMETHODIMP CShellExtension::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi) +{ + // HIWORD(lpcmi->lpVerb)が0の時だけ処理する。 + if (HIWORD(lpcmi->lpVerb) == 0){ + // LOWORD(lpcmi->lpVerb) はクリックされたメニューIDです。 + // これは QueryContextMenu() の InsertMenu で指定した コマンド ID - idCmdFirst です。 + UINT idCmd = LOWORD(lpcmi->lpVerb); + + //wsprintf(menu_item + 128, L"ID = %d", idCmd); + //MessageBox(lpcmi->hwnd, NULL, menu_item + 128, MB_OK); + + if (idCmd <= 2) + return DoCommand(idCmd); + if (idCmd == 3) + return DoCommand7zip(); + } + return E_INVALIDARG; +} + +// Windows 2000/XP には非対応なのでヘルプのテキストは表示しない +STDMETHODIMP CShellExtension::GetCommandString( + UINT_PTR idCmd, UINT uFlags, UINT FAR *reserved, LPSTR pszName, UINT cchMax) +{ + if ((idCmd < 1) || (idCmd > 3)) // コマンド番号が範囲外 + return E_FAIL; + + if (uFlags == GCS_VALIDATEW){ + return S_OK; + } else if (uFlags == GCS_VERBW){ + int offset; + if (idCmd == 1){ // Create + offset = offset_c; + } else if (idCmd == 2){ // Verify + offset = offset_v; + } else if (idCmd == 3){ // Archive + offset = offset_a; + } + lstrcpyn((LPWSTR)pszName, menu_item + offset, cchMax); + } else { + return E_INVALIDARG; + } + + return NOERROR; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// 取得データを検査する +int CShellExtension::CheckData(void){ + // 以下のコードでまず、HDROPを得る + FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + STGMEDIUM stg = { TYMED_HGLOBAL }; + HDROP hDrop; + + if (FAILED(m_pDataObj->GetData(&fmt, &stg))) + return E_INVALIDARG; + + // HDROPを取得 + hDrop = (HDROP)GlobalLock(stg.hGlobal); + if (hDrop == NULL) + return E_INVALIDARG; + + // ファイル数チェック + UINT uNumFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0); + + // ファイルが0個なら帰る(一応チェック) + if (uNumFiles == 0){ + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + return E_INVALIDARG; + } + + single_file = 0; + if (uNumFiles == 1){ // ファイルが一個なら + wchar_t buf[MAX_PATH + 32]; + unsigned int attr; + + if (DragQueryFile(hDrop, 0, buf, sizeof(buf)/2) == 0){ + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + return E_INVALIDARG; + } + + // ファイルの属性を検査する + attr = GetFileAttributes(buf); + if (((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) && + ((attr & FILE_ATTRIBUTE_SYSTEM) == 0)){ + single_file = 1; + } + } + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + + return NOERROR; +} + +int CShellExtension::DoCommand(UINT idCmd){ + wchar_t path[MAX_PATH], buf[MAX_PATH + 32]; + unsigned int i, len, uNumFiles; + + // DLL が存在するディレクトリ + // DLL と呼び出す EXE ファイルは同じディレクトリに置くこと + len = GetModuleFileName(g_inst, path, MAX_PATH); + if ((len == 0) || (len >= MAX_PATH)) + return E_INVALIDARG; + for (i = len - 1; i > 0; i--){ + if (path[i] == '\\'){ + path[i] = 0; + break; + } + } + //MessageBox(NULL, path , L"DLL path", MB_OK); + + // 以下のコードでまず、HDROPを得る + FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + STGMEDIUM stg = { TYMED_HGLOBAL }; + HDROP hDrop; + + if (FAILED(m_pDataObj->GetData(&fmt, &stg))) + return E_INVALIDARG; + + // HDROPを取得 + hDrop = (HDROP)GlobalLock(stg.hGlobal); + if (hDrop == NULL) + return E_INVALIDARG; + uNumFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0); // ファイル数 + + // 検査なら + if ((idCmd == 2) || ((idCmd == 0) && (uNumFiles == 1))){ + wchar_t argvs[MAX_PATH + 16]; + + // コマンドラインを作る + if (idCmd == 2){ + wcscpy(argvs, L"/verify "); + } else { + argvs[0] = 0; + } + + // 選択されたファイル名の取得 + if (DragQueryFile(hDrop, 0, buf, sizeof(buf)/2) == 0){ + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + return E_INVALIDARG; + } + + // コマンドラインに追加する + if (wcschr(buf, ' ') != NULL){ // スペースを含む場合は"で囲む + wcscat(argvs, L"\""); + wcscat(argvs, buf); + wcscat(argvs, L"\""); + } else { + wcscat(argvs, buf); + } + + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + + //MessageBox(NULL, argvs, L"param command", MB_OK); + ShellExecute(NULL, L"open", L"MultiPar.exe", argvs, path, SW_SHOWNORMAL); + return NOERROR; + } + + // ファイル数チェック + len = 0; + for (i = 0; i < uNumFiles; i++){ + // 選択されたファイル名の文字数 + len += DragQueryFile(hDrop, i, NULL, 0); + } + len += uNumFiles * 3 + 8; // コマンドと "" の分だけ余裕を見ておく + + // 文字数が多いならファイル・リストを使う + if (len >= 32768){ + char buf2[MAX_PATH * 3]; + wchar_t list_path[MAX_PATH]; + unsigned long rv; + HANDLE hFile; + + // アプリケーション・データのディレクトリを決める + rv = 0; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, SHGFP_TYPE_CURRENT, list_path))){ + //MessageBox(NULL, list_path, L"program files path", MB_OK); + // ProgramFiles の位置と比較する + int i, max = 0; + while (list_path[max] != 0) + max++; + for (i = 0; i < max; i++){ + if (list_path[i] != path[i]) + break; + } + if (i == max){ + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, list_path))){ + //MessageBox(NULL, list_path, L"application data path", MB_OK); + i = 0; + while (list_path[i] != 0) + i++; + if (i < MAX_PATH - 10 - 18){ + wcscat(list_path, L"\\MultiPar"); + rv = 2; // AppData を使う + } + } + } + } + if (rv != 2) + wcscpy(list_path, path); // DLL が存在するディレクトリを使う + wcscat(list_path, L"\\MultiPar_list.tmp"); + //MessageBox(NULL, list_path, L"file-list path", MB_OK); + + // テキスト・ファイルを開く + hFile = CreateFile(list_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE){ + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + return E_INVALIDARG; + } + + // ファイルごとに記録する + for (i = 0; i < uNumFiles; i++){ + // 選択されたファイル名の取得 + if (DragQueryFile(hDrop, i, buf, sizeof(buf)/2) == 0){ + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + CloseHandle(hFile); + return E_INVALIDARG; + } + + // UTF-8 に変換する + len = WideCharToMultiByte(CP_UTF8, 0, buf, -1, buf2, MAX_PATH * 3, NULL, NULL); + if (len > 0){ // len は末尾の null 文字を含む + buf2[len - 1] = 0x0A; // 末尾に改行を追加する + if (!WriteFile(hFile, buf2, len, &rv, NULL)){ + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + CloseHandle(hFile); + } + } + } + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + + // テキスト・ファイルを閉じる + CloseHandle(hFile); + + // コマンドラインを作る(先頭の空白は実行ファイルのパスの代わり) + wcscpy(buf, L" /create /list "); + if (wcschr(list_path, ' ') != NULL){ // スペースを含む場合は"で囲む + wcscat(buf, L"\""); + wcscat(buf, list_path); + wcscat(buf, L"\""); + } else { + wcscat(buf, list_path); + } + //MessageBox(NULL, buf, L"file-list command", MB_OK); + ShellExecute(NULL, L"open", L"MultiPar.exe", buf, path, SW_SHOWNORMAL); + + } else { // ファイル数が少ないならコマンドラインで渡す + wchar_t argvs[32768]; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + // コマンドラインを作る(先頭の空白は実行ファイルのパスの代わり) + wcscpy(argvs, L" /create"); + + // ファイルごとに記録する + for (i = 0; i < uNumFiles; i++){ + // 選択されたファイル名の取得 + if (DragQueryFile(hDrop, i, buf, sizeof(buf)/2) == 0){ + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + return E_INVALIDARG; + } + + // コマンドラインに追加していく + if (wcschr(buf, ' ') != NULL){ // スペースを含む場合は"で囲む + wcscat(argvs, L" \""); + wcscat(argvs, buf); + wcscat(argvs, L"\""); + } else { + wcscat(argvs, L" "); + wcscat(argvs, buf); + } + } + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + + //len = wcslen(argvs); + //wsprintf(buf, L"length = %d", len); + //MessageBox(NULL, buf, L"param length", MB_OK); + //MessageBox(NULL, argvs, L"param command", MB_OK); + // Windows 2000 だと ShellExecute のコマンドラインは 2000文字ぐらいまでなので、パスが長いと無理 + // ShellExecute(NULL, L"open", L"MultiPar.exe", argvs, path, SW_SHOWNORMAL); + // CreateProcess の引数は 32768文字ぐらいまでいける。 + wcscat(path, L"\\MultiPar.exe"); + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOWNORMAL; + if (CreateProcess(path, argvs, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)){ + // スレッドハンドルとプロセスハンドルの解放 + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + } + + return NOERROR; +} + +int CShellExtension::DoCommand7zip(void){ + wchar_t path[MAX_PATH], buf[MAX_PATH], argvs[32768]; + unsigned int i, len, max, uNumFiles; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + // DLL が存在するディレクトリ + // DLL と呼び出す EXE ファイルは同じディレクトリに置くこと + len = GetModuleFileName(g_inst, path, MAX_PATH); + if ((len == 0) || (len >= MAX_PATH)) + return E_INVALIDARG; + for (i = len - 1; i > 0; i--){ + if (path[i] == '\\'){ + path[i] = 0; + break; + } + } + //MessageBox(NULL, path , L"DLL path", MB_OK); + + get_path_7zip(buf); // 7-Zip のディレクトリを取得する + if (buf[0] == 0){ + menu_behavior |= 32; // 次からメニューを表示しない + //MessageBoxA(GetDesktopWindow(), "Cannot find 7-Zip." , "MultiPar shell extension", MB_OK | MB_ICONERROR); + return E_INVALIDARG; + } + + // 以下のコードでまず、HDROPを得る + FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + STGMEDIUM stg = { TYMED_HGLOBAL }; + HDROP hDrop; + + if (FAILED(m_pDataObj->GetData(&fmt, &stg))) + return E_INVALIDARG; + + // HDROPを取得 + hDrop = (HDROP)GlobalLock(stg.hGlobal); + if (hDrop == NULL) + return E_INVALIDARG; + uNumFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0); // ファイル数 + + // コマンドラインを作る + if (wcschr(buf, ' ') != NULL){ // スペースを含む場合は"で囲む + wcscpy(argvs, L"\""); + wcscat(argvs, buf); + wcscat(argvs, L"\""); + } else { + wcscpy(argvs, buf); + } + // 「-saa」を追加すると書庫ファイルを別の形式で圧縮できるようになる + // 「--」を追加するとファイル名の先頭が「-」でもオプション扱いにならない + //wcscat(argvs, L" a -ad -saa -- "); + wcscat(argvs, L" /archive:7zip "); // MultiPar 用のコマンドに置き換える + // 選択された最初のファイル名 + if (DragQueryFile(hDrop, 0, buf, sizeof(buf)/2) == 0){ + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + return E_INVALIDARG; + } + len = (unsigned int)wcslen(buf); + if (uNumFiles > 1){ // ファイルが複数ならその親フォルダーの名前を書庫に付ける + for (i = len - 1; i > 0; i--){ + if (buf[i] == '\\'){ // 親フォルダー名の末尾 + max = i; + for (i = i - 1; i > 0; i--){ + if (buf[i] == '\\'){ // 親フォルダー名の先頭 + for (len = 1; i + len < max; len++) + buf[max + len] = buf[i + len]; + buf[max + len] = 0; + break; + } + } + break; + } + } + } + // コマンドラインに書庫名を追加する + if (wcschr(buf, ' ') != NULL){ // スペースを含む場合は"で囲む + wcscat(argvs, L"\""); + wcscat(argvs, buf); + wcscat(argvs, L"\""); + } else { + wcscat(argvs, buf); + } + + // ファイル名の文字数チェック + len = 0; + for (i = 0; i < uNumFiles; i++){ + // 選択されたファイル名の文字数 + len += DragQueryFile(hDrop, i, NULL, 0); + } + len += uNumFiles * 3 + (unsigned int)wcslen(argvs); // コマンドと "" の分だけ余裕を見ておく + + // 文字数が多いならファイル・リストを使う + if (len >= 32768){ + char buf2[MAX_PATH * 3]; + wchar_t list_path[MAX_PATH]; + unsigned long rv; + HANDLE hFile; + + // アプリケーション・データのディレクトリを決める + rv = 0; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, SHGFP_TYPE_CURRENT, list_path))){ + //MessageBox(NULL, list_path, L"program files path", MB_OK); + // ProgramFiles の位置と比較する + max = 0; + while (list_path[max] != 0) + max++; + for (i = 0; i < max; i++){ + if (list_path[i] != path[i]) + break; + } + if (i == max){ + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, list_path))){ + //MessageBox(NULL, list_path, L"application data path", MB_OK); + i = 0; + while (list_path[i] != 0) + i++; + if (i < MAX_PATH - 10 - 18){ + wcscat(list_path, L"\\MultiPar"); + rv = 2; // AppData を使う + } + } + } + } + if (rv != 2) + wcscpy(list_path, path); // DLL が存在するディレクトリを使う + wcscat(list_path, L"\\MultiPar_list.tmp"); + //MessageBox(NULL, list_path, L"file-list path", MB_OK); + + // テキスト・ファイルを開く + hFile = CreateFile(list_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE){ + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + return E_INVALIDARG; + } + + // ファイルごとに記録する + for (i = 0; i < uNumFiles; i++){ + // 選択されたファイル名の取得 + if (DragQueryFile(hDrop, i, buf, sizeof(buf)/2) == 0){ + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + CloseHandle(hFile); + return E_INVALIDARG; + } + + // UTF-8 に変換する + len = WideCharToMultiByte(CP_UTF8, 0, buf, -1, buf2, MAX_PATH * 3, NULL, NULL); + if (len > 0){ // len は末尾の null 文字を含む + buf2[len - 1] = 0x0A; // 末尾に改行を追加する + if (!WriteFile(hFile, buf2, len, &rv, NULL)){ + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + CloseHandle(hFile); + } + } + } + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + + // テキスト・ファイルを閉じる + CloseHandle(hFile); + + // コマンドラインにファイル・リスト名を追加する + wcscat(argvs, L" @\""); + wcscat(argvs, list_path); + wcscat(argvs, L"\""); + + } else { // ファイル数が少ないならコマンドラインで渡す + // ファイルごとに記録する + for (i = 0; i < uNumFiles; i++){ + // 選択されたファイル名の取得 + if (DragQueryFile(hDrop, i, buf, sizeof(buf)/2) == 0){ + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + return E_INVALIDARG; + } + + // コマンドラインに追加していく + if (wcschr(buf, ' ') != NULL){ // スペースを含む場合は"で囲む + wcscat(argvs, L" \""); + wcscat(argvs, buf); + wcscat(argvs, L"\""); + } else { + wcscat(argvs, L" "); + wcscat(argvs, buf); + } + } + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + } + + // 書庫ファイルを MultiPar で開く + //MessageBox(NULL, argvs, L"command param", MB_OK); + wcscat(path, L"\\MultiPar.exe"); + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOWNORMAL; + if (CreateProcess(path, argvs, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)){ + // スレッドハンドルとプロセスハンドルの解放 + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + + return NOERROR; +} + diff --git a/source/ShellExt/ShellExt.def b/source/ShellExt/ShellExt.def new file mode 100644 index 0000000..070da1e --- /dev/null +++ b/source/ShellExt/ShellExt.def @@ -0,0 +1,6 @@ +EXPORTS + DllCanUnloadNow PRIVATE + DllGetClassObject PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE + diff --git a/source/ShellExt/ShellExt.rc b/source/ShellExt/ShellExt.rc new file mode 100644 index 0000000..718fa51 Binary files /dev/null and b/source/ShellExt/ShellExt.rc differ diff --git a/source/ShellExt/ShellExt.vcxproj b/source/ShellExt/ShellExt.vcxproj new file mode 100644 index 0000000..e3238a7 --- /dev/null +++ b/source/ShellExt/ShellExt.vcxproj @@ -0,0 +1,124 @@ + + + + + Release + Win32 + + + Release + x64 + + + + {72C5F97E-8894-4B47-A588-8F294293F55A} + ShellExt + Win32Proj + 10.0 + + + + DynamicLibrary + v143 + Unicode + true + + + DynamicLibrary + v143 + Unicode + true + + + + + + + + + + + + + <_ProjectFileVersion>16.0.31025.104 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + false + MultiParShlExt + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + false + false + MultiParShlExt64 + + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreaded + false + true + false + + Level3 + + + + $(OutDir)MultiParShlExt.dll + ShellExt.def + false + Windows + true + true + true + MachineX86 + UseLinkTimeCodeGeneration + + + + + X64 + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreaded + false + true + false + + Level3 + + + + _UNICODE;UNICODE;_WIN64;%(PreprocessorDefinitions) + + + $(OutDir)MultiParShlExt64.dll + ShellExt.def + false + Windows + true + true + true + MachineX64 + UseLinkTimeCodeGeneration + + + + + + + + + + + + \ No newline at end of file diff --git a/source/ShellExt/par_ARGB.ico b/source/ShellExt/par_ARGB.ico new file mode 100644 index 0000000..4708221 Binary files /dev/null and b/source/ShellExt/par_ARGB.ico differ