ダブルクリックでデスクトップに解凍してほしい。
zip ファイルを中身を見て、必要ファイルを取り出すとか面倒くさい。
ダブルクリックで解凍して、エクスプローラーで表示してほしい。
特に、USBメモリやSambaのフォルダを開くときに、ダブルクリックでデスクトップへ展開が重要なのである。
レジストリで操作する。
前に、試したことで、レジストリをイジれれば、好きな箇所にフォルダを作成し展開できることはわかった。
解凍後にエクスプローラーで開けてほしい。
今度の欲求は、解凍後にフォルダが見えないという点だ。
解凍(展開・伸長)後に、エクスプローラーでフォルダを開いてほしい。
レジストリの登録を次のようにしたい。
7-Zip.desktop
というProgIdを作り、zip
の関連付けを変更する。
Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\SOFTWARE\Classes\7-Zip.desktop] [HKEY_CURRENT_USER\SOFTWARE\Classes\7-Zip.desktop\DefaultIcon] @="\"C:\\Program Files (x86)\\Lhaplus\\LplsIcon.dll\",101" [HKEY_CURRENT_USER\SOFTWARE\Classes\7-Zip.desktop\shell] [HKEY_CURRENT_USER\SOFTWARE\Classes\7-Zip.desktop\shell\open] [HKEY_CURRENT_USER\SOFTWARE\Classes\7-Zip.desktop\shell\open\command] @="\"C:\\Users\\takuya\\.app\\7zipToDesktop.exe\" %1" [HKEY_CURRENT_USER\SOFTWARE\Classes\.zip] @="7-Zip.desktop"
個々に必要なExeかスクリプトを作る
WSHやPSで書いても良かったんだけど。WindowsだしExe作ってやろうと思って、Windowsアプリケーションを書き始めた。
だいたい、書けた。
展開先の設定画面とかはコンパイルすればいいので、要らない。
ソースコード
シェルスクリプトで頑張ろうと思ったけど、WSHはVBScript/JScript の未来が不透明だし、Powershellは面倒くさいし、いまさらcmd.exeを書く気になれなかった。
VisualStudioのC++なら、現代ではMicrosoftが無償で使わせてくれるので、超久しぶり書いたわ。
https://github.com/takuya/win-7zip-onclick/blob/master/7zipToDesktop/7zipToDesktop.cpp
// 7zipToDesktop.cpp : Defines the entry point for the application. // // License: GPLv3 // author : github.com/takuya // modified : 2024-05-29 // #include<iostream> #include <Windows.h> #include <filesystem> #include <regex> namespace fs = std::filesystem; using string = std::string; using wstring = std::wstring; fs::path get_desktop() { char* env_var = nullptr; size_t size; _dupenv_s(&env_var, &size, "USERPROFILE"); if (env_var == nullptr) { return ""; } std::string UserProfile(env_var); return fs::path(UserProfile) / "Desktop"; } string get_output_path(string src) { std::string dstDir = get_desktop().string(); fs::path src_path(src); fs::path basename = src_path.stem(); std::regex extensionRegex("\\.\\w+$"); std::string basename_noext = std::regex_replace(basename.string(), extensionRegex, ""); fs::path dstPath = fs::path(dstDir) / basename_noext; return dstPath.string(); } void openExplorer(string path) { string application("explorer.exe"); string out_dir = get_output_path(path); if (!fs::exists(out_dir)) { return; } HINSTANCE result = ShellExecuteA(nullptr, nullptr, application.c_str(), out_dir.c_str(), NULL, SW_SHOWNORMAL ); } int extractToDesktop(string archivePath) { fs::path desktopPath = get_desktop(); if (desktopPath.empty()) { return 1; } string args = archivePath; args = " x " + args + " -aos -o" + desktopPath.string() + "\\*"; string cmd = R"(C:\Program Files\7-Zip\7zG.exe)" + args; wstring cmdW(cmd.begin(), cmd.end()); LPCWSTR lpwargs = cmdW.c_str(); LPWSTR lpargs = const_cast<LPWSTR>(lpwargs); STARTUPINFO si = {}; PROCESS_INFORMATION pi = {}; if (CreateProcessW(NULL, lpargs, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { std::cerr << "Failed to execute command." << std::endl; return 2; } return 0; } int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd) { if (lpCmdLine == NULL) { return 1; } // std::string を std::wstring に変換 string args = lpCmdLine; extractToDesktop(args); openExplorer(args); return 0; }
書くときに調べたメモ
Win32なんて超久しぶりなので、一つずつ調べて書いた。
- シェルコマンドの実行方法
- CreateProcess
- std::system
- ShellExecuteA
- 環境変数の取得
- ファイルパスの扱い
- basename / stem
- extname / replace extension
- join / path
- WinMainを使って画面なしExe
- namespace
- using
- include
- string
- wstring と LPCWSTR
- Raw 書式
- LPCWSTR と LPCSTR の変換
特に、Windowsアプリケーションは文字列型キャストが本当に面倒くさい。c++で書いても、Cへキャストしないといけない。
文字列型の備忘録( 参考資料 )
Item | 8-bit(ANSI) 16-bit ( | Wide) | Varies |
---|---|---|---|
character | CHAR | WCHAR | TCHAR |
string | LPSTR | LPWSTR | LPTSTR |
string | (const) LPCSTR | LPCWSTR | LPCTSTR |
略称がややこしいが、ConstとWideだけ覚えておけばいい。
- LPCSTR は Long Pointer Const String
- LPCWSTR は Long Pointer Wide Const String
- LPSTR は Long Pointer String
CreateProcess の第1引数をいれると、第二引数が無視される仕様なのが、ハマりどころだった。
エクスプローラーの起動
#include<windows.h> #include<iostream> int main() { LPCSTR application = "explorer.exe"; LPCSTR parameters = "C:\\Users\\takuya\\Desktop"; HINSTANCE result = ShellExecuteA(nullptr,nullptr, application, // アプリケーション parameters, // 引数 NULL, // ディレクトリ SW_SHOWNORMAL // ウィンドウの表示状態 ); if ((int)result <= 32) { MessageBoxA(NULL, "explorer.exeの実行に失敗しました。", "エラー", MB_OK | MB_ICONERROR); } else { MessageBoxA(NULL, "explorer.exeは成功しました。", "成功", MB_OK | MB_ICONASTERISK); } }
curl.exe の実行
#include<windows.h> #include<iostream> int main() { // LPCSTR application = "C:\\Windows\\System32\\curl.exe"; LPCSTR parameters = " -v https://g.co -o C:\\Users\\takuya\\Desktop\\out.html"; // HINSTANCE result = ShellExecuteA(nullptr,nullptr, application, // アプリケーション parameters, // 引数 NULL, // ディレクトリ SW_SHOWNORMAL // ウィンドウの表示状態 ); return (int)result; }
ファイル(フルパス)から、拡張子を除去
#include<windows.h> #include<iostream> #include <filesystem> namespace fs = std::filesystem; int main() { std::string pathString = R"(C:\Users\takuya\Downloads\sample - 2024 04.zip)"; fs::path path(pathString); fs::path basename = path.replace_extension(); std::cout << basename.string() << std::endl; return 0; }
ファイル(フルパス)からファイル名(Basename)を取得
#include<windows.h> #include<iostream> #include <filesystem> namespace fs = std::filesystem; int main() { std::string pathString = R"(C:\Users\takuya\Downloads\sample - 2024 04.zip)"; fs::path path(pathString); fs::path basename = path.stem(); std::cout << basename.string() << std::endl; return 0; }
ファイル名とディレクトリ名を結合
#include<windows.h> #include<iostream> #include <filesystem> namespace fs = std::filesystem; int main() { std::string pathString = R"(C:\Users\takuya\Downloads\sample - 2024 04.zip)"; std::string dir = R"(C:\Users\takuya\Desktop)"; fs::path path(pathString); fs::path basename = path.stem(); fs::path dstPath = fs::path(dir) / basename; std::cout << dstPath.string() << std::endl; return 0; }
環境変数の値を取得し、Desktopのパスを取得
#include<windows.h> #include<iostream> #include <filesystem> namespace fs = std::filesystem; int main() { // 環境変数 USERPROFILE の値を取得 char* env_var = nullptr; size_t size; _dupenv_s(&env_var, &size, "USERPROFILE"); if (env_var == nullptr) { return 1; } std::string UserProfile(env_var); fs::path desktopPath = fs::path(UserProfile) / "Desktop"; std::cout << desktopPath.string() << std::endl; return 0; }
コマンド(エクスプローラー)起動をstd::string から
先程の例は、C++ というより、LPCSTRを使ったCサンプルなので。
もう少し、c++ らしく。
#include<windows.h> #include<iostream> namespace fs = std::filesystem; using string = std::string; int main() { string application("explorer.exe"); string parameters = R"(C:\Windows)"; HINSTANCE result = ShellExecuteA(nullptr, nullptr, application.c_str(), parameters.c_str(), NULL, SW_SHOWNORMAL ); return 0; }
もっと簡単に、std::systemを使う
#include<string> using string = std::string; int main(){ string application("explorer.exe"); string parameters = R"(C:\Windows)"; string cmd = application + " " + parameters; std::system(cmd.c_str()); }
プロセス実行して待つ
system
は、終了待ちする。それいいけど、CreateProcess系
がWindowsの正統派かもしれない。
Windowsの文字列変換が全然わからないので、不慣れなWindowsプログラミングでCreateProcessは文字列変換が地獄かもしれない。
#include<iostream> #include <Windows.h> //namespace fs = std::filesystem; using string = std::string; using wstring = std::wstring; int main() { LPCWSTR programPath = L"C:\\Windows\\System32\\cmd.exe"; LPWSTR args = const_cast<LPWSTR>(L"/c echo Hello world!"); STARTUPINFO si = {}; PROCESS_INFORMATION pi = {}; if (CreateProcessW(programPath, args, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { std::cerr << "Failed to execute command." << std::endl; } return 0; }
何も起きない(ウィンドウ開かない)アプリケーション
#include <windows.h> int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd){ return 0; }
メッセージボックスだけ
#include <windows.h> #include <iostream> using wstring = std::wstring; int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd){ wstring msg = L"ハローワールド"; MessageBox(NULL, msg.c_str(), L"Command Line Arguments", MB_OK | MB_ICONINFORMATION); return 0; }
WinMainでコマンドラインの引数を取ってメッセージボックスにだす
#include <windows.h> #include <iostream> using string = std::string; using wstring = std::wstring; int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd){ // 引数無しは、動作無し。 if (lpCmdLine == NULL) { return 1; } // std::string を std::wstring に変換 string commandLineStr(lpCmdLine); wstring commandLine = std::wstring(commandLineStr.begin(), commandLineStr.end()); MessageBox(NULL, msg.c_str(), L"Command Line Arguments", MB_OK | MB_ICONINFORMATION); return 0; }