【问题标题】:What is the correct way to create a single-instance WPF application?创建单实例 WPF 应用程序的正确方法是什么?
【发布时间】:2010-09-06 08:34:18
【问题描述】:

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

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

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

【问题讨论】:

  • 当应用程序终止时,CLR 不会自动释放任何未释放的互斥锁吗?
  • @Cocowalla:终结器应该释放非托管互斥锁,除非它不知道互斥锁是由托管应用创建还是附加到现有应用。
  • 只有一个应用实例是合理的。但是将参数传递给已经存在的应用程序在我看来有点愚蠢。我看不出有任何理由这样做。如果您将应用程序与文件扩展名相关联,您应该打开与用户想要打开文档一样多的应用程序。这是每个用户都期望的标准行为。
  • @Cocowalla CLR 不管理本机资源。但是,如果一个进程终止,所有句柄都会被系统(操作系统,而不是 CLR)释放。
  • 我更喜欢@huseyint 的回答。它使用 Microsoft 自己的“SingleInstance.cs”类,因此您不必担心 Mutexes 和 IntPtrs。此外,不依赖于 VisualBasic (yuk)。有关更多信息,请参阅codereview.stackexchange.com/questions/20871/…...

标签: c# .net wpf mutex


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    通常,这是我用于单实例Windows Forms 应用程序的代码:

    [STAThread]
    public static void Main()
    {
        String assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
    
        using (Mutex mutex = new Mutex(false, assemblyName))
        {
            if (!mutex.WaitOne(0, false))
            {
                Boolean shownProcess = false;
                Process currentProcess = Process.GetCurrentProcess();
    
                foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName))
                {
                    if (!process.Id.Equals(currentProcess.Id) && process.MainModule.FileName.Equals(currentProcess.MainModule.FileName) && !process.MainWindowHandle.Equals(IntPtr.Zero))
                    {
                        IntPtr windowHandle = process.MainWindowHandle;
    
                        if (NativeMethods.IsIconic(windowHandle))
                            NativeMethods.ShowWindow(windowHandle, ShowWindowCommand.Restore);
    
                        NativeMethods.SetForegroundWindow(windowHandle);
    
                        shownProcess = true;
                    }
                }
    
                if (!shownProcess)
                    MessageBox.Show(String.Format(CultureInfo.CurrentCulture, "An instance of {0} is already running!", assemblyName), assemblyName, MessageBoxButtons.OK, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
            }
            else
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form());
            }
        }
    }
    

    原生组件在哪里:

    [DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern Boolean IsIconic([In] IntPtr windowHandle);
    
    [DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern Boolean SetForegroundWindow([In] IntPtr windowHandle);
    
    [DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern Boolean ShowWindow([In] IntPtr windowHandle, [In] ShowWindowCommand command);
    
    public enum ShowWindowCommand : int
    {
        Hide                   = 0x0,
        ShowNormal             = 0x1,
        ShowMinimized          = 0x2,
        ShowMaximized          = 0x3,
        ShowNormalNotActive    = 0x4,
        Minimize               = 0x6,
        ShowMinimizedNotActive = 0x7,
        ShowCurrentNotActive   = 0x8,
        Restore                = 0x9,
        ShowDefault            = 0xA,
        ForceMinimize          = 0xB
    }
    

    【讨论】:

    • 这个实现的问题是你不能将任何命令行参数从第二个实例提供回第一个实例。为了更好的解释look here.
    • 看起来不像问题要求的那样。无论如何,这不会是一致的行为......被终止的实例不应修改现有实例的行为。如果您希望您的应用程序表现不同,请关闭当前进程并使用不同的参数启动一个新进程。
    • 但这种行为是办公室默认的工作方式。您通过双击打开第一个文档并开始一个新过程。您打开第二个文档,它会在第一个实例中获得一个窗口。
    • 我还是不明白。这不是请求的功能。
    • 是的。问题的最后一段是:代码还需要通知已经运行的实例用户尝试启动第二个实例,并且如果存在任何命令行参数,还可以传递任何命令行参数。
    【解决方案3】:

    这里有一个解决方案:

    Protected Overrides Sub OnStartup(e As StartupEventArgs)
        Const appName As String = "TestApp"
        Dim createdNew As Boolean
        _mutex = New Mutex(True, appName, createdNew)
        If Not createdNew Then
            'app is already running! Exiting the application
            MessageBox.Show("Application is already running.")
            Application.Current.Shutdown()
        End If
        MyBase.OnStartup(e)
    End Sub
    

    【讨论】:

    • 我喜欢简单的解决方案,所以我先尝试了这个...无法让它工作。
    【解决方案4】:

    这是我的 2 美分

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

    【讨论】:

    • 什么是“NativeMethods”类?
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

      只需使用StreamWriter,这个怎么样?

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

      try
      {
          OpenFlag = new StreamWriter(Path.GetTempPath() + "OpenedIfRunning");
      }
      catch (System.IO.IOException) //file in use
      {
          Environment.Exit(0);
      }
      

      【讨论】:

        【解决方案7】:

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

        它使用 MemoryMappedFile 将命令行参数发送到第一个实例:

        /// <summary>
        /// This class allows restricting the number of executables in execution, to one.
        /// </summary>
        public sealed class SingletonApplicationEnforcer
        {
            readonly Action<IEnumerable<string>> processArgsFunc;
            readonly string applicationId;
            Thread thread;
            string argDelimiter = "_;;_";
        
            /// <summary>
            /// Gets or sets the string that is used to join 
            /// the string array of arguments in memory.
            /// </summary>
            /// <value>The arg delimeter.</value>
            public string ArgDelimeter
            {
                get
                {
                    return argDelimiter;
                }
                set
                {
                    argDelimiter = value;
                }
            }
        
            /// <summary>
            /// Initializes a new instance of the <see cref="SingletonApplicationEnforcer"/> class.
            /// </summary>
            /// <param name="processArgsFunc">A handler for processing command line args 
            /// when they are received from another application instance.</param>
            /// <param name="applicationId">The application id used 
            /// for naming the <seealso cref="EventWaitHandle"/>.</param>
            public SingletonApplicationEnforcer(Action<IEnumerable<string>> processArgsFunc, 
                string applicationId = "DisciplesRock")
            {
                if (processArgsFunc == null)
                {
                    throw new ArgumentNullException("processArgsFunc");
                }
                this.processArgsFunc = processArgsFunc;
                this.applicationId = applicationId;
            }
        
            /// <summary>
            /// Determines if this application instance is not the singleton instance.
            /// If this application is not the singleton, then it should exit.
            /// </summary>
            /// <returns><c>true</c> if the application should shutdown, 
            /// otherwise <c>false</c>.</returns>
            public bool ShouldApplicationExit()
            {
                bool createdNew;
                string argsWaitHandleName = "ArgsWaitHandle_" + applicationId;
                string memoryFileName = "ArgFile_" + applicationId;
        
                EventWaitHandle argsWaitHandle = new EventWaitHandle(
                    false, EventResetMode.AutoReset, argsWaitHandleName, out createdNew);
        
                GC.KeepAlive(argsWaitHandle);
        
                if (createdNew)
                {
                    /* This is the main, or singleton application. 
                        * A thread is created to service the MemoryMappedFile. 
                        * We repeatedly examine this file each time the argsWaitHandle 
                        * is Set by a non-singleton application instance. */
                    thread = new Thread(() =>
                        {
                            try
                            {
                                using (MemoryMappedFile file = MemoryMappedFile.CreateOrOpen(memoryFileName, 10000))
                                {
                                    while (true)
                                    {
                                        argsWaitHandle.WaitOne();
                                        using (MemoryMappedViewStream stream = file.CreateViewStream())
                                        {
                                            var reader = new BinaryReader(stream);
                                            string args;
                                            try
                                            {
                                                args = reader.ReadString();
                                            }
                                            catch (Exception ex)
                                            {
                                                Debug.WriteLine("Unable to retrieve string. " + ex);
                                                continue;
                                            }
                                            string[] argsSplit = args.Split(new string[] { argDelimiter }, 
                                                                            StringSplitOptions.RemoveEmptyEntries);
                                            processArgsFunc(argsSplit);
                                        }
        
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine("Unable to monitor memory file. " + ex);
                            }
                        });
        
                    thread.IsBackground = true;
                    thread.Start();
                }
                else
                {
                    /* Non singleton application instance. 
                        * Should exit, after passing command line args to singleton process, 
                        * via the MemoryMappedFile. */
                    using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(memoryFileName))
                    {
                        using (MemoryMappedViewStream stream = mmf.CreateViewStream())
                        {
                            var writer = new BinaryWriter(stream);
                            string[] args = Environment.GetCommandLineArgs();
                            string joined = string.Join(argDelimiter, args);
                            writer.Write(joined);
                        }
                    }
                    argsWaitHandle.Set();
                }
        
                return !createdNew;
            }
        }
        

        【讨论】:

          【解决方案8】:

          基于 Matt Davis 的回答,为方便起见,将其封装在一个类中。

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

          示例用法:

          private void Application_Startup(object sender, StartupEventArgs e)
          {
              if (!SingleAppInstanceChecker.IsNotRunning())
              {
                  MessageBox.Show("Application is already running.");
          
                  // Exit application using:
                  // Environment.Exit(1);
                  // Application.Current.Shutdown();
                  // Etc...
          
                  return;
              }
              
              // Allow startup and continue with normal processing
              // ...
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-11-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-03
            相关资源
            最近更新 更多