それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

7zipでワンタッチ解凍(展開)をしたい。

ダブルクリックでデスクトップに解凍してほしい。

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アプリケーションを書き始めた。

だいたい、書けた。

展開先の設定画面とかはコンパイルすればいいので、要らない。

ソースコード

シェルスクリプトで頑張ろうと思ったけど、WSHVBScript/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;
}

参考資料