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

Nid*_*ocu 631 .net c# wpf mutex

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

我知道这与一些叫做互斥的神秘事物有关,我很少能找到一个不愿意停下来解释其中一个是什么的人.

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

Mat*_*vis 510

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

首先,它不需要依赖于Microsoft.VisualBasic程序集.如果我的项目已经依赖于该程序集,我可能会主张使用接受的答案中显示的方法.但事实上,我不使用Microsoft.VisualBasic程序集,我宁愿不为我的项目添加不必要的依赖项.

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


UPDATE

截至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]
    ...
}
Run Code Online (Sandbox Code Playgroud)

拥有一个命名的互斥锁允许我们跨多个线程和进程堆栈同步,这正是我正在寻找的魔力.

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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,如果我们的应用程序正在运行,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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 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);
}
Run Code Online (Sandbox Code Playgroud)
  • 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Nam,`Mutex`构造函数只需要一个字符串,因此你可以提供你想要的任何字符串名称,例如"This Is My Mutex".由于"互斥锁"是可供其他进程使用的系统对象,因此通常希望该名称是唯一的,因此它不会与同一系统上的其他"互斥锁"名称冲突.在文章中,看起来神秘的字符串是'Guid'.您可以通过调用`System.Guid.NewGuid()`以编程方式生成它.在文章的情况下,用户可能通过Visual Studio生成它,如下所示:http://msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx (17认同)
  • @BlueRaja,你启动了第一个应用实例.当您启动第二个应用程序实例时,它会检测到另一个实例已在运行并准备关闭.在此之前,它会向第一个实例发送"SHOWME"本机消息,这会将第一个实例带到顶部..NET中的事件不允许跨进程通信,这就是使用本机消息的原因. (9认同)
  • 有没有办法从其他实例传递命令行,也许? (7认同)
  • 互斥方法是否假设同一用户正在尝试再次启动应用程序?当然,在"切换用户"之后将"应用程序的现有实例放到前台"是没有意义的 (6认同)
  • 在此答案使用较少的代码和较少的库并提供顶级功能的基础上,我将使这成为新接受的答案.如果有人知道使用API​​将表单放到顶部更正确的方法,请随意添加. (3认同)
  • @matt david - 不要担心'运送'Microsoft.VisualBasic - 它已经在GAC中了.http://stackoverflow.com/questions/226517/is-the-microsoft-visualbasic-namespace-true-net-code (3认同)
  • @Matt Davis,你不能使用System.Guid.NewGuid(),因为每次你得到不同的GUID,实际上你的程序的每个实例都声称它是单个实例.您必须使用一些在编译时定义的所有实例共有的标记.VS生成GUID就是这样. (3认同)
  • 该问题要求提供 WPF 的方法,而不是答案建议的 WinForms。我将 [this answer](http://stackoverflow.com/a/5484315/433718) 用于 WPF 应用程序。它也作为答案发布在这里:http://stackoverflow.com/a/2932076/433718 (3认同)
  • 您可以添加有关互斥体的注释吗?看起来现在有 120 多个程序正在使用相同的互斥锁:https://github.com/search?q=8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F&type=Code (3认同)
  • @macias,我从未说过你会调用`System.Guid.NewGuid()`然后将结果传递给Mutex.你明智地指出,那显然是错的.问题是字符串来自哪里.我只是说可以通过编程方式或使用Visual Studio生成GUID. (2认同)
  • @OneWorld,这个解决方案与 UI 框架无关。该示例展示了如何使用 WinForms 来完成此操作,但在 WPF 中使用它也同样容易。您发布的解决方案使用 .NET Remoting,这是 Microsoft 保留的旧技术,仅用于向后兼容。这不是我会赞同的解决方案。http://msdn.microsoft.com/en-us/library/kwdt6w2k(v=vs.100).aspx (2认同)
  • 虽然这篇文章很老,回答@gyurisc可能会有助于未来的探险家.这个答案适用于实现单实例应用程序.我们的客户群有一段时间可以使用类似的解决方案.要将参数传递给已经运行的实例,我们可以简单地将参数写入文件并将其放在App的AppData中.哪个可以通过运行应用程序来获取.该文件的名称可能是DatetTime.Ticks,以确保获取最新的args,以防万一! (2认同)
  • 由于某种原因,如果你设置 `this.ShowInTaskbar = false` 这个方法将不起作用。它只是不会在 WndProc 方法中接收 WM_SHOWME 消息。 (2认同)
  • 您可以将“NamedPipe”与“NamedPipeClientStream”和“NamePipeServerStream”一起使用,而不是使用旧的本机消息系统。它更现代,允许您传递任何补充信息(例如要求活动实例退出的消息)。我不知道这种方法的性能成本,但我使用过它,没有发现问题。对于我的使用,我只需调用“npServer.WaitForConnection()”,然后立即关闭流以创建新的 NamedPipeServerStream 并等待其他通知。这是一个无限循环。 (2认同)
  • @Serg 我想我正在寻找“本地\”,因为我想限制每个会话,而不是计算机。不管怎么说,还是要谢谢你! (2认同)
  • 考虑在调用“WaitOne”时捕获“AbandonedMutexException”。这表明应用程序的前一个实例在没有释放互斥锁的情况下突然结束。您可能不在乎,因为它当前不再运行,可以继续启动一个新实例。 (2认同)
  • @MattDavis 问题是,如果人们在没有真正理解它的情况下复制和粘贴你的互斥体。任何使用此代码的人都应该以与您相同的方式生成自己的互斥体,这样它是唯一的,并且不会在同一系统上的其他程序中使用 (2认同)
  • @Tomas Zubiri 据我了解你的问题,不。当打开应用程序的每个实例时,它需要能够找到由第一个应用程序实例创建的互斥体。因此,为了使该解决方案发挥作用,必须修复名称。如果每个实例生成一个随机字符串,然后在该随机字符串上查找互斥体,则每个应用程序实例将创建一个新的互斥体,并且所有实例都将保持打开状态。现在,如果您的意思是互斥体的名称可以是随机字符串而不是基于 Guid 的字符串,是的。例如,您可以使用“MyRandomlyNamedMutex”。 (2认同)

Dal*_*gan 103

你可以使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

其次,我们需要创建一个可以管理我们实例的类.在我们讨论之前,我们实际上将重用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

第三,是时候创建我们的EntryPoint了.我们不会像通常那样新建应用程序,而是利用我们的SingleInstanceManager.

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

好吧,我希望你能够遵循一切,并能够使用这个实现,并使其成为你自己的.

  • 我坚持使用互斥解决方案,因为它与表单无关. (7认同)
  • 原谅我,但除非我遗漏了一些东西,否则你要避免编写3行代码,而是重新使用框架来编写相当繁重的代码来完成它.那么节省在哪里? (3认同)
  • 我使用它是因为我在使用其他方法时遇到了问题,但我很确定它在后台使用了远程处理。我的应用程序有两个相关的问题 - 一些客户说它试图给家里打电话,即使他们告诉它不要。当他们仔细查看时,连接是到 localhost。尽管如此,他们最初并不知道这一点。另外,我不能将远程处理用于其他目的(我认为?),因为它已经用于此目的。当我尝试互斥方法时,我可以再次使用远程处理。 (2认同)
  • 有可能在winforms中吗? (2认同)
  • 如果您不在应用程序实例上调用 InitializeComponent(),您将无法解析资源... _application = new SingleInstanceApplication(); _application.InitializeComponent(); _application.Run(); (2认同)
  • 这种方法在微软官方WPF示例中进行了描述:https://github.com/microsoft/WPF-Samples/tree/master/Application%20Management/SingleInstanceDetection 我不明白对WinForms依赖的仇恨,正如作者提到的,它是关于重用框架组件而不是重新发明轮子。我个人认为自定义互斥解决方案并不比框架内现成的组件更容易维护。WinForm 与框架一起提供,而不是额外的依赖项。至于绩效,需要采取措施。 (2认同)

jas*_*ldo 83

这里开始.

跨进程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();
    }    
  }    
}
Run Code Online (Sandbox Code Playgroud)

