エクスプローラー上でファイルを開き、アプリケーションを起動する場合。
既に起動しているアプリケーションにファイルを渡したい時、どうしたらよいか。
主に、以下の手順を組むことになるだろう。
- 多重起動の検出
- 元プロセスを探す
- 元プロセスへファイルパスを送る
- 元プロセスでファイルパスを受け取る
これら各手順の方法をざっくり解説していこうと思う。
多重起動の検出
using System.Threading; static void Main() { using (var mutex = new Mutex(true, "ApplicationMutexName", out bool createdNew)) { if (createdNew) { // 初回起動時 // 通常通りアプリケーションを実行する処理 Run(); } else { // 多重起動時 // ここで元プロセスにデータを送信する処理を入れたり、単に終了したりする return; } } }
多重起動かどうかを判別するには、Mutexを使う。
Mutexは、排他制御、同期のための機構…らしい。
「マルチスレッドで、同じリソースを扱うけど、同時に処理されると困る…」
といった場合に用いられる、スレッドのロックの一種と言えば、分かりやすいだろうか?
性質は以下の通り。
- 一スレッドだけが取得・所有できる
- 固有の識別子を持たせて、他のアプリケーションからもアクセスできる
- OSとかカーネルとかそういうレベルでの操作が入るので、速度は遅い
"ApplicationMutexName"はMutexを識別するための任意の文字列を自分で指定する。
他のアプリと重複すると困ったことになるので、被らなさそうなものをきちんと設定しよう。
元プロセスを探す
private Process FindPrevProcess() { var currentProcess = Process.GetCurrentProcess(); var currentPath = currentProcess.MainModule.FileName; var allProcesses = Process.GetProcessesByName(currentProcess.ProcessName); foreach (var process in allProcesses) { if (process.Id != currentProcess.Id) { try { if (string.Compare(process.MainModule.FileName, currentPath, true) == 0) { return process; } } catch (Exception) { continue; } } } return null; }
多重起動時、自プロセスと同じ、元プロセスを探す。
ロジックは以下の通り。
- 自プロセスのファイルパスを取得する
- 自プロセスと同じプロセス名のプロセス一覧を取得する
- プロセス一覧を調べ上げ、元プロセスを探す
元プロセスであるかの判定は以下の通り。
- 自プロセスとは異なるプロセスIDである
- 自プロセスと同じファイルパスのプロセスである
ファイル絡みでエラーが出たり出なかったりするので、一応例外処理を挟んでたり。
元プロセスへファイルパスを送る
private const int WM_COPYDATA = 0x004A; [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref COPYDATASTRUCT lParam); [StructLayout(LayoutKind.Sequential)] private struct COPYDATASTRUCT { public IntPtr dwData; // (IntPtr.Zero) public uint cbData; // lpDataが指すデータのサイズ public string lpData; // 渡されるデータ } public static void SendTextToPrevProcess(string text) { var prevProcess = FindPrevProcess(); if (prevProcess != null) { var copyData = new COPYDATASTRUCT { dwData = IntPtr.Zero, cbData = (uint)Encoding.Default.GetByteCount(text) + 1, lpData = text }; SendMessage(prevProcess.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, ref copyData); } }
ファイルパス、に限定されず、文字列データを送信する方法である。
元プロセスにファイルパスなどデータを送るのは、プロセス間通信…という考え方になってくる。
まぁ実際にはもっと簡単に考えて、ウィンドウメッセージのやり取りで送信ができる。
元プロセスに、Win32APIのSendMessage関数で、WM_COPYDATAメッセージを送信する。
送信するデータは、COPYDATASTRUCTという構造体に納めなければならない。
dwDataはIntPtr.Zeroでいいっぽい*1、cbDataは送信するデータのバイト数。
lpDataは本来、送信するデータのポインタらしいが、文字列はそのままでも良さそう。
元プロセスでファイルパスを受け取る
protected override void WndProc(ref Message m) { if (m.Msg == WM_COPYDATA) { // 構造体から文字列を取り出す var copyData = Marshal.PtrToStructure<COPYDATASTRUCT>(m.LParam); // 受け取った文字列 var text = copyData.lpData; } base.WndProc(ref m); }
受け取り側、メインウィンドウのメッセージ処理。
送られてきたメッセージのデータはポインタである。
なのでポインタの先にある構造体を引っ張ってきて、実際の文字列を取り出す。
*1:32bit・64bitアプリケーションでサイズが変わるらしいので注意