实际应用中,程序设计人员有时希望某一时刻只运行应用程序的单个实例。或许是因为应用程序需要访问专用的特殊资源,如访问调制解调器或是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; //以应用程序名作为互斥名
//创建互斥元.如果互斥元