我正在处理来自Windows的应用程序关联扩展文件。所以当你从Windows双击一个文件时,它将执行我的程序,我从那里处理文件,如:
procedure TMainForm.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 0 to ParamCount -1 do
begin
if SameText(ExtractFileExt(ParamStr(i)), '.ext1') then
begin
// handle my file..
// break if needed
end else
if SameText(ExtractFileExt(ParamStr(i)), '.ext2') then
begin
// handle my file..
// break if needed
end else
end;
end;
这几乎是我想要的,但是当我测试时,我意识到它不会考虑只使用我的程序的一个实例。
因此,例如,如果我从Windows中选择了几个文件并同时打开它们,这将创建与我的程序相同数量的实例,并打开文件数。
什么是一个很好的方法来解决这个问题,以便不是打开我的程序的几个实例,而是打开Windows的任何其他文件只会集中回到唯一的实例,并且我正常处理文件?
由于
更新
我在这里发现了一篇好文章:http://www.delphidabbler.com/articles?article=13&part=2我认为这就是我需要的,并展示了如何使用rhooligan提到的Windows API。我现在要读完它..
答案 0 :(得分:8)
这是一些简单的示例代码,可以完成工作。我希望这是不言自明的。
program StartupProject;
uses
SysUtils,
Messages,
Windows,
Forms,
uMainForm in 'uMainForm.pas' {MainForm};
{$R *.res}
procedure Main;
var
i: Integer;
Arg: string;
Window: HWND;
CopyDataStruct: TCopyDataStruct;
begin
Window := FindWindow(SWindowClassName, nil);
if Window=0 then begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end else begin
FillChar(CopyDataStruct, Sizeof(CopyDataStruct), 0);
for i := 1 to ParamCount do begin
Arg := ParamStr(i);
CopyDataStruct.cbData := (Length(Arg)+1)*SizeOf(Char);
CopyDataStruct.lpData := PChar(Arg);
SendMessage(Window, WM_COPYDATA, 0, NativeInt(@CopyDataStruct));
end;
SetForegroundWindow(Window);
end;
end;
begin
Main;
end.
unit uMainForm;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms, StdCtrls;
type
TMainForm = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure WMCopyData(var Message: TWMCopyData); message WM_COPYDATA;
public
procedure ProcessArgument(const Arg: string);
end;
var
MainForm: TMainForm;
const
SWindowClassName = 'VeryUniqueNameToAvoidUnexpectedCollisions';
implementation
{$R *.dfm}
{ TMainForm }
procedure TMainForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WinClassName := SWindowClassName;
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 1 to ParamCount do begin
ProcessArgument(ParamStr(i));
end;
end;
procedure TMainForm.ProcessArgument(const Arg: string);
begin
ListBox1.Items.Add(Arg);
end;
procedure TMainForm.WMCopyData(var Message: TWMCopyData);
var
Arg: string;
begin
SetString(Arg, PChar(Message.CopyDataStruct.lpData), (Message.CopyDataStruct.cbData div SizeOf(Char))-1);
ProcessArgument(Arg);
Application.Restore;
Application.BringToFront;
end;
end.
答案 1 :(得分:2)
逻辑就是这样的。启动应用程序时,将遍历正在运行的进程列表,并查看您的应用程序是否已在运行。如果它正在运行,则需要激活该实例的窗口然后退出。
您需要执行此操作的所有内容都在Windows API中。我在CodeProject.com上找到了处理进程的示例代码:
http://www.codeproject.com/KB/system/Win32Process.aspx
在查找和激活窗口时,基本方法是使用窗口类名称找到感兴趣的窗口,然后激活它。
http://www.vb6.us/tutorials/activate-window-api
希望这给你一个很好的起点。
答案 2 :(得分:0)
这里有很多答案说明如何实现这一点。我想说明为什么不使用FindWindow方法。
我正在使用FindWindow(类似于David H所示的那个),我看到它从Win10开始失败 - 我不知道他们在Win10中改变了什么。
我认为应用程序启动的时间与我们通过CreateParams设置唯一ID的时间之间的差距太大,因此另一个实例在某种程度上有时间在此间隙/间隔中运行。
想象一下,两个实例的起始距离仅为1毫秒(让我们说用户点击EXE文件,然后按下输入并在短时间内意外按下它)。两个实例都将检查是否存在具有该唯一ID的窗口,但是它们都没有机会设置标志/唯一ID,因为创建表单很慢并且仅在构造表单时设置唯一ID。因此,两个实例都将运行。
所以,我建议使用 CreateSemaphore 解决方案:
https://stackoverflow.com/a/460480/46207
Marjan V已经提出了这个解决方案,但没有解释为什么它更好/更安全。
答案 3 :(得分:-1)
我会使用互斥锁。您在程序启动时创建一个。
创建失败时意味着另一个实例已在运行。然后,使用命令行参数向此实例发送消息并关闭。当您的应用程序收到带有命令行的消息时,它可以像您正在执行的那样解析参数,检查它是否已打开文件并继续进行。
处理此应用专用消息也是将应用程序放到前面的地方(如果尚未安装)。请礼貌地(SetForegroundWindow)这样做,而不是试图强迫你的应用程序在所有其他人面前。
function CreateMutexes(const MutexName: String): boolean;
// Creates the two mutexes to see if the program is already running.
// One of the mutexes is created in the global name space (which makes it
// possible to access the mutex across user sessions in Windows XP); the other
// is created in the session name space (because versions of Windows NT prior
// to 4.0 TSE don't have a global name space and don't support the 'Global\'
// prefix).
var
SecurityDesc: TSecurityDescriptor;
SecurityAttr: TSecurityAttributes;
begin
// By default on Windows NT, created mutexes are accessible only by the user
// running the process. We need our mutexes to be accessible to all users, so
// that the mutex detection can work across user sessions in Windows XP. To
// do this we use a security descriptor with a null DACL.
InitializeSecurityDescriptor(@SecurityDesc, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(@SecurityDesc, True, nil, False);
SecurityAttr.nLength := SizeOf(SecurityAttr);
SecurityAttr.lpSecurityDescriptor := @SecurityDesc;
SecurityAttr.bInheritHandle := False;
if (CreateMutex(@SecurityAttr, False, PChar(MutexName)) <> 0 )
and (CreateMutex(@SecurityAttr, False, PChar('Global\' + MutexName)) <> 0 ) then
Result := True
else
Result := False;
end;
initialization
if not CreateMutexes('MyAppNameIsRunningMutex') then
//Find and SendMessage to running instance
;
end.
注意:上面的代码是根据InnoSetup网站上的示例改编的。 InnoSetup创建安装程序应用程序,并在安装程序中使用此方法检查正在安装的应用程序(以前的版本)是否已在运行。
找到另一个实例并向其发送消息,我将留下另一个问题(或者您可以使用David的答案中的WM_COPYDATA方法)。实际上,有一个StackOverflow问题完全解决了这个问题:How to get the process thread that owns a mutex获取拥有互斥锁的进程/线程可能有点挑战,但这个问题的答案确实解决了从一个问题获取信息的方法实例到另一个。
答案 4 :(得分:-1)
Windows有不同的方法来处理与可执行文件的文件关联。
“命令行”方法只是最简单的方法,但也是最有限的方法。
它还支持DDE(虽然官方弃用它仍然有效)和COM(参见http://msdn.microsoft.com/en-us/library/windows/desktop/cc144171(v=vs.85).aspx)。
如果我没记错,DDE和COM都会让您的应用程序收到所选文件的完整列表。
答案 5 :(得分:-1)
我自己使用了窗口/消息方法,添加了用于跟踪另一个实例是否正在运行的事件:
但消息/窗口的缺点非常重要:
所以目前我是命名管道方法的粉丝(虽然还没有实现它)。
它适用于所有用户会话(带有命名空间&#34; Global&#34;)或仅用于当前会话;它不依赖于UI使用的字符串(没有本地化和修改问题);它适用于控制台和服务应用程序(您需要在单独的线程/消息循环中实现管道读取)。