《金山词霸》已经运行了的情况下,再次点击《金山词霸》的图标,那么它不会再运行另外一个《金山词霸》,而是将已有的《金山词霸》给激活,始终只能运行一个《金山词霸》的实例。
在我们的程序当中如果要实现类似《金山词霸》的功能,就要解决两个问题,首先是要判断该程序已有一个实例在运行,其次是要将已运行的应用程序实例激活,同时退出第二个应用程序实例。
对于第一个问题,我们可以通过设置命名互斥对象或命名信标对象,在程序启动的时候检测互斥对象或信标对象,如互斥对象或信标对象已存在,则可以判断此程序已有一个实例正在运行。
第二个问题是如何找到已经运行的应用程序实例,如果我们能够找到已运行实例主窗口的指针,即可调用SetForegroundWindow来激活该实例。我们可以通过两种形式找到已运行实例的主窗口,一种形式是通过调用FindWindowEx去查找正在运行的窗口的句柄,这种方式用得比较多一些,而本文通过另一种形式去查找正在运行的窗口的句柄。通过调用SetProp给应用程序主窗口设置一个标记,用GetDesktopWindow 可以获取Windows环境下的桌面窗口的句柄,所有应用程序的主窗口都可以看成该窗口的子窗口,接着我们就可以用GetWindow函数来获得这些窗口的句柄。然后再用Win32 SDK函数GetProp查找每一个应用程序的主窗口是否包含有我们设置的标记,这样就可以找到我们要找的第一个实例主窗口。
1.在应用程序类InitInstance()函数中判断是否已有一个应用程序实例正在运行。
BOOL CServer2App::InitInstance()
{
//创建命名信标对象(信号量)
HANDLE hSem=CreateSemaphore(NULL,1,1,_T("jzny农机调度Server2"));
//信标对象创建成功
if(hSem)
{
//信标对象已经存在,则程序已有一个实例在运行
if(ERROR_ALREADY_EXISTS==GetLastError())
{
//关闭信号量句柄。
CloseHandle(hSem);
//获取桌面窗口的一个子窗口
//HWND GetDesktopWindow(VOID)函数功能:该函数返回桌面窗口的句柄。桌面窗口覆盖整个屏幕。桌面窗口是一个要在其上绘制所有的图标和其他窗口的区域。
HWND hWndPrev=::GetWindow(::GetDesktopWindow(),GW_CHILD);
//判断窗口是否有我们预先设置的标记,如有则是我们寻找的窗口并将它激活
//BOOL isWindow(HWND hWnd);返回值:如果窗口句柄标识了一个已存在的窗口,返回值为非零;如果窗口句柄未标识一个已存在窗口,返回值为零
while(::IsWindow(hWndPrev))
{
if(::GetProp(hWndPrev,_T("jzny农机调度")))
{
//如果主窗口已最小化,则恢复其大小。
//if (::IsIconic(hWndPrev))
//{
::ShowWindow(hWndPrev,SW_RESTORE);
//}
//将应用程序的主窗口激活。
::SetForegroundWindow(hWndPrev);
return FALSE;
//退出实例。
}
//继续寻找下一个窗口。
hWndPrev = ::GetWindow(hWndPrev,GW_HWNDNEXT);
}
AfxMessageBox(_T("已有一个实例在运行,但找不到它的主窗口!"));
}
}
else
{
AfxMessageBox(_T("创建信标对象失败,程序退出!"));
return FALSE;
}
CWinApp::InitInstance();
// 标准初始化
// 如果未使用这些功能并希望减小
// 最终可执行文件的大小,则应移除下列
// 不需要的特定初始化例程
// 更改用于存储设置的注册表项
// TODO: 应适当修改该字符串,
// 例如修改为公司或组织名
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
WSADATA wsa;
//加载winsock动态链接库
int ret = WSAStartup(MAKEWORD(2,0),&wsa);//成功加载返回值应为0
VERIFY(ret == 0);
CServer2Dlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: 在此处放置处理何时用“确定”来关闭
// 对话框的代码
}
else if (nResponse == IDCANCEL)
{
// TODO: 在此放置处理何时用“取消”来关闭
// 对话框的代码
}
VERIFY(WSACleanup() != SOCKET_ERROR);
// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
// 而不是启动应用程序的消息泵。
return FALSE;/**/
}
2.在框架类的OnInitDialog()函数中设置查找标记。
BOOL CServer2Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
.........
//设置查找标记。
::SetProp(m_hWnd,_T("jzny农机调度"),(HANDLE)1);
.........
}
3.在程序退出是删除设置的标记,在框架类中响应WM_DESTROY消息,进行处理。
void CServer2Dlg::OnDestroy()
{
CDialog::OnDestroy();
//删除所设置的标记。
::RemoveProp(m_hWnd,_T("jzny农机调度"));
//PostMessage(WM_QUIT);
//TODO: 在此处添加消息处理程序代码
}
4、信标对象
信标对象,也叫信号灯,用于限制资源访问数量,他包含一个引用计数,一个当前可用资源数,一个最大可用资源数。如果当前可用资源数大于0,信标对象处于有信号状态。当可用资源数等于0,信标对象处于无信号状态。
和信标对象相关的函数:
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName); BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);
函数CreateSemaphore的参数1为NULL,参数2为当前可用资源初始值,参数3为最大可用资源数,参数4为名字。当参数2的值等于0时,信标对象处于无信号状态,这时内核将调用等待函数的线程置于睡眠状态,如果参数2的值大于0,信标对象处于有信号状态,这时内核将调用等待函数的线程置于运行状态,并将信标对象的当前可用资源数减1。函数ReleaseSemaphore的参数1为信标对象的句柄,参数2为要释放的资源数,参数3返回原来可用资源数,调用此函数将当前可用资源数加上参数2的值。当一个线程访问完可用资源后,应该调用ReleaseSemaphore函数使当前可用资源数递增。要在不同进程中访问同一信标对象,调用CreateSemaphore函数并传递信标对象的名称,得到已经在其它进程创建的信标对象的句柄。CE下没有OpenSemaphore函数。另外我还要说明一点,等待函数默认将信标对象的当前可用资源数减1,但线程可能一次使用多个资源,这就可能出现问题了。为避免问题出现,应该遵守一个线程只使用一个资源的原则。