【问题标题】:Delphi Restricting a single instance of a program based on command line parametersDelphi 根据命令行参数限制程序的单个实例
【发布时间】:2014-12-08 17:58:44
【问题描述】:

我知道我以前做过,但似乎不记得是怎么做的。

我有一个程序,我已设置为使用可执行名称上的互斥锁运行单例。 单位 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.

【问题讨论】:

  • 应用程序foo.exe -a -b是否应该被视为等于foo.exe -b -afoo.exe -B -a
  • 互斥体名称的参数最多可以是MAX_PATH long,这还不够,例如通过约。 250 个字符长 (MAX_PATH - 'EPRmailer') 命令行参数。在这种情况下,您的互斥锁创建失败,但不是因为错误 ERROR_ALREADY_EXISTS 并且您将从函数返回错误值。为了防止这种情况,我会在这里考虑一个哈希(在对参数进行排序之后)。
  • 如果 Application.Title 足够独特,则可以使用它来代替 Application.ExeName,因为它要短得多并且不包含“\”字符。
  • 我看不出您的IsAppRunning(以当前形式)如何以您描述的方式使用。所以我怀疑当你上次使用它来强制执行基于参数的单实例时,它的实现方式略有不同。或者也许你改变了它以适应那个要求。

标签: delphi singleton delphi-xe6


【解决方案1】:

您使用了错误的方法。与其重命名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 documentation

【讨论】:

  • 如果您传递的值超过 MAX_PATH long InstanceName 值,此函数将始终返回 False。此外,PWideChar 演员表对旧的 Delphi 版本没有好处;-) 实际上,写 Result := (CreateMutex(nil, False, PChar(InstanceName)) &lt;&gt; 0) and (GetLastError = ERROR_ALREADY_EXISTS); 就足够了,但仍然存在超过互斥体名称长度的风险。
  • 另外,它只有在你调用IsAppRunning() 一次时才会起作用,因为互斥锁没有被关闭,所以后续调用会检测到ERROR_ALREADY_EXISTS
  • 嗯,命令行参数的反斜杠限制更糟糕。
  • 这种方法的问题是您将进程文件名用作互斥锁名称的一部分。想想如果用户在启动应用程序之前简单地复制 EXE 文件和/或更改其文件名会发生什么。然后将允许运行多个实例。最好使用更独特的东西,比如 guid。
  • @RemyLebeau 提醒:OP 确实希望允许多个实例。只是没有使用相同的命令行参数。我看不到 GUID 在这里有什么帮助? Dalija 指出了如何稍微改变设计以便IsAppRunning 可以支持要求。但除此之外:OP 可能应该对参数进行排序(如果 Rufo 爵士评论的答案是“是”)并对它们进行哈希处理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-25
  • 1970-01-01
  • 2012-02-05
  • 1970-01-01
  • 1970-01-01
  • 2015-02-23
相关资源
最近更新 更多