在。net下使用c#和WPF(而不是Windows窗体或控制台),创建一个只能作为单个实例运行的应用程序的正确方法是什么?
我知道它与某种叫做互斥的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么。
代码还需要通知已经运行的实例,用户试图启动第二个实例,如果存在命令行参数,还可能传递任何命令行参数。
在。net下使用c#和WPF(而不是Windows窗体或控制台),创建一个只能作为单个实例运行的应用程序的正确方法是什么?
我知道它与某种叫做互斥的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么。
代码还需要通知已经运行的实例,用户试图启动第二个实例,如果存在命令行参数,还可能传递任何命令行参数。
当前回答
请检查这里提出的解决方案,它使用信号量来确定现有实例是否已经在运行,适用于WPF应用程序,并且可以通过使用TcpListener和TcpClient将参数从第二个实例传递给第一个已经运行的实例:
它不仅适用于。net Framework,也适用于。net Core。
其他回答
我在这里找不到一个简单的解决方案,所以我希望有人会喜欢这个:
更新2018-09-20
把这段代码放在Program.cs中:
using System.Diagnostics;
static void Main()
{
Process thisProcess = Process.GetCurrentProcess();
Process[] allProcesses = Process.GetProcessesByName(thisProcess.ProcessName);
if (allProcesses.Length > 1)
{
// Don't put a MessageBox in here because the user could spam this MessageBox.
return;
}
// Optional code. If you don't want that someone runs your ".exe" with a different name:
string exeName = AppDomain.CurrentDomain.FriendlyName;
// in debug mode, don't forget that you don't use your normal .exe name.
// Debug uses the .vshost.exe.
if (exeName != "the name of your executable.exe")
{
// You can add a MessageBox here if you want.
// To point out to users that the name got changed and maybe what the name should be or something like that^^
MessageBox.Show("The executable name should be \"the name of your executable.exe\"",
"Wrong executable name", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Following code is default code:
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
通常,这是我用于单实例Windows窗体应用程序的代码:
[STAThread]
public static void Main()
{
String assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
using (Mutex mutex = new Mutex(false, assemblyName))
{
if (!mutex.WaitOne(0, false))
{
Boolean shownProcess = false;
Process currentProcess = Process.GetCurrentProcess();
foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName))
{
if (!process.Id.Equals(currentProcess.Id) && process.MainModule.FileName.Equals(currentProcess.MainModule.FileName) && !process.MainWindowHandle.Equals(IntPtr.Zero))
{
IntPtr windowHandle = process.MainWindowHandle;
if (NativeMethods.IsIconic(windowHandle))
NativeMethods.ShowWindow(windowHandle, ShowWindowCommand.Restore);
NativeMethods.SetForegroundWindow(windowHandle);
shownProcess = true;
}
}
if (!shownProcess)
MessageBox.Show(String.Format(CultureInfo.CurrentCulture, "An instance of {0} is already running!", assemblyName), assemblyName, MessageBoxButtons.OK, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
}
else
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form());
}
}
}
本地组件的位置:
[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean IsIconic([In] IntPtr windowHandle);
[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean SetForegroundWindow([In] IntPtr windowHandle);
[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean ShowWindow([In] IntPtr windowHandle, [In] ShowWindowCommand command);
public enum ShowWindowCommand : int
{
Hide = 0x0,
ShowNormal = 0x1,
ShowMinimized = 0x2,
ShowMaximized = 0x3,
ShowNormalNotActive = 0x4,
Minimize = 0x6,
ShowMinimizedNotActive = 0x7,
ShowCurrentNotActive = 0x8,
Restore = 0x9,
ShowDefault = 0xA,
ForceMinimize = 0xB
}
下面是我使用的一个轻量级解决方案,它允许应用程序将一个已经存在的窗口带到前台,而无需求助于自定义窗口消息或盲目地搜索进程名。
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
static readonly string guid = "<Application Guid>";
static void Main()
{
Mutex mutex = null;
if (!CreateMutex(out mutex))
return;
// Application startup code.
Environment.SetEnvironmentVariable(guid, null, EnvironmentVariableTarget.User);
}
static bool CreateMutex(out Mutex mutex)
{
bool createdNew = false;
mutex = new Mutex(false, guid, out createdNew);
if (createdNew)
{
Process process = Process.GetCurrentProcess();
string value = process.Id.ToString();
Environment.SetEnvironmentVariable(guid, value, EnvironmentVariableTarget.User);
}
else
{
string value = Environment.GetEnvironmentVariable(guid, EnvironmentVariableTarget.User);
Process process = null;
int processId = -1;
if (int.TryParse(value, out processId))
process = Process.GetProcessById(processId);
if (process == null || !SetForegroundWindow(process.MainWindowHandle))
MessageBox.Show("Unable to start application. An instance of this application is already running.");
}
return createdNew;
}
编辑:你也可以静态地存储和初始化互斥量和createdNew,但是一旦你完成了它,你需要显式地释放/释放互斥量。就我个人而言,我更喜欢将互斥锁保持在本地,因为即使应用程序在未到达Main结束时就关闭,它也会被自动销毁。
您还可以使用CodeFluent Runtime,这是一组免费的工具。它提供了一个SingleInstance类来实现单个实例应用程序。
这是我的整个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);
}
}