【问题标题】:How to implement single instance per machine application?如何实现每个机器应用程序的单个实例?
【发布时间】:2010-11-19 07:50:10
【问题描述】:

我必须限制我的 .net 4 WPF 应用程序,以便每台机器只能运行一次。请注意,我说的是每台机器,而不是每个会话。
到目前为止,我使用简单的互斥锁实现了单实例应用程序,但不幸的是,这样的互斥锁是每个会话的。

有没有办法创建机器范围的互斥锁,或者有没有其他解决方案可以为每个机器应用程序实现一个实例?

【问题讨论】:

  • 您所说的session是指Windows Session
  • 是的,Windows 会话。所以它必须允许每台机器运行一次,而不是每个用户。
  • 实际上Mutex 可以帮助实现每个Application Domain 的单个实例,而不是每个会话。
  • 这里最好的问题是为什么?最常见的原因是因为应用程序使用了某种独特的资源。通常,最佳答案是与该独特资源直接相关的答案。例如,当唯一资源是一个文件时,答案是独占锁定该文件。

标签: c# .net mutex single-instance


【解决方案1】:

我会使用一个全局 Mutex 对象来执行此操作,该对象必须在您的应用程序的生命周期内保留。

MutexSecurity oMutexSecurity;

//Set the security object
oMutexSecurity = new MutexSecurity();
oMutexSecurity.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null), MutexRights.FullControl, AccessControlType.Allow));

//Create the global mutex and set its security
moGlobalMutex = new Mutex(True, "Global\\{5076d41c-a40a-4f4d-9eed-bf274a5bedcb}", bFirstInstance);
moGlobalMutex.SetAccessControl(oMutexSecurity);

如果这是您的应用程序在全球范围内运行的第一个实例,则返回 bFirstInstance。如果您省略了互斥锁的全局部分或将其替换为本地,则互斥锁将仅适用于每个会话(这可能是您当前代码的工作方式)。

我相信我首先从Jon Skeet 获得了这项技术。

关于 Mutex 对象的 MSDN 主题解释了 Mutex 对象的两个作用域,并强调了为什么这在使用终端服务时很重要(参见倒数第二个注释)。

【讨论】:

  • 哇,这似乎确实有效!我不知道互斥锁名称中的全局/本地功能。 MSDN 也没有提及它。当我测试从另一个帐户运行该应用程序时,当 Mutex 已经存在时,我现在收到拒绝访问错误。 OpenExisting(..) 方法找不到它。如果仅捕获此异常就足够了,那么我会非常高兴:-) 非常感谢!
  • @Marc - 创建时需要设置互斥锁安全性,以免第二个进程抛出异常。
  • 太棒了,你刚刚解决了我的问题 :-) 到目前为止,这个解决方案在我的测试中似乎运行良好。非常感谢!
  • 重要提示:此解决方案不适用于非英语系统,因为“用户”或“所有人”组已本地化。
  • 您能否编辑此代码并将“用户”替换为“新 SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null)”?这样它也适用于非英语安装,谢谢。
【解决方案2】:

我认为您需要做的是使用system sempahore 来跟踪您的应用程序的实例。

如果您使用接受名称的构造函数创建 Semaphore 对象,则它与该名称的操作系统信号量相关联。

命名系统信号量在整个操作系统中都是可见的,可用于同步进程的活动。

编辑:请注意,我不知道这种方法是否适用于机器上的多个 Windows 会话。我认为它应该作为操作系统级别的构造,但我不能肯定地说,因为我还没有这样测试过。

编辑 2:我不知道这一点,但在阅读 Stevo2000 的答案后,我也做了一些查找,我认为使对象适用于全局命名空间的“全局\”前缀也适用于信号量和信号量,如果以这种方式创建,应该可以工作。

【讨论】:

  • 听起来是一个非常有趣的想法,所以我尝试了一下。不幸的是,它与 Mutex 类相同,其他用户可以安全地创建具有相同名称的信号量。请注意,我没有使用将 SemaphoreSecurity 对象作为参数的构造函数。也许可以通过这个实现一些目标?
  • 嗯..也许我没有完全正确地满足您的要求,但在您的应用程序中 - 当它启动时,首先您使用 OpenExisting("MyAppSemaphore") 检查是否存在名为“MyAppSemaphore”的信号量。如果存在,则关闭您的应用程序。如果没有,那么您使用新的信号量(1、1、“MyAppSemaphore”)创建一个系统信号量。这不是你要找的吗?
  • 是的,但我有以下情况: User1 启动创建“MyAppSemaphore”的应用程序。 User1 将无法启动该应用程序的另一个实例。现在 User2 登录(终端服务器、快速用户切换等)并启动应用程序,它再次愉快地创建另一个“MyAppSemaphore”。所以这不是一个解决方案(我需要每台机器一个实例,如问题中所述)。
  • @Marc - 我明白了。所以只是为了澄清你的情况。说您只需要在终端服务器上跨该服务器上的多个远程用户会话运行您的应用程序的一个实例是否正确?
  • 是的。它应该是每台机器一个应用程序实例,如果这台机器是终端服务器也是如此。 (我可以接受这样一个事实,如果使用多个终端服务器的负载平衡,这将是不可能的)。
