715

在 .NET(而不是Windows 窗体或控制台)下使用 C# 和 WPF,创建只能作为单个实例运行的应用程序的正确方法是什么?

我知道这与一些称为互斥锁的神话有关,我很少能找到有人愿意停下来解释其中一个是什么。

代码还需要通知已经运行的实例用户尝试启动第二个实例,并且可能还传递任何命令行参数(如果存在)。

4

38 回答 38

589

这是一篇关于 Mutex 解决方案的非常好的文章。这篇文章描述的方法是有利的,原因有两个。

首先,它不需要依赖于 Microsoft.VisualBasic 程序集。如果我的项目已经依赖于该程序集,我可能会提倡使用另一个答案中显示的方法。但事实上,我不使用 Microsoft.VisualBasic 程序集,我宁愿不向我的项目添加不必要的依赖项。

其次,本文展示了当用户尝试启动另一个实例时如何将应用程序的现有实例带到前台。这是这里描述的其他 Mutex 解决方案没有解决的非常好的方法。


更新

截至 2014 年 8 月 1 日,我上面链接的文章仍然有效,但博客已经有一段时间没有更新了。这让我担心它最终可能会消失,随之而来的是提倡的解决方案。我在这里复制文章的内容以供后代使用。这些话完全属于Sanity Free Coding的博客所有者。

今天我想重构一些禁止我的应用程序运行多个自身实例的代码。

以前我使用System.Diagnostics.Process在进程列表中搜索 myapp.exe 的实例。虽然这可行,但它会带来很多开销,我想要一些更清洁的东西。

知道我可以为此使用互斥锁(但以前从未这样做过),我着手削减我的代码并简化我的生活。

在我的应用程序主类中,我创建了一个名为Mutex的静态对象:

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    ...
}

拥有一个命名的互斥体允许我们在多个线程和进程之间进行堆栈同步,这正是我正在寻找的魔法。

Mutex.WaitOne有一个重载,它指定我们等待的时间量。由于我们实际上并不想同步我们的代码(更多的是检查它是否当前正在使用),我们使用带有两个参数的重载:Mutex.WaitOne(Timespan timeout, bool exitContext)。如果可以进入,则等待一个返回 true,否则返回 false。在这种情况下,我们根本不想等待;如果我们的互斥锁正在被使用,跳过它,然后继续,所以我们传入 TimeSpan.Zero(等待 0 毫秒),并将 exitContext 设置为 true,这样我们就可以在尝试获取锁之前退出同步上下文。使用它,我们将 Application.Run 代码包装在如下内容中:

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            MessageBox.Show("only one instance at a time");
        }
    }
}

因此,如果我们的应用程序正在运行,WaitOne 将返回 false,并且我们将收到一个消息框。

我没有显示消息框,而是选择使用一个小的 Win32 来通知我正在运行的实例有人忘记了它已经在运行(通过将其自身置于所有其他窗口的顶部)。为了实现这一点,我使用PostMessage向每个窗口广播一条自定义消息(自定义消息是由我正在运行的应用程序向RegisterWindowMessage 注册 的,这意味着只有我的应用程序知道它是什么)然后我的第二个实例退出。正在运行的应用程序实例将接收该通知并对其进行处理。为了做到这一点,我在主窗体中覆盖了WndProc并监听了我的自定义通知。当我收到该通知时,我将表单的 TopMost 属性设置为 true 以将其置于顶部。

这是我最终得到的结果:

  • 程序.cs
static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            // send our Win32 message to make the currently running instance
            // jump on top of all the other windows
            NativeMethods.PostMessage(
                (IntPtr)NativeMethods.HWND_BROADCAST,
                NativeMethods.WM_SHOWME,
                IntPtr.Zero,
                IntPtr.Zero);
        }
    }
}
  • NativeMethods.cs
// this class just wraps some Win32 stuff that we're going to use
internal class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}
  • Form1.cs(正面部分)
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    protected override void WndProc(ref Message m)
    {
        if(m.Msg == NativeMethods.WM_SHOWME) {
            ShowMe();
        }
        base.WndProc(ref m);
    }
    private void ShowMe()
    {
        if(WindowState == FormWindowState.Minimized) {
            WindowState = FormWindowState.Normal;
        }
        // get our current "TopMost" value (ours will always be false though)
        bool top = TopMost;
        // make our form jump to the top of everything
        TopMost = true;
        // set it back to whatever it was
        TopMost = top;
    }
}
于 2009-02-07T01:18:20.277 回答
116

您可以使用 Mutex 类,但您很快就会发现您需要自己实现代码来传递参数等。好吧,当我阅读Chris Sell 的书时,我在 WinForms 中编程时学到了一个技巧。这个技巧使用了我们在框架中已经可用的逻辑。我不了解你,但是当我了解可以在框架中重用的东西时,这通常是我采取的路线,而不是重新发明轮子。除非它当然不能满足我的所有需求。

当我进入 WPF 时,我想出了一种在 WPF 应用程序中使用相同代码的方法。该解决方案应根据您的问题满足您的需求。

首先,我们需要创建我们的应用程序类。在这个类中,我们将覆盖 OnStartup 事件并创建一个名为 Activate 的方法,稍后将使用该方法。

public class SingleInstanceApplication : System.Windows.Application
{
    protected override void OnStartup(System.Windows.StartupEventArgs e)
    {
        // Call the OnStartup event on our base class
        base.OnStartup(e);

        // Create our MainWindow and show it
        MainWindow window = new MainWindow();
        window.Show();
    }

    public void Activate()
    {
        // Reactivate the main window
        MainWindow.Activate();
    }
}

其次,我们需要创建一个可以管理我们的实例的类。在我们进行之前,我们实际上将重用 Microsoft.VisualBasic 程序集中的一些代码。因为,我在这个例子中使用了 C#,所以我必须引用程序集。如果您使用的是 VB.NET,则无需执行任何操作。我们将使用的类是 WindowsFormsApplicationBase 并从它继承我们的实例管理器,然后利用属性和事件来处理单个实例。

public class SingleInstanceManager : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
{
    private SingleInstanceApplication _application;
    private System.Collections.ObjectModel.ReadOnlyCollection<string> _commandLine;

    public SingleInstanceManager()
    {
        IsSingleInstance = true;
    }

    protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
    {
        // First time _application is launched
        _commandLine = eventArgs.CommandLine;
        _application = new SingleInstanceApplication();
        _application.Run();
        return false;
    }

    protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
    {
        // Subsequent launches
        base.OnStartupNextInstance(eventArgs);
        _commandLine = eventArgs.CommandLine;
        _application.Activate();
    }
}

基本上,我们使用 VB 位来检测单个实例并进行相应的处理。OnStartup 将在第一个实例加载时触发。再次重新运行应用程序时会触发 OnStartupNextInstance。如您所见,我可以通过事件参数获取命令行上传递的内容。我将值设置为实例字段。您可以在此处解析命令行,也可以通过构造函数和对 Activate 方法的调用将其传递给您的应用程序。

第三,是时候创建我们的入口点了。我们将利用我们的 SingleInstanceManager,而不是像通常那样更新应用程序。

public class EntryPoint
{
    [STAThread]
    public static void Main(string[] args)
    {
        SingleInstanceManager manager = new SingleInstanceManager();
        manager.Run(args);
    }
}

好吧,我希望您能够遵循一切并能够使用此实现并使其成为您自己的。

于 2008-08-21T04:17:57.887 回答
90

这里

跨进程互斥锁的一个常见用途是确保一次只能运行程序的实例。这是如何完成的:

class OneAtATimePlease {

  // Use a name unique to the application (eg include your company URL)
  static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");

  static void Main()
  {
    // Wait 5 seconds if contended – in case another instance
    // of the program is in the process of shutting down.
    if (!mutex.WaitOne(TimeSpan.FromSeconds (5), false))
    {
        Console.WriteLine("Another instance of the app is running. Bye!");
        return;
    }

    try
    {    
        Console.WriteLine("Running - press Enter to exit");
        Console.ReadLine();
    }
    finally
    {
        mutex.ReleaseMutex();
    }    
  }    
}

Mutex 的一个很好的特性是,如果应用程序在没有首先调用 ReleaseMutex 的情况下终止,CLR 将自动释放 Mutex。

于 2008-08-21T00:47:28.983 回答
67

MSDN 实际上有一个 C# 和 VB 的示例应用程序可以做到这一点:http: //msdn.microsoft.com/en-us/library/ms771662 (v=VS.90).aspx

开发单实例检测的最常见和最可靠的技术是使用 Microsoft .NET Framework 远程处理基础结构 (System.Remoting)。Microsoft .NET Framework(2.0 版)包括一个类型 WindowsFormsApplicationBase,它封装了所需的远程处理功能。要将此类型合并到 WPF 应用程序中,需要从它派生一个类型,并将其用作应用程序静态入口点方法 Main 和 WPF 应用程序的 Application 类型之间的垫片。shim 检测应用程序何时首次启动,以及何时尝试后续启动,并控制 WPF 应用程序类型以确定如何处理启动。

  • 对于 C#,人们只需深吸一口气,然后忘记整个“我不想包含 VisualBasic DLL”。因为这个Scott Hanselman 所说的,以及这几乎是解决问题的最干净的方法,并且是由比你更了解框架的人设计的。
  • 从可用性的角度来看,事实是如果您的用户正在加载应用程序并且它已经打开并且您正在给他们一个错误消息,'Another instance of the app is running. Bye'那么他们将不会是一个非常快乐的用户。您只需(在 GUI 应用程序中)切换到该应用程序并传入提供的参数 - 或者如果命令行参数没有意义,那么您必须弹出可能已最小化的应用程序。

该框架已经对此提供了支持-只是某些白痴将DLL命名为Microsoft.VisualBasic它并没有被放入Microsoft.ApplicationUtils或类似的东西中。克服它 - 或打开反射器。

提示:如果您完全按原样使用此方法,并且您已经拥有一个包含资源等的 App.xaml,那么您也需要查看一下

于 2010-09-27T21:48:24.097 回答
25

此代码应转到 main 方法。在此处查看有关 WPF 中主要方法的更多信息。

[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);

private const int SW_SHOWMAXIMIZED = 3;

static void Main() 
{
    Process currentProcess = Process.GetCurrentProcess();
    var runningProcess = (from process in Process.GetProcesses()
                          where
                            process.Id != currentProcess.Id &&
                            process.ProcessName.Equals(
                              currentProcess.ProcessName,
                              StringComparison.Ordinal)
                          select process).FirstOrDefault();
    if (runningProcess != null)
    {
        ShowWindow(runningProcess.MainWindowHandle, SW_SHOWMAXIMIZED);
       return; 
    }
}

方法二

static void Main()
{
    string procName = Process.GetCurrentProcess().ProcessName;
    // get the list of all processes by that name

    Process[] processes=Process.GetProcessesByName(procName);

    if (processes.Length > 1)
    {
        MessageBox.Show(procName + " already running");  
        return;
    } 
    else
    {
        // Application.Run(...);
    }
}

注意:以上方法假定您的流程/应用程序具有唯一名称。因为它使用进程名称来查找是否有任何现有处理器。因此,如果您的应用程序有一个非常常见的名称(即:记事本),上述方法将不起作用。

于 2011-08-24T22:48:30.580 回答
20

好吧,我有一个一次性的类,可以轻松地用于大多数用例:

像这样使用它:

static void Main()
{
    using (SingleInstanceMutex sim = new SingleInstanceMutex())
    {
        if (sim.IsOtherInstanceRunning)
        {
            Application.Exit();
        }

        // Initialize program here.
    }
}

这里是:

/// <summary>
/// Represents a <see cref="SingleInstanceMutex"/> class.
/// </summary>
public partial class SingleInstanceMutex : IDisposable
{
    #region Fields

    /// <summary>
    /// Indicator whether another instance of this application is running or not.
    /// </summary>
    private bool isNoOtherInstanceRunning;

    /// <summary>
    /// The <see cref="Mutex"/> used to ask for other instances of this application.
    /// </summary>
    private Mutex singleInstanceMutex = null;

