如何将启动参数传递给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
相关产品推荐
相关产品推荐

