有些時候,我們需要我們的程序只運行一個實例,筆者自己作程序也有這樣的情況,於是自已探究一番。忙活一陣後,總算小有收獲,不敢獨享,在天極發表出來,供大家參考。
既然是從根本上解決問題,對於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函數來創建一個唯一命名的互斥對象的方法來檢測是否已經存在了另一個實例。
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
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”,這樣就在代碼編輯器中打開了工程文件,它的代碼看起來像這樣:
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:當某調用者所請求創建的互斥對象已經被命名了並且存在,這時這個調用者為“第二個調用者”。