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

我知道它与某种叫做互斥的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么。

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


当前回答

这是通过事件实现的相同的事情。

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

其他回答

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

The most common and reliable technique for developing single-instance detection is to use the Microsoft .NET Framework remoting infrastructure (System.Remoting). The Microsoft .NET Framework (version 2.0) includes a type, WindowsFormsApplicationBase, which encapsulates the required remoting functionality. To incorporate this type into a WPF application, a type needs to derive from it, and be used as a shim between the application static entry point method, Main, and the WPF application's Application type. The shim detects when an application is first launched, and when subsequent launches are attempted, and yields control the WPF Application type to determine how to process the launches.

For C# people just take a deep breath and forget about the whole 'I don't wanna include VisualBasic DLL'. Because of this and what Scott Hanselman says and the fact that this pretty much is the cleanest solution to the problem and is designed by people who know a lot more about the framework than you do. From a usability standpoint the fact is if your user is loading an application and it is already open and you're giving them an error message like 'Another instance of the app is running. Bye' then they're not gonna be a very happy user. You simply MUST (in a GUI application) switch to that application and pass in the arguments provided - or if command line parameters have no meaning then you must pop up the application which may have been minimized.

这个框架已经支持这个功能了——只是有些白痴把DLL命名为Microsoft罢了。VisualBasic,它没有被放到微软。ApplicationUtils之类的。克服它——或者打开Reflector。

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

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

[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结束时就关闭,它也会被自动销毁。

基于命名互斥的方法不是跨平台的,因为命名互斥在Mono中不是全局的。基于进程枚举的方法没有任何同步,可能会导致不正确的行为(例如,同时启动的多个进程可能都根据时间自行终止)。在控制台应用程序中不需要基于windows系统的方法。这个解决方案建立在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.");
            }
        }
    }
}

请看下面的代码。它是防止一个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();
    }
}

Just some thoughts: There are cases when requiring that only one instance of an application is not "lame" as some would have you believe. Database apps, etc. are an order of magnitude more difficult if one allows multiple instances of the app for a single user to access a database (you know, all that updating all the records that are open in multiple instances of the app on the users machine, etc.). First, for the "name collision thing, don't use a human readable name - use a GUID instead or, even better a GUID + the human readable name. Chances of name collision just dropped off the radar and the Mutex doesn't care. As someone pointed out, a DOS attack would suck, but if the malicious person has gone to the trouble of getting the mutex name and incorporating it into their app, you are pretty much a target anyway and will have to do MUCH more to protect yourself than just fiddle a mutex name. Also, if one uses the variant of: new Mutex(true, "some GUID plus Name", out AIsFirstInstance), you already have your indicator as to whether or not the Mutex is the first instance.