    /// <summary>
    /// An indicator whether this object is beeing actively disposed or not.
    /// </summary>
    private bool disposed;

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the <see cref="SingleInstanceMutex"/> class.
    /// </summary>
    public SingleInstanceMutex()
    {
        this.singleInstanceMutex = new Mutex(true, Assembly.GetCallingAssembly().FullName, out this.isNoOtherInstanceRunning);
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets an indicator whether another instance of the application is running or not.
    /// </summary>
    public bool IsOtherInstanceRunning
    {
        get
        {
            return !this.isNoOtherInstanceRunning;
        }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Closes the <see cref="SingleInstanceMutex"/>.
    /// </summary>
    public void Close()
    {
        this.ThrowIfDisposed();
        this.singleInstanceMutex.Close();
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            /* Release unmanaged ressources */

            if (disposing)
            {
                /* Release managed ressources */
                this.Close();
            }

            this.disposed = true;
        }
    }

    /// <summary>
    /// Throws an exception if something is tried to be done with an already disposed object.
    /// </summary>
    /// <remarks>
    /// All public methods of the class must first call this.
    /// </remarks>
    public void ThrowIfDisposed()
    {
        if (this.disposed)
        {
            throw new ObjectDisposedException(this.GetType().Name);
        }
    }

    #endregion
}
于 2010-02-19T10:39:42.000 回答
16

WPF Single Instance Application是一种使用 Mutex 和 IPC 的新东西,并且还将任何命令行参数传递给正在运行的实例。

于 2010-05-28T19:58:11.210 回答
13

作为标记答案的参考的代码C# .NET 单实例应用程序是一个很好的开始。

但是,我发现它不能很好地处理已经存在的实例打开模式对话框的情况,无论该对话框是托管对话框(如另一个窗体,如关于框),还是非托管对话框(如OpenFileDialog 即使使用标准的 .NET 类)。使用原始代码,主窗体被激活,但模态窗体保持不活动状态,这看起来很奇怪,而且用户必须单击它才能继续使用该应用程序。

因此,我创建了一个 SingleInstance 实用程序类来为 Winforms 和 WPF 应用程序自动处理所有这些。

窗体

1)像这样修改程序类:

static class Program
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(Program).FullName);

    [STAThread]
    static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

2)像这样修改主窗口类:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    protected override void WndProc(ref Message m)
    {
        // if needed, the singleton will restore this window
        Program.Singleton.OnWndProc(this, m, true);

        // TODO: handle specific messages here if needed
        base.WndProc(ref m);
    }
}

WPF:

1)像这样修改 App 页面(并确保将其构建操作设置为 page 以便能够重新定义 Main 方法):

public partial class App : Application
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(App).FullName);

    [STAThread]
    public static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        App app = new App();
        app.InitializeComponent();
        app.Run();
    }
}

2)像这样修改主窗口类:

public partial class MainWindow : Window
{
    private HwndSource _source;

    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        _source = (HwndSource)PresentationSource.FromVisual(this);
        _source.AddHook(HwndSourceHook);
    }

    protected virtual IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        // if needed, the singleton will restore this window
        App.Singleton.OnWndProc(hwnd, msg, wParam, lParam, true, true);

        // TODO: handle other specific message
        return IntPtr.Zero;
    }

这是实用程序类:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;

namespace SingleInstanceUtilities
{
    public sealed class SingleInstance
    {
        private const int HWND_BROADCAST = 0xFFFF;

        [DllImport("user32.dll")]
        private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern int RegisterWindowMessage(string message);

        [DllImport("user32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        public SingleInstance(string uniqueName)
        {
            if (uniqueName == null)
                throw new ArgumentNullException("uniqueName");

            Mutex = new Mutex(true, uniqueName);
            Message = RegisterWindowMessage("WM_" + uniqueName);
        }

        public Mutex Mutex { get; private set; }
        public int Message { get; private set; }

        public void RunFirstInstance(Action action)
        {
            RunFirstInstance(action, IntPtr.Zero, IntPtr.Zero);
        }

        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        public void RunFirstInstance(Action action, IntPtr wParam, IntPtr lParam)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            if (WaitForMutext(wParam, lParam))
            {
                try
                {
                    action();
                }
                finally
                {
                    ReleaseMutex();
                }
            }
        }

        public static void ActivateWindow(IntPtr hwnd)
        {
            if (hwnd == IntPtr.Zero)
                return;

            FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
        }

        public void OnWndProc(IntPtr hwnd, int m, IntPtr wParam, IntPtr lParam, bool restorePlacement, bool activate)
        {
            if (m == Message)
            {
                if (restorePlacement)
                {
                    WindowPlacement placement = WindowPlacement.GetPlacement(hwnd, false);
                    if (placement.IsValid && placement.IsMinimized)
                    {
                        const int SW_SHOWNORMAL = 1;
                        placement.ShowCmd = SW_SHOWNORMAL;
                        placement.SetPlacement(hwnd);
                    }
                }

                if (activate)
                {
                    SetForegroundWindow(hwnd);
                    FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
                }
            }
        }

#if WINFORMS // define this for Winforms apps
        public void OnWndProc(System.Windows.Forms.Form form, int m, IntPtr wParam, IntPtr lParam, bool activate)
        {
            if (form == null)
                throw new ArgumentNullException("form");

            if (m == Message)
            {
                if (activate)
                {
                    if (form.WindowState == System.Windows.Forms.FormWindowState.Minimized)
                    {
                        form.WindowState = System.Windows.Forms.FormWindowState.Normal;
                    }

                    form.Activate();
                    FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(form.Handle));
                }
            }
        }

        public void OnWndProc(System.Windows.Forms.Form form, System.Windows.Forms.Message m, bool activate)
        {
            if (form == null)
                throw new ArgumentNullException("form");

            OnWndProc(form, m.Msg, m.WParam, m.LParam, activate);
        }
#endif

        public void ReleaseMutex()
        {
            Mutex.ReleaseMutex();
        }

        public bool WaitForMutext(bool force, IntPtr wParam, IntPtr lParam)
        {
            bool b = PrivateWaitForMutext(force);
            if (!b)
            {
                PostMessage((IntPtr)HWND_BROADCAST, Message, wParam, lParam);
            }
            return b;
        }

        public bool WaitForMutext(IntPtr wParam, IntPtr lParam)
        {
            return WaitForMutext(false, wParam, lParam);
        }

        private bool PrivateWaitForMutext(bool force)
        {
            if (force)
                return true;

            try
            {
                return Mutex.WaitOne(TimeSpan.Zero, true);
            }
            catch (AbandonedMutexException)
            {
                return true;
            }
        }
    }

    // NOTE: don't add any field or public get/set property, as this must exactly map to Windows' WINDOWPLACEMENT structure
    [StructLayout(LayoutKind.Sequential)]
    public struct WindowPlacement
    {
        public int Length { get; set; }
        public int Flags { get; set; }
        public int ShowCmd { get; set; }
        public int MinPositionX { get; set; }
        public int MinPositionY { get; set; }
        public int MaxPositionX { get; set; }
        public int MaxPositionY { get; set; }
        public int NormalPositionLeft { get; set; }
        public int NormalPositionTop { get; set; }
        public int NormalPositionRight { get; set; }
        public int NormalPositionBottom { get; set; }

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool SetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool GetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);

