Delphi基于命令行参数限制程序单一实例

5

我知道以前做过这件事,但似乎想不起来怎么做了。

我有一个程序,我已经设置为使用可执行文件名称上的互斥体来运行单例模式。

单位 GlobalSU;

interface
 function IsAppRunning: Boolean;

implementation

uses
 Windows, SysUtils, Forms;

function IsAppRunning: Boolean;
var
 rtn : Cardinal;
begin
  result := False;
  CreateMutex(nil, False, PWideChar(ExtractFileName(Application.ExeName)));
  rtn := GetLastError;
  if rtn = ERROR_ALREADY_EXISTS then
   result := True;
end;

该程序接受特定的命令行参数来指示要执行的数据。我不希望使用相同的命令行参数运行超过一个程序实例,但我想能够以不同的参数启动第二个实例。
大约一年前,我曾这样做过,但现在不记得具体方法了。我通过在DPR中使用命令行参数修改名称,然后使用互斥体进行测试。
我尝试重命名Application.ExeName,但它是只读的,所以我必须更改其他内容。
下面的代码不能编译,但是添加了以说明我要做什么。 顺便说一下,“##”始终是第三个参数的前两个字符,但我会用正则表达式进行测试。
program EPRmailer;

uses
  Vcl.Forms,
  uMainMailer in 'uMainMailer.pas' {frmMainMailer},
  configXML in 'configXML.pas',
  GlobalSU in 'GlobalSU.pas',
  CVUtils in 'CVUtils.pas',
  QMConst in 'QMConst.pas',
  ServerAttachmentDMu in 'ServerAttachmentDMu.pas';

{$R *.res}
 var
   i : integer;
begin

  for i := 0 to ParamCount do
  if TestParam('##', ParamStr(i)) then
  Application.ExeName := Application.ExeName + '-' + ParamStr(i);

  if IsAppRunning then exit;

  Application.Initialize;
  ReportMemoryLeaksOnShutdown := DebugHook <> 0;
  Application.MainFormOnTaskbar := false;
  Application.CreateForm(TfrmMainMailer, frmMainMailer);
  frmMainMailer.RunEPR;

end.

1
应该将应用程序 foo.exe -a -b 视为等同于 foo.exe -b -afoo.exe -B -a 吗? - Sir Rufo
2
互斥体名称的参数最多可以达到MAX_PATH长,这对于传递大约250个字符长(MAX_PATH - 'EPRmailer')的命令行参数是不够的。在这种情况下,您的互斥体创建将失败,但不是因为错误ERROR_ALREADY_EXISTS,并且您将从函数返回错误值。为了防止这种情况,我会考虑在此处使用哈希(在排序参数之后)。 - TLama
如果Application.Title足够独特,它可以代替Application.ExeName使用,因为它更短且不包含''字符。 - Dalija Prasnikar
我无法看出你的IsAppRunning(以其当前形式)如何能按照你所描述的方式使用。因此,我怀疑当你最后一次使用它来根据参数实现单实例时,它是以稍微不同的方式实现的。或者说,你可能改变了它以适应那个要求。 - Disillusioned
1个回答

8
您正在使用错误的方法。而不是重命名 Application.ExeName,您应该向测试重复应用程序的函数发送可配置字符串。
function CreateSingleInstance(const InstanceName: string): boolean;
var
  MutexHandle: THandle;
begin
  MutexHandle := CreateMutex(nil, false, PChar(InstanceName));
  // if MutexHandle created check if already exists
  if (MutexHandle <> 0) then
    begin
      if GetLastError = ERROR_ALREADY_EXISTS then
        begin
          Result := false;
          CloseHandle(MutexHandle);
        end
      else Result := true;
    end
  else Result := false;
end;

var
  MyInstanceName: string;
begin
  Application.Initialize;
  // Initialize MyInstanceName here
  ...
  if CreateSingleInstance(MyInstanceName) then
    begin
      // Form creation 
      ...
    end
  else Application.Terminate;
end. 

函数CreateSingleInstance只能在应用程序中使用一次,因为它分配的互斥量将保持活动状态,直到应用程序终止,然后Windows将自动关闭互斥句柄。

注意: 如果 MyInstanceName 超过 MAX_PATH 个字符或包含反斜杠 '\' 字符,则函数将失败。

CreateMutex 文档


如果您传递超过 MAX_PATH 长度的 InstanceName 值,此函数将始终返回 False。另外,对于旧版 Delphi,PWideChar 强制转换是不好的;-)实际上,只需编写 Result := (CreateMutex(nil, False, PChar(InstanceName)) <> 0) and (GetLastError = ERROR_ALREADY_EXISTS); 即可,但仍存在超出互斥体名称长度的风险。 - TLama
1
哦,那个反斜杠限制对于命令行参数来说更糟糕。 - TLama
MyInstanceName := ExtractFileName(Application.ExeName) + '-' + ParamStr(i);如果创建单个实例(MyInstanceName),则执行以下操作: begin Application.CreateForm(TfrmMainMailer, frmMainMailer); //frmMainMailer.RunEPR; showMessage(MyInstanceName+' 正在运行'); end 否则,终止应用程序。我喜欢这种方式,因为我想保护进程,即使调用不同目录中的另一个程序(具有相同的##CompanyName)。谢谢大家。 - Meta Mussel
2
该方法的问题在于您将进程文件名用作互斥体名称的一部分。如果用户在启动应用程序之前仅复制EXE文件和/或更改其文件名,则会发生什么。然后允许运行多个实例。最好使用更唯一的内容,例如GUID。 - Remy Lebeau
1
@RemyLebeau 提醒:OP希望允许多个实例,但不希望使用相同的命令行参数。我不明白GUID如何在这里有所帮助?Dalija已经指出如何稍微更改设计,以便IsAppRunning可以支持要求。但是除此之外:如果Sir Rufo的评论回答是“是”,OP可能应该对参数进行排序并进行哈希处理。 - Disillusioned
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接