让程序只运行一个实例

来源:百度文库 编辑:神马文学网 时间:2024/04/28 17:54:57
         在我们的程序当中如果要实现只运行一个实例的功能,就要解决两个问题,首先是要判断该程序已有一个实例在运行,其次是要将已运行的应用程序实例激活,同时退出第二个应用程序实例。对于第一个问题,我们可以通过设置命名互斥对象或命名信标对象,在程序启动的时候检测互斥对象或信标对象,如互斥对象或信标对象已存在,则可以判断此程序已有一个实例正在运行。第二个问题是如何找到已经运行的应用程序实例,如果我们能够找到已运行实例主窗口的指针,即可调用SetForegroundWindow来激活该实例。我们可以通过两种形式找到已运行实例的主窗口,一种形式是通过调用FindWindowEx去查找正在运行的窗口的句柄,这种方式用得比较多一些,而本文通过另一种形式去查找正在运行的窗口的句柄。通过调用SetProp给应用程序主窗口设置一个标记,用GetDesktopWindow可以获取Windows环境下的桌面窗口的句柄,所有应用程序的主窗口都可以看成该窗口的子窗口,接着我们就可以用GetWindow函数来获得这些窗口的句柄。然后再用Win32SDK函数GetProp查找每一个应用程序的主窗口是否包含有我们设置的标记,这样就可以找到我们要找的第一个实例主窗口。 1、在应用程序类InitInstance()函数中判断是否已有一个应用程序实例正在运行。BOOL CEllipseWndApp::InitInstance()
{
    // 用应用程序名创建信号量
    HANDLE hSem = CreateSemaphore(NULL, 1, 1, m_pszExeName);
    // 信号量已存在?
    // 信号量存在,则程序已有一个实例运行
    if (GetLastError() == ERROR_ALREADY_EXISTS)
    {
        // 关闭信号量句柄
        CloseHandle(hSem);
        // 寻找先前实例的主窗口
        HWND hWndPrevious = ::GetWindow(::GetDesktopWindow(),GW_CHILD);
        while (::IsWindow(hWndPrevious))
        {
            // 检查窗口是否有预设的标记?
            // 有,则是本人们寻找的主窗
            if (::GetProp(hWndPrevious, m_pszExeName))
            {
                // 主窗口已最小化,则恢复其大小
                if (::IsIconic(hWndPrevious))
                    ::ShowWindow(hWndPrevious,SW_RESTORE);
               
                // 将主窗激活
                ::SetForegroundWindow(hWndPrevious);
                // 将主窗的对话框激活
                ::SetForegroundWindow(
                ::GetLastActivePopup(hWndPrevious));
                // 退出本实例
                return FALSE;
            }
            // 继续寻找下一个窗口
            hWndPrevious = ::GetWindow(hWndPrevious,GW_HWNDNEXT);
        }
        // 前一实例已存在,但找不到其主窗
        // 可能出错了
        // 退出本实例
        return FALSE;
    }     ......} 2、在框架类的OnCreate()函数中设置查找标记。int CEllipseWndDlg::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if (CDialog::OnCreate(lpCreateStruct) == -1)
    return -1;
    // 设置寻找标记
    ::SetProp(m_hWnd, AfxGetApp()->m_pszExeName, (HANDLE)1);
    return 0;
}  
3、在程序退出是删除设置的标记,在框架类中响应WM_DESTROY消息,进行处理。void CEllipseWndDlg::OnDestroy() 
{
    CDialog::OnDestroy();
    // 删除寻找标记
    ::RemoveProp(m_hWnd, AfxGetApp()->m_pszExeName); 
}         查看代码和VC的帮助后,发现问题在于原文在创建信号量和设置寻找标记时使用的是 CWinApp 的成员变量m_pszExeName,该成员变量其实是应用程序执行文件的名称去掉扩展名后的部分,而不是应用程序名。真正的应用程序名应为成员变量 m_pszAppName, 于是将用到m_pszExeName的三处代码均改为m_pszAppName,重新编译执行,情况消失。
最后再提供一个方法和一个信息:
    另一种使应用程序只能运行一个实例的方法,只需在InitInstance()的最开始添
加下列语句即可: 
                  HANDLE m_hMutex=CreateMutex(NULL,TRUE, m_pszAppName); 
                  if(GetLastError()==ERROR_ALREADY_EXISTS) { return FALSE; }
但这种方法的不足之处是不能将已经启动的实例激活。