如何判断我的程序的另一个实例是否已经在运行?

发布于 2024-07-12 07:10:33 字数 96 浏览 6 评论 0原文

我如何判断我的程序的一个实例是否正在运行? 我以为我可以用一个数据文件来做到这一点,但它只会很混乱:(

我想这样做,因为我只希望在某个时刻打开 1 个实例。

How do i tell if one instance of my program is running?
I thought I could do this with a data file but it would just be messy :(

I want to do this as I only want 1 instance to ever be open at one point.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

烧了回忆取暖 2024-07-19 07:10:33

正如乔恩首先建议的那样,您可以尝试创建互斥体。 调用 CreateMutex。 如果返回非空句柄,请调用 GetLastError。 它会告诉您是否是该互斥锁的创建者,或者该互斥锁之前是否已打开 (Error_Already_Exists)。 请注意,不需要获取互斥体的所有权。 互斥锁不用于互斥。 它被使用是因为它是一个命名的内核对象。 事件或信号量也可以起作用。

互斥技术给你一个布尔答案:是的,还有另一个实例,或者不,没有。

您常常想了解的不仅仅是这些。 例如,您可能想知道另一个实例的主窗口的句柄,以便您可以告诉它代替另一个实例进入前台。 这就是内存映射文件可以派上用场的地方; 它可以保存有关第一个实例的信息,以便后续实例可以引用它。

选择互斥锁的名称时要小心。 仔细阅读文档,并记住某些字符(例如反斜杠)在某些操作系统版本中是不允许的,但在其他操作系统版本中某些功能是必需的。

还要记住其他用户的问题。 如果您的程序可以通过远程桌面或快速用户切换来运行,那么可能有其他用户已经在运行您的程序,并且您可能并不真正希望限制当前用户运行您的程序。 在这种情况下,不要使用全局名称。 如果您确实想要限制所有用户的访问,那么请确保互斥对象的安全属性使得每个人都能够打开它的句柄。 对 lpSecurityAttributes 参数使用空指针是不够的; MSDN 提到的“默认安全描述符”为当前用户提供了完全访问权限,而对其他用户则没有访问权限。

您可以编辑程序的 DPR 文件。 这通常是做这种事情的好地方。 如果您等到其中一个窗体的 OnCreate 事件发生,那么您的程序已经有一点正常运行的动力,因此在此时尝试终止程序是很笨拙的。 最好在完成太多 UI 工作之前终止。 例如:

var
  mutex: THandle;
  mutexName: string;
begin
  mutexName := ConstructMutexName();

  mutex := CreateMutex(nil, False, PChar(mutexName));

  if mutex = 0 then
    RaiseLastOSError; // Couldn't open handle at all.

  if GetLastError = Error_Already_Exists then begin
    // We are not the first instance.
    SendDataToPreviousInstance(...);
    exit;
  end;
  // We are the first instance.

  // Do NOT close the mutex handle here. It must
  // remain open for the duration of your program,
  // or else later instances won't be able to
  // detect this instance.

  Application.Initialize;
  Application.CreateForm(...);
  Application.Run;
end.

存在一个何时关闭互斥体句柄的问题。 你不必关闭它。 当您的进程最终终止时(即使它崩溃),操作系统将自动关闭任何未完成的句柄,并且当没有更多句柄打开时,互斥对象将被销毁(从而允许程序的另一个实例启动并认为自己是第一个例子)。

但无论如何您可能想关闭该句柄。 假设您选择实现我在代码中提到的 SendDataToPreviousInstance 函数。 如果您想变得更有趣,那么您可以考虑前一个实例已经关闭并且无法接受新数据的情况。 那么你就不会真的想关闭第二个实例。 第一个实例可以在知道要关闭时立即关闭互斥体句柄,实际上成为“跛脚鸭”实例。 第二个实例将尝试创建互斥体句柄,成功,并认为自己是真正的第一个实例。 前一个实例将不间断地关闭。 使用CloseHandle关闭互斥锁; 例如,从主窗体的 OnClose 事件处理程序或您调用 Application.Terminate 的其他位置调用它。

As Jon first suggested, you can try creating a mutex. Call CreateMutex. If you get a non-null handle back, then call GetLastError. It will tell you whether you were the one who created the mutex or whether the mutex was already open before (Error_Already_Exists). Note that it is not necessary to acquire ownership of the mutex. The mutex is not being used for mutual exclusion. It's being used because it is a named kernel object. An event or semaphore could work, too.

The mutex technique gives you a Boolean answer: Yes, there is another instance, or no, there is not.

You frequently want to know more than just that. For instance, you might want to know the handle of the other instance's main window so you can tell it to come to the foreground in place of your other instance. That's where a memory-mapped file can come in handy; it can hold information about the first instance so later instances can refer to it.

Be careful when choosing the name of the mutex. Read the documentation carefully, and keep in mind that some characters (such as backslash) are not allowed in some OS versions, but are required for certain features in other OS versions.

Also remember the problem of other users. If your program could be run via remote desktop or fast user switching, then there could be other users already running your program, and you might not really want to restrict the current user from running your program. In that case, don't use a global name. If you do want to restrict access for all users, then make sure the mutex object's security attributes are such that everyone will be able to open a handle to it. Using a null pointer for the lpSecurityAttributes parameter is not sufficient for that; the "default security descriptor" that MSDN mentions gives full access to the current user and no access to others.

You're allowed to edit the DPR file of your program. That's usually a good place to do this kind of thing. If you wait until the OnCreate event of one of your forms, then your program already has a bit of momentum toward running normally, so it's clumsy to try to terminate the program at that point. Better to terminate before too much UI work has been done. For example:

var
  mutex: THandle;
  mutexName: string;
begin
  mutexName := ConstructMutexName();

  mutex := CreateMutex(nil, False, PChar(mutexName));

  if mutex = 0 then
    RaiseLastOSError; // Couldn't open handle at all.

  if GetLastError = Error_Already_Exists then begin
    // We are not the first instance.
    SendDataToPreviousInstance(...);
    exit;
  end;
  // We are the first instance.

  // Do NOT close the mutex handle here. It must
  // remain open for the duration of your program,
  // or else later instances won't be able to
  // detect this instance.

  Application.Initialize;
  Application.CreateForm(...);
  Application.Run;
end.

There's a question of when to close the mutex handle. You don't have to close it. When your process finally terminates (even if it crashes), the OS will automatically close any outstanding handles, and when there are no more handles open, the mutex object will be destroyed (thus allowing another instance of your program to start and consider itself to be the first instance).

But you might want to close the handle anyway. Suppose you chose to implement the SendDataToPreviousInstance function I mentioned in the code. If you want to get fancy, then you could account for the case that the previous instance is already shutting down and is unable to accept new data. Then you won't really want to close the second instance. The first instance could close the mutex handle as soon as it knows it's shutting down, in effect becoming a "lame duck" instance. The second instance will try to create the mutex handle, succeed, and consider itself the real first instance. The previous instance will close uninterrupted. Use CloseHandle to close the mutex; call it from your main form's OnClose event handler, or wherever else you call Application.Terminate, for example.

我早已燃尽 2024-07-19 07:10:33

您可以创建一个信号量并停止执行(将代码放入 *.dpr 文件中)并将正在运行的应用程序带到屏幕上。

var
  Semafor: THandle;

begin
  { Don't start twice ... if already running bring this instance to front }
  Semafor := CreateSemaphore(nil, 0, 1, 'MY_APPLICATION_IS_RUNNING');
  if ((Semafor <> 0) and { application is already running }
     (GetLastError = ERROR_ALREADY_EXISTS)) then 
  begin
    RestoreWindow('TMyApplication');
    CloseHandle(Semafor);
    Halt;
  end;

  Application.CreateForm(....);    
  Application.Initialize;
  Application.Run;
  CloseHandle(Semafor);
end;

编辑(添加了RestoreWindow方法):

aFormName是应用程序中主表单类的名称。

procedure RestoreWindow(aFormName: string);
var
  Wnd,
  App: HWND;    
begin
  Wnd := FindWindow(PChar(aFormName), nil);
  if (Wnd <> 0) then 
  begin { Set Window to foreground }
    App := GetWindowLong(Wnd, GWL_HWNDPARENT);
    if IsIconic(App) then 
      ShowWindow(App, SW_RESTORE);

    SetForegroundwindow(App);
  end;
end;

You can create a Semaphore and stop execution (put the code into your *.dpr file) and bring you running application to the screen.

var
  Semafor: THandle;

begin
  { Don't start twice ... if already running bring this instance to front }
  Semafor := CreateSemaphore(nil, 0, 1, 'MY_APPLICATION_IS_RUNNING');
  if ((Semafor <> 0) and { application is already running }
     (GetLastError = ERROR_ALREADY_EXISTS)) then 
  begin
    RestoreWindow('TMyApplication');
    CloseHandle(Semafor);
    Halt;
  end;

  Application.CreateForm(....);    
  Application.Initialize;
  Application.Run;
  CloseHandle(Semafor);
end;

EDIT (added the RestoreWindow method):

The aFormName is the name of your main form class in your application.

procedure RestoreWindow(aFormName: string);
var
  Wnd,
  App: HWND;    
begin
  Wnd := FindWindow(PChar(aFormName), nil);
  if (Wnd <> 0) then 
  begin { Set Window to foreground }
    App := GetWindowLong(Wnd, GWL_HWNDPARENT);
    if IsIconic(App) then 
      ShowWindow(App, SW_RESTORE);

    SetForegroundwindow(App);
  end;
end;
久隐师 2024-07-19 07:10:33

全能的 JVCL 有一个用于此目的的组件。 请参阅“TJvAppInstances”。

The all-mighty JVCL has a component for this purpose. See "TJvAppInstances".

放肆 2024-07-19 07:10:33

正常的解决方案是创建一个命名的系统范围互斥体

  • 如果您成功创建了它,那么您就是正在运行的应用程序。
  • 如果你不这样做,你就知道还有另一种。

编辑:

我没有提供代码,因为我不知道Delphi。 如果有帮助的话,我可以提供 C# 代码。

The normal solution is to create a named, system-wide mutex.

  • If you manage to create it, you're the one running application.
  • If you don't, you know there's a different one.

EDIT:

I haven't provided code as I don't know Delphi. I can provide C# code if that would be helpful though.

茶花眉 2024-07-19 07:10:33

您创建一个系统互斥体

我没有 Delphi 代码,但这里有 C++ 代码:

HANDLE Mutex;

const char MutexName[] = "MyUniqueProgramName";

Mutex = OpenMutex(MUTEX_ALL_ACCESS, false, MutexName);

if (Mutex)
     throw Exception("Program is already running.");
else
     Mutex = CreateMutex(NULL, true, MutexName);

You create a system mutex.

I don't have Delphi code, but here's C++ code:

HANDLE Mutex;

const char MutexName[] = "MyUniqueProgramName";

Mutex = OpenMutex(MUTEX_ALL_ACCESS, false, MutexName);

if (Mutex)
     throw Exception("Program is already running.");
else
     Mutex = CreateMutex(NULL, true, MutexName);
你的他你的她 2024-07-19 07:10:33

您可以简单地使用 FindWindow windows api 函数。 在delphi中,窗口的类名与类名相同,您可以通过重写CreateParams函数来重新定义类名。 要检查窗口是否存在,请在创建主窗口之前、Application.Initialize 之前添加代码;

Program test
var 
  handle :HWND;
begin
  handle := FindWindow('TMySuperApp', nil);

  if IsWindow(handle) then
  begin 
       //app is running
       exit;
  end.

  Application.Initialize;
  Application.CreateForm(TMySuperApp, SuperApp);
  Application.Run;
end;

You can simply use FindWindow windows api function. In delphi class name of the window is the same as class name, you can redefine class name by overriding CreateParams function. To check if window exists add code before main window is created , before Application.Initialize;

Program test
var 
  handle :HWND;
begin
  handle := FindWindow('TMySuperApp', nil);

  if IsWindow(handle) then
  begin 
       //app is running
       exit;
  end.

  Application.Initialize;
  Application.CreateForm(TMySuperApp, SuperApp);
  Application.Run;
end;
疑心病 2024-07-19 07:10:33

我想在 Rob Kennedy 的出色回答(除了以下事实:最好用他的代码创建一个函数,而不是将所有内容复制到 DPR 文件中。您只需要两个参数,即互斥体,以及一个布尔值(互斥体应该是每个用户的还是系统范围的)。

答案并没有过多考虑互斥体的命名。 如果您希望通过 Inno Setup(也许还有其他安装工具)安装程序,则应该仔细选择名称,因为互斥体可用于让安装程序检查应用程序当前是否正在运行,并提醒用户他们应该关闭应用程序的所有实例。 如果您选择允许每个用户运行一个程序实例,您可能还需要创建第二个系统范围的互斥体,因为设置可能需要根本没有正在运行的应用程序实例,以便能够替换文件。 用于与 InnoSetup 安装程序同步的名称必须是硬编码的。

I'd like to add one point to the excellent answer by Rob Kennedy (apart from the fact that it would be best to make a function out of his code instead of copying everything into the DPR file. You only need two parameters, the name of the mutex, and a boolean whether the mutext should be per-user or system-wide).

The answer does not give much consideration to the naming of the mutex. If you expect your program to be installed via Inno Setup (and maybe other setup tools too) you should choose the name carefully, as the mutex can be used to have the setup program check whether the application is currently running, and alert the user that they should close all instances of the application. If you choose to allow one instance of the program per user you may need to create a second system-wide mutex too, as the setup may need to have no running instances of the application at all in order to be able to replace files. The name that is to be used for synchronization with an InnoSetup installer must be hard-coded.

不甘平庸 2024-07-19 07:10:33

我想说,您可以采用几种不同的策略。 但最简单的一个(而不是特定于平台的)是您自己建议的,即在程序开始时检查是否在一组特定位置创建了锁定文件。 如果此锁定文件存在,则另一个实例已经在运行,如果不存在,则没有另一个实例在运行。 当您的程序退出时,您将删除锁定文件。

然而,采用这种策略你会遇到另一个问题,如果你的程序崩溃了会发生什么? 锁定文件仍然存在,需要处理这种特殊情况。

另一种策略是系统范围的互斥解决方案,您可以在操作系统中注册您的存在(或者这也可能是自动完成的)。 当第二个实例尝试启动时,它会检查是否已经存在具有特定 ID 的活动进程。 如果它已经存在,第二个进程选择不启动,并且可以选择将第一个进程的窗口置于焦点中(如果相关进程拥有一个窗口)。

然而,该策略是特定于平台的,并且不同平台的实施方式会有所不同。

I would say that there are several different strategies that you can employ. But the easiest one (and not platform specific) is the one you yourself suggested, namely to, at the start of the program check to see if there is a lock file created in a set, specific location. If this lock file exists, then another instance is already running, if it doesn't exist, then there is not another instance running. When your program exits, you delete the lock file.

However, employing this strategy you have another problem, what happens if your program crashes? The lock file still remains, and this specific case need to be handled.

Another strategy is the system-wide mutex solution, where you register your presence within the operating system (or it's also plausible that this is done automagically). When a second instance then tries to start, it checks if there's already a process active with a specific ID. If it already exists, the second process chooses not to start, and optionally brings the first process' window in focus (if the process in question owns a window that is).

However, this strategy is platform specific, and the implementation will differ from platform to platform.

忆沫 2024-07-19 07:10:33

Controlling the number of application instances:

http://delphi.about.com/od/windowsshellapi/l/aa100703a.htm

终难愈 2024-07-19 07:10:33

如果您想同时停止执行您的应用多次(将代码放入*.dpr文件项目的强>)。
将在第二个应用程序运行后显示一条消息并立即停止它。

Forms,
  Unit1 in 'Unit1.pas' {Form1},
// add this units ....
TlHelp32,SysUtils,Windows,Dialogs;

{$R *.res}


function ProcessCount(const ExeName: String): Integer;
var
  ContinueLoop: BOOL;
  FSnapshotHandle: THandle;
  FProcessEntry32: TProcessEntry32;
begin
  FSnapshotHandle:= CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  FProcessEntry32.dwSize:= SizeOf(FProcessEntry32);
  ContinueLoop:= Process32First(FSnapshotHandle, FProcessEntry32);
  Result:= 0;
  while Integer(ContinueLoop) <> 0 do begin
    if ((UpperCase(ExtractFileName(FProcessEntry32.szExeFile)) =
      UpperCase(ExeName)) or (UpperCase(FProcessEntry32.szExeFile) =
      UpperCase(ExeName))) then Inc(Result);
    ContinueLoop:= Process32Next(FSnapshotHandle, FProcessEntry32);
  end;
  CloseHandle(FSnapshotHandle);
end;


begin
  if ProcessCount(ExtractFileName(Application.ExeName)) > 1 then begin
    MessageDlg('Application is already running!', mtError, [mbOK], 0);
    Application.Terminate;
  end else begin

  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
  end;

end.

If You want to stop execution your app more then once in the same time (put the code into *.dpr file of the project).
will show a message after second app will be running and stop it instantly .

Forms,
  Unit1 in 'Unit1.pas' {Form1},
// add this units ....
TlHelp32,SysUtils,Windows,Dialogs;

{$R *.res}


function ProcessCount(const ExeName: String): Integer;
var
  ContinueLoop: BOOL;
  FSnapshotHandle: THandle;
  FProcessEntry32: TProcessEntry32;
begin
  FSnapshotHandle:= CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  FProcessEntry32.dwSize:= SizeOf(FProcessEntry32);
  ContinueLoop:= Process32First(FSnapshotHandle, FProcessEntry32);
  Result:= 0;
  while Integer(ContinueLoop) <> 0 do begin
    if ((UpperCase(ExtractFileName(FProcessEntry32.szExeFile)) =
      UpperCase(ExeName)) or (UpperCase(FProcessEntry32.szExeFile) =
      UpperCase(ExeName))) then Inc(Result);
    ContinueLoop:= Process32Next(FSnapshotHandle, FProcessEntry32);
  end;
  CloseHandle(FSnapshotHandle);
end;


begin
  if ProcessCount(ExtractFileName(Application.ExeName)) > 1 then begin
    MessageDlg('Application is already running!', mtError, [mbOK], 0);
    Application.Terminate;
  end else begin

  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
  end;

end.
弥枳 2024-07-19 07:10:33

请参阅本单元(使用 CreateMutex): UiApp

此外,在此页面上,您可以阅读使用不同方法(互斥锁、FindWindows 等)进行这项工作的优点和缺点。

该单元具有在检测到此情况时激活应用程序的先前实例的解决方案。

问候并原谅我的英语不好。


Neftalí -德国埃斯特维兹-

See this unit (using CreateMutex): UiApp

Additionally at this page, you can read the advantages and disadvantages for to this work with differents methods (mutex, FindWindows,...).

This unit have the solution to activate the previos instance of the application when this is detected.

Regards and excuse-me for my bad english.


Neftalí -Germán Estévez-

生生不灭 2024-07-19 07:10:33

过去,我使用套接字来防止多个实例同时运行。 如果套接字正在使用中,则不要继续程序,如果可用,则让一切正常运行。

In the past, I've used a socket to prevent multiple instances from running at the same time. If the socket is in use, don't continue the program, if it's available let everything run as normal.

蘑菇王子 2024-07-19 07:10:33

很遗憾地提到这一点,但所有方法(ExeName、互斥体、信号量、管道名称、RAM 上的文件映射等)都不起作用,应用程序可以运行多次。

第一个是第一个:在提升模式下,RAM 上的文件映射看起来可以工作,但使用 ProcessExplorer,您可以手动关闭句柄,从而允许运行多个应用程序实例。

对于其余方法:多个用户可以复制应用程序(使用不同的exename)或将其安装在多个不同的位置......等等。

但最糟糕的部分是应用程序必须只运行一次,无论有多少本地用户已记录。

当应用程序在一个用户帐户中运行并且另一用户帐户登录完成时,所有方法都会失败,该应用程序可以在另一用户上运行,因此不会只有一个实例同时运行。

我认为避免多次的唯一方法是强制应用程序仅在提升模式下运行,然后它可以检查其他用户是否正在运行该应用程序。

在多用户中,我认为没有一种实用的方法不允许超过一个实例......因为映射的内存的句柄可以由第三方应用程序(如进程资源管理器)关闭。

我仍在寻找一种方法来允许每台计算机仅一个实例。

PD:我没有提到在不同的虚拟机中运行应用程序,因为如果没有服务器控制方法,这是不可能的......并且也没有提到服务器的使用,因为应用程序需要在没有网络访问的情况下运行,在本地运行(但是多个用户可以运行它,所希望的是一次仅在一个用户上运行并且一次仅运行一次,而不是同时运行两个实例,无论是同一用户还是不同用户)。

Sorry to mention this, but none of the methods (ExeName, Mutex, Semaphore, Piped Names, File Mapping on RAM, etc) work at all, the appllication can be run more than once.

First is First: in elevated mode it looks like File Mapping on RAM could work, but with ProcessExplorer you can manually close the handle and that allows to run more than one instance of the APP.

For the rest of methods: Multiple users can copy the app (with different exename) or install it in multiple different locations ... etc.

But the worst part is when the application must be run only once, no matter how many local users are logged.

All methods fails when APP is running in one user account and another user account logon is done, on that other user the app can be run, so no only one instance is running at the same time.

The only way i see to avoid more than once is to force the application to be run only in elevated mode and then it can check if another user is running the app.

In multi-user i see no practical way of not allowing more than one instance ... since the handle to the mapped ram can be closed by third part apps (like process explorer).

I am still searching for a way to allow only one instance per computer.

P.D.: I did not mention running the app inside different virtual machines, since that is directly impossible without a server control method ... and the use of a server is also not mentioned because the app need to be run without network access, runs localy (but multiple users can run it, what is desired is to only run at one user at a time and only once at a time, not two instances running at the same time, no matter same user or different users).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文