【问题标题】:Passing a string to an already running instance of an application将字符串传递给已运行的应用程序实例
【发布时间】:2008-10-21 03:52:43
【问题描述】:

我有一个应用程序,它检测是否有另一个应用程序实例正在运行,如果找到则退出。这部分似乎工作可靠。我的应用程序需要一个命令行参数,我想将它传递给已经运行的实例。到目前为止,我有以下代码:

Project1.dpr

program Project1;

uses
  ...
  AppInstanceControl in 'AppInstanceControl.pas';

  if not AppInstanceControl.RestoreIfRunning(Application.Handle) then
  begin
    Application.Initialize;
    Application.MainFormOnTaskbar := True;
    Application.CreateForm(TFormMain, FormMain);
    Application.Run;
  end;

end.

AppInstanceControl.pas

{ 基于 Zarko Gajic 在http://delphi.about.com/library/code/ncaa100703a.htm} 找到的代码

unit AppInstanceControl;

interface

uses
  Windows,
  SysUtils;

function RestoreIfRunning(const AAppHandle: THandle; const AMaxInstances: integer = 1): boolean;

implementation

uses
  Messages;

type
  PInstanceInfo = ^TInstanceInfo;
  TInstanceInfo = packed record
    PreviousHandle: THandle;
    RunCounter: integer;
  end;

var
  UMappingHandle: THandle;
  UInstanceInfo: PInstanceInfo;
  UMappingName: string;

  URemoveMe: boolean = True;

function RestoreIfRunning(const AAppHandle: THandle; const AMaxInstances: integer = 1): boolean;
var
  LCopyDataStruct : TCopyDataStruct;
begin
  Result := True;

  UMappingName := StringReplace(
                   ParamStr(0),
                   '\',
                   '',
                   [rfReplaceAll, rfIgnoreCase]);

  UMappingHandle := CreateFileMapping($FFFFFFFF,
                                     nil,
                                     PAGE_READWRITE,
                                     0,
                                     SizeOf(TInstanceInfo),
                                     PChar(UMappingName));

  if UMappingHandle = 0 then
    RaiseLastOSError
  else
  begin
    if GetLastError <> ERROR_ALREADY_EXISTS then
    begin
      UInstanceInfo := MapViewOfFile(UMappingHandle,
                                    FILE_MAP_ALL_ACCESS,
                                    0,
                                    0,
                                    SizeOf(TInstanceInfo));

      UInstanceInfo^.PreviousHandle := AAppHandle;
      UInstanceInfo^.RunCounter := 1;

      Result := False;
    end
    else //already runing
    begin
      UMappingHandle := OpenFileMapping(
                                FILE_MAP_ALL_ACCESS, 
                                False, 
                                PChar(UMappingName));
      if UMappingHandle <> 0 then
      begin
        UInstanceInfo := MapViewOfFile(UMappingHandle,
                                      FILE_MAP_ALL_ACCESS,
                                      0,
                                      0,
                                      SizeOf(TInstanceInfo));

        if UInstanceInfo^.RunCounter >= AMaxInstances then
        begin
          URemoveMe := False;

          if IsIconic(UInstanceInfo^.PreviousHandle) then
            ShowWindow(UInstanceInfo^.PreviousHandle, SW_RESTORE);
          SetForegroundWindow(UInstanceInfo^.PreviousHandle);
        end
        else
        begin
          UInstanceInfo^.PreviousHandle := AAppHandle;
          UInstanceInfo^.RunCounter := 1 + UInstanceInfo^.RunCounter;

          Result := False;
        end
      end;
    end;
  end;
  if (Result) and (CommandLineParam <> '') then
  begin
    LCopyDataStruct.dwData := 0; //string
    LCopyDataStruct.cbData := 1 + Length(CommandLineParam);
    LCopyDataStruct.lpData := PChar(CommandLineParam);

    SendMessage(UInstanceInfo^.PreviousHandle, WM_COPYDATA, Integer(AAppHandle), Integer(@LCopyDataStruct));
  end;
end; (*RestoreIfRunning*)

initialization

finalization
  //remove this instance
  if URemoveMe then
  begin
    UMappingHandle := OpenFileMapping(
                        FILE_MAP_ALL_ACCESS, 
                        False, 
                        PChar(UMappingName));
    if UMappingHandle <> 0 then
    begin
      UInstanceInfo := MapViewOfFile(UMappingHandle,
                                  FILE_MAP_ALL_ACCESS,
                                  0,
                                  0,
                                  SizeOf(TInstanceInfo));

      UInstanceInfo^.RunCounter := -1 + UInstanceInfo^.RunCounter;
    end
    else
      RaiseLastOSError;
  end;

  if Assigned(UInstanceInfo) then UnmapViewOfFile(UInstanceInfo);
  if UMappingHandle <> 0 then CloseHandle(UMappingHandle);

end.

并在主窗体单元中:

procedure TFormMain.WMCopyData(var Msg: TWMCopyData);
var
  LMsgString: string;
begin
  Assert(Msg.CopyDataStruct.dwData = 0);
  LMsgString := PChar(Msg.CopyDataStruct.lpData);

  //do stuff with the received string

end;

我很确定问题是我试图将消息发送到正在运行的应用程序实例的句柄,但试图在主窗体上处理消息。我想我有两个选择:

A) 从应用程序的句柄以某种方式获取其主窗体的句柄并将消息发送到那里。

