“怎麼讓我的程序在運行時不能重復打開?”經常在論壇上看到有朋友問這方面的問題。本文將比較詳細的說明這一問題,並給出一個較為完善的解決方案。
盡管這已經不是一個新問題了,但這裡還是簡要的說明一下這種技術:這的確是一個相當有用的技術,可能你經常會注意到相當多的程序在運行之後當你再次點擊運行時,它只是會回到原來的窗口,而不會運行兩個程序。就如同你在運行delphi時,在外部點開另一個工程文件時,delphi只是會簡單的將你的當前工程置換而不是運行兩個Delphi。這樣的好處是顯而易見的:你不必擔心你的程序在某些情況下被別的軟件惡意運行多次而吃光內存造成當機。下面我們做進一部的說明:
熟悉win32編程的朋友(特別是多線程編程),相信對互斥對象已經相當熟悉了,它常被用做線程間同步的技術手段。這裡我們使用它來防止程序重復運行。我們只是簡要的提一下互斥對象,並不做深入研究:互斥對象把第一次建立它的程序作為主程序,這樣我們只用檢測互斥對象是否已經有主程序就判斷程序是否已經運行過,這裡需要涉及到一個api函數:WaitForSingleObject該函數的第一個參數為用以檢測的互斥對象,第2個參數的表示函數返回結果前的滯留時間,如果改函數返回wait_TimeOut就表明互斥對象已經有了一個主程序。修改了的工程文件代碼如下:(注意:以下的代碼都出現在工程文件中,而不是單元文件中,並且這裡都在最簡單的Delphi默認建立的工程基礎上修改)
var
myMutex:HWND;
begin
myMutex:=CreateMutex(nil,false,'hkOneCopy');// CreateMutex建立互斥對象,並且給互斥對象起一個唯一的名字。
if WaitForSingleObject(myMutex,0)<>wait_TimeOut then//程序沒有被運行過
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
End;
End;
下面的工作是來完善這個程序,我們不僅希望程序可以不被重復運行,而且我們也希望當用戶再次點擊程序可執行文件時,已經運行的程序能夠做出一些響應。在這裡我們希望它能夠變為最上層的活動窗口以提醒用戶程序已經被運行。為了達到這個目的,我們必須先獲得已經運行程序的窗口句柄,以便使用SetForeGroundWindow(handle)來使程序窗口最前並激活。為了得到這個句柄,我們必須使用windows枚舉函數EnumWindows來遍歷Windows的窗口列表,該函數可以使用一個回調函數作為參數,並用這個回調函數來對每一個系統中的窗口進行調用直到最後一個窗口或回調函數返回false為止,這個回調函數規定有兩個參數(handle,Cardinal,只用注意第一個handle參數它表示由枚舉函數當前遍歷到的窗口句柄)。我們只要編寫這個函數並在其中不斷的比較當前遍歷到的窗口類名和我們的程序的主窗口類名,以及比較窗口可執行文件的名稱和我們程序的名稱直到找到相同的為止,將這時的窗口句柄保存下來就可以了,下面的代碼加上了適當的注釋:
function EnumWndProc(hwnd:Thandle;param:Cardinal):bool;stdcall;
//由於用於api回調函數,請使用Windows傳統的參數傳遞方式stdcall
var
ClassName,WinMoudleName:string;
WinInstance:THandle;
begin
result:=true;
SetLength(ClassName,100);
GetClassName(hwnd,pchar(ClassName),length(ClassName));//獲得當前遍歷窗口的類名
ClassName:=pchar(ClassName);//在字符串後加結束符,確定字符串結束
if ClassName=TForm1.ClassName then//比較
begin
WinInstance:=GetWindowLong(hwnd,GWL_HINSTANCE);//獲得當前遍歷窗口的實例
setlength(WinMoudleName,100);
GetModuleFileName(WinInstance,pchar(WinMoudleName),length(WinMoudleName));
//獲得當前遍歷窗口的程序文件名
WinMoudleName:=pchar(WinMoudleName);
if WinMoudleName=MoudleName then//MoudleName為工程全局變量,自身程序的文件名
begin
FindHid:=hwnd;//FindHid為工程全局變量保存找到的句炳
result:=false;//找到以後就結束遍歷
end;
end;
end;
下面是全部的工程文件:
var
hMutex,FindHid:HWND;
MoudleName:string;
begin
hMutex:=CreateMutex(nil,false,'hkOneCopy');
if WaitForSingleObject(hMutex,0)<>wait_TimeOut then
begin
……//略去的代碼在前文
end
else
begin
SetLength(MoudleName,100);
GetModuleFileName(HInstance,pchar(MoudleName),length(MoudleName));
//獲得自己程序文件名
MoudleName:=pchar(MoudleName);
EnumWindows(@EnumWndProc,0);//調用枚舉函數
if FindHid<>0 then
SetForegroundWindow(FindHid);
end;
end.