创建单实例WPF应用程序的正确方法是什么?


656

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

我知道它与某种称为互斥体的神话事物有关,我很少能找到一个烦人的人来阻止并解释其中的一个。

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


14
当应用程序终止时,CLR不会自动释放任何未发布的互斥体吗?
Cocowalla

1
@Cocowalla:终结器应该处理非托管的互斥锁,除非它不知道该互斥锁是由托管的应用程序创建还是附加到现有的互斥锁。
伊格纳西奥·索勒·加西亚

仅拥有一个应用程序实例是合理的。但是,将参数传递给已经存在的应用程序对我来说似乎有点愚蠢。我看不出这样做的任何理由。如果将应用程序与文件扩展名相关联,则应打开与用户要打开文档数量一样多的应用程序。这是每个用户都会期望的标准行为。
埃里克·厄勒

9
@Cocowalla CLR不管理本机资源。但是,如果进程终止,则系统(操作系统,而不是CLR)释放所有句柄。
IInspectable 2014年

1
我更喜欢@huseyint的答案。它使用Microsoft自己的“ SingleInstance.cs”类,因此您不必担心Mutexes和IntPtrs。此外,不依赖VisualBasic(yuk)。见codereview.stackexchange.com/questions/20871/...更多...
Heliac

Answers:


537

这是有关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,以将其置于顶部。

我最终得到的是:

  • Program.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;
    }
}

5
基于此答案使用更少的代码和更少的库,并提供最高功能,我将使它成为新的公认答案。如果有人知道使用API​​将表格显示在顶部的更正确方法,请随时添加。
Nidonocu

11
@BlueRaja,您启动第一个应用程序实例。当启动第二个应用程序实例时,它将检测到另一个实例已在运行,并准备关闭。在这样做之前,它会向第一个实例发送“ SHOWME”本机消息,从而将第一个实例带到顶部。.NET中的事件不允许跨进程通信,这就是使用本机消息的原因。
马特·戴维斯

7
有没有办法从其他实例传递命令行?
gyurisc 2010年

22
@Nam,Mutex构造函数只需要一个字符串,因此您可以提供所需的任何字符串名称,例如“ This Is My Mutex”。因为'Mutex'是可用于其他进程的系统对象,所以您通常希望该名称唯一,以免与同一系统上的其他'Mutex'名称冲突。在本文中,看起来很神秘的字符串是“ Guid”。您可以通过调用以编程方式生成此代码System.Guid.NewGuid()。就本文而言,用户可能是通过Visual Studio生成的,如下所示: msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx
Matt Davis 2010年

6
互斥锁方法是否假定同一用户正在尝试再次启动该应用程序?在“切换用户”之后,毫无疑问地将“应用程序的现有实例放到前台”没有意义
dumbledad 2012年

107

您可以使用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方法的调用将其传递给应用程序。

第三,是时候创建我们的EntryPoint了。无需像通常那样更新应用程序,我们将利用SingleInstanceManager。

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

好吧,我希望您能够了解所有内容并能够使用此实现并将其实现为自己的实现。


9
我会坚持使用互斥锁解决方案,因为它与表单无关。
Steven Sudit

1
我之所以使用它,是因为我对其他方法有疑问,但是我很确定它在后台使用了远程处理。我的应用程序有两个相关问题-一些客户说,即使他们告知不要拨打电话,它也会尝试打电话给家里。当他们看起来更仔细时,连接到本地主机。不过,他们最初并不知道这一点。另外,我不能将远程处理用于其他目的(我认为吗?),因为远程处理已用于此目的。当我尝试互斥方法时,可以再次使用远程处理。
理查德·沃森

4
请原谅我,但是除非我缺少任何内容,否则您避免编写3行代码,而是重新使用框架以编写大量代码来做到这一点。那么节省的钱在哪里呢?
greenoldman 2011年

2
有可能在winforms中吗?
2012年

1
如果您没有在应用程序实例上调用InitializeComponent(),则将无法解析资源... _application = new SingleInstanceApplication(); _application.InitializeComponent(); _application.Run();
尼克,

84

这里

跨进程Mutex的常见用法是确保一次只能运行程序实例。这是完成的过程:

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。


5
我必须说,我很喜欢这个答案,而不是公认的答案,这仅仅是因为它不依赖WinForms。就我个人而言,我的大部分开发工作都已转移到WPF上,而我不想为此而引入WinForm库。
Switters,