B) 在应用程序而不是主窗体级别处理接收消息。

我也不确定该怎么做。有更好的方法吗?

谢谢。

【问题讨论】:

    标签: delphi winapi


    【解决方案1】:

    如果您使用 WM_COPYDATA,则无需创建文件映射。这就是 WM_COPYDATA 的全部意义所在 - 它为您完成了所有这些工作。

    发送字符串

    procedure IPCSendMessage(target: HWND;  const message: string);
    var
      cds: TCopyDataStruct;
    begin
      cds.dwData := 0;
      cds.cbData := Length(message) * SizeOf(Char);
      cds.lpData := Pointer(@message[1]);
    
      SendMessage(target, WM_COPYDATA, 0, LPARAM(@cds));
    end;
    

    接收字符串

    procedure TForm1.WMCopyData(var msg: TWMCopyData);
    var
      message: string;
    begin
      SetLength(message, msg.CopyDataStruct.cbData div SizeOf(Char));
      Move(msg.CopyDataStruct.lpData^, message[1], msg.CopyDataStruct.cbData);
    
      // do something with the message e.g.
      Edit1.Text := message;
    end;
    

    根据需要修改以发送其他数据。

    【讨论】:

    • 文件映射的目的是确保我的应用程序只有 AMaxInstances 正在运行(在我的情况下为 1 个实例)。将消息发送到正确的目标是我遇到的问题。不过还是谢谢。
    • 除非我错过了什么?
    • 当一个应用程序作为服务运行而另一个应用程序从默认窗口状态(例如在登录的用户会话中)运行时,WM-COPYDATA 将不起作用。使用内核对象更加健壮并且支持这一点。
    【解决方案2】:

    事实证明,这确实很难可靠地做到。我只花了两个小时试图从一个五分钟的解决方案中找出所有的小故障:(不过现在似乎可以工作了。

    以下代码在 D2007 中适用于新式 (MainFormOnTaskbar = True) 和旧式方法。因此,我相信它也适用于较旧的 Delphi 版本。它在应用程序处于最小化和正常状态下进行了测试。

    测试项目可在http://17slon.com/krama/ReActivate.zip 获得(小于 3 KB)。

    为了在线阅读、索引和备份,所有重要的单元都附在下面。

    主程序

    program ReActivate;
    
    uses
      Forms,
      GpReActivator, 
      raMain in 'raMain.pas' {frmReActivate};
    
    {$R *.res}
    
    begin
       if ReactivateApplication(TfrmReActivate, WM_REACTIVATE) then
        Exit;
    
      Application.Initialize;
      Application.MainFormOnTaskbar := True;
    //  Application.MainFormOnTaskbar := False;
      Application.CreateForm(TfrmReActivate, frmReActivate);
      Application.Run;
    end.
    

    主机

    unit raMain;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs;
    
    const
      WM_REACTIVATE = WM_APP;
    
    type
      TfrmReActivate = class(TForm)
      private
      public
        procedure ReActivate(var msg: TMessage); message WM_REACTIVATE;
      end;
    
    var
      frmReActivate: TfrmReActivate;
    
    implementation
    
    {$R *.dfm}
    
    uses
      GpReactivator;
    
    { TfrmReActivate }
    
    procedure TfrmReActivate.ReActivate(var msg: TMessage);
    begin
      GpReactivator.Activate;
    end;                         
    
    end.
    

    辅助单元

    unit GpReActivator;
    
    interface
    
    uses
      Classes;
    
    procedure Activate;
    function ReActivateApplication(mainFormClass: TComponentClass; reactivateMsg: cardinal):
      boolean;
    
    implementation
    
    uses
      Windows,
      Messages,
      SysUtils,
      Forms;
    
    type
      TProcWndInfo = record
        ThreadID     : DWORD;
        MainFormClass: TComponentClass;
        FoundWindow  : HWND;
      end; { TProcWndInfo }
      PProcWndInfo = ^TProcWndInfo;
    
    var
      fileMapping      : THandle;
      fileMappingResult: integer;
    
    function ForceForegroundWindow(hwnd: THandle): boolean;
    var
      foregroundThreadID: DWORD;
      thisThreadID      : DWORD;
      timeout           : DWORD;
    begin
      if GetForegroundWindow = hwnd then
        Result := true
      else begin
    
        // Windows 98/2000 doesn't want to foreground a window when some other
        // window has keyboard focus
    
        if ((Win32Platform = VER_PLATFORM_WIN32_NT) and (Win32MajorVersion > 4)) or
          ((Win32Platform = VER_PLATFORM_WIN32_WINDOWS) and
          ((Win32MajorVersion > 4) or ((Win32MajorVersion = 4) and (Win32MinorVersion > 0)))) then
        begin
    
          // Code from Karl E. Peterson, www.mvps.org/vb/sample.htm
          // Converted to Delphi by Ray Lischner
          // Published in The Delphi Magazine 55, page 16
    
          Result := false;
          foregroundThreadID := GetWindowThreadProcessID(GetForegroundWindow,nil);
          thisThreadID := GetWindowThreadPRocessId(hwnd,nil);
          if AttachThreadInput(thisThreadID, foregroundThreadID, true) then begin
            BringWindowToTop(hwnd); //IE 5.5 - related hack
            SetForegroundWindow(hwnd);
            AttachThreadInput(thisThreadID, foregroundThreadID, false);
            Result := (GetForegroundWindow = hwnd);
          end;
          if not Result then begin
    
            // Code by Daniel P. Stasinski <dannys@karemor.com>
    
            SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, @timeout, 0);
            SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, TObject(0), SPIF_SENDCHANGE);
            BringWindowToTop(hwnd); //IE 5.5 - related hack
            SetForegroundWindow(hWnd);
            SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, TObject(timeout), SPIF_SENDCHANGE);
          end;
        end
        else begin
          BringWindowToTop(hwnd); //IE 5.5 - related hack
          SetForegroundWindow(hwnd);
        end;
    
        Result := (GetForegroundWindow = hwnd);
      end;
    end; { ForceForegroundWindow }
    
    procedure Activate;
    begin
      if (Application.MainFormOnTaskBar and (Application.MainForm.WindowState = wsMinimized))
         or
         ((not Application.MainFormOnTaskBar) and (not IsWindowVisible(Application.MainForm.Handle)))
      then
        Application.Restore
      else
        Application.BringToFront;
      ForceForegroundWindow(Application.MainForm.Handle);
    end; { Activate }
    
    function IsTopDelphiWindow(wnd: HWND): boolean;
    var
      parentWnd: HWND;
      winClass  : array [0..1024] of char;
    begin
      parentWnd := GetWindowLong(wnd, GWL_HWNDPARENT);
      Result :=
        (parentWnd = 0)
        or
        (GetWindowLong(parentWnd, GWL_HWNDPARENT) = 0) and
        (GetClassName(parentWnd, winClass, SizeOf(winClass)) <> 0) and
        (winClass = 'TApplication');
    end; { IsTopDelphiWindow }
    
    function EnumGetProcessWindow(wnd: HWND; userParam: LPARAM): BOOL; stdcall;
    var
      procWndInfo: PProcWndInfo;
      winClass   : array [0..1024] of char;
    begin
      procWndInfo := PProcWndInfo(userParam);
      if (GetWindowThreadProcessId(wnd, nil) = procWndInfo.ThreadID) and
         (GetClassName(wnd, winClass, SizeOf(winClass)) <> 0) and
         IsTopDelphiWindow(wnd) and
         (string(winClass) = procWndInfo.MainFormClass.ClassName) then
      begin
        procWndInfo.FoundWindow := Wnd;
        Result := false;
      end
      else
        Result := true;
    end; { EnumGetProcessWindow }
    
    function GetThreadWindow(threadID: cardinal; mainFormClass: TComponentClass): HWND;
    var
      procWndInfo: TProcWndInfo;
    begin
      procWndInfo.ThreadID := threadID;
      procWndInfo.MainFormClass := mainFormClass;
      procWndInfo.FoundWindow := 0;
      EnumWindows(@EnumGetProcessWindow, LPARAM(@procWndInfo));
      Result := procWndInfo.FoundWindow;
    end; { GetThreadWindow }
    
    function ReActivateApplication(mainFormClass: TComponentClass; reactivateMsg: cardinal):
      boolean;
    var
      mappingData: PDWORD;
    begin
      Result := false;
      if fileMappingResult = NO_ERROR then begin // first owner
        mappingData := MapViewOfFile(fileMapping, FILE_MAP_WRITE, 0, 0, SizeOf(DWORD));
        Win32Check(assigned(mappingData));
        mappingData^ := GetCurrentThreadID;
        UnmapViewOfFile(mappingData);
      end
      else if fileMappingResult = ERROR_ALREADY_EXISTS then begin // app already started
        mappingData := MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, SizeOf(DWORD));
        if mappingData^ <> 0 then begin // 0 = race condition
          PostMessage(GetThreadWindow(mappingData^, mainFormClass), reactivateMsg, 0, 0);
          Result := true;
        end;
        UnmapViewOfFile(mappingData);
        Exit;
      end
      else
        RaiseLastWin32Error;
    end; { ReActivateApplication }
    
    initialization
      fileMapping := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0,
        SizeOf(DWORD), PChar(StringReplace(ParamStr(0), '\', '', [rfReplaceAll, rfIgnoreCase])));
      Win32Check(fileMapping <> 0);
      fileMappingResult := GetLastError;
    finalization
      if fileMapping <> 0 then
        CloseHandle(fileMapping);
    end.
    

    所有代码都发布到公共领域,无需考虑许可即可使用。

    【讨论】:

    • 我刚刚注意到问题是“如何传递字符串”。傻我。您的 copydata 很好,但您应该使用与上述相同的技术从线程 ID 获取主窗口句柄。
    • 看起来它仍然可靠地完成了它所要做的事情。我会将您的一些方法与我的回答结合起来。感谢您的帮助。
    【解决方案3】:

    我最终将 MainForm 的句柄保存到文件映射中的 InstanceInfo 记录中,然后将消息发送到前一个实例的主窗体句柄(如果有的话)。

    在项目dpr中:

      if not AppInstanceControl.RestoreIfRunning(Application.Handle) then
      begin
        Application.Initialize;
        Application.MainFormOnTaskbar := True;
        Application.CreateForm(TFormMain, FormMain);
        SetRunningInstanceMainFormHandle(FormMain.Handle);
        Application.Run;
      end else
        SendMsgToRunningInstanceMainForm('Message string goes here');
    

    AppInstanceControl.pas

    type
      PInstanceInfo = ^TInstanceInfo;
      TInstanceInfo = packed record
        PreviousHandle: THandle;
        PreviousMainFormHandle: THandle;
        RunCounter: integer;
      end;
    
    procedure SetRunningInstanceMainFormHandle(const AMainFormHandle: THandle);
    begin
      UMappingHandle := OpenFileMapping(
                                FILE_MAP_ALL_ACCESS,
                                False,
                                PChar(UMappingName));
      if UMappingHandle <> 0 then
      begin
        UInstanceInfo := MapViewOfFile(UMappingHandle,
                                      FILE_MAP_ALL_ACCESS,
                                      0,
                                      0,
                                      SizeOf(TInstanceInfo));
    
        UInstanceInfo^.PreviousMainFormHandle := AMainFormHandle;
      end;
    end;
    
    procedure SendMsgToRunningInstanceMainForm(const AMsg: string);
    var
      LCopyDataStruct : TCopyDataStruct;
    begin
      UMappingHandle := OpenFileMapping(
                                FILE_MAP_ALL_ACCESS,
                                False,
                                PChar(UMappingName));
      if UMappingHandle <> 0 then
      begin
        UInstanceInfo := MapViewOfFile(UMappingHandle,
                                      FILE_MAP_ALL_ACCESS,
                                      0,
                                      0,
                                      SizeOf(TInstanceInfo));
    
    
        LCopyDataStruct.dwData := 0; //string
        LCopyDataStruct.cbData := 1 + Length(AMsg);
        LCopyDataStruct.lpData := PChar(AMsg);
    
        SendMessage(UInstanceInfo^.PreviousMainFormHandle, WM_COPYDATA, Integer(Application.Handle), Integer(@LCopyDataStruct));
      end;
    end;
    

    这似乎工作可靠。我打算发布完整的源代码,但我想合并一些 gabr 的代码,这些代码看起来更可靠地首先将焦点设置到正在运行的实例上。

    【讨论】:

      【解决方案4】:

      为什么不使用 DDE? 看看这个搜索返回的链接:http://www.google.com/search?q=delphi+dde

      【讨论】:

      • 有趣。看来我今晚要读书了。我最终可能会使用 DDE,但我仍然希望使用我开始使用的方法来让事情顺利进行。
      猜你喜欢
      • 2017-05-30
      • 2014-09-28
      • 2015-02-12
      • 2016-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-16
      相关资源
      最近更新 更多