Mutex的一个很好的功能是,如果应用程序终止而没有首先调用ReleaseMutex,CLR将自动释放Mutex.

  • 我必须说,我喜欢这个答案远远超过了被接受的答案,因为它不依赖于WinForms.就个人而言,我的大多数开发都已经转向WPF,我不想为这样的东西拉入WinForm库. (5认同)
  • 当然,要成为一个完整的答案,你还必须描述将参数传递给另一个实例:) (5认同)

Sim*_*ver 56

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类型之间用作填充程序.填充程序检测首次启动应用程序的时间,以及何时尝试后续启动,并控制WPF应用程序类型以确定如何处理启动.

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

框架已经支持这个 - 只是有些白痴命名为DLL Microsoft.VisualBasic并且它没有被放入Microsoft.ApplicationUtils或类似的东西.克服它 - 或打开Reflector.

提示:如果你完全按原样使用这种方法,并且你已经拥有了带有资源等的App.xaml,那么你也想看看这个.


Cha*_*thJ 22

这段代码应该转到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; 
    }
}
Run Code Online (Sandbox Code Playgroud)

方法2

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(...);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 此外,如果您的计算机上正在运行任何其他同名程序,这将不起作用。`ProcessName` 返回减去 `exe` 的可执行文件名。如果您创建了一个名为“记事本”的应用程序,并且 Windows 记事本正在运行,它会在您的应用程序运行时检测到它。 (3认同)
  • 感谢您的回答。我发现了很多类似的问题,而且答案总是如此详尽和/或令人困惑,我发现它们毫无用处。这个(方法#1)简单明了,最重要的是它实际上帮助我运行我的代码。 (2认同)

Oli*_*ich 19

好吧,我有一个一次性类,适用于大多数用例:

像这样使用它:

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

        // Initialize program here.
    }
}
Run Code Online (Sandbox Code Playgroud)

