在我們的程序當中如果要實現類似《360軟件管家》的功能,就要解決兩個問題,首先是要判斷該程序已有一個實例在運行,其次是要將已運行的應用程序實例激活,同時退出第二個應用程序實例。
對於第一個問題,我們可以通過設置命名互斥對象或命名信標對象,在程序啟動的時候檢測互斥對象或信標對象,如互斥對象或信標對象已存在,則可以判斷此程序已有一個實例正在運行。
第二個問題是如何找到已經運行的應用程序實例,如果我們能夠找到已運行實例主窗口的指針,即可調用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,"維新");
}
至此,使應用程序只運行一個實例的功能就完成了。