(轉)防止程序啓動兩次的方法

方法一

 

在工程文件中, WinMain函數里加上以下代碼(此代碼在BCB6.0下運行):

HANDLE hMutex = CreateMutex(NULL, false"Process");
         
if (GetLastError() == ERROR_ALREADY_EXISTS)
         
{
            CloseHandle(hMutex);
            MessageBox(Application
->Handle, "程序已經在運行中,不能重複啓動!""提示", MB_OK +MB_ICONWARNING);
            Application
->Terminate();
            
return 0;
         }

         Application
->CreateForm(__classid(TForm1), &Form1);


主要使用到CreateMutex()函數和GetLastError()以及一個常量ERROR_ALREADY_EXISTS.

方法二


當然, 你的程序有窗體的話, 還可以使用FindWindow().

void *handle = FindWindow(NULL, WindowName.c_str()); 
if (handle!=NULL)
    
return 0


進程的互斥運行

 

(其他文章的一條評論)“FindWindow是最好的,其他互斥量在釋放失敗的時候會使程序再也打不開(直到註銷或重啓系統)
一般程序都是應用軟件,如果是多個用戶,你憑什麼不讓第二個用戶運行?”

 

  正常情況下,一個進程的運行一般是不會影響到其他正在運行的進程的。但是對於某些有特殊要求的如以獨佔方式使用串行口等硬件設備的程序就要求在其進程運行期間不允許其他試圖使用此端口設備的程序運行的,而且此類程序通常也不允許運行同一個程序的多個實例。這就引出了進程互斥的問題。

  實現進程互斥的核心思想比較簡單:進程在啓動時首先檢查當前系統是否已經存在有此進程的實例,如果沒有,進程將成功創建並設置標識實例已經存在的標記。此後再創建進程時將會通過該標記而知曉其實例已經存在,從而保證進程在系統中只能存在一個實例。具體可以採取內存映射文件、有名事件量、有名互斥量以及全局共享變量等多種方法來實現。下面就分別對其中具有代表性的有名互斥量和全局共享變量這兩種方法進行介紹:

// 創建互斥量
HANDLE m_hMutex = CreateMutex(NULL, FALSE, "Sample07");
// 檢查錯誤代碼
if (GetLastError() == ERROR_ALREADY_EXISTS) {
 
// 如果已有互斥量存在則釋放句柄並復位互斥量
 CloseHandle(m_hMutex);
 m_hMutex 
= NULL;
 
// 程序退出
 return FALSE;
}


上面這段代碼演示了有名互斥量在進程互斥中的用法。代碼的核心是CreateMutex()對有名互斥量的創建。CreateMutex()函數可用來創建一個有名或無名的互斥量對象,其函數原型爲:

HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, 
// 指向安全屬性的指針
 BOOL bInitialOwner, // 初始化互斥對象的所有者
 LPCTSTR lpName // 指向互斥對象名的指針
);


如果函數成功執行,將返回一個互斥量對象的句柄。如果在CreateMutex()執行前已經存在有相同名字的互斥量,函數將返回這個已經存在互斥量的句柄,並且可以通過GetLastError()得到錯誤代碼ERROR_ALREADY_EXIST。可見,通過對錯誤代碼ERROR_ALREADY_EXIST的檢測可以實現CreateMutex()對進程的互斥。

        建立互斥體,用來同步。如果一個線程獲取了互斥體,則要獲取該互斥體的第二個線程將被掛起,直到第一個線程釋放該互斥體。

參數
lpMutexAttributes
指向一個SECURITY_ATTRIBUTES結構的指針,這個結構決定互斥體句柄是否被子進程繼承。    
bInitialOwner
布爾類型,決定互斥體的創建者是否爲擁有者
lpName
指向互斥體名字字符串的指針。互斥體可以有名字。
互斥體的好處是可以在進程間共享

心得體會:
    CreateMutex() 用於有獨佔要求的程序 (在其進程運行期間不允許其他使用此端口設備的程序運行,或不允許同名程序運行)。如有同名程序運行,則通過 GetLastError()得到錯誤代碼 ERROR_ALREADY_EXIST。

剛纔又執行了下得出的結果(程序名samp)
       一般情況下:一進入調試階段,進程管理器中就出現了samp進程,執行到CreateMutex時返回進程句柄,執行到if(GetLastError() == ERROR_ALREADY_EXISTS ) 進行判斷時,跳過不執行if中的內容,所以表示沒有互斥。
       調試之前先運行debug中的samp.exe再調試:一進入調試階段,進程管理器中就出現了兩個samp進程,執行到CreateMutex時返回進程句柄,執行到if(GetLastError() == ERROR_ALREADY_EXISTS ) 進行判斷時,執行if中的內容,表示有互斥。

 

方法三:

建議使用原子。
C/C++ code
BOOL CMyDlg::OnInitDialog() { CDialog::OnInitDialog(); ATOM atomCX = 0; atomCX = GlobalFindAtom("YOUR ATOM");//查找原子 if (atomCX) { if (MessageBox("程序重複啓動,是否終止前次啓動的程序?","提問:",MB_ICONQUESTION|MB_YESNO) == IDYES) { //關閉前次程序進程 GlobalDeleteAtom(atomCX); HANDLE m_hfindhandle=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); DWORD dCurrentId = GetCurrentProcessId(); PROCESSENTRY32* info=new PROCESSENTRY32; info->dwSize=sizeof(PROCESSENTRY32); if(Process32First(m_hfindhandle,info)) { CString m_strname;//進程的名稱 while(Process32Next(m_hfindhandle,info)!=FALSE) { m_strname=info->szExeFile; m_strname.MakeUpper(); if (m_strname == "YOUREXE.EXE") { if (dCurrentId != info->th32ProcessID) { HANDLE h=OpenProcess(PROCESS_ALL_ACCESS,TRUE,info->th32ProcessID); if(h!=NULL) { Sleep(100); TerminateProcess(h,0); } } } } CloseHandle(m_hfindhandle);//關閉進程否則會出現錯誤 if (info) { delete info; } } } else { CDialog::OnCancel(); return TRUE; } } atomCX = GlobalAddAtom("YOUR ATOM");//註冊本次程序原子 return TRUE; }
另貼(未整理)

方法1:

在InitInstance裏面 加上
HWND oldHWnd = NULL;
EnumWindows(EnumWndProc, (LPARAM)&oldHWnd);
if(oldHWnd != NULL)
{
::ShowWindow(oldHWnd, SW_SHOWMAXIMIZED);
::SetForegroundWindow(oldHWnd);
return FALSE;
}

 

這個也是比較簡單的

==================方法的分割線================

方法2:

有很多種方法。例如:
使應用程序只能運行一個實例

Windows是多進程操作系統,框架生成的應用程序可以多次運行,形成多個運行實例。
但在有些情況下爲保證應用程序的安全運行,要求程序只能運行一個實例,比如程
序要使用只能被一個進程單獨使用的特殊硬件(例如調制解調器)時,必須限制程
序只運行一個實例。

這裏涉及兩個基本的問題,一是在程序的第二個實例啓動時,如何發現該程序已有
一個實例在運行,而是如何將第一個實例激活,而第二個實例退出。

對於第一個問題,可以通過給應用程序設置信號量,實例啓動時首先檢測該信號量,
如已存在,則說明程序已運行一個實例。

第二個問題的難點是獲取第一個實例的主窗對象指針或句柄,然後便可用
SetForegroundWindow來激活。雖然FindWindow函數能尋找正運行着的窗口,但該函
數要求指明所尋找窗口的標題或窗口類名,不是實現通用方法的途徑。
我們可以用Win32 SDK函數SetProp來給應用程序主窗設置一個特有的標記。
用GetDesktopWindow 可以獲取Windows系統主控窗口對象指針或句柄,所有應用程
序主窗都可看成該窗口的子窗口,即可用GetWindow函數來獲得它們的對象指針或句
柄。用Win32 SDK函數GetProp查找每一應用程序主窗是否包含有我們設置的特定標
記便可確定它是否我們要尋找的第一個實例主窗。使第二個實例退出很簡單,只要
讓其應用程序對象的InitInstance函數返回FALSE即可。此外,當主窗口退出時,應
用RemoveProp函數刪除我們爲其設置的標記。

下面的InitInstance、OnCreate和OnDestroy函數代碼將實現上述的操作:

BOOL CEllipseWndApp::InitInstance()
{
// 用應用程序名創建信號量
HANDLE hSem = CreateSemaphore(NULL, 1, 1, m_pszExeName);

// 信號量已存在?
// 信號量存在,則程序已有一個實例運行
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
// 關閉信號量句柄
CloseHandle(hSem);
// 尋找先前實例的主窗口
HWND hWndPrevious = ::GetWindow(::GetDesktopWindow(),GW_CHILD);
while (::IsWindow(hWndPrevious))
{
// 檢查窗口是否有預設的標記?
// 有,則是我們尋找的主窗
if (::GetProp(hWndPrevious, m_pszExeName))
{
// 主窗口已最小化,則恢復其大小
if (::IsIconic(hWndPrevious))
::ShowWindow(hWndPrevious,SW_RESTORE);

// 將主窗激活
::SetForegroundWindow(hWndPrevious);

// 將主窗的對話框激活
::SetForegroundWindow(
::GetLastActivePopup(hWndPrevious));

// 退出本實例
return FALSE;
}
// 繼續尋找下一個窗口
hWndPrevious = ::GetWindow(hWndPrevious,GW_HWNDNEXT);
}
// 前一實例已存在,但找不到其主窗
// 可能出錯了
// 退出本實例
return FALSE;
}
AfxEnableControlContainer();
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic();// Call this when linking to MFC statically
#endif

CEllipseWndDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}

int CEllipseWndDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;
// 設置尋找標記
::SetProp(m_hWnd, AfxGetApp()->m_pszExeName, (HANDLE)1);
return 0;
}

void CEllipseWndDlg::OnDestroy()
{
CDialog::OnDestroy();
// 刪除尋找標記
::RemoveProp(m_hWnd, AfxGetApp()->m_pszExeName);
}


--------------------------------------------------------------------------------

對以上代碼的補充:
查看代碼和VC的幫助後,發現問題在於原文在創建信號量和設置尋找標記時使用
的是 CWinApp 的成員變量 m_pszExeName,該成員變量其實是應用程序執行文件的名
稱去掉擴展名後的部分,而不是應用程序名。

真正的應用程序名應爲成員變量 m_pszAppName,
於是將用到m_pszExeName的三處代碼均改爲m_pszAppName,重新編譯執行,情況消失。

最後再提供一個方法和一個信息:

另一種使應用程序只能運行一個實例的方法,只需在InitInstance()的最開始添
加下列語句即可:
HANDLE m_hMutex=CreateMutex(NULL,TRUE, m_pszAppName);
if(GetLastError()==ERROR_ALREADY_EXISTS) { return FALSE; }

但這種方法的不足之處是不能將已經啓動的實例激活。    

===============方法的分割線===========

方法3:

A. 使用編譯器來實現(使用MFC框架):

在.cpp文件開頭加上以下幾行: #pragma data_seg("Shared")
int volatile g_lAppInstance =0;
#pragma data_seg()

我們來看一下上面的內容:
第一句 #pragma data_seg("Shared") 創建一個稱爲Shared 的新節。
第二句 int volatile g_lAppInstance =0 將 g_lAppInstance 放入Shared節中。注意此時只有將g_lAppInstance初始化,編譯器纔會將其放入Shared節中,否則,將放入Shared以外的節。(實際上Visual C++ 編譯器提供了一個allocate 說明符,使我們可以將數據放到任何節中。)
第三句指示編譯器Shared 節結束。 #pragma comment(linker,"/section:Shared,RWS")

這一句,我們使編譯鏈接器知道我們的Shared節具有讀,寫,共享的屬性。這是我們實現互斥運行的關鍵。這樣我們就可以在應用程序之間的多個實例之間共享g_lAppInstance 變量。
在InitInstance() 函數中加入: if(++g_lAppInstance>1)
{
AfxMessageBox("程序已經運行!");
return FALSE;
}
以上代碼的作用是在程序是開始對g_lAppInstancd 加1 ,如果發現其值大於1,那麼顯示Message Box ,並返回FALSE。(注意:在MFC中如果InitInstance返回FALSE,程序將不會被運行!)  

 

=================還是分割線方法的==============

方法 4:

使用命名互斥對象:

使用API函數CreateMutex來創建命名互斥對象來實現程序互斥是一個比較通用的方法,我們可以在IninInstance()函數加入如下代碼: HANDLE hObject = CreateMutex(NULL,FALSE,"LJPXYXC");
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
CloseHandle(hObject);
AfxMessageBox("程序已經運行!");
return FALSE;
}
以上的CreateMutext函數創建一個稱爲“LJPXYXC”的命名的互斥對象,當程序的第二個實例運例時,調用CreateMutex 返回ERROR_ALREADY_EXISTS。我們根據這個值來實現程序的互斥運行。   

=================還有其他的哦=============

方法其他 :

OnCreate(LPCREATESTRUCT lpCreateStruct)裏面

m_hMutex=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"自己取一個名字");
if(m_hMutex==NULL)
CreateMutex(NULL,TRUE,"自己取一個名字");
else
{
MessageBox("程序已經運行!");
return -1;
}
//在.h文件的類聲明裏面添加:
HANDLE m_hMutex;

================再其他============

用命名的互斥!
在你的程序啓動是先判斷一個互斥A有沒有,如果互斥A不存在,就說明該程序沒有啓動,則啓動該程序
並創建該互斥A,在程序結束時銷燬互斥A

如果在啓動程序時發向互斥A存在,怎說明程序已經啓動了,則程序推出,並找到已經運行的程序的窗口,將它置在最前端!SetForegroundWindow

 

==============常用的其他===========

常用的方法

"m_hMutex=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"自己取一個名字");
if(m_hMutex==NULL)
CreateMutex(NULL,TRUE,"自己取一個名字");
else
{
MessageBox("程序已經運行!");
return -1;
}
//在.h文件的類聲明裏面添加:
HANDLE m_hMutex;
"   
    

發佈了4 篇原創文章 · 獲贊 3 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章