这里是:

/// <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
}
Run Code Online (Sandbox Code Playgroud)


hus*_*int 14

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

  • 链接现已损坏。 (5认同)

Sim*_*ier 11

作为标记答案的参考的代码C#.NET Single Instance Application是一个很好的开始.

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

因此,我创建了一个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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
Run Code Online (Sandbox Code Playgroud)

这是实用类:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Nat*_*iri 9

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

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);
}
Run Code Online (Sandbox Code Playgroud)


Bru*_*uce 8

只是一些想法:有些情况下,要求只有一个应用程序的实例不是"跛脚",因为有些人会相信你.如果允许单个用户的多个应用实例访问数据库,那么数据库应用程序等就会变得更加困难(您知道,所有这些都会更新在用户的多个应用实例中打开的所有记录机器等).首先,对于"名称冲突的事情,不要使用人类可读的名称 - 使用GUID,或者更好的GUID +人类可读的名称.名称冲突的机会刚刚从雷达上掉下来,而互斥体并不关心正如有人指出的那样,DOS攻击会很糟糕,但是如果恶意的人已经遇到了获取互斥锁名称并将其合并到应用程序中的麻烦,那么无论如何你都是一个目标,并且必须做更多的事情来保护你自己而不仅仅是提到一个互斥锁名称.另外,如果使用以下变量:new Mutex(true,"some GUID plus Name",out AIsFirstInstance),你已经有了关于Mutex是否是第一个实例的指标.


小智 6

对这样一个看似简单的问题的答案很多.只是为了解决这个问题,这是我解决这个问题的方法.

创建互斥锁可能很麻烦,因为JIT-er只会将您用于代码的一小部分,并希望将其标记为垃圾收集准备就绪.它非常想要你认为你不会那么长时间使用那个Mutex.实际上,只要应用程序正在运行,您就想要挂在此Mutex上.告诉垃圾收集器单独留给Mutex的最好方法是告诉它让它保持活力,尽管不同代的车库收集.例:

var m = new Mutex(...);
...
GC.KeepAlive(m);
Run Code Online (Sandbox Code Playgroud)

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

  • 在应用程序类中存储它的共享副本会不会更容易? (2认同)

Joe*_*tti 6

看起来有一种非常好的方法可以解决这个问题:

WPF单实例应用程序

这提供了一个可以添加的类来管理所有互斥和消息传递cruff,以简化您的实现,使其变得简单无比.


Dan*_*Dan 6

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

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

此代码需要引用PresentationFramework,和System.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.
    }
}
Run Code Online (Sandbox Code Playgroud)

源代码:

/// <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);
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


Mat*_*son 5

您永远不应该使用命名的互斥锁来实现单实例应用程序(或者至少不是生产代码).恶意代码可以轻松DoS(拒绝服务)你的屁股......

  • "你永远不应该使用一个命名的互斥体" - 永远不要说永远.如果恶意代码在我的机器上运行,我可能已经被软管了. (7认同)
  • 至少在Windows下,互斥锁具有访问控制权,因此一个人可以玩你的对象.至于命名冲突本身,这就是UUID/GUID发明的原因. (2认同)

小智 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();
    }
}
Run Code Online (Sandbox Code Playgroud)


new*_*guy 5

但不使用互斥体,简单的答案:

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

if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;
Run Code Online (Sandbox Code Playgroud)

将其放入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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 如果两个进程同时启动,它们可能都会看到两个活动进程并自行终止。 (5认同)

归档时间:

查看次数:

189260 次

最近记录:

6 年,1 月 前