简体   繁体   English

如何实现每台机器应用程序的单实例?

[英]How to implement single instance per machine application?

I have to restrict my .net 4 WPF application so that it can be run only once per machine. 我必须限制我的.net 4 WPF应用程序,以便每台计算机只能运行一次。 Note that I said per machine, not per session. 请注意,我说每台机器,而不是每个会话。
I implemented single instance applications using a simple mutex until now, but unfortunately such a mutex is per session. 到目前为止,我使用简单的互斥锁实现了单实例应用程序,但不幸的是,每个会话都有这样的互斥锁。

Is there a way to create a machine wide mutex or is there any other solution to implement a single instance per machine application? 有没有办法创建一个机器范围的互斥锁,还是有任何其他解决方案来实现每个机器应用程序的单个实例?

I would do this with a global Mutex object that must be kept for the life of your application. 我会使用全局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);

Where bFirstInstance returns if this is the first instance of your application running globally. 如果这是全局运行的应用程序的第一个实例,则返回bFirstInstance If you omited the Global part of the mutex or replaced it with Local then the mutex would only be per session (this is proberbly how your current code is working). 如果您省略了互斥锁的全局部分或将其替换为本地,那么互斥锁将仅针对每个会话(这可能是您当前代码的工作原理)。

I believe that I got this technique first from Jon Skeet . 我相信我首先从Jon Skeet那里获得了这项技术。

The MSDN topic on the Mutex object explains about the two scopes for a Mutex object and highlights why this is important when using terminal services (see second to last note). Mutex对象上的MSDN主题解释了Mutex对象的两个范围,并重点介绍了在使用终端服务时这一点很重要的原因(请参阅倒数第二个)。

I think what you need to do is use a system sempahore to track the instances of your application. 我认为您需要做的是使用系统sempahore来跟踪应用程序的实例。

If you create a Semaphore object using a constructor that accepts a name, it is associated with an operating-system semaphore of that name. 如果使用接受名称的构造函数创建Semaphore对象,则它与该名称的操作系统信号量相关联。

Named system semaphores are visible throughout the operating system, and can be used to synchronize the activities of processes. 命名系统信号量在整个操作系统中都是可见的,可用于同步进程的活动。

EDIT: Note that I am not aware if this approach works across multiple windows sessions on a machine. 编辑:请注意,我不知道这种方法是否适用于计算机上的多个Windows会话。 I think it should as its an OS level construct but I cant say for sure as i havent tested it that way. 我认为它应该作为一个操作系统级别的构造,但我不能肯定地说,因为我没有那样测试它。

EDIT 2: I did not know this but after reading Stevo2000's answer, i did some looking up as well and I think that the "Global\\" prefixing to make the the object applicable to the global namespace would apply to semaphores as well and semaphore, if created this way, should work. 编辑2:我不知道这一点但是在阅读了Stevo2000的答案之后,我也做了一些查找,我认为使全局命名空间适用的对象的“全局”前缀也适用于信号量和信号量,如果以这种方式创建,应该工作。

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

How about using the registry? 如何使用注册表?

  1. You can create a registry entry under HKEY_LOCAL_MACHINE . 您可以registry entry under HKEY_LOCAL_MACHINE创建一个registry entry under HKEY_LOCAL_MACHINE
  2. Let the value be the flag if the application is started or not. 如果应用程序启动,则将值设为flag
  3. Encrypt the key using some standard symmetric key encryption method so that no one else can tamper with the value. Encrypt the key使用一些标准的symmetric key encryption方法,这样没有人可以与篡改价值。
  4. On application start-up check for the key and abort\\continue accordingly. On application start-up check for the key和中止\\继续相应。
  5. Do not forget to obfuscate your assembly , which does this encryption\\decryption part, so that no one can hack the key in registry by looking at the code in reflector. 不要忘记obfuscate your assembly ,它使用这个加密\\解密部分,这样就没有人可以通过查看反射器中的代码来破解注册表中的密钥。

I did something similar once. 我曾做过类似的事。

When staring up the application list, I checked all running processes for a process with identical name, and if it existed I would not allow to start the program. 在查看应用程序列表时,我检查了所有正在运行的进程以查找名称相同的进程,如果存在,我将不允许启动该程序。

This is not bulletproof of course, since if another application have the exact same process name, your application will never start, but if you use a non-generic name it will probably be more than good enough. 当然,这不是防弹,因为如果另一个应用程序具有完全相同的进程名称,您的应用程序将永远不会启动,但如果您使用非通用名称,它可能会非常好。

For the sake of completeness, I'd like to add the following which I just found now: 为了完整起见,我想添加我刚才发现的以下内容:
This web site has an interesting approach in sending Win32 messages to other processes. 这个网站有一个有趣的方法将Win32消息发送到其他进程。 This would fix the problem of the user renaming the assembly to bypass the test and of other assemblies with the same name. 这将解决用户重命名程序集以绕过测试和具有相同名称的其他程序集的问题。
They're using the message to activate the main window of the other process, but it seems like the message could be a dummy message only used to see whether the other process is responding to it to know whether it is our process or not. 他们正在使用该消息来激活其他进程的主窗口,但似乎该消息可能是一个虚拟消息,仅用于查看其他进程是否响应它以了解它是否是我们的进程。

Note that I haven't tested it yet. 请注意,我还没有测试过它。

See below for full example of how a single instace app is done in WPF 3.5 有关如何在WPF 3.5中完成单个instace应用程序的完整示例,请参见下文

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

Second part: 第二部分:

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

Third part: 第三方:

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

You may need to add soem references and add some using statements but it shoudl work. 您可能需要添加soem引用并添加一些using语句,但它可能会起作用。

You can also download a VS example complete solution by downloading the source code of the book from here . 您也可以从此处下载本书的源代码,下载VS示例完整解决方案。

Taken From "Pro WPF in C#3 2008 , Apress , Matthew MacDonald" , buy the book is gold. 摘自“Pro WPF in C#3 2008,Apress,Matthew MacDonald”,买这本书就是黄金。 I did. 我做到了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM