🐶
エクスプローラーで特定のフォルダを開くときはWinApiを使おう
個人でファイラーみたいなアプリを作っているのですが、特定のフォルダをエクスプローラーで開くという処理を実装したときにタスクマネージャーを確認したところ、explorer.exe
プロセスが新たに起動していることに気が付きました。
この新たに起動したexplorer.exe
プロセスはCPUもメモリもしっかり使用します。
そしてウィンドウを閉じてもexplorer.exe
プロセスは起動し続けます。(ウィンドウを閉じて一定時間経過すると終了する場合があるかも?)
これじゃまずいですよね。
ほかの方が開発したアプリでこの現象が発生するものは、名称は伏せますがチラホラあります。
有名どころのChrome
、Firefox
、VSCode
ではこの現象が発生しませんでした。
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
ShellExecute
の呼び出しはUseShellExecute
とVerb = "Open"
で代わりにならないでしょうか?おお、確かにできますね。
ただ、開いたフォルダ内の特定のファイルを選択するにはどうしましょうね...。