        private const int SW_SHOWMINIMIZED = 2;

        public bool IsMinimized
        {
            get
            {
                return ShowCmd == SW_SHOWMINIMIZED;
            }
        }

        public bool IsValid
        {
            get
            {
                return Length == Marshal.SizeOf(typeof(WindowPlacement));
            }
        }

        public void SetPlacement(IntPtr windowHandle)
        {
            SetWindowPlacement(windowHandle, ref this);
        }

        public static WindowPlacement GetPlacement(IntPtr windowHandle, bool throwOnError)
        {
            WindowPlacement placement = new WindowPlacement();
            if (windowHandle == IntPtr.Zero)
                return placement;

            placement.Length = Marshal.SizeOf(typeof(WindowPlacement));
            if (!GetWindowPlacement(windowHandle, ref placement))
            {
                if (throwOnError)
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                return new WindowPlacement();
            }
            return placement;
        }
    }

    public static class FormUtilities
    {
        [DllImport("user32.dll")]
        private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetActiveWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern bool IsWindowVisible(IntPtr hWnd);

        [DllImport("kernel32.dll")]
        public static extern int GetCurrentThreadId();

        private delegate bool EnumChildrenCallback(IntPtr hwnd, IntPtr lParam);

        [DllImport("user32.dll")]
        private static extern bool EnumThreadWindows(int dwThreadId, EnumChildrenCallback lpEnumFunc, IntPtr lParam);

        private class ModalWindowUtil
        {
            private const int GW_OWNER = 4;
            private int _maxOwnershipLevel;
            private IntPtr _maxOwnershipHandle;

            private bool EnumChildren(IntPtr hwnd, IntPtr lParam)
            {
                int level = 1;
                if (IsWindowVisible(hwnd) && IsOwned(lParam, hwnd, ref level))
                {
                    if (level > _maxOwnershipLevel)
                    {
                        _maxOwnershipHandle = hwnd;
                        _maxOwnershipLevel = level;
                    }
                }
                return true;
            }

            private static bool IsOwned(IntPtr owner, IntPtr hwnd, ref int level)
            {
                IntPtr o = GetWindow(hwnd, GW_OWNER);
                if (o == IntPtr.Zero)
                    return false;

                if (o == owner)
                    return true;

                level++;
                return IsOwned(owner, o, ref level);
            }

            public static void ActivateWindow(IntPtr hwnd)
            {
                if (hwnd != IntPtr.Zero)
                {
                    SetActiveWindow(hwnd);
                }
            }

            public static IntPtr GetModalWindow(IntPtr owner)
            {
                ModalWindowUtil util = new ModalWindowUtil();
                EnumThreadWindows(GetCurrentThreadId(), util.EnumChildren, owner);
                return util._maxOwnershipHandle; // may be IntPtr.Zero
            }
        }

        public static void ActivateWindow(IntPtr hwnd)
        {
            ModalWindowUtil.ActivateWindow(hwnd);
        }

        public static IntPtr GetModalWindow(IntPtr owner)
        {
            return ModalWindowUtil.GetModalWindow(owner);
        }
    }
}
于 2013-05-13T12:40:03.607 回答
11

这是一个允许您拥有应用程序的单个实例的示例。当任何新实例加载时,它们会将其参数传递给正在运行的主实例。

public partial class App : Application
{
    private static Mutex SingleMutex;
    public static uint MessageId;

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        IntPtr Result;
        IntPtr SendOk;
        Win32.COPYDATASTRUCT CopyData;
        string[] Args;
        IntPtr CopyDataMem;
        bool AllowMultipleInstances = false;

        Args = Environment.GetCommandLineArgs();

        // TODO: Replace {00000000-0000-0000-0000-000000000000} with your application's GUID
        MessageId   = Win32.RegisterWindowMessage("{00000000-0000-0000-0000-000000000000}");
        SingleMutex = new Mutex(false, "AppName");

        if ((AllowMultipleInstances) || (!AllowMultipleInstances && SingleMutex.WaitOne(1, true)))
        {
            new Main();
        }
        else if (Args.Length > 1)
        {
            foreach (Process Proc in Process.GetProcesses())
            {
                SendOk = Win32.SendMessageTimeout(Proc.MainWindowHandle, MessageId, IntPtr.Zero, IntPtr.Zero,
                    Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                    2000, out Result);

                if (SendOk == IntPtr.Zero)
                    continue;
                if ((uint)Result != MessageId)
                    continue;

                CopyDataMem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.COPYDATASTRUCT)));

                CopyData.dwData = IntPtr.Zero;
                CopyData.cbData = Args[1].Length*2;
                CopyData.lpData = Marshal.StringToHGlobalUni(Args[1]);

                Marshal.StructureToPtr(CopyData, CopyDataMem, false);

                Win32.SendMessageTimeout(Proc.MainWindowHandle, Win32.WM_COPYDATA, IntPtr.Zero, CopyDataMem,
                    Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                    5000, out Result);

                Marshal.FreeHGlobal(CopyData.lpData);
                Marshal.FreeHGlobal(CopyDataMem);
            }

            Shutdown(0);
        }
    }
}

public partial class Main : Window
{
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        HwndSource Source;

        Source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
        Source.AddHook(new HwndSourceHook(Window_Proc));
    }

    private IntPtr Window_Proc(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam, ref bool Handled)
    {
        Win32.COPYDATASTRUCT CopyData;
        string Path;

        if (Msg == Win32.WM_COPYDATA)
        {
            CopyData = (Win32.COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.COPYDATASTRUCT));
            Path = Marshal.PtrToStringUni(CopyData.lpData, CopyData.cbData / 2);

            if (WindowState == WindowState.Minimized)
            {
                // Restore window from tray
            }

            // Do whatever we want with information

            Activate();
            Focus();
        }

        if (Msg == App.MessageId)
        {
            Handled = true;
            return new IntPtr(App.MessageId);
        }

        return IntPtr.Zero;
    }
}

public class Win32
{
    public const uint WM_COPYDATA = 0x004A;

    public struct COPYDATASTRUCT
    {
        public IntPtr dwData;
        public int    cbData;
        public IntPtr lpData;
    }

    [Flags]
    public enum SendMessageTimeoutFlags : uint
    {
        SMTO_NORMAL             = 0x0000,
        SMTO_BLOCK              = 0x0001,
        SMTO_ABORTIFHUNG        = 0x0002,
        SMTO_NOTIMEOUTIFNOTHUNG = 0x0008
    }

