我們在使用《金山詞霸》時發現,在《金山詞霸》已經運行了的情況下,再次點擊《金山詞霸》的圖標,那麼它不會再運行另外一個《金山詞霸》,而是將已有的《金山詞霸》給激活,始終只能運行一個《金山詞霸》的實例。 在我們的程序當中如果要實現類似《金山詞霸》的功能,就要解決兩個問題,首先是要判斷該程序已有一個實例在運行,其次是要將已運行的應用程序實例激活,同時退出第二個應用程序實例。 對於第一個問題,我們可以通過設置命名互斥對象或命名信標對象,在程序啓動的時候檢測互斥對象或信標對象,如互斥對象或信標對象已存在,則可以判斷此程序已有一個實例正在運行。 第二個問題是如何找到已經運行的應用程序實例,如果我們能夠找到已運行實例主窗口的指針,即可調用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類加上一個成員變量:
然後,在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類加上一個成員變量:
然後,在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); }
Findwindow的方法
一、 實現方法
對於具有窗口的應用程序,可以用靜態函數CWnd::FindWindow()查找固定窗口,來判斷程序是否已經運行。函數原型爲:
CWnd* PASCAL FindWindow( LPCTSTR lpszClassName, LPCTSTR lpszWindowName ); |
這個函數有兩個參數,第一個是要找的窗口的類,第二個是要找的窗口的標題。在搜索的時候不一定兩者都知道,但至少要知道其中的一個。有的窗口的標題是比較容易得到的,如"計算器",所以搜索時應使用標題進行搜索。但有的軟件的標題不是固定的,如"記事本",如果打開的文件不同,窗口標題也不同,這時使用窗口類搜索就比較方便。如果找到了滿足條件的窗口,這個函數返回該窗口的指針,否則返回值爲NULL。
考慮到程序的健壯性,我們還需要判斷窗口是否處於最小化狀態、是否有彈出式子窗口,這就需要使用CWnd:: GetLastActivePopup()、CWnd::IsIconic()函數,它們的原型分別爲:
CWnd* GetLastActivePopup( ) |
該函數返回一個指定父窗口中最近激活過的彈出式窗口的指針。如果窗口本身是剛剛激活的,或窗口不包含任何彈出窗口,那麼該函數返回指向父窗口自身的指針。
該函數用來判斷當前窗口是否處於最小化狀態,如果窗口處於最小化狀態,函數返回值爲True,否則返回Flase。
對於處於最小化狀態的窗口,可以調用CWnd::ShowWindow( int nCmdShow )恢復窗口的正常狀態,該函數的原型爲:
BOOL ShowWindow( int nCmdShow ) |
如窗口之前是可見的,函數調用後返回True,否則返回False。參數nCmdShow的值可以爲以下任意個常數:
SW_HIDE:隱藏窗口,活動狀態給令一個窗口;
SW_MINIMIZE:最小化窗口,活動狀態給另一個窗口;
SW_RESTORE:用原來的大小和位置顯示一個窗口,同時令其進入活動狀態;
SW_SHOW:用當前的大小和位置顯示一個窗口,同時令其進入活動狀態;
SW_SHOWMAXIMIZED:最大化窗口,並將其激活;
SW_SHOWMINIMIZED:最小化窗口,並將其激活;
SW_SHOWMINNOACTIVE:最小化一個窗口,同時不改變活動窗口;
SW_SHOWNA:用當前的大小和位置顯示一個窗口,不改變活動窗口;
SW_SHOWNOACTIVATE:用最近的大小和位置顯示一個窗口,不改變活動窗口;
SW_SHOWNORMAL:與SW_RESTORE相同;
最後不要忘記了用CWnd:: SetForegroundWindow()函數將彈出窗口設置爲桌面的最前端。
有了上面的知識,我們就可以修改程序中應用程序類的InitInstance()函數,如果程序已經運行,也即是可以發現相應的程序窗口,那麼就顯示該窗口,InitInstance()函數就返回False,程序提前退出,否則就正常運行。
二、 編程步驟
1、 啓動Visual C++6.0,生成一個基於對話框的應用程序,程序命名爲"Instance";
2、 修改程序的InitInstance()函數;
3、 添加代碼,編譯運行程序;
三、 程序代碼
///////////////////////////////////////////////////////////////////////////// // CInstanceApp initialization BOOL CInstanceApp::InitInstance() { if (!FirstInstance()) return FALSE; AfxEnableControlContainer(); #ifdef _AFXDLL Enable3dControls(); // Call this when using MFC in a shared DLL #else Enable3dControlsStatic(); // Call this when linking to MFC statically #endif CInstanceDlg 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; }
BOOL CInstanceApp::FirstInstance() { CWnd *pWndPrev, *pWndChild;
// Determine if another window with our class name and Window title exists... // The title "Instance " is set up latter, in the InitDialog function. if (pWndPrev = CWnd::FindWindow(NULL, "Instance ")) { pWndChild = pWndPrev- >GetLastActivePopup(); // if so, does it have any popups? if (pWndPrev- >IsIconic()) pWndPrev- >ShowWindow(SW_RESTORE); // If iconic, restore the main window pWndChild- >SetForegroundWindow(); // Bring the window to the foreground return FALSE; } else return TRUE; // First instance. Proceed as normal. } |
|
|
|