🐶

エクスプローラーで特定のフォルダを開くときはWinApiを使おう

2024/09/20に公開2

個人でファイラーみたいなアプリを作っているのですが、特定のフォルダをエクスプローラーで開くという処理を実装したときにタスクマネージャーを確認したところ、explorer.exeプロセスが新たに起動していることに気が付きました。

この新たに起動したexplorer.exeプロセスはCPUもメモリもしっかり使用します。
そしてウィンドウを閉じてもexplorer.exeプロセスは起動し続けます。(ウィンドウを閉じて一定時間経過すると終了する場合があるかも?)
これじゃまずいですよね。
ほかの方が開発したアプリでこの現象が発生するものは、名称は伏せますがチラホラあります。
有名どころのChromeFirefoxVSCodeではこの現象が発生しませんでした。

C#での再現コード

これを実行すると、スクショのようになります。

static void Main(string[] args)
{
  // 特定のフォルダを開く
  System.Diagnostics.Process.Start("explorer.exe", @"C:\Program Files");

  // 特定のファイルを選択した状態でフォルダを開く
  System.Diagnostics.Process.Start("explorer.exe", @"/select, C:\Program Files\Google");
}

C#での解決策

既存のexplorer.exeプロセスに指定したフォルダを開かせるという方法をとる必要があります。
以下のWinApiを使用します。

[DllImport("shell32.dll", CharSet = CharSet.Auto)]
public static extern int ShellExecute(IntPtr hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);

[DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern int SHOpenFolderAndSelectItems(
    IntPtr pidlFolder,
    uint cidl,
    [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl,
    uint dwFlags);

[DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern int SHParseDisplayName(
    [MarshalAs(UnmanagedType.LPWStr)] string pszName,
    IntPtr pbc,
    out IntPtr ppidl,
    uint sfgaoIn,
    out uint psfgaoOut);

[DllImport("ole32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern void CoTaskMemFree(IntPtr pv);

特定のフォルダを開く

ShellExecute(IntPtr.Zero, "open", @"C:\Program Files", null, null, 1);

特定のファイルを選択した状態でフォルダを開く

ちょっと手順が増えます。

var pidlFolder = IntPtr.Zero;
var pidlFile = IntPtr.Zero;

try
{
    // フォルダのPIDLを取得
    uint psfgaoOut;
    var hr = SHParseDisplayName(@"C:\Program Files", IntPtr.Zero, out pidlFolder, 0, out psfgaoOut);
    if (hr != 0)
    {
        Console.WriteLine("開くフォルダのPIDLの取得に失敗しました。");
        return;
    }

    // ファイルのPIDLを取得
    hr = SHParseDisplayName(@"C:\Program Files\Google", IntPtr.Zero, out pidlFile, 0, out psfgaoOut);
    if (hr != 0)
    {
        Console.WriteLine("選択するファイルのPIDLの取得に失敗しました。");
        return;
    }

    // 特定のファイルを選択した状態でフォルダを開く
    IntPtr[] fileArray = { pidlFile };
    hr = SHOpenFolderAndSelectItems(pidlFolder, (uint)fileArray.Length, fileArray, 0);
    if (hr != 0)
    {
        Console.WriteLine("特定のファイルを選択した状態でフォルダを開く処理に失敗しました。");
        return;
    }
}
finally
{
    // PIDLを解放
    CoTaskMemFree(pidlFolder);
    CoTaskMemFree(pidlFile);
}

以上の処理を行えばexplorer.exeプロセスが複数起動するのではなく、単一プロセスのままフォルダを開くことができます。

もしもSystem.Diagnostics.Process.Start()を使用してexplorer.exeを起動しているのであれば、見直してみるのもいいと思います。

Discussion

Mayuki SawatariMayuki Sawatari

ShellExecute の呼び出しは UseShellExecuteVerb = "Open" で代わりにならないでしょうか?

Process.Start(new ProcessStartInfo
{
    UseShellExecute = true,
    Verb = "Open",
    FileName  = @"C:\Program Files",
});
nabezokodaikonnabezokodaikon

おお、確かにできますね。
ただ、開いたフォルダ内の特定のファイルを選択するにはどうしましょうね...。