C#/.NET Frameworkにおける、二重起動の防止については、様々なサイトで解説が成されています。パッと探してみたところでもこれだけの解説サイトが見つかりました。
- 二重起動を禁止する: .NET Tips: C#, VB.NET, Visual Studio
- @IT:.NET TIPS Windowsアプリケーションの多重起動を禁止するには? – C# VB.NET Windowsフォーム
- C# – 二重起動を確実に禁止する
他にも、”C# 二重起動“でGoogleにて検索を行えば、数多くの解説ページを見つけることができます。
しかし、これらの解説で行えるのは、ソフトウェアの二重起動の抑制のみで、それ以上の動作が欲しい場合には一工夫が必要となります。
今回私が直面した要求は、次の2点でした。
- 二重起動時に、既存のプロセスのメインウインドウをアクティブにする。
- 二重起動時に渡されたコマンドライン引数を、既存のプロセスのメインウインドウに渡す。
これらについても、それぞれ様々なサイトで解説が行われています。
しかしながら、両者の要求を同時に満たせる解説は見あたらず、また何だかんだと問題があったりしたため、自分で作ってしまうことにしました。
※とはいえ、多くの解説サイトを参考にさせていただいています。
ちなみに、このクラスを使用するためにはNet.Kyoh.Windows.Win32APIクラスが必要になります。
まず、MutexForm.cs。
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Soap;
using System.Threading;
using System.Windows.Forms;
using Net.Kyoh.Windows;
namespace Net.Kyoh.Tips.MutexForm
{
public partial class MutexForm : Form
{
private uint WM_APP_CLOSENEWFORM;
private uint WM_APP_FINDEXISTINGFORMS;
private Mutex _mutex;
private SoapFormatter format = new SoapFormatter();
#region イベント
public class AnotherFormClosingEventArgs : CancelEventArgs
{
public AnotherFormClosingEventArgs() : base() { }
public AnotherFormClosingEventArgs(bool cancel) : base(cancel) { }
}
public class AnotherFormClosedEventArgs : EventArgs
{
public string[] CommandLine { get; internal set; }
public AnotherFormClosedEventArgs() : this(new string[0]) { }
public AnotherFormClosedEventArgs(string[] args) { CommandLine = args; }
}
private object AnotherFormClosingKey = new object();
public delegate void AnotherFormClosingHandler(object sender, AnotherFormClosingEventArgs e);
/// <summary>
/// 二重起動時、既存のプロセスからフォームを閉じる要求を受けると発生します。
/// </summary>
public event AnotherFormClosingHandler AnotherFormClosing
{
add { Events.AddHandler(AnotherFormClosingKey, value); }
remove { Events.RemoveHandler(AnotherFormClosingKey, value); }
}
private object AnotherFormClosedKey = new object();
public delegate void AnotherFormClosedHandler(object sender, AnotherFormClosedEventArgs e);
/// <summary>
/// 二重起動のプロセスのメインフォームが現在のインスタンスからの要求に従って閉じると発生します。
/// </summary>
public event AnotherFormClosedHandler AnotherFormClosed
{
add { Events.AddHandler(AnotherFormClosedKey, value); }
remove { Events.RemoveHandler(AnotherFormClosedKey, value); }
}
protected void RaiseEvent(object key, EventArgs e)
{
Delegate temp = Events[key];
if (temp != null)
temp.DynamicInvoke(this, e);
}
#endregion
protected MutexForm()
{
InitializeComponent();
WM_APP_CLOSENEWFORM = Win32API.RegisterWindowMessage(GetType().FullName + "::CloseNewForm");
WM_APP_FINDEXISTINGFORMS = Win32API.RegisterWindowMessage(GetType().FullName + "::FindExistingForms");
// 二重起動のチェックと二重起動時のメッセージ発行
_mutex = new Mutex(false, GetType().FullName);
if (!_mutex.WaitOne(0, false))
Win32API.PostMessage(Win32API.HWND_BROADCAST, WM_APP_FINDEXISTINGFORMS, this.Handle, this.Handle);
}
protected override void WndProc(ref Message m)
{
// 二重起動したプロセスから、既存プロセス探索のメッセージを受けた
// → 自分が既存のプロセスであることを表明し、クローズ要求を発行する。また、このフォームをアクティブにする。
if (m.Msg == WM_APP_FINDEXISTINGFORMS)
{
if (m.WParam != IntPtr.Zero && m.WParam != this.Handle && m.WParam != Win32API.HWND_BROADCAST && m.LParam == m.WParam)
if (Win32API.SendMessage(m.WParam, WM_APP_CLOSENEWFORM, this.Handle, this.Handle) != IntPtr.Zero)
Activate();
}
// 既存プロセスから、フォームのクローズ要求を受けた
// → コマンドライン引数をSoap文字列で引き渡し、フォームを閉じる。
else if (m.Msg == WM_APP_CLOSENEWFORM)
{
if (m.WParam != IntPtr.Zero && m.WParam != this.Handle && m.WParam != Win32API.HWND_BROADCAST && m.LParam == m.WParam)
{
AnotherFormClosingEventArgs e = new AnotherFormClosingEventArgs();
RaiseEvent(AnotherFormClosingKey, e);
// イベントのキャンセル要求により中止する。
if (e.Cancel)
Win32API.ReplyMessage(IntPtr.Zero);
else
{
string[] args = Environment.GetCommandLineArgs();
using (MemoryStream stream = new MemoryStream())
{
format.Serialize(stream, args);
byte[] buffer = stream.GetBuffer();
IntPtr pBuffer = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, pBuffer, buffer.Length);
CopyData copy = new CopyData() { cbData = (uint)buffer.Length, lpData = pBuffer };
Win32API.SendMessage(m.WParam, Win32API.WM_COPYDATA, this.Handle, ref copy);
Marshal.FreeHGlobal(pBuffer);
}
Close();
Win32API.ReplyMessage(new IntPtr(1));
}
}
}
// 二重起動したプロセスから、コマンドライン引数の引き渡しが行われた
// → コマンドラインを受け取り、イベントを発生させる。
else if (m.Msg == Win32API.WM_COPYDATA)
{
CopyData copy = (CopyData)Marshal.PtrToStructure(m.LParam, typeof(CopyData));
byte[] buffer = new byte[copy.cbData];
Marshal.Copy(copy.lpData, buffer, 0, buffer.Length);
Win32API.ReplyMessage(IntPtr.Zero);
string[] args;
using (MemoryStream stream = new MemoryStream(buffer, false))
args = (string[])format.Deserialize(stream);
RaiseEvent(AnotherFormClosedKey, new AnotherFormClosedEventArgs(args));
}
base.WndProc(ref m);
}
}
}
つづいて、MutexForm.Designer.cs。
namespace Net.Kyoh.Tips.MutexForm
{
partial class MutexForm
{
/// <summary>
/// 必要なデザイナ変数です。
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// 使用中のリソースをすべてクリーンアップします。
/// </summary>
/// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
if (_mutex != null)
{
_mutex.Close();
_mutex = null;
}
}
base.Dispose(disposing);
}
#region Windows フォーム デザイナで生成されたコード
/// <summary>
/// デザイナ サポートに必要なメソッドです。このメソッドの内容を
/// コード エディタで変更しないでください。
/// </summary>
private void InitializeComponent()
{
this.SuspendLayout();
//
// MutexForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 269);
this.Name = "MutexForm";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
}
}
このMutexFormクラスを継承して、フォームを自作しておけば、このフォームが二重に呼び出されることはありません。
が、MutexFormクラスのコンストラクタにあるとおり、このクラスではMutexの要求を「クラスのフルネーム」で行っています。
従って、異なるアプリケーション同士であっても、同じ名前空間・同じクラス名を使っていると衝突を起こして、後から起動した方のアプリケーションが起動できなくなってしまいます。適切な名前空間内で、できればMutexFormクラスを継承したフォーム、という形で使ってください。
[2010/5/13]
ソースコードの表示が、きちんとマークアップできていませんでした。修正します。
同じ話題で、二重起動の禁止と、コマンドライン引数などの受け渡し » kyoh.netなんてエントリも追加されています。こちらのエントリのことを忘れていた・・・。
後から書いたほうがよりクール(と個人的に思える)実装になっているので、そちらをどうぞ。