You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将启动参数传递给Delphi FMX已运行的应用实例

单实例带参数传递实现方案

你当前代码的核心问题是Mutex判断逻辑写反了:MutexErr = ERROR_SUCCESS代表当前是第一个启动的实例,不需要发消息,直接处理参数或者启动主窗体即可。只有当Mutex创建返回ERROR_ALREADY_EXISTS时,才代表已有实例在运行,需要将参数发送给已运行实例后退出当前进程。

1. 项目入口(.dpr)逻辑修正

var
  AppMutex: TMutex;
  MutexErr: DWORD;
  HMainWnd: HWND;
  // 发送WM_COPYDATA需要的变量
  CharCount: Integer;
  Data: PChar;
  PData: PChar;
  CopyDataStruct: TCopyDataStruct;
  i: Integer;
const
  // 全局唯一Mutex名称,替换为你自己应用的唯一标识,避免重名
  UNIQUE_MUTEX_NAME = 'MyFMXApp_Unique_Mutex_20240520';
  // 唯一窗口类名,用于查找已运行的主窗体
  UNIQUE_WINDOW_CLASS = 'MyFMXApp_MainWindow_Class';
  // COPYDATA安全校验标识,避免其他程序恶意发消息
  cCopyDataSecurityToken = 12345678;
begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;

  // 创建全局Mutex
  AppMutex := TMutex.Create(nil, False, UNIQUE_MUTEX_NAME);
  MutexErr := GetLastError;

  try
    if MutexErr = ERROR_ALREADY_EXISTS then
    begin
      // 已有实例运行,查找主窗口句柄
      HMainWnd := FindWindow(PChar(UNIQUE_WINDOW_CLASS), nil);
      if (HMainWnd <> 0) and (ParamCount > 0) then
      begin
        // 打包所有启动参数
        CharCount := 0;
        for i := 1 to ParamCount do
          Inc(CharCount, Length(ParamStr(i)) + 1);
        Inc(CharCount); // 末尾多一个#0标识参数结束

        Data := StrAlloc(CharCount);
        try
          PData := Data;
          for i := 1 to ParamCount do
          begin
            StrPCopy(PData, ParamStr(i));
            Inc(PData, Length(ParamStr(i)) + 1);
          end;
          PData^ := #0;

          CopyDataStruct.cbData := CharCount * SizeOf(Char);
          CopyDataStruct.lpData := Data;
          CopyDataStruct.dwData := cCopyDataSecurityToken;

          // 发送参数给已运行实例
          SendMessage(HMainWnd, WM_COPYDATA, 0, LPARAM(@CopyDataStruct));
        finally
          StrDispose(Data);
        end;
      end;
      // 直接退出当前进程
      Exit;
    end;

    // 第一个实例正常启动流程
    if ParamCount > 0 then
    begin
      // 第一个实例带参数启动时,直接在这里处理你的业务逻辑
      // 比如根据参数启动其他程序之类的
    end;
    Application.CreateForm(TfrmMain, frmMain);
    Application.Run;
  finally
    AppMutex.Free;
  end;
end.

2. 主窗体添加消息监听处理

FMX框架不能直接用VCL的WndProc重写方式,需要手动修改窗口类名并绑定消息处理函数:

unit MainFrm;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, Winapi.Windows,
  Winapi.Messages;

type
  TfrmMain = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FMsgWnd: HWND;
    // WM_COPYDATA消息处理函数
    procedure WMCopyData(var Msg: TMessage); message WM_COPYDATA;
  public
  end;

const
  // 和dpr里的定义保持一致
  UNIQUE_WINDOW_CLASS = 'MyFMXApp_MainWindow_Class';
  cCopyDataSecurityToken = 12345678;

var
  frmMain: TfrmMain;

implementation

{$R *.fmx}

procedure TfrmMain.FormCreate(Sender: TObject);
var
  ClassInfo: WNDCLASSEX;
begin
  // 修改主窗口的类名,方便后续FindWindow查找
  if GetClassInfoEx(HInstance, UNIQUE_WINDOW_CLASS, ClassInfo) = 0 then
  begin
    FillChar(ClassInfo, SizeOf(ClassInfo), 0);
    ClassInfo.cbSize := SizeOf(ClassInfo);
    ClassInfo.lpfnWndProc := @DefWindowProc;
    ClassInfo.hInstance := HInstance;
    ClassInfo.lpszClassName := PChar(UNIQUE_WINDOW_CLASS);
    RegisterClassEx(ClassInfo);
  end;
  SetWindowLongPtr(Handle, GWL_STYLE, GetWindowLongPtr(Handle, GWL_STYLE) or WS_CHILD);
  SetClassLongPtr(Handle, GCLP_WNDPROC, LONG_PTR(@DefWindowProc));
  SetWindowLongPtr(Handle, GWL_ID, 0);
  SetWindowLongPtr(Handle, GWL_HINSTANCE, LONG_PTR(HInstance));
  SetClassName(Handle, PChar(UNIQUE_WINDOW_CLASS));

  // 绑定WM_COPYDATA消息处理
  FMsgWnd := AllocateHWnd(WMCopyData);
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  if FMsgWnd <> 0 then
    DeallocateHWnd(FMsgWnd);
end;

procedure TfrmMain.WMCopyData(var Msg: TMessage);
var
  CopyData: PCopyDataStruct;
  Params: TArray<string>;
  P: PChar;
begin
  CopyData := PCopyDataStruct(Msg.LParam);
  // 先校验安全标识,过滤其他程序发来的恶意消息
  if CopyData.dwData <> cCopyDataSecurityToken then
  begin
    Msg.Result := 0;
    Exit;
  end;

  // 解析收到的参数
  P := PChar(CopyData.lpData);
  while P^ <> #0 do
  begin
    Params := Params + [string(P)];
    Inc(P, StrLen(P) + 1);
  end;

  // 这里处理你的业务逻辑,比如根据收到的参数启动其他程序
  // 示例:ShowMessage('收到参数:' + string.Join(',', Params));

  Msg.Result := 1;
end;

注意事项

  • Mutex名称和窗口类名要替换成你自己应用的唯一标识,避免和其他软件重名冲突
  • 本方案是Windows平台专属,如果你需要兼容跨平台场景,需要替换成本地Socket等跨平台进程通信方案
  • 测试时建议编译成exe后再运行测试,IDE调试时可能会因为调试进程的干扰导致Mutex判断异常
  • 传递的参数如果包含空格、特殊符号,Delphi的ParamStr会自动拆分,不需要额外处理

内容的提问来源于stack exchange,提问作者touristas_ath

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 01:24:07