これは、単一インスタンスのWPFアプリケーションを作成するためにこれまでに実装したコードです。
_#region Using Directives
using System;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
#endregion
namespace MyWPF
{
public partial class MainApplication : Application, IDisposable
{
#region Members
private Int32 m_Message;
private Mutex m_Mutex;
#endregion
#region Methods: Functions
private IntPtr HandleMessages(IntPtr handle, Int32 message, IntPtr wParameter, IntPtr lParameter, ref Boolean handled)
{
if (message == m_Message)
{
if (MainWindow.WindowState == WindowState.Minimized)
MainWindow.WindowState = WindowState.Normal;
Boolean topmost = MainWindow.Topmost;
MainWindow.Topmost = true;
MainWindow.Topmost = topmost;
}
return IntPtr.Zero;
}
private void Dispose(Boolean disposing)
{
if (disposing && (m_Mutex != null))
{
m_Mutex.ReleaseMutex();
m_Mutex.Close();
m_Mutex = null;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region Methods: Overrides
protected override void OnStartup(StartupEventArgs e)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Boolean mutexCreated;
String mutexName = String.Format(CultureInfo.InvariantCulture, "Local\\{{{0}}}{{{1}}}", Assembly.GetType().GUID, Assembly.GetName().Name);
m_Mutex = new Mutex(true, mutexName, out mutexCreated);
m_Message = NativeMethods.RegisterWindowMessage(mutexName);
if (!mutexCreated)
{
m_Mutex = null;
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, m_Message, IntPtr.Zero, IntPtr.Zero);
Current.Shutdown();
return;
}
base.OnStartup(e);
MainWindow window = new MainWindow();
MainWindow = window;
window.Show();
HwndSource.FromHwnd((new WindowInteropHelper(window)).Handle).AddHook(new HwndSourceHook(HandleMessages));
}
protected override void OnExit(ExitEventArgs e)
{
Dispose();
base.OnExit(e);
}
#endregion
}
}
_
すべてが完璧に機能します...しかし、私はそれについていくつかの疑いがあり、私のアプローチをどのように改善できるかについてのあなたの提案を受け取りたいです。
1)IDisposable
メンバー(IDisposable
)を使用していたため、コード分析からMutex
インターフェイスの実装を求められました。 Dispose()
実装は十分ですか?それが呼び出されることは決してないので、私はそれを避けるべきですか?
2)m_Mutex = new Mutex(true, mutexName, out mutexCreated);
を使用して結果を確認するか、m_Mutex = new Mutex(false, mutexName);
を使用してからm_Mutex.WaitOne(TimeSpan.Zero, false);
を確認する方が良いですか?マルチスレッドの場合は...
3)RegisterWindowMessage
API呼び出しは_UInt32
_...を返す必要がありますが、HwndSourceHook
はメッセージ値として_Int32
_のみを受け入れます...予期しない動作(たとえば_Int32.MaxValue
_)より大きい結果ですか?
4)OnStartup
オーバーライドで...別のインスタンスが既に実行されていて、アプリケーションをシャットダウンする場合でも、base.OnStartup(e);
を実行する必要がありますか?
5)Topmost
値を設定する必要のない既存のインスタンスを一番上に持ってくるより良い方法はありますか?たぶんActivate()
?
6)私のアプローチに欠陥がありますか?マルチスレッド、悪い例外処理などに関する何か?たとえば... OnStartup
とOnExit
の間でアプリケーションがクラッシュした場合はどうなりますか?
1)標準的なDispose実装のように見えます。本当に必要なわけではありませんが(ポイント6を参照)、害はありません。 (閉鎖時の掃除は、家を焼く前に家を掃除するようなものですが、私見ですが、この問題に関する意見は異なります。)
とにかく、直接呼び出されなくても、クリーンアップメソッドの名前として「Dispose」を使用しないのはなぜですか。 「クリーンアップ」と呼ぶこともできますが、人間用のコードも作成することを忘れないでください。Disposeは見慣れているように見えます。だから、「廃棄」に行きます。
2)私はいつもm_Mutex = new Mutex(false, mutexName);
を見てきましたが、技術的な利点というよりは慣習だと思います。
3)MSDNから:
メッセージが正常に登録された場合、戻り値は0xC000〜0xFFFFの範囲のメッセージ識別子です。
だから私は心配しません。通常、このクラスの関数では、UIntは「Intに収まらないため、UIntを使用してさらに何かをしましょう」には使用されませんが、「関数は負の値を返さない」という契約を明確にします。
4)#1と同じ理由で、シャットダウンする場合は呼び出しを避けます
5)いくつかの方法があります。 Win32で最も簡単な方法は、2番目のインスタンスにSetForegroundWindowの呼び出しを実行させることです(こちらをご覧ください: http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx );ただし、同等のWPF機能があるかどうか、またはそれをPInvokeする必要があるかどうかはわかりません。
6)
たとえば、OnStartupとOnExitの間でアプリケーションがクラッシュするとどうなりますか?
問題ありません。プロセスが終了すると、そのプロセスが所有するすべてのハンドルが解放されます。ミューテックスも解放されます。
要するに、私の推奨事項:
たとえば、テクニック(ウィンドウにメッセージを送信/投稿しようとする-応答がない場合、スタックしている)とMSKテクニックを使用して、古いプロセスを見つけて終了できます。その後、正常に起動します。
いくつかの選択肢がありますが、
Mutex
Mutex myMutex ;
private void Application_Startup(object sender, StartupEventArgs e)
{
bool aIsNewInstance = false;
myMutex = new Mutex(true, "MyWPFApplication", out aIsNewInstance);
if (!aIsNewInstance)
{
MessageBox.Show("Already an instance is running...");
App.Current.Shutdown();
}
}
プロセスマネージャー
private void Application_Startup(object sender, StartupEventArgs e)
{
Process proc = Process.GetCurrentProcess();
int count = Process.GetProcesses().Where(p=>
p.ProcessName == proc.ProcessName).Count();
if (count > 1)
{
MessageBox.Show("Already an instance is running...");
App.Current.Shutdown();
}
}
リスナーソケットを使用する
別のアプリケーションに信号を送る1つの方法は、そのアプリケーションへのTcp接続を開くことです。ソケットを作成し、ポートにバインドし、バックグラウンドスレッドで接続をリッスンします。これが成功した場合、正常に実行します。そうでない場合は、そのポートに接続します。これは、2番目のアプリケーションの起動が試行されたことを他のインスタンスに通知します。必要に応じて、元のインスタンスはメインウィンドウを前面に表示できます。
「セキュリティ」ソフトウェア/ファイアウォールが問題になる場合があります。
ユーザーエクスペリエンスを少し向上させたかった-別のインスタンスが既に実行されている場合は、2番目のインスタンスに関するエラーを表示するのではなく、アクティブ化しましょう。これが私の実装です。
名前付きMutexを使用して、1つのインスタンスのみが実行されていることを確認し、EventWaitHandleという名前を付けて、あるインスタンスから別のインスタンスに通知を渡します。
App.xaml.cs:
/// <summary>Interaction logic for App.xaml</summary>
public partial class App
{
#region Constants and Fields
/// <summary>The event mutex name.</summary>
private const string UniqueEventName = "{GUID}";
/// <summary>The unique mutex name.</summary>
private const string UniqueMutexName = "{GUID}";
/// <summary>The event wait handle.</summary>
private EventWaitHandle eventWaitHandle;
/// <summary>The mutex.</summary>
private Mutex mutex;
#endregion
#region Methods
/// <summary>The app on startup.</summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
private void AppOnStartup(object sender, StartupEventArgs e)
{
bool isOwned;
this.mutex = new Mutex(true, UniqueMutexName, out isOwned);
this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
// So, R# would not give a warning that this variable is not used.
GC.KeepAlive(this.mutex);
if (isOwned)
{
// Spawn a thread which will be waiting for our event
var thread = new Thread(
() =>
{
while (this.eventWaitHandle.WaitOne())
{
Current.Dispatcher.BeginInvoke(
(Action)(() => ((MainWindow)Current.MainWindow).BringToForeground()));
}
});
// It is important mark it as background otherwise it will prevent app from exiting.
thread.IsBackground = true;
thread.Start();
return;
}
// Notify other instance so it could bring itself to foreground.
this.eventWaitHandle.Set();
// Terminate this instance.
this.Shutdown();
}
#endregion
}
MainWindow.csのBringToForeground:
/// <summary>Brings main window to foreground.</summary>
public void BringToForeground()
{
if (this.WindowState == WindowState.Minimized || this.Visibility == Visibility.Hidden)
{
this.Show();
this.WindowState = WindowState.Normal;
}
// According to some sources these steps gurantee that an app will be brought to foreground.
this.Activate();
this.Topmost = true;
this.Topmost = false;
this.Focus();
}
Startup = "AppOnStartup"を追加します(ありがとうvhanla!):
<Application x:Class="MyClass.App"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
Startup="AppOnStartup">
<Application.Resources>
</Application.Resources>
</Application>
私のために働く:)
WPFの場合:
public partial class App : Application
{
private static Mutex _mutex = null;
protected override void OnStartup(StartupEventArgs e)
{
const string appName = "MyAppName";
bool createdNew;
_mutex = new Mutex(true, appName, out createdNew);
if (!createdNew)
{
//app is already running! Exiting the application
Application.Current.Shutdown();
}
base.OnStartup(e);
}
}
それを処理する最も簡単な方法は、名前付きセマフォを使用することです。このようなものを試してみてください...
public partial class App : Application
{
Semaphore sema;
bool shouldRelease = false;
protected override void OnStartup(StartupEventArgs e)
{
bool result = Semaphore.TryOpenExisting("SingleInstanceWPFApp", out sema);
if (result) // we have another instance running
{
App.Current.Shutdown();
}
else
{
try
{
sema = new Semaphore(1, 1, "SingleInstanceWPFApp");
}
catch
{
App.Current.Shutdown(); //
}
}
if (!sema.WaitOne(0))
{
App.Current.Shutdown();
}
else
{
shouldRelease = true;
}
base.OnStartup(e);
}
protected override void OnExit(ExitEventArgs e)
{
if (sema != null && shouldRelease)
{
sema.Release();
}
}
}
私はこれに単純なTCPソケットを使用しました(Javaでは、10年前)。
2番目のインスタンスを防止するには、
これは次のように行うことができます(これはWPFアプリの場合(App()を参照)、WinFormsでも機能します)。
public partial class App : Application
{
public App()
{
// initiate it. Call it first.
preventSecond();
}
private const string UniqueEventName = "{GENERATE-YOUR-OWN-GUID}";
private void preventSecond()
{
try
{
EventWaitHandle.OpenExisting(UniqueEventName); // check if it exists
this.Shutdown();
}
catch (WaitHandleCannotBeOpenedException)
{
new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName); // register
}
}
}
2番目のバージョン:上記に加えて、ウィンドウを表示するように他のインスタンスに信号を送ります(WinFormsのMainWindow部分を変更します):
public partial class App : Application
{
public App()
{
// initiate it. Call it first.
//preventSecond();
SingleInstanceWatcher();
}
private const string UniqueEventName = "{GENERATE-YOUR-OWN-GUID}";
private EventWaitHandle eventWaitHandle;
/// <summary>prevent a second instance and signal it to bring its mainwindow to foregorund</summary>
/// <seealso cref="https://stackoverflow.com/a/23730146/1644202"/>
private void SingleInstanceWatcher()
{
// check if it is allready open.
try
{
// try to open it - if another instance is running, it will exist
this.eventWaitHandle = EventWaitHandle.OpenExisting(UniqueEventName);
// Notify other instance so it could bring itself to foreground.
this.eventWaitHandle.Set();
// Terminate this instance.
this.Shutdown();
}
catch (WaitHandleCannotBeOpenedException)
{
// listen to a new event
this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
}
// if this instance gets the signal to show the main window
new Task(() =>
{
while (this.eventWaitHandle.WaitOne())
{
Current.Dispatcher.BeginInvoke((Action)(() =>
{
// could be set or removed anytime
if (!Current.MainWindow.Equals(null))
{
var mw = Current.MainWindow;
if (mw.WindowState == WindowState.Minimized || mw.Visibility != Visibility.Visible)
{
mw.Show();
mw.WindowState = WindowState.Normal;
}
// According to some sources these steps gurantee that an app will be brought to foreground.
mw.Activate();
mw.Topmost = true;
mw.Topmost = false;
mw.Focus();
}
}));
}
})
.Start();
}
}
クラスのドロップとしてのこのコードは、@ Selfcontained-C-Sharp-WPF-compatible-utility-classes/tils.SingleInstance.cs
これは簡単な解決策です。この場合、MainWindow.xamlで起動ファイル(アプリケーションの起動元のビュー)を開きます。 MainWindow.xaml.csファイルを開きます。コンストラクターに移動し、intializecomponent()の後にこのコードを追加します。
Process Currentproc = Process.GetCurrentProcess();
Process[] procByName=Process.GetProcessesByName("notepad"); //Write the name of your exe file in inverted commas
if(procByName.Length>1)
{
MessageBox.Show("Application is already running");
App.Current.Shutdown();
}
System.Diagnosticsを追加することを忘れないでください
古いインスタンスをフォアグラウンドに持ってくる例を次に示します。
_public partial class App : Application
{
[DllImport("user32", CharSet = CharSet.Unicode)]
static extern IntPtr FindWindow(string cls, string win);
[DllImport("user32")]
static extern IntPtr SetForegroundWindow(IntPtr hWnd);
[DllImport("user32")]
static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32")]
static extern bool OpenIcon(IntPtr hWnd);
private static Mutex _mutex = null;
protected override void OnStartup(StartupEventArgs e)
{
const string appName = "LinkManager";
bool createdNew;
_mutex = new Mutex(true, appName, out createdNew);
if (!createdNew)
{
ActivateOtherWindow();
//app is already running! Exiting the application
Application.Current.Shutdown();
}
base.OnStartup(e);
}
private static void ActivateOtherWindow()
{
var other = FindWindow(null, "!YOUR MAIN WINDOW TITLE HERE!");
if (other != IntPtr.Zero)
{
SetForegroundWindow(other);
if (IsIconic(other))
OpenIcon(other);
}
}
}
_
ただし、メインウィンドウのタイトルがdurigランタイムを変更しない場合にのみ機能します。
編集:
Startup
をオーバーライドする代わりに、_App.xaml
_でOnStartup
イベントを使用することもできます。
_// App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
const string appName = "LinkManager";
bool createdNew;
_mutex = new Mutex(true, appName, out createdNew);
if (!createdNew)
{
ActivateOtherWindow();
//app is already running! Exiting the application
Application.Current.Shutdown();
}
}
// App.xaml
<Application x:Class="MyApp.App"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp"
StartupUri="MainWindow.xaml" Startup="Application_Startup"> //<- startup event
_
この場合、base.OnStartup(e)
を呼び出さないことを忘れないでください!