32

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

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

4

12 回答 12

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

于 2009-01-19T23:20:26.500 回答
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;
于 2009-01-20T08:39:31.520 回答
17

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

于 2009-01-19T23:18:01.763 回答
5

正常的解决方案是创建一个命名的、系统范围的 mutex

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

编辑:

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

于 2009-01-19T23:11:19.497 回答
5

您创建一个系统 互斥体

我没有 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);
于 2009-01-19T23:15:04.093 回答
2

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

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

于 2009-01-20T08:02:34.573 回答
1

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

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

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

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

于 2009-01-19T23:28:38.357 回答
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;
于 2009-01-19T23:52:16.310 回答
1

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

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

于 2012-02-23T12:11:11.983 回答
1

如果您想同时停止执行 您的应用程序不止一次将代码放入项目的*.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.
于 2018-10-10T16:56:19.243 回答
0

见本单元(使用 CreateMutex): UiApp

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

当检测到这一点时,该单元具有激活应用程序的 previos 实例的解决方案。

我的英语不好,请原谅我。


Neftalí -Germán Estévez-

于 2009-01-20T15:13:02.723 回答
-1

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

于 2009-01-19T23:37:55.887 回答