    [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
    public static extern uint RegisterWindowMessage(string lpString);
    [DllImport("user32.dll")]
    public static extern IntPtr SendMessageTimeout(
        IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam,
        SendMessageTimeoutFlags fuFlags, uint uTimeout, out IntPtr lpdwResult);
}
于 2011-05-07T08:17:44.093 回答
10

只是一些想法:在某些情况下,要求只有一个应用程序实例不像某些人所相信的那样“蹩脚”。如果允许单个用户使用多个应用程序实例来访问数据库,那么数据库应用程序等会困难一个数量级(您知道,所有这些都会更新在用户的应用程序的多个实例中打开的所有记录)机等)。首先,对于“名称冲突,不要使用人类可读的名称 - 使用 GUID 代替,或者更好的是 GUID + 人类可读的名称。名称冲突的可能性刚刚从雷达上消失,互斥体不在乎. 正如有人指出的那样,DOS 攻击会很糟糕,但如果恶意者不厌其烦地获取互斥锁名称并将其合并到他们的应用程序中,无论如何,您几乎都是目标,并且必须做更多的事情来保护自己,而不仅仅是摆弄一个互斥锁名称。此外,如果使用以下变体:new Mutex(true, "some GUID plus Name", out AIsFirstInstance),您已经知道 Mutex 是否是第一个实例。

于 2009-12-17T21:25:51.297 回答
8

以下代码是我注册单实例应用程序的 WCF 命名管道解决方案。这很好,因为它还会在另一个实例尝试启动时引发一个事件,并接收另一个实例的命令行。

它面向 WPF,因为它使用System.Windows.StartupEventHandler类,但这很容易修改。

此代码需要引用PresentationFrameworkSystem.ServiceModel

用法:

class Program
{
    static void Main()
    {
        var applicationId = new Guid("b54f7b0d-87f9-4df9-9686-4d8fd76066dc");

        if (SingleInstanceManager.VerifySingleInstance(applicationId))
        {
            SingleInstanceManager.OtherInstanceStarted += OnOtherInstanceStarted;

            // Start the application
        }
    }

    static void OnOtherInstanceStarted(object sender, StartupEventArgs e)
    {
        // Do something in response to another instance starting up.
    }
}

源代码:

/// <summary>
/// A class to use for single-instance applications.
/// </summary>
public static class SingleInstanceManager
{
  /// <summary>
  /// Raised when another instance attempts to start up.
  /// </summary>
  public static event StartupEventHandler OtherInstanceStarted;

  /// <summary>
  /// Checks to see if this instance is the first instance running on this machine.  If it is not, this method will
  /// send the main instance this instance's startup information.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  /// <returns>True if this instance is the main instance.</returns>
  public static bool VerifySingleInstace(Guid guid)
  {
    if (!AttemptPublishService(guid))
    {
      NotifyMainInstance(guid);

      return false;
    }

    return true;
  }

  /// <summary>
  /// Attempts to publish the service.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  /// <returns>True if the service was published successfully.</returns>
  private static bool AttemptPublishService(Guid guid)
  {
    try
    {
      ServiceHost serviceHost = new ServiceHost(typeof(SingleInstance));
      NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
      serviceHost.AddServiceEndpoint(typeof(ISingleInstance), binding, CreateAddress(guid));
      serviceHost.Open();

      return true;
    }
    catch
    {
      return false;
    }
  }

  /// <summary>
  /// Notifies the main instance that this instance is attempting to start up.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  private static void NotifyMainInstance(Guid guid)
  {
    NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
    EndpointAddress remoteAddress = new EndpointAddress(CreateAddress(guid));
    using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(binding, remoteAddress))
    {
      ISingleInstance singleInstance = factory.CreateChannel();
      singleInstance.NotifyMainInstance(Environment.GetCommandLineArgs());
    }
  }

  /// <summary>
  /// Creates an address to publish/contact the service at based on a globally unique identifier.
  /// </summary>
  /// <param name="guid">The identifier for the application.</param>
  /// <returns>The address to publish/contact the service.</returns>
  private static string CreateAddress(Guid guid)
  {
    return string.Format(CultureInfo.CurrentCulture, "net.pipe://localhost/{0}", guid);
  }

  /// <summary>
  /// The interface that describes the single instance service.
  /// </summary>
  [ServiceContract]
  private interface ISingleInstance
  {
    /// <summary>
    /// Notifies the main instance that another instance of the application attempted to start.
    /// </summary>
    /// <param name="args">The other instance's command-line arguments.</param>
    [OperationContract]
    void NotifyMainInstance(string[] args);
  }

  /// <summary>
  /// The implementation of the single instance service interface.
  /// </summary>
  private class SingleInstance : ISingleInstance
  {
    /// <summary>
    /// Notifies the main instance that another instance of the application attempted to start.
    /// </summary>
    /// <param name="args">The other instance's command-line arguments.</param>
    public void NotifyMainInstance(string[] args)
    {
      if (OtherInstanceStarted != null)
      {
        Type type = typeof(StartupEventArgs);
        ConstructorInfo constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
        StartupEventArgs e = (StartupEventArgs)constructor.Invoke(null);
        FieldInfo argsField = type.GetField("_args", BindingFlags.Instance | BindingFlags.NonPublic);
        Debug.Assert(argsField != null);
        argsField.SetValue(e, args);

        OtherInstanceStarted(null, e);
      }
    }
  }
}
于 2013-04-19T05:38:40.217 回答
6

对于这样一个看似简单的问题,有这么多答案。在这里稍微改变一下是我对这个问题的解决方案。

创建 Mutex 可能很麻烦,因为 JIT-er 只看到您在一小部分代码中使用它,并希望将其标记为准备好进行垃圾回收。它非常想超越您认为您不会长时间使用该互斥锁的智能。实际上,只要您的应用程序正在运行,您就想一直挂在这个 Mutex 上。告诉垃圾收集器不要打扰你 Mutex 的最好方法是告诉它在不同代的车库收集中保持它的活力。例子:

var m = new Mutex(...);
...
GC.KeepAlive(m);

我从这个页面提出了这个想法:http ://www.ai.uga.edu/~mc/SingleInstance.html

于 2010-05-05T00:54:24.510 回答
6

看起来有一个非常好的方法来处理这个:

WPF 单实例应用程序

这提供了一个您可以添加的类,该类管理所有互斥锁和消息传递,以简化您的实现,使其变得微不足道。

