程序只啟動一個實例的幾種方法


 

我們在使用《金山詞霸》時發現,在《金山詞霸》已經運行了的情況下,再次點擊《金山詞霸》的圖標,那么它不會再運行另外一個《金山詞霸》,而是將已有的《金山詞霸》給激活,始終只能運行一個《金山詞霸》的實例。 
在我們的程序當中如果要實現類似《金山詞霸》的功能,就要解決兩個問題,首先是要判斷該程序已有一個實例在運行,其次是要將已運行的應用程序實例激活,同時退出第二個應用程序實例。 
  對於第一個問題,我們可以通過設置命名互斥對象或命名信標對象,在程序啟動的時候檢測互斥對象或信標對象,如互斥對象或信標對象已存在,則可以判斷此程序已有一個實例正在運行。 
  第二個問題是如何找到已經運行的應用程序實例,如果我們能夠找到已運行實例主窗口的指針,即可調用SetForegroundWindow來激活該實例。我們可以通過兩種形式找到已運行實例的主窗口,一種形式是通過調用FindWindowEx去查找正在運行的窗口的句柄,這種方式用得比較多一些,而本文通過另一種形式去查找正在運行的窗口的句柄。通過調用SetProp給應用程序主窗口設置一個標記,用GetDesktopWindow 可以獲取Windows環境下的桌面窗口的句柄,所有應用程序的主窗口都可以看成該窗口的子窗口,接着我們就可以用GetWindow函數來獲得這些窗口的句柄。然后再用Win32 SDK函數GetProp查找每一個應用程序的主窗口是否包含有我們設置的標記,這樣就可以找到我們要找的第一個實例主窗口。 
下面演示代碼是以一個單文檔應用程序為例,工程名字是Mutex。 
1、在應用程序類InitInstance()函數中判斷是否已有一個應用程序實例正在運行。 
BOOL CMutexApp::InitInstance() 

//創建命名信標對象。 
HANDLE hSem=CreateSemaphore(NULL,1,1,"維新"); 
if(hSem) //信標對象創建成功。 

//信標對象已經存在,則程序已有一個實例在運行。 
if(ERROR_ALREADY_EXISTS==GetLastError()) 

CloseHandle(hSem); //關閉信號量句柄。 

//獲取桌面窗口的一個子窗口。 
HWND hWndPrev=::GetWindow(::GetDesktopWindow(),GW_CHILD); 

while(::IsWindow(hWndPrev)) 

//判斷窗口是否有我們預先設置的標記,如有,則是我們尋找的窗口,並將它激活。 
if(::GetProp(hWndPrev,"維新")) 

//如果主窗口已最小化,則恢復其大小。 
if (::IsIconic(hWndPrev)) 
::ShowWindow(hWndPrev,SW_RESTORE); 

//將應用程序的主窗口激活。 
::SetForegroundWindow(hWndPrev); 
return FALSE; //退出實例。 

//繼續尋找下一個窗口。 
hWndPrev = ::GetWindow(hWndPrev,GW_HWNDNEXT); 


AfxMessageBox("已有一個實例在運行,但找不到它的主窗口!"); 


else 

AfxMessageBox("創建信標對象失敗,程序退出!"); 
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 

// Change the registry key under which our settings are stored. 
// TODO: You should modify this string to be something appropriate 
// such as the name of your company or organization. 
SetRegistryKey(_T("Local AppWizard-Generated Applications")); 

LoadStdProfileSettings(); // Load standard INI file options (including MRU) 

// Register the application's document templates. Document templates 
// serve as the connection between documents, frame windows and views. 

CSingleDocTemplate* pDocTemplate; 
pDocTemplate = new CSingleDocTemplate( 
IDR_MAINFRAME, 
RUNTIME_CLASS(CMutexDoc), 
RUNTIME_CLASS(CMainFrame), // main SDI frame window 
RUNTIME_CLASS(CMutexView)); 
AddDocTemplate(pDocTemplate); 

// Parse command line for standard shell commands, DDE, file open 
CCommandLineInfo cmdInfo; 
ParseCommandLine(cmdInfo); 

// Dispatch commands specified on the command line 
if (!ProcessShellCommand(cmdInfo)) 
return FALSE; 

// The one and only window has been initialized, so show and update it. 
m_pMainWnd->ShowWindow(SW_SHOW); 
m_pMainWnd->UpdateWindow(); 

return TRUE; 

2、在框架類的OnCreate()函數中設置查找標記。 
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 

if (CFrameWnd::OnCreate(lpCreateStruct) == -1) 
return -1; 

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP 
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || 
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) 

TRACE0("Failed to create toolbar\n"); 
return -1; // fail to create 


