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

Posted

技术标签:

【中文标题】如何判断我的程序的另一个实例是不是已经在运行?【英文标题】:How can I tell if another instance of my program is already running?如何判断我的程序的另一个实例是否已经在运行? 【发布时间】:2010-10-02 08:28:53 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

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

如果您设法创建它,那么您就是一个正在运行的应用程序。 如果你不这样做,你就知道有一个不同的。

编辑:

我没有提供代码,因为我不了解 Delphi。如果有帮助,我可以提供 C# 代码。

【讨论】:

【参考方案2】:

您创建一个系统 互斥体

我没有 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);

【讨论】:

你有一个竞争条件。始终调用 CreateMutex,而不是 OpenMutex。然后使用 GetLastError 找出谁赢得了比赛。 @Rob Kennedy,你是对的。几乎立即启动程序的两个实例的代码可能会失败。你的建议是正确的。 这个答案应该被删除。【参考方案3】:

The all-mighty JVCL 有一个用于此目的的组件。请参阅“TJvAppInstances”。

【讨论】:

【参考方案4】:

正如 Jon 最初建议的那样,您可以尝试创建互斥锁。致电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 的任何其他位置调用它。

【讨论】:

应用退出时是否需要关闭mutex句柄? 不,因为像所有其他操作系统资源一样的互斥锁在拥有进程终止时被释放/释放/不管。与应用程序崩溃后可能仍然存在的临时文件相比,这也是该解决方案的一大优势。 强烈建议在互斥锁名称前添加“Global\\”,以便互斥锁是全局范围的。如果您作为另一个会话登录或将应用程序作为服务运行,则可以在单独的非冲突命名空间中创建非全局互斥锁。 爱因斯坦,是的,如果 Arthur 想要的话,全局互斥锁是很好的。不过,根据他之前的一个问题,我认为不是。我记得,Win2k 之前的版本不允许名称中包含反斜杠,因此您也不一定总是使用相同的名称。 Sam,操作系统会为您完成。但您可能想提前关闭它。如果当前实例正在关闭,它将无法接受来自另一个实例的消息。到那时,“新”实例可能会更好地承担其“该”实例的角色并让第一个实例死亡。【参考方案5】:

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

但是,采用这种策略你还有另一个问题,如果你的程序崩溃了怎么办?锁定文件仍然存在,需要处理这种特殊情况。

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

但是,此策略是特定于平台的,并且实施会因平台而异。

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

您可以简单地使用 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;

【讨论】:

这个使用什么单位 HWND 和 findwindow 没有被识别。如果我的“窗口”隐藏在图标托盘中,这也会起作用吗? 它将需要“Windows”。这是对 Mutex 的有用补充,因为您可以将前一个窗口置于前面。但是如果用户连续打开你的应用两次,第一次可能还没有打开它的窗口,因此可能会错过。【参考方案8】:

我想在excellent answer by Rob Kennedy 上加一点(除了最好从他的代码中创建一个函数而不是将所有内容复制到 DPR 文件中。你只需要两个参数,互斥体的名称,以及互斥体应该是每个用户还是系统范围的布尔值)。

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

【讨论】:

【参考方案9】:

您可以创建一个信号量并停止执行(将代码放入您的 *.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;

EDIT(添加了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;

【讨论】:

这很好,但我无法让“RestoreWindow('TMyApplication')”工作我必须实现这个还是需要使用我目前没有使用的单元 阅读“procedure RestoreWindow(WinHandle : HWND);”因为我需要首先运行的应用程序的句柄,所以我需要获取保持信号量的应用程序的句柄。不确定这是一个新问题还是一个扩展 Ups ...忘记添加RestoreWindow方法,...将编辑答案 不知道我是否弄错了 AformName 但它没有打开旧实例。 我认为我们遇到了范围蔓延的情况。您在答案的 RestoreWindow 部分上停留了很多。如果您想了解更多详细信息,我建议您查找或提出另一个问题:“如何让我的程序的一个实例激活另一个实例?”【参考方案10】:

查看这个单元(使用 CreateMutex):UiApp

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

当检测到这种情况时,此单元具有激活应用程序的前一个实例的解决方案。

请原谅我的英语不好。


内夫塔利 - Germán Estévez-

【讨论】:

【参考方案11】:

控制应用实例的数量:

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

【讨论】:

【参考方案12】:

如果您想停止执行您的应用多次同一时间(将代码放入*.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.

【讨论】:

以上是关于如何判断我的程序的另一个实例是不是已经在运行?的主要内容,如果未能解决你的问题,请参考以下文章

如何使我的应用程序单例应用程序? [复制]

如何编写一个SHELL脚本,然后在发现我的程序DOWN掉之后,运行我的另一个JAVA程序。

如何检查 WPF 应用程序是不是已在运行? [复制]

如何确保我的应用程序只有一个实例运行?

如何找到一个实例的类,然后在objective-c中声明该类的另一个实例?

当我在另一个视图中使用 CoreData 保存新数据时如何更新模型的另一个实例