于 2011-08-11T17:11:57.043 回答
5

看下面的代码。它是防止 WPF 应用程序的多个实例的一个伟大而简单的解决方案。

private void Application_Startup(object sender, StartupEventArgs e)
{
    Process thisProc = Process.GetCurrentProcess();
    if (Process.GetProcessesByName(thisProc.ProcessName).Length > 1)
    {
        MessageBox.Show("Application running");
        Application.Current.Shutdown();
        return;
    }

    var wLogin = new LoginWindow();

    if (wLogin.ShowDialog() == true)
    {
        var wMain = new Main();
        wMain.WindowState = WindowState.Maximized;
        wMain.Show();
    }
    else
    {
        Application.Current.Shutdown();
    }
}
于 2012-11-30T13:37:02.957 回答
4

您永远不应该使用命名互斥锁来实现单实例应用程序(或至少不用于生产代码)。恶意代码很容易 DoS(拒绝服务)你的屁股......

于 2009-01-26T20:27:14.963 回答
4

这是我使用的。它结合了进程枚举来执行切换和互斥锁以防止“活跃的点击者”:

public partial class App
{
    [DllImport("user32")]
    private static extern int OpenIcon(IntPtr hWnd);

    [DllImport("user32.dll")]
    private static extern bool SetForegroundWindow(IntPtr hWnd);

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        var p = Process
           .GetProcessesByName(Process.GetCurrentProcess().ProcessName);
            foreach (var t in p.Where(t => t.MainWindowHandle != IntPtr.Zero))
            {
                OpenIcon(t.MainWindowHandle);
                SetForegroundWindow(t.MainWindowHandle);
                Current.Shutdown();
                return;
            }

            // there is a chance the user tries to click on the icon repeatedly
            // and the process cannot be discovered yet
            bool createdNew;
            var mutex = new Mutex(true, "MyAwesomeApp", 
               out createdNew);  // must be a variable, though it is unused - 
            // we just need a bit of time until the process shows up
            if (!createdNew)
            {
                Current.Shutdown();
                return;
            }

            new Bootstrapper().Run();
        }
    }
于 2010-06-22T00:04:21.567 回答
4

我找到了更简单的解决方案,类似于 Dale Ragan 的解决方案,但稍作修改。它几乎可以完成您需要的一切,并且基于标准的 Microsoft WindowsFormsApplicationBase 类。

首先,您创建 SingleInstanceController 类,您可以在所有其他使用 Windows 窗体的单实例应用程序中使用该类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualBasic.ApplicationServices;


namespace SingleInstanceController_NET
{
    public class SingleInstanceController
    : WindowsFormsApplicationBase
    {
        public delegate Form CreateMainForm();
        public delegate void StartNextInstanceDelegate(Form mainWindow);
        CreateMainForm formCreation;
        StartNextInstanceDelegate onStartNextInstance;
        public SingleInstanceController(CreateMainForm formCreation, StartNextInstanceDelegate onStartNextInstance)
        {
            // Set whether the application is single instance
            this.formCreation = formCreation;
            this.onStartNextInstance = onStartNextInstance;
            this.IsSingleInstance = true;

            this.StartupNextInstance += new StartupNextInstanceEventHandler(this_StartupNextInstance);                      
        }

        void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
        {
            if (onStartNextInstance != null)
            {
                onStartNextInstance(this.MainForm); // This code will be executed when the user tries to start the running program again,
                                                    // for example, by clicking on the exe file.
            }                                       // This code can determine how to re-activate the existing main window of the running application.
        }

        protected override void OnCreateMainForm()
        {
            // Instantiate your main application form
            this.MainForm = formCreation();
        }

        public void Run()
        {
            string[] commandLine = new string[0];
            base.Run(commandLine);
        }
    }
}

然后你可以在你的程序中使用它,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using SingleInstanceController_NET;

namespace SingleInstance
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static Form CreateForm()
        {
            return new Form1(); // Form1 is used for the main window.
        }

        static void OnStartNextInstance(Form mainWindow) // When the user tries to restart the application again,
                                                         // the main window is activated again.
        {
            mainWindow.WindowState = FormWindowState.Maximized;
        }
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);            
            SingleInstanceController controller = new SingleInstanceController(CreateForm, OnStartNextInstance);
            controller.Run();         
        }
    }
}

程序和 SingleInstanceController_NET 解决方案都应该引用 Microsoft.VisualBasic 。如果您只是想在用户尝试重新启动正在运行的程序时将正在运行的应用程序重新激活为普通窗口,则 SingleInstanceController 中的第二个参数可以为空。在给定的示例中,窗口被最大化。

于 2011-04-04T09:06:08.060 回答
4

2017 年 1 月 25 日更新。在尝试了几件事之后,我决定使用 VisualBasic.dll,它更容易并且效果更好(至少对我而言)。我让我之前的答案作为参考......

作为参考,这就是我在不传递参数的情况下所做的(我找不到任何这样做的理由......我的意思是一个应用程序,其参数从一个实例传递到另一个实例)。如果需要文件关联,则应(根据用户的标准期望)为每个文档实例化一个应用程序。如果您必须将参数传递给现有应用程序,我想我会使用 vb dll。

不传递 args(只是单实例应用程序),我不喜欢注册新的 Window 消息并且不覆盖 Matt Davis 解决方案中定义的消息循环。虽然添加一个 VisualBasic dll 没什么大不了的,但我不喜欢添加一个新的引用只是为了做单实例应用程序。此外,我更喜欢使用 Main 实例化一个新类,而不是从 App.Startup 覆盖调用 Shutdown 以确保尽快退出。

希望任何人都会喜欢它......或者会激发一点:-)

项目启动类应设置为“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);
                        }
                    }
                }
            }
        }
    }
}

窗口助手:

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);
于 2013-12-05T23:00:54.160 回答
3

虽然不使用 Mutex,但简单的答案:

System.Diagnostics;    
...
string thisprocessname = Process.GetCurrentProcess().ProcessName;

if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;

把它放在里面Program.Main()
示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;

namespace Sample
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //simple add Diagnostics namespace, and these 3 lines below 
            string thisprocessname = Process.GetCurrentProcess().ProcessName;
            if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Sample());
        }
    }
}

您可以添加MessageBox.Showif-statement 并输入“应用程序已在运行”。
这可能对某人有帮助。

于 2016-12-30T04:30:21.317 回答
3

[我在下面提供了控制台和 wpf 应用程序的示例代码。]