5
当然,要获得完整答案,您还必须描述将参数传递给另一个实例的方法:)
Simon Buchan,

@Jason,很好,谢谢!但是我更喜欢不传递任何超时信息。它是如此主观,并且取决于许多变量。如果您想启动另一个应用程序,只需更快地释放互斥锁即可。.例如,一旦用户确认关闭,即可
Eric Ouellet 2013年

@EricOuellet:几乎每个带有选项卡的程序都可以执行此操作-Photoshop,Sublime Text,Chrome...。希望它也像新进程一样显示UI。
西蒙·布坎

@西蒙,你是对的。我只是问自己一个非常古老的问题... MDI与SDI(多文档界面与单文档界面)。当谈论标签时,指的是MDI。1998年,Microsoft的一本书建议淘汰所有MDI应用程序。Microsoft将Word,Excel ...切换为SDI,我认为它更简单,更好。我了解到Chrome和其他产品(现在是IE)都希望重新使用MDI。我个人(无所事事/个人感觉)认为选择文件assoc时打开新应用还是更好的选择。但是我更好地理解了现在提出的问题。谢谢 !
Eric Ouellet 2013年

58

MSDN实际上为C#和VB都提供了一个示例应用程序来完全做到这一点:http : //msdn.microsoft.com/zh-cn/library/ms771662(v=VS.90).aspx

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

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

该框架已经对此提供了支持-只是有一个白痴将其命名为DLL Microsoft.VisualBasic,并且没有放入该框架中。Microsoft.ApplicationUtils或类似的支持。克服它-或打开Reflector。

提示:如果完全按原样使用此方法,并且已经具有包含资源等的App.xaml,那么您也将要对此进行研究


感谢您加入“也来看看”链接。那正是我所需要的。顺便说一句,链接中的解决方案3是最好的。
Eternal21 '11

我也是在可能的情况下委派该框架和特别设计的库的倡导者。
Eniola

23

此代码应转到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(...);
    }
}

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


1
另外,如果您的计算机上运行了其他任何同名程序,则此操作将无效。ProcessName返回可执行文件名减去exe。如果您创建一个名为“记事本”的应用程序,并且Windows记事本正在运行,它将在您的应用程序运行时检测到它。
Jcl 2015年

1
感谢您的回答。我发现了很多类似的问题,答案总是那么复杂和/或令人困惑,我发现它们毫无用处。该方法(方法1)简单明了,实际上,所有这些实际上都帮助我使代码运行。
ElDoRado1239

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
}

1
这个很容易上班。在更改Application.Exit()之前,它不会关闭第二个应用程序。简单的回报;但除此之外,它很棒。尽管我承认我将更仔细地研究以前的解决方案,因为它使用了界面。blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/...
HAL9000

15

WPF单实例应用程序是一种使用Mutex和IPC东西,并且还将任何命令行参数传递给正在运行的实例的新方法。


我成功地使用了它。如果将NamedPipes与此合并,则还可以将命令行参数传递给原始应用程序。类“ SingleInstance.cs”是由Microsoft编写的。我在CodeProject上的Arik Poznanski博客的可读性版本中添加了另一个链接。
Heliac

链接现在已断开。
Mike Lowery

11

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

但是,我发现当已经存在的实例打开一个模式对话框时,无论该对话框是托管对话框(如另一个窗体(如About框))还是非托管对话框(如即使使用标准的.NET类,也可以使用OpenFileDialog。使用原始代码,主窗体已激活,但模态窗体仍处于非活动状态,这看起来很奇怪,此外,用户必须单击它才能继续使用该应用程序。

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

Winforms

1)像这样修改Program类:

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

10

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

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

这是我该做什么的一个很好的例子。内森(Nathan),是否所有的参数都使用此方法发送?我的应用程序中有7个左右的代码,我认为这段代码可以工作。
2012年

1
在我的示例中,仅发送第一个参数,但是可以更改它以便所有参数都被发送。
内森·莫因瓦济里

8

只是有些想法:在某些情况下,仅要求应用程序的一个实例不像某些人所相信的那样是“ lam脚的”。如果一个数据库允许单个用户访问该应用程序的多个实例,那么数据库应用程序等的难度将增加一个数量级(您知道,所有这些操作都会更新在该用户的多个应用程序实例中打开的所有记录机器等)。首先,对于“名称冲突”,不要使用人类易读的名称-而是使用GUID,或者甚至更好的GUID +人类易读的名称。名称冲突的可能性刚刚被忽略,互斥体不在乎正如某人所指出的那样,DOS攻击会很糟糕,但是如果恶意软件人冒了获取互斥量名称并将其合并到其应用程序中的麻烦,无论如何,您几乎都是目标,并且除了保护互斥量名称外,还需要做更多的工作来保护自己。另外,如果使用以下变量:new Mutex(true,“ some GUID plus Name”,出自AIsFirstInstance),则您已经有了关于Mutex是否是第一个实例的指示符。


6

这个看似简单的问题有这么多答案。我只是想在这里稍作改动,这就是我对这个问题的解决方案。

创建Mutex可能会很麻烦,因为JIT-er仅看到您将其用于代码的一小部分,并希望将其标记为可进行垃圾回收。它非常想让您以为您不会再使用该Mutex这么长的时间了。实际上,只要您的应用程序运行,您就希望挂在此Mutex上。告诉垃圾收集器让您自己离开Mutex的最好方法是告诉它,使其在不同年代的车库收集中保持活力。例:

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

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


3
将其共享副本存储在应用程序类中会不会更容易?
rossisdead 2010年


6

以下代码是我的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);
      }
    }
  }
}

5

永远不要使用命名的互斥体来实现单实例应用程序(或者至少不用于生产代码)。恶意代码可以轻易地您的资产进行DoS(拒绝服务)...


8
“永远不要使用命名的互斥量”-永远不要说永不。如果我的计算机上正在运行恶意代码,则可能已经被我清除。

实际上,它甚至不必是恶意代码。可能只是偶然的名称冲突。
马特·戴维森

那你该怎么办
凯文·贝里奇

更好的问题是您想要该行为的可能原因是什么。不要将您的应用设计为单实例应用=)。我知道这是一个la脚的答案,但是从设计的角度来看,这几乎总是正确的答案。如果不了解有关该应用程序的更多信息,那么很难说更多。
马特·戴维森

2
至少在Windows下,互斥对象具有访问控制,因此一个人可以玩弄您的对象。至于名称冲突本身,这就是发明UUID / GUID的原因。
NuSkooler

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();
    }
}

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();
        }
    }

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中的第二个参数可以为null。在给定的示例中,窗口被最大化。


4

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

就像参考一样,这是我不传递参数的方式(我找不到任何理由……我的意思是单个应用程序的参数要从一个实例传递到另一个实例)。如果需要文件关联,则应(按用户标准期望)为每个文档实例化一个应用程序。如果您必须将args传递到现有应用程序,我想我会使用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);
                        }
                    }
                }
            }
        }
    }
}

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);

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并将“应用程序已在运行”。
这可能对某人有帮助。


4
如果两个进程同时开始,则它们都可能会看到两个活动进程并自行终止。
AT

@AT是的,这对于以管理员身份或其他
身份

如果制作应用程序副本并重命名,则可以同时运行原始副本和副本。
Dominique Bijnens

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.");
            }
        }
    }
}

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();
}

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

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

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



1

我向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);
}

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();
    }
}

1

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

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

布尔值createdNew将返回false:

如果系统上某处已经创建了名为“ YourApplicationNameHere”的互斥对象实例

布尔值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);
}
}

1

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

Program.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);
        }
    }
}

1

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

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


1

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

更新于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());
}

这将引入竞争条件。必须使用互斥锁。
georgiosd

1
无法保证如果您同时启动两个实例,它将可以正常工作。就像从两个不同的线程更新变量一样。棘手的冒险业务。用力,卢克:)
georgiosd

@georgiosd啊,我明白你的意思了。就像有人启动.exe并更改名称一样。是的,这将是启动它多次的一种方式,但是如果名称更改了,.exe通常将不起作用。我将更新我的回答^^谢谢Luke:D指出这一点:)
Deniz 18'9

1
不只是@Deniz。如果您真的非常快地启动了两个进程,那么有机会执行进程列表或获取它们的方法,而仍然只有一个进程出现。这可能是与您无关的
极端

@georgiosd你能证明吗?因为我只是为您测试了它,呵呵。但这对我来说是不可能的,甚至是“非常快”的!:P所以我不明白为什么您会以某种事实而不是甚至不喜欢这个无辜的代码
为生
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.