【解决方案3】:

您可以在 %PROGRAMDATA% 的某处打开具有独占权限的文件 启动的第二个实例将尝试打开同一个文件,如果它已经打开则失败。

【讨论】:

  • 这听起来是个好主意。我考虑过创建/删除锁定文件,但这会涉及访问权限问题。但只需打开/关闭文件就可以解决问题。感谢您的想法!
【解决方案4】:

如何使用注册表?

  1. 您可以创建registry entry under HKEY_LOCAL_MACHINE
  2. 如果应用程序是否启动,则设为flag
  3. Encrypt the key 使用一些标准的symmetric key encryption 方法,这样其他人就不能篡改该值。
  4. On application start-up check for the key 并相应地中止\继续。
  5. 不要忘记obfuscate your assembly,它负责加密\解密部分,这样任何人都无法通过查看反射器中的代码来破解注册表中的密钥。

【讨论】:

  • 感谢您的想法,但我不喜欢注册表,抱歉。并且在尝试创建条目HKEY_LOCAL_MACHINE时不会有类似在程序文件中创建文件的访问权限问题吗?
【解决方案5】:

我曾经做过类似的事情。

在启动应用程序列表时,我检查了所有正在运行的进程是否有同名的进程,如果存在,我将不允许启动程序。

这当然不是万无一失的,因为如果另一个应用程序具有完全相同的进程名称,您的应用程序将永远无法启动,但如果您使用非通用名称,它可能就足够了。

【讨论】:

  • 问题是用户可以重命名程序集,他会没事的。但事实上,如果没有其他解决方案,那么我将使用这种技术。也许可以通过 WCF 通道完成一些事情......
  • 在受限账户下运行的程序能否看到其他账户的进程?
  • 是的,它可以,我只是通过创建一个只是“用户”组的一部分的用户来测试它,这个用户可以看到其他人的进程。只是部分属性会产生拒绝访问异常,但至少可以使用进程名。
【解决方案6】:

为了完整起见,我想添加以下我刚刚找到的内容:
这个web site 有一个有趣的方法将 Win32 消息发送到其他进程。这将解决用户重命名程序集以绕过测试以及其他同名程序集的问题。
他们正在使用该消息来激活另一个进程的主窗口,但似乎该消息可能是一个虚拟消息,仅用于查看其他进程是否响应它以了解它是否是我们的进程。

请注意,我还没有测试过。

【讨论】:

    【解决方案7】:

    有关如何在 WPF 3.5 中完成单个实例应用程序的完整示例,请参见下文

    public class SingleInstanceApplicationWrapper :
    Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
    {
    public SingleInstanceApplicationWrapper()
    {
    // Enable single-instance mode.
    this.IsSingleInstance = true;
    }
    // Create the WPF application class.
    private WpfApp app;
    protected override bool OnStartup(
    Microsoft.VisualBasic.ApplicationServices.StartupEventArgs e)
    {
    app = new WpfApp();
    app.Run();
    return false;
    }
    // Direct multiple instances.
    protected override void OnStartupNextInstance(
    Microsoft.VisualBasic.ApplicationServices.StartupNextInstanceEventArgs e)
    {
    if (e.CommandLine.Count > 0)
    {
    app.ShowDocument(e.CommandLine[0]);
    }
    }
    }
    

    第二部分:

    public class WpfApp : System.Windows.Application
    {
    protected override void OnStartup(System.Windows.StartupEventArgs e)
    {
    base.OnStartup(e);
    WpfApp.current = this;
    // Load the main window.
    DocumentList list = new DocumentList();
    this.MainWindow = list;
    list.Show();
    // Load the document that was specified as an argument.
    if (e.Args.Length > 0) ShowDocument(e.Args[0]);
    }
    public void ShowDocument(string filename)
    {
    try
    {
    Document doc = new Document();
    doc.LoadFile(filename);
    doc.Owner = this.MainWindow;
    doc.Show();
    // If the application is already loaded, it may not be visible.
    // This attempts to give focus to the new window.
    doc.Activate();
    }
    catch
    {
    MessageBox.Show("Could not load document.");
    }
    }
    }
    

    第三部分:

     public class Startup
        {
        [STAThread]
        public static void Main(string[] args)
        {
        SingleInstanceApplicationWrapper wrapper =
        new SingleInstanceApplicationWrapper();
        wrapper.Run(args);
        }
        }
    

    您可能需要添加一些引用并添加一些 using 语句,但它应该可以工作。

    您也可以通过here下载本书的源代码来下载VS示例完整解决方案。

    取自“Pro WPF in C#3 2008 , Apress , Matthew MacDonald”,买书是金子。我做到了。

    【讨论】:

    • 正如我在问题中添加的那样,我需要每台机器的单个实例。您发布的代码非常知名和赞赏,但仅用于处理单个实例 PER SESSION/USER/LOGIN。我从 Adam Nathan 那里购买了“WPF4 Unleashed”一书,我对此非常满意 :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多