createdNew在创建命名的 Mutex 实例后,您只需检查变量的值(下面的示例!)。

布尔值createdNew将返回false:

如果已在系统某处创建名为“YourApplicationNameHere”的 Mutex 实例

布尔值createdNew将返回true:

如果这是系统上第一个名为“YourApplicationNameHere”的互斥锁。


控制台应用程序 - 示例:

static Mutex m = null;

static void Main(string[] args)
{
    const string mutexName = "YourApplicationNameHere";
    bool createdNew = false;

    try
    {
        // Initializes a new instance of the Mutex class with a Boolean value that indicates 
        // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
        // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.

        using (m = new Mutex(true, mutexName, out createdNew))
        {
            if (!createdNew)
            {
                Console.WriteLine("instance is alreday running... shutting down !!!");
                Console.Read();
                return; // Exit the application
            }

            // Run your windows forms app here
            Console.WriteLine("Single instance app is running!");
            Console.ReadLine();
        }


    }
    catch (Exception ex)
    {

        Console.WriteLine(ex.Message);
        Console.ReadLine();
    }
}

WPF-示例:

public partial class App : Application
{
static Mutex m = null;

protected override void OnStartup(StartupEventArgs e)
{

    const string mutexName = "YourApplicationNameHere";
    bool createdNew = false;

    try
    {
        // Initializes a new instance of the Mutex class with a Boolean value that indicates 
        // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
        // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.

        m = new Mutex(true, mutexName, out createdNew);

        if (!createdNew)
        {
            Current.Shutdown(); // Exit the application
        }

    }
    catch (Exception)
    {
        throw;
    }

    base.OnStartup(e);
}


protected override void OnExit(ExitEventArgs e)
{
    if (m != null)
    {
        m.Dispose();
    }
    base.OnExit(e);
}
}
于 2017-12-16T19:01:25.390 回答
2

我向 NativeMethods 类添加了一个 sendMessage 方法。

显然 postmessage 方法有效,如果应用程序没有显示在任务栏中,但是使用 sendmessage 方法可以解决这个问题。

class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}
于 2015-01-26T08:35:05.830 回答
2

基于命名互斥锁的方法不是跨平台的,因为命名互斥锁在 Mono 中不是全局的。基于进程枚举的方法没有任何同步并且可能导致不正确的行为(例如,同时启动的多个进程可能都根据时间自行终止)。基于窗口系统的方法在控制台应用程序中是不可取的。该解决方案建立在 Divin 的回答之上,解决了所有这些问题:

using System;
using System.IO;

namespace TestCs
{
    public class Program
    {
        // The app id must be unique. Generate a new guid for your application. 
        public static string AppId = "01234567-89ab-cdef-0123-456789abcdef";

        // The stream is stored globally to ensure that it won't be disposed before the application terminates.
        public static FileStream UniqueInstanceStream;

        public static int Main(string[] args)
        {
            EnsureUniqueInstance();

            // Your code here.

            return 0;
        }

        private static void EnsureUniqueInstance()
        {
            // Note: If you want the check to be per-user, use Environment.SpecialFolder.ApplicationData instead.
            string lockDir = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                "UniqueInstanceApps");
            string lockPath = Path.Combine(lockDir, $"{AppId}.unique");

            Directory.CreateDirectory(lockDir);

            try
            {
                // Create the file with exclusive write access. If this fails, then another process is executing.
                UniqueInstanceStream = File.Open(lockPath, FileMode.Create, FileAccess.Write, FileShare.None);

                // Although only the line above should be sufficient, when debugging with a vshost on Visual Studio
                // (that acts as a proxy), the IO exception isn't passed to the application before a Write is executed.
                UniqueInstanceStream.Write(new byte[] { 0 }, 0, 1);
                UniqueInstanceStream.Flush();
            }
            catch
            {
                throw new Exception("Another instance of the application is already running.");
            }
        }
    }
}
于 2017-08-20T05:28:30.240 回答
2

我在这里找不到简短的解决方案,所以我希望有人会喜欢这个:

2018 年 9 月 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());
}
于 2018-04-12T12:43:58.117 回答
2

我在我的解决方案中使用Mutex来防止多个实例。

static Mutex mutex = null;
//A string that is the name of the mutex
string mutexName = @"Global\test";
//Prevent Multiple Instances of Application
bool onlyInstance = false;
mutex = new Mutex(true, mutexName, out onlyInstance);

if (!onlyInstance)
{
  MessageBox.Show("You are already running this application in your system.", "Already Running..", MessageBoxButton.OK);
  Application.Current.Shutdown();
}
于 2019-07-22T07:18:54.817 回答
1

使用互斥锁解决方案:

using System;
using System.Windows.Forms;
using System.Threading;

namespace OneAndOnlyOne
{
static class Program
{
    static String _mutexID = " // generate guid"
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        Boolean _isNotRunning;
        using (Mutex _mutex = new Mutex(true, _mutexID, out _isNotRunning))
        {
            if (_isNotRunning)
            {
                Application.Run(new Form1());
            }
            else
            {
                MessageBox.Show("An instance is already running.");
                return;
            }
        }
    }
}
}
于 2013-10-19T20:23:46.310 回答
1

这是我使用的一个轻量级解决方案,它允许应用程序将已经存在的窗口带到前台,而无需求助于自定义窗口消息或盲目搜索进程名称。

[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;
}

编辑:您还可以静态存储和初始化 mutex 和 createdNew,但是一旦完成,您需要显式处置/释放 mutex。就个人而言,我更喜欢将互斥锁保持在本地,因为即使应用程序在未到达 Main 末尾的情况下关闭,它也会自动处理掉。

于 2013-11-29T04:42:03.510 回答
1

这是通过 Event 实现的相同的东西。

public enum ApplicationSingleInstanceMode
{
    CurrentUserSession,
    AllSessionsOfCurrentUser,
    Pc
}

public class ApplicationSingleInstancePerUser: IDisposable
{
    private readonly EventWaitHandle _event;

    /// <summary>
    /// Shows if the current instance of ghost is the first
    /// </summary>
    public bool FirstInstance { get; private set; }

    /// <summary>
    /// Initializes 
    /// </summary>
    /// <param name="applicationName">The application name</param>
    /// <param name="mode">The single mode</param>
    public ApplicationSingleInstancePerUser(string applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession)
    {
        string name;
        if (mode == ApplicationSingleInstanceMode.CurrentUserSession)
            name = $"Local\\{applicationName}";
        else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
            name = $"Global\\{applicationName}{Environment.UserDomainName}";
        else
            name = $"Global\\{applicationName}";

        try
        {
            bool created;
            _event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
            FirstInstance = created;
        }
        catch
        {
        }
    }

