エクスプローラー上でファイルを開き、アプリケーションを起動する場合。
既に起動しているアプリケーションにファイルを渡したい時、どうしたらよいか。
主に、以下の手順を組むことになるだろう。
- 多重起動の検出
- 元プロセスを探す
- 元プロセスへファイルパスを送る
- 元プロセスでファイルパスを受け取る
これら各手順の方法をざっくり解説していこうと思う。
多重起動の検出
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;
public uint cbData;
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);
}
受け取り側、メインウィンドウのメッセージ処理。
送られてきたメッセージのデータはポインタである。
なのでポインタの先にある構造体を引っ張ってきて、実際の文字列を取り出す。