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

时间:2009-01-19 23:07:21

标签: delphi persistence mutex instance semaphore

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

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

12 个答案:

答案 0 :(得分:39)

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的任何其他地方调用它。

答案 1 :(得分:18)

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

答案 2 :(得分:17)

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

答案 3 :(得分:6)

您创建系统 互斥

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

答案 4 :(得分:5)

正常的解决方案是创建一个名为“系统范围 互斥锁

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

编辑:

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

答案 5 :(得分:2)

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

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

答案 6 :(得分:1)

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

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

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

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

答案 7 :(得分:1)

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

答案 8 :(得分:1)

控制应用程序实例的数量:

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

答案 9 :(得分:0)

查看此单元(使用CreateMutex):UiApp

此外,在此页面中,您可以使用不同的方法(mutex,FindWindows,...)了解此工作的优缺点。

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

关心我的坏英语。


Neftalí-GermánEstévez -

答案 10 :(得分:0)

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

答案 11 :(得分:-1)

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