    public void Dispose()
    {
        _event.Dispose();
    }
}
于 2016-07-11T11:56:17.780 回答
1

这就是我最终处理这个问题的方式。请注意,调试代码仍然在那里进行测试。此代码位于 App.xaml.cs 文件的 OnStartup 中。(WPF)

        // Process already running ? 
        if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1)
        {

            // Show your error message
            MessageBox.Show("xxx is already running.  \r\n\r\nIf the original process is hung up you may need to restart your computer, or kill the current xxx process using the task manager.", "xxx is already running!", MessageBoxButton.OK, MessageBoxImage.Exclamation);

            // This process 
            Process currentProcess = Process.GetCurrentProcess();

            // Get all processes running on the local computer.
            Process[] localAll = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);

            // ID of this process... 
            int temp = currentProcess.Id;
            MessageBox.Show("This Process ID:  " + temp.ToString());

            for (int i = 0; i < localAll.Length; i++)
            {
                // Find the other process 
                if (localAll[i].Id != currentProcess.Id)
                {
                    MessageBox.Show("Original Process ID (Switching to):  " + localAll[i].Id.ToString());

                    // Switch to it... 
                    SetForegroundWindow(localAll[i].MainWindowHandle);

                }
            }

            Application.Current.Shutdown();

        }

这可能有我还没有发现的问题。如果我遇到任何我会更新我的答案。

于 2016-08-09T14:35:08.073 回答
1

C# Winforms 的省时解决方案...

程序.cs:

using System;
using System.Windows.Forms;
// needs reference to Microsoft.VisualBasic
using Microsoft.VisualBasic.ApplicationServices;  

namespace YourNamespace
{
    public class SingleInstanceController : WindowsFormsApplicationBase
    {
        public SingleInstanceController()
        {
            this.IsSingleInstance = true;
        }

        protected override void OnStartupNextInstance(StartupNextInstanceEventArgs e)
        {
            e.BringToForeground = true;
            base.OnStartupNextInstance(e);
        }

        protected override void OnCreateMainForm()
        {
            this.MainForm = new Form1();
        }
    }

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            string[] args = Environment.GetCommandLineArgs();
            SingleInstanceController controller = new SingleInstanceController();
            controller.Run(args);
        }
    }
}
于 2018-02-07T06:26:42.850 回答
1

请从此处检查建议的解决方案,该解决方案使用信号量来确定现有实例是否已经在运行,适用于 WPF 应用程序,并且可以使用 TcpListener 和 TcpClient 将参数从第二个实例传递到第一个已经运行的实例:

它也适用于 .NET Core,不仅适用于 .NET Framework。

于 2020-01-22T00:43:41.453 回答
0

通常,这是我用于单实例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
}
于 2013-01-15T12:20:54.640 回答
0

您还可以使用免费的工具集CodeFluent Runtime。它提供了一个SingleInstance类来实现单实例应用程序。

于 2014-09-15T08:54:54.023 回答
0

这是一个解决方案:

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
于 2016-07-05T20:55:52.760 回答
0

这是我的 2 美分

 static class Program
    {
        [STAThread]
        static void Main()
        {
            bool createdNew;
            using (new Mutex(true, "MyApp", out createdNew))
            {
                if (createdNew) {
                    Application.EnableVisualStyles();
                    Application.SetCompatibleTextRenderingDefault(false);
                    var mainClass = new SynGesturesLogic();
                    Application.ApplicationExit += mainClass.tray_exit;
                    Application.Run();
                }
                else
                {
                    var current = Process.GetCurrentProcess();
                    foreach (var process in Process.GetProcessesByName(current.ProcessName).Where(process => process.Id != current.Id))
                    {
                        NativeMethods.SetForegroundWindow(process.MainWindowHandle);
                        break;
                    }
                }
            }
        }
    }
于 2016-08-26T07:11:53.400 回答
0

如果从其他路径调用 exe,我喜欢允许多个实例的解决方案。我修改了CharithJ解决方案方法1:

   static class Program {
    [DllImport("user32.dll")]
    private static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
    [DllImport("User32.dll")]
    public static extern Int32 SetForegroundWindow(IntPtr hWnd);
    [STAThread]
    static void Main() {
        Process currentProcess = Process.GetCurrentProcess();
        foreach (var process in Process.GetProcesses()) {
            try {
                if ((process.Id != currentProcess.Id) && 
                    (process.ProcessName == currentProcess.ProcessName) &&
                    (process.MainModule.FileName == currentProcess.MainModule.FileName)) {
                    ShowWindow(process.MainWindowHandle, 5); // const int SW_SHOW = 5; //Activates the window and displays it in its current size and position. 
                    SetForegroundWindow(process.MainWindowHandle);
                    return;
                }
            } catch (Exception ex) {
                //ignore Exception "Access denied "
            }
        }

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}
于 2016-09-29T16:51:23.127 回答
0

简单地使用 a StreamWriter,这个怎么样?

System.IO.File.StreamWriter OpenFlag = null;   //globally

try
{
    OpenFlag = new StreamWriter(Path.GetTempPath() + "OpenedIfRunning");
}
catch (System.IO.IOException) //file in use
{
    Environment.Exit(0);
}
于 2017-01-11T19:49:15.867 回答
0

我最喜欢的解决方案来自 MVP Daniel Vaughan: Enforcing Single Instance Wpf Applications

它使用 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;
    }
}
于 2020-07-03T09:45:26.127 回答
-1

基于马特戴维斯的回答,为方便起见,将其封装在一个类中。

public static class SingleAppInstanceChecker
{
    /// <summary>
    /// Arbitrary unique string
    /// </summary>
    private static Mutex _mutex = new Mutex(true, "0d12ad74-026f-40c3-bdae-e178ddee8602");

    public static bool IsNotRunning()
    {
        return _mutex.WaitOne(TimeSpan.Zero, true);
    }
}

示例用法:

private void Application_Startup(object sender, StartupEventArgs e)
{
    if (!SingleAppInstanceChecker.IsNotRunning())
    {
        MessageBox.Show("Application is already running.");

        // Exit application using:
        // Environment.Exit(1);
        // Application.Current.Shutdown();
        // Etc...

        return;
    }
    
    // Allow startup and continue with normal processing
    // ...
}
于 2021-07-01T17:59:47.173 回答