使用互斥物件讓程式只執行一次 (轉)

amyz發表於2007-11-13
使用互斥物件讓程式只執行一次 (轉)[@more@]

使用互斥讓只執行一次

“怎麼讓我的程式在執行時不能重複開啟?”經常在論壇上看到有朋友問這方面的問題。本文將比較詳細的說明這一問題,並給出一個較為完善的解決方案。

儘管這已經不是一個新問題了,但這裡還是簡要的說明一下這種技術:這的確是一個相當有用的技術,可能你經常會注意到相當多的程式在執行之後當你再次點選執行時,它只是會回到原來的視窗,而不會執行兩個程式。就如同你在執行時,在外部點開另一個工程時,delphi只是會簡單的將你的當前工程置換而不是執行兩個delphi。這樣的好處是顯而易見的:你不必擔心你的程式在某些情況下被別的惡意執行多次而吃光造成當機。下面我們做進一部的說明:

熟悉的朋友(特別是多執行緒程式設計),相信對互斥物件已經相當熟悉了,它常被用做執行緒間同步的技術手段。這裡我們使用它來防止程式重複執行。我們只是簡要的提一下互斥物件,並不做深入研究:互斥物件把第一次建立它的程式作為主程式,這樣我們只用檢測互斥物件是否已經有主程式就判斷程式是否已經執行過,這裡需要涉及到一個:WaitForSingle該函式的第一個引數為用以檢測的互斥物件,第2個引數的表示函式返回結果前的滯留時間,如果改函式返回wait_TimeOut就表明互斥物件已經有了一個主程式。修改了的工程檔案程式碼如下:(注意:以下的程式碼都出現在工程檔案中,而不是單元檔案中,並且這裡都在最簡單的delphi預設建立的工程基礎上修改)

var:namespace prefix = o ns = "urn:schemas--com::office" />

 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)來使程式視窗最前並啟用。為了得到這個控制程式碼,我們必須使用列舉函式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.

為了使我們的程式更完美,讓它能在重複執行的時候展現更多的特性(如delphi中的置換工程檔案為當前開啟的工程),你還可以向找到的視窗控制程式碼傳送使用者訊息,再在視窗的訊息處理函式中做相應的處理,你一定可以讓我們的程式更眩!

參考文獻:

  《delphi開發者指南》


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-982084/,如需轉載,請註明出處,否則將追究法律責任。

相關文章