在。net下使用c#和WPF(而不是Windows窗体或控制台),创建一个只能作为单个实例运行的应用程序的正确方法是什么?
我知道它与某种叫做互斥的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么。
代码还需要通知已经运行的实例,用户试图启动第二个实例,如果存在命令行参数,还可能传递任何命令行参数。
在。net下使用c#和WPF(而不是Windows窗体或控制台),创建一个只能作为单个实例运行的应用程序的正确方法是什么?
我知道它与某种叫做互斥的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么。
代码还需要通知已经运行的实例,用户试图启动第二个实例,如果存在命令行参数,还可能传递任何命令行参数。
当前回答
我最喜欢的解决方案来自MVP丹尼尔·沃恩: 强制执行单实例Wpf应用程序
它使用MemoryMappedFile将命令行参数发送给第一个实例:
/// <summary>
/// This class allows restricting the number of executables in execution, to one.
/// </summary>
public sealed class SingletonApplicationEnforcer
{
readonly Action<IEnumerable<string>> processArgsFunc;
readonly string applicationId;
Thread thread;
string argDelimiter = "_;;_";
/// <summary>
/// Gets or sets the string that is used to join
/// the string array of arguments in memory.
/// </summary>
/// <value>The arg delimeter.</value>
public string ArgDelimeter
{
get
{
return argDelimiter;
}
set
{
argDelimiter = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SingletonApplicationEnforcer"/> class.
/// </summary>
/// <param name="processArgsFunc">A handler for processing command line args
/// when they are received from another application instance.</param>
/// <param name="applicationId">The application id used
/// for naming the <seealso cref="EventWaitHandle"/>.</param>
public SingletonApplicationEnforcer(Action<IEnumerable<string>> processArgsFunc,
string applicationId = "DisciplesRock")
{
if (processArgsFunc == null)
{
throw new ArgumentNullException("processArgsFunc");
}
this.processArgsFunc = processArgsFunc;
this.applicationId = applicationId;
}
/// <summary>
/// Determines if this application instance is not the singleton instance.
/// If this application is not the singleton, then it should exit.
/// </summary>
/// <returns><c>true</c> if the application should shutdown,
/// otherwise <c>false</c>.</returns>
public bool ShouldApplicationExit()
{
bool createdNew;
string argsWaitHandleName = "ArgsWaitHandle_" + applicationId;
string memoryFileName = "ArgFile_" + applicationId;
EventWaitHandle argsWaitHandle = new EventWaitHandle(
false, EventResetMode.AutoReset, argsWaitHandleName, out createdNew);
GC.KeepAlive(argsWaitHandle);
if (createdNew)
{
/* This is the main, or singleton application.
* A thread is created to service the MemoryMappedFile.
* We repeatedly examine this file each time the argsWaitHandle
* is Set by a non-singleton application instance. */
thread = new Thread(() =>
{
try
{
using (MemoryMappedFile file = MemoryMappedFile.CreateOrOpen(memoryFileName, 10000))
{
while (true)
{
argsWaitHandle.WaitOne();
using (MemoryMappedViewStream stream = file.CreateViewStream())
{
var reader = new BinaryReader(stream);
string args;
try
{
args = reader.ReadString();
}
catch (Exception ex)
{
Debug.WriteLine("Unable to retrieve string. " + ex);
continue;
}
string[] argsSplit = args.Split(new string[] { argDelimiter },
StringSplitOptions.RemoveEmptyEntries);
processArgsFunc(argsSplit);
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine("Unable to monitor memory file. " + ex);
}
});
thread.IsBackground = true;
thread.Start();
}
else
{
/* Non singleton application instance.
* Should exit, after passing command line args to singleton process,
* via the MemoryMappedFile. */
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(memoryFileName))
{
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
var writer = new BinaryWriter(stream);
string[] args = Environment.GetCommandLineArgs();
string joined = string.Join(argDelimiter, args);
writer.Write(joined);
}
}
argsWaitHandle.Set();
}
return !createdNew;
}
}
其他回答
MSDN实际上有一个c#和VB的示例应用程序可以做到这一点:http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx
The most common and reliable technique for developing single-instance detection is to use the Microsoft .NET Framework remoting infrastructure (System.Remoting). The Microsoft .NET Framework (version 2.0) includes a type, WindowsFormsApplicationBase, which encapsulates the required remoting functionality. To incorporate this type into a WPF application, a type needs to derive from it, and be used as a shim between the application static entry point method, Main, and the WPF application's Application type. The shim detects when an application is first launched, and when subsequent launches are attempted, and yields control the WPF Application type to determine how to process the launches.
For C# people just take a deep breath and forget about the whole 'I don't wanna include VisualBasic DLL'. Because of this and what Scott Hanselman says and the fact that this pretty much is the cleanest solution to the problem and is designed by people who know a lot more about the framework than you do. From a usability standpoint the fact is if your user is loading an application and it is already open and you're giving them an error message like 'Another instance of the app is running. Bye' then they're not gonna be a very happy user. You simply MUST (in a GUI application) switch to that application and pass in the arguments provided - or if command line parameters have no meaning then you must pop up the application which may have been minimized.
这个框架已经支持这个功能了——只是有些白痴把DLL命名为Microsoft罢了。VisualBasic,它没有被放到微软。ApplicationUtils之类的。克服它——或者打开Reflector。
提示:如果你完全按原样使用这种方法,并且你已经有了一个带有资源等的App.xaml,你也会想要看看这个。
更新2017-01-25。在尝试了一些东西之后,我决定使用VisualBasic.dll,它更容易,工作效果更好(至少对我来说)。我让我之前的答案只是作为参考…
只是作为参考,这是我如何不传递参数(我找不到任何理由这样做…我指的是带有参数的单个应用程序,这些参数可以从一个实例传递到另一个实例)。 如果需要文件关联,那么应用程序应该(根据用户的标准期望)为每个文档实例化。如果你必须传递args到现有的应用程序,我想我会使用vb dll。
不传递参数(只是单实例应用程序),我更喜欢不注册一个新的窗口消息,不覆盖Matt Davis解决方案中定义的消息循环。虽然添加一个VisualBasic dll不是一个大问题,但我不喜欢添加一个新的引用只是做单个实例应用程序。此外,我更喜欢用Main实例化一个新类,而不是调用Shutdown from app. startup重写以确保尽快退出。
希望大家都喜欢……或者会启发一点:-)
项目启动类应该设置为“SingleInstanceApp”。
public class SingleInstanceApp
{
[STAThread]
public static void Main(string[] args)
{
Mutex _mutexSingleInstance = new Mutex(true, "MonitorMeSingleInstance");
if (_mutexSingleInstance.WaitOne(TimeSpan.Zero, true))
{
try
{
var app = new App();
app.InitializeComponent();
app.Run();
}
finally
{
_mutexSingleInstance.ReleaseMutex();
_mutexSingleInstance.Close();
}
}
else
{
MessageBox.Show("One instance is already running.");
var processes = Process.GetProcessesByName(Assembly.GetEntryAssembly().GetName().Name);
{
if (processes.Length > 1)
{
foreach (var process in processes)
{
if (process.Id != Process.GetCurrentProcess().Id)
{
WindowHelper.SetForegroundWindow(process.MainWindowHandle);
}
}
}
}
}
}
}
WindowHelper:
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
namespace HQ.Util.Unmanaged
{
public class WindowHelper
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
这是我的整个App.xaml.cs,这段代码也将启动的程序实例带到前台:
public partial class App : Application
{
private static Mutex _mutex = null;
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
protected override void OnStartup(StartupEventArgs e)
{
_mutex = new Mutex(true, "YourMutexName", out var createdNew);
if (!createdNew)
{
using (var currentProcess = Process.GetCurrentProcess())
{
foreach (var process in Process.GetProcessesByName(currentProcess.ProcessName))
{
if (process.Id != currentProcess.Id)
{
ShowWindow(process.MainWindowHandle, 9);
SetForegroundWindow(process.MainWindowHandle);
}
process.Dispose();
}
}
// app is already running! Exiting the application
Shutdown();
}
base.OnStartup(e);
}
protected override void OnExit(ExitEventArgs e)
{
_mutex.Dispose();
base.OnExit(e);
}
}
这里有一个解决方案:
Protected Overrides Sub OnStartup(e As StartupEventArgs)
Const appName As String = "TestApp"
Dim createdNew As Boolean
_mutex = New Mutex(True, appName, createdNew)
If Not createdNew Then
'app is already running! Exiting the application
MessageBox.Show("Application is already running.")
Application.Current.Shutdown()
End If
MyBase.OnStartup(e)
End Sub
您还可以使用CodeFluent Runtime,这是一组免费的工具。它提供了一个SingleInstance类来实现单个实例应用程序。