有些时候,我们需要我们的程序只运行一个实例,笔者自己作程序也有这样的情况,于是自已探究一番。忙活一阵后,总算小有收获,不敢独享,在天极发表出来,供大家参考。
既然是从根本上解决问题,对于Windows程序而言,就从WinMain函数入口,这是因为在VC中使用SDK的方式编写程序最透明,并且WinMain是作为VC编译器生成EXE文件的默认入口函数。
WinMain的函数原型:
int WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
);
在WinMain中一共有四个参数,其中第二参数hPrevInstance是一个HINSTANCE,表示程序运行上一个实例的句柄。根据Msdn的说明,这个参数在Win32系统上总为NULL。不过我们还可以通过使用GreateMutex函数来创建一个唯一命名的互斥对象的方法来检测是否已经存在了另一个实例。
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)GreateMutex的函数原型:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
第一个参数lpMutexAttributes,指向一个SECURITY_ATTRIBUTES结构的指针,用来决定是否允许子进程继承函数返回的句柄。如果这个参数为NULL(空),则不允许被继承。
第二个参数bInitialOwner,如果这个参数为真并且是由调用者(指调用CreateMutex函数的)创建互斥对象,那么由调用的线程(调用CreateMutex函数的线程)获得最初的拥有权。除此之外,调用的线程都不能获得最初的拥有权。决定是否由调用者创建互斥对象,请看“返回值”(Return Values)小节。
第三个函数lpName,指向用来命名互斥对象的以NULL(空)结尾的字符串,这个名字的字符个数限制在MAX_PATH个数字符内。同时这个名字区分大小写。如果参数lpName为NULL(空),将创建一个没有命名的互斥对象。如果参数lpName所指向的字符串和下列各项之中任意一项匹配:existing event,semaphore,waitable timer,job,or file-mapping object,函数将失败并伴随着调用(尽快)GetLastError函数会返回ERROR_INVALID_HANDLE常数。引发这样的结果是由于为这些互斥对象分配了重复的命名空间(这些能够引起重复的命名空间可以在Msdn中通过搜索CreateMutex查看)。
返回值,如果函数成功,返回值是一个新创建的互斥对象的句柄。如果函数失败,返回值为NULL(空)。如果得到的互斥对象是一个在这个函数之前(指这一次调用CreateMutex函数之前)就被命名了的互斥对象并且存在,那么返回值是已存在对象的句柄,同时(尽快)调用GetLastError函数会返回ERROR_ALREADY_EXISTS。无论如何,当第二个调用者(注1)只有有限的访问权限,CreateMutex将会失败,同时会伴随着使用GetLastError返回ERROR_ACCESS_DENIED,那么调用者应该使用OpenMutex函数(更多的信息大家可以到Msdn中查看,现在这些信息已经足够我们用来保证我们的程序只运行一个实例了)。
现在我确定思路:首先创建一个互斥对象,如果创建成功(CreateMutex返回值不为NULL)并调用GetLastError函数返回ERROR_ALREADY_EXISTS常数,说明当前进程不是应用程序的第一个实例,结束程序的运行。
我们在VC中以一个没有窗口,也不用MFC的Win32应用程序作例子。打开VC6,新建一个工程,类型选择Win32 Application,工程名为:OnlyOne,单击OK,选择一个空的工程,完成。为工程添加一个新的C++源代码文件,命名为:OnlyOne.c,并输入如下代码:
#include Windows.h
int
WINAPI
WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd
)
{
char strAppName[] = "OnlyOne";
HANDLE hMutex = NULL;
//创建互斥对象
hMutex = CreateMutex(NULL, FALSE, strAppName);
if (hMutex != NULL)
{
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
MessageBox(NULL,TEXT("不是第一次运行这个程序。"),TEXT("OnlyOne"),MB_OK | MB_ICONINFORMATION);
//关闭互斥对象,退出程序
CloseHandle(hMutex);
return (-1);
} else
{
MessageBox(NULL,TEXT("第一次运行这个程序。"),TEXT("OnlyOne"),MB_OK | MB_ICONINFORMATION);
}
} else
{
MessageBox(NULL,TEXT("创建互斥对象失败。"),TEXT("OnlyOne"),MB_OK | MB_ICONINFORMATION);
}
//关闭互斥对象
CloseHandle(hMutex);
return (-1);
}
同理,这个方法适用于所有的原生Win32应用程序,能够正常调用GreateMutex和GetLastError两个函数即可,Delphi程序(这次是一个带有窗口的程序)可以参考以下步骤和代码:
在Delphi中建立一个应用程序,然后单击菜单“Project”,单击“View Source”,这样就在代码编辑器中打开了工程文件,它的代码看起来像这样:
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)program OnlyOne;
uses
Forms,
uOnlyOneWindow in 'uOnlyOneWindow.pas' {OnlyOneWindow };
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TOnlyOneWindow, OnlyOneWindow);
Application.Run;
end.
我将其代码更改如下:
program OnlyOne;
uses
Windows,
Forms,
uOnlyOneWindow in 'uOnlyOneWindow.pas' {OnlyOneWindow};
{$R *.res}
var
hAppMutex: THandle;
begin
Application.Initialize;
//创建互斥对象
hAppMutex := CreateMutex(nil, false, PChar('OnlyOne'));
if (hAppMutex = 0) then
begin
MessageBox(0,PChar('创建互斥对象失败!'),PChar('Error'),MB_OK + MB_ICONINFORMATION);
exit;
end;
//查看是否是第一次运行程序
if ((hAppMutex 0) and (GetLastError() = ERROR_ALREADY_EXISTS)) then
begin
MessageBox(0,PChar('不是第一次运行这个程序!'),PChar('OK'),MB_OK + MB_ICONINFORMATION);
//关闭互斥对象,退出程序
CloseHandle(hAppMutex);
exit;
end;
Application.CreateForm(TOnlyOneWindow, OnlyOneWindow);
Application.Run;
//关闭互斥对象
CloseHandle(hAppMutex);
end.
注意:
1.在User中,要把Windows放在Form前头;
2.开始创建互斥对象的代码要在Application.Initialize之后;
3.关闭互斥对象操作要放在Application.Run之后;
这样,我们只用了较少的代码和较少的系统资源消耗就实现了应用程序检测自己是否被多次运行,从而保证只运行一个示例这样的效果。
以上程序在Visual C++ 6.0(SP6)、Delphi 7(Build 8.1)中编译,在Windows XP SP2中测试通过。
注1:当某调用者所请求创建的互斥对象已经被命名了并且存在,这时这个调用者为“第二个调用者”。