if (!m_wndStatusBar.Create(this) || 
!m_wndStatusBar.SetIndicators(indicators, 
sizeof(indicators)/sizeof(UINT))) 

TRACE0("Failed to create status bar\n"); 
return -1; // fail to create 


// TODO: Delete these three lines if you don't want the toolbar to 
// be dockable 

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); 
EnableDocking(CBRS_ALIGN_ANY); 
DockControlBar(&m_wndToolBar); 


//設置查找標記。 
::SetProp(m_hWnd,"維新",(HANDLE)1); 

return 0; 

3、在程序退出是刪除設置的標記,在框架類中響應WM_DESTROY消息,進行處理。 
void CMainFrame::OnDestroy() 

CFrameWnd::OnDestroy(); 

// TODO: Add your message handler code here 
//刪除所設置的標記。 
::RemoveProp(m_hWnd,"維新"); 

至此,使應用程序只運行一個實例的功能就完成了。

 

 
程序只啟動一個實例的幾種方法
 
有些時候,我們要求一個程序在系統中只能啟動一個實例。比如,Windows自帶的播放軟件Windows Medea Player在Windows里就只能啟動一個實例。原因很簡單,如果同時啟動幾個實例,卻播放不同的文件,那么聲音和圖像就會引起混亂。在設計模式中,就有一個SINGLETON模式,該模式就是讓類只有一個實例。(關於SINGLETON模式,可以看我那篇《重讀《設計模式》之學習筆記(三)--SINGLETON模式的疑惑 》)。
    對於程序而言,我們只有在程序啟動的時候去檢測某個設置,如果程序沒有啟動,就把設置更新為程序已經啟動,然后正常啟動程序;如果程序已經啟動,那么就終止程序的啟動。在程序退出的時候把設置恢復為程序沒有啟動。按照上面的思路,我們很容易就能想出下面的兩種方法:
    一,文件法
    在硬盤上創建一個文件,在文件里設置一個值,根據這個值來判斷程序是否已經啟動。
    二,注冊表法
    在注冊表中創建一個鍵,根據該鍵的鍵值來決定是否要啟動程序。
    但是,上面的兩種方法,都有I/O操作。我覺得這不是最好的方法。下面就介紹兩種不用I/O操作的方法。思路跟上面是一樣的,在進程啟動的時候去檢測某個設置是否繼續啟動進程。由於要判斷同一個程序是否已經啟動一個實例,也就是說會有兩個進程去訪問同一個設置,所以該設置應該是可以誇進程訪問的,比如上面兩種方法中的文件和注冊表。我們在用VC進行開發時,還可以用文件映射和互斥量。下面是詳細的說明:
    VC在創建工程的時候,會自動創建一個App的類。比如,你的工程名是StarLee,那么這個App類的類名就是CStarLeeApp。在進程啟動和退出的時候會分別調用該類的兩個方法:InitInstance()和ExitInstance()。所以,我們的代碼都是添加在這兩個方法里面的。
    三,文件映射法

    首先,給App類加上一個成員變量: 
HANDLE m_hFileMapping;

    然后,在App類的InitInstance()方法的最前面加上下面的代碼:

m_hFileMapping = CreateFileMapping(NULL, NULL, PAGE_READONLY, 0, 13, "StarLee");

// 檢測是否已經創建FileMapping
// 如果已經創建,就終止進程的啟動
if ((m_hFileMapping != NULL) && (GetLastError() == ERROR_ALREADY_EXISTS))
{
    CloseHandle(m_hFileMapping);
    
    MessageBox(NULL, "該進程已經啟動", "錯誤", MB_OK);

    return FALSE;
}

    最后,在App類的ExitInstance()方法里加上下面的代碼:

if (m_hFileMapping != NULL)
    CloseHandle(m_hFileMapping);

    四,互斥量法
    首先,給App類加上一個成員變量:

HANDLE m_hMutex;

    然后,在App類的InitInstance()方法的最前面加上下面的代碼:

m_hMutex = CreateMutex(NULL, TRUE, "StarLee"); 

// 檢測是否已經創建Mutex
// 如果已經創建,就終止進程的啟動
if ((m_hMutex != NULL) && (GetLastError() == ERROR_ALREADY_EXISTS)) 
{
    ReleaseMutex(m_hMutex);

    MessageBox(NULL, "該進程已經啟動", "錯誤", MB_OK);
 
    return FALSE;
}

    最后,在App類的ExitInstance()方法里加上下面的代碼:

if (m_hMutex != NULL)
{
    ReleaseMutex(m_hMutex);
    CloseHandle(m_hMutex);
}

    上面兩種方法的思路和代碼添加的步驟都是一樣的,當然效果也一樣,選擇任何一種方法都能達到讓進程只啟動一個實例的目的。

 
 
 
 
 
 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM