关闭

Delphi中如何防止运行一个应用程序的多个实例

标签: delphinullapi工作
2387人阅读 评论(0) 收藏 举报
分类:

Delphi中如何防止运行一个应用程序的多个实例


吴淑华
01-5-31 下午 01:56:54


实际应用中,程序设计人员有时希望某一时刻只运行应用程序的单个实例。或许是因为应用程序需要访问专用的特殊资源,如访问调制解调器或是CD-ROM驱动器,或许是因为应用程序需占用大量的系统资源,为保证其工作正常只能运行单一实例。
无论何种原因,如果在运行第二个实例时,简单地终止先前的实例,那么程序就会显得十分粗糙。为避免这个问题,一般要求在第二个实例终止前,把第一个实例的窗口送到栈顶。一种Win16的方法是简单地检测应用程序先前实例的句柄,如果其值不为NULL,就可以标识其他的实例。然而,在Win32中,这个值总为NULL。因此,只能选用其他方法。
这里提供的方法不依赖于应用程序具体的窗口标题也不依赖于登录窗口类。不但可以终止第二个实例,而且可以把第一个实例带到前台。下面本文结合具体实例,详细介绍一下实现步骤,并总结实现这些步骤的技术要点。
一、实现步骤
1.程序功能描述
该程序除主窗口MainForm外,还包括用户登录子窗口(密码检查窗口)LoginForm。程序启动后,首先需通过用户登录后才能进入应用程序主窗口。如果程序不包括用户登录子窗口,即程序启动后直接进入主窗口,实现步骤中需去掉步骤3和步骤5,会更简单一些。本实例之所以包括登录窗口,是考虑到实际应用中有时会遇到类似情况,如果了解了后者如何实现,当然也就掌握了前者的实现方法。
2.编辑TMainForm.FormCreate(Sender: TObject)
首先检查应用程序其他的实例是否启动。方法是创建一个mutex(Mutex很象临界区,除了在访问多进程时能同步数据外。)如果所命名的mutex已经存在,就说明应用程序的另一个实例在运行。通过将唯一的一个名字传送给CreateMutex可以命名mutex。在此以应用程序名作为第三个参数。代码如下:
procedure TMainForm.FormCreate(Sender: TObject);
var
mutexName: String;//互斥元名
hPrevWnd, hData: HWND; //hPrevWnd---窗口句柄, hData---GetProp返回的属性值
result: TModalResult;
LoginDlg: TLoginForm;
begin
mutexName := Application.ExeName; //以应用程序名作为互斥名
 
//创建互斥元.如果互斥元已经存在,这就是应用程序的第二个事例.
//注意:当应用程序结束时,互斥元自动关闭.
CreateMutex(Nil, TRUE, PChar(mutexName));
if (GetLastError() = ERROR_ALREADY_EXISTS) then
begin
...
end;
...
end;
3.编辑TLoginForm.FormCreate
由于该实例刚运行时要先调用登录子窗体LoginForm,只有通过了密码检查后才能启动应用程序主窗体MainForm。为了避免该程序的前一个实例刚运行到登录窗口时,就运行程序的第二个实例,采用标记登录窗口的方法,并在步骤4中对该标记进行判断。代码如下:
procedure TLoginForm.FormCreate(Sender: TObject);
begin
//进行窗口初始化
......
 
//设置窗口属性,以便在该应用程序系统启动时进行判断
SetProp(Handle, PChar(Application.ExeName), 2);
end;
4.寻找先前实例
当断定应用程序的另一个实例正在运行后,首先把先前的实例调到前台并给予焦点,并最大化显示该窗口。
在此,通过调用SDK的函数SetProp添加一个和窗口句柄组合成对的字符串/数据句柄来标记一个窗口。当检查出其他实例正在运行时,可通过搜索所有顶层窗口的标记,找到先前应用程序的主窗口。对每一个窗口来说,通过调用SDK的GetProp函数,可以找到先前实例的标记。如果窗口包含该标记,则找到了主窗口。窗口找到后,最大化并送到前台。具体代码如下:
procedure TMainForm.FormCreate(Sender: TObject);
var
mutexName: String;//互斥元名
hPrevWnd, hData: HWND; //hPrevWnd---窗口句柄, hData---GetProp返回的属性值
result :TModalResult;
LoginDlg :TLoginForm;
begin
mutexName := Application.ExeName; //以应用程序名作为互斥名
 
//创建互斥元.如果互斥元已经存在,这就是应用程序的第二个事例.
//注意:当应用程序结束时,互斥元自动关闭.
CreateMutex(Nil, TRUE, PChar(mutexName));
if (GetLastError() = ERROR_ALREADY_EXISTS) then
begin
//查找该应用程序的前一个主窗口句柄.
hPrevWnd := GetDesktopWindow();
hPrevWnd := GetWindow(hPrevWnd, GW_CHILD);
while (IsWindow(hPrevWnd)) do
begin
//判断此窗口属性标志是否与设置的主窗口或登录窗口标志相符.
hData := GetProp(hPrevWnd, PChar(mutexName));
if (hData = 1) or (hData = 2) then
begin
//判断是主窗口还是登录窗口,并将窗口获得焦点.
if hData = 1 then //主窗口
ShowWindow(hPrevWnd, SW_MAXIMIZE)
else //登录窗口
ShowWindow(hPrevWnd,SW_RESTORE);
SetForegroundWindow(hPrevWnd);
 
//如果此窗口有弹出窗口,设置焦点到弹出窗口.
SetForegroundWindow(GetLastActivePopup(hPrevWnd));
break;
end else
//没有找到窗口,转到窗口列表中下一个窗口.
hPrevWnd := GetWindow(hPrevWnd, GW_HWNDNEXT);
end;
Application.Terminate;
exit;
end;
5.删除登录窗口先前实例标识符
删除步骤3中创建的窗口标记。
procedure TLoginForm.FormDestroy(Sender: TObject);
begin
RemoveProp(Handle, PChar(Application.ExeName));
end;
6.删除主窗体先前实例表示符
删除步骤4中创建的窗口标记。
procedure TMainForm.FormDestroy(Sender: TObject);
begin
RemoveProp(Handle, PChar(Application.ExeName));
end;
 
二、技术要点
1.定位先前窗口,使用mutex比用FindWindow更安全,因为在实例完成创建主窗口之前,应用程序的第二个实例有可能启动。使用mutex可以防止此类情况发生。
2.要寻找应用程序先前实例主窗口,可用FindWindow寻找有标题的窗口。该方法需要知道主程序窗口的标题,但如果应用程序动态更新标题,则该方法不适用。也可用FindWindow来寻找具有具体注册窗口类的窗口。但该方法需要注册用户自己的窗口类,而且以后版本升级时,可能需要修改代码。而采用SDK的SetProp函数来“标记”窗口可以避免上述问题。
3.通过调用API函数GetDesktopWindow和GetWindow可以搜索所有的顶层窗口。再通过判断窗口标记找到先前实例窗口。
 
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:177250次
    • 积分:2762
    • 等级:
    • 排名:第13494名
    • 原创:87篇
    • 转载:29篇
    • 译文:0篇
    • 评论:28条
    文章分类
    最新评论