摘 要:本文主要对多种防止程序开启多个实例的方案进行总结,得出三种行之有效的方案,并对之进行比较分析。还示范如何使用Callback函数枚举窗口。
关键字:实例,Mutex,枚举,Callback。
在开发的软件的过程中,我们通常会给程序加上“防止程序启动一个以上实例”的功能,以免用户打开程序的多个实例。要达到这样的目的,方法是很多,它们的针对性也略有不同。经过笔者在实际开发中的摸索,总结出以下三种方便实用方案(开发环境为Borland Delphi):
一、用CreateMutex函数
CreateMutex函数可为程序建立以某一字符串命名的Mutex(互斥)对象,这样我们就可以在程序启动的时候,以该Mutex对象是否存在来判断程序是否运行。该函数的用法很简单,共有三个参数:lpMutexAttributes、bInitialOwner和lpName 。第一个参数在Win95环境下可忽略,即传入nil值;第二个参数是布尔型参数,其取值确定所建立Mutex对象是否指定所有者(ownership),所以要用True;第三个参数就是所建立Mutex对象的名字,必须指定一字符串。
具体实现方法:在程序启动时调用函数CreateMutex来 建立以某一字符串命名的Mutex对象,如果在CreateMutex调用前,以该字符串命名的Mutex已存在,则调用CreateMutex函数后系统会返回ERROR_ALREADY_EXISTS错误消息,我们就是以此错误信息为依据判断程序是否已运行的。详细程序如下:
procedure Running_CreateMutex;
var MutexHandle: THandle;
begin
//建立一个用'MyTestApp'来命名的唯一标记
MutexHandle := CreateMutex(nil, TRUE, 'MyTestApp');
if MutexHandle <> 0 then//如果建立失败,
begin //错误信息是“ERROR_ALREADY_EXISTS”,则表明程序已运行;
if GetLastError = ERROR_ALREADY_EXISTS then
begin
MessageBox(0,'Application is running!','Test', mb_IconHand);
CloseHandle(MutexHandle);
Halt;//结束本程序
end
end;
end;
以上方法的缺点是很明显的,就是在发现程序已运行时,只能简单的提示一下,令用户感到莫名其妙。所以,在很多时候我们需要的是,在发现程序有一个实例已运行时,就将该已运行的实例调出前台,显示在用户面前,这样更加贴近人的逻辑思维、更体贴用户。这样,以上方法就显得力不从心了,必须用以下方法:
二、用FindWindow函数
用FindWindow函数可对窗口的类名或标题进行查找,如果成功找到窗口,则返回该窗口的句柄(Handle)。得到窗口的句柄后,我们就可用ShowWindow函数将窗口调出前台。该函数有两个参数:lpClassName和lpWindowName ,它们分别是要查找窗口的类名和标题。
procedure Running_FindWindows;
var hWnd:THandle;
begin
hWnd:=FindWindow(nil,'MyTestApp');//搜索窗口
if hWnd<>0 then //如果找到指定的窗口
begin
if IsIconic(hWnd) then //如果已被最小化
ShowWindow(hWnd,SW_RESTORE) //则把它恢复
Else //如果窗口被其他窗口遮住,则将它提到前景来
SetForegroundWindow(hWnd);
Halt; //结束本程序
end;
end;
FindWindow函数可通过指定一个窗口的类名进行查找,也可指定一个窗口的标题文字(字符串)进行查找,由或者两者相结合查找,但必须指出,这里的窗口必须是应用程序的主窗口。
但是,有些时侯我们是不能完全确定类名或窗口标题的。例如我们常用的Microsoft Word,它的主窗口标题就是随着您打开不同的文档而变化的,Photoshop等软件也是这样的。那么上面的方法也达不到我们的目的,看看以下方法。
三、用EnumWindows函数
EnumWindows函数可枚举系统屏幕中的所有窗口,我们可逐一提取其标题字符串,然后检查该字符串是否含有某一子字符串,如Microsoft Word的主窗口标题是以字符串“Microsoft Word -”加打开文档的文件名而定的,也就是说,无论你打开什么文档,Word主窗口的标题必定含有字符串“Microsoft Word -”。所以我们可以此来判断指定的窗口是否存在,进而判断程序是否运行。要用EnumWindows函数,我们必须用stdcall声明一个Callback 函数,其固定的入口参数是:(Wnd: HWnd;LPARAM: lParam),以此Callback 函数的指针作为EnumWindows函数的第一参数,而第二参数在这里不用理会。以下是一Callback 函数的例子,其中主窗口的标题是“MyTestApp”:
function EnumApps(Wnd: HWnd;LPARAM: lParam): boolean; stdcall;
var WndCaption : array[0..254] of char;
begin
Result := True;
GetWindowText(Wnd,@WndCaption, 254);//获取窗口的Caption
if(Pos('MyTest',WndCaption)>=1)then //如果窗口的Caption含某子字符串
begin
if IsIconic(Wnd) then //如果已被最小化
ShowWindow(Wnd,SW_RESTORE)//则把它恢复
else //如果窗口被其他窗口遮住,则将它提到前景来
SetForegroundWindow(Wnd);
Result:=False;
Halt; //结束本程序
end;
end;
完成了上面的函数后,再用函数EnumWindows(@EnumApps,0)调用它就大功告成了。
procedure Running_EnumWindows;
begin
EnumWindows(@EnumApps,0); //枚举所有窗口
end;
有一点是要注意的,在调试第二和第三种方案时,必须先把Delphi关闭再运行本程序,因为Delphi在设计阶段的也存在同样标题的窗口(设计窗口)。
以上程序在Delphi 4.0 、简体中文Windows 98编译通过。另外,本文所阐述的三种方案用到的是纯Windows API函数,所以不单适用于Delphi,同样可用于VB、VC++、Borland C++ Builder等编程环境。
|