有時候在開發應用程序時,希望控制程序運行唯一的實例。例如,最常用的mp3播放軟件Winamp,由於它需要獨占計算機中的音頻設備,因此該程序只允許自身運行唯一的一個例程。在Visual C++的開發實踐中,對於16位的Windows系統,應用程序的hPrevInstance句柄保存了應用程序上一個運行的實例,可以用該值來檢查是否有實例運行;然而在32位Windows系統下,這個值總是NULL,所以無法利用該值來實現程序運行唯一實例。本實例給出了解決這個問題的簡單辦法,只要將程序中稍微改動一下就可以了。
一、 實現方法
對於具有窗口的應用程序,可以用靜態函數CWnd::FindWindow()查找固定窗口,來判斷程序是否已經運行。函數原型為:
CWnd* PASCAL FindWindow( LPCTSTR lpszClassName, LPCTSTR lpszWindowName );
這個函數有兩個參數,第一個是要找的窗口的類,第二個是要找的窗口的標題。在搜索的時候不一定兩者都知道,但至少要知道其中的一個。有的窗口的標題是比較容易得到的,如"計算器",所以搜索時應使用標題進行搜索。但有的軟件的標題不是固定的,如"記事本",如果打開的文件不同,窗口標題也不同,這時使用窗口類搜索就比較方便。如果找到了滿足條件的窗口,這個函數返回該窗口的指針,否則返回值為NULL。
考慮到程序的健壯性,我們還需要判斷窗口是否處於最小化狀態、是否有彈出式子窗口,這就需要使用CWnd:: GetLastActivePopup()、CWnd::IsIconic()函數,它們的原型分別為:
CWnd* GetLastActivePopup( )
該函數返回一個指定父窗口中最近激活過的彈出式窗口的指針。如果窗口本身是剛剛激活的,或窗口不包含任何彈出窗口,那麼該函數返回指向父窗口自身的指針。
BOOL IsIconic( )
該函數用來判斷當前窗口是否處於最小化狀態,如果窗口處於最小化狀態,函數返回值為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.
}
四、 小結
上述方法雖然實現起來很簡單,但是它對於無窗口的應用程序卻無能為力。為了解決這個問題,可以通過動態連接庫DLL實現更通用的控制程序運行的方法。在DLL中使用#pragma data_seg指令實現共享數據段,在該數據段中定義一個變量long m_nRun,並設置其初始值為-1,同時還要在DLL的入口點函數DllMain返回成功值的語句前添加語句m_nRun++,意思是在應用程序啟動連接DLL成功時對已經運行的實例進行計數,然後在DLL中導出一個函數來返回該變量的值。最後將應用程序的工程設置為依賴於該DLL的工程,在應用程序根據DLL中的m_nRun變量的值來判斷是否程序已經運行了。