【UE4源代码分析】-006 检查程序是否是第一个实例

本文介绍了一种利用命名互斥量检测程序是否为首次运行的方法,并通过实例展示了具体实现过程。

【 UE4源代码分析】-006 检查程序是否是第一个实例

  本文主要结合在工作中遇到的需要保持在一台计算机上只允许一个程序实例运行的需求,结合在阅读UE4源代码的时候关于NamedMutex的一点思考,记录下如果检测程序是否是本计算机上运行的第一个实例的问题。

1、问题的由来

  最近在开发过程中,由于软件需要和计算机的数据采集卡硬件打交道,数据采集卡SDK在重复初始化的时候会初始化失败。虽然在初始化失败的时候回弹出窗口并退出程序,但在客户体验来说有点low。
  由于我平时是网易云音乐的用户,在使用网易云音乐的时候发现,当在一台计算机上已经运行了一个客户端的情况下,第二次启动网易云音乐的请求会直接把第一个程序实例的主界面显示出来,既响应了用户的运行请求,又不会弹出警告框影响用户体验。
  这两件事的内在都是程序开始运行之后,如何检测自己是不是第一个运行的该应用程序的实例。

2、UE4的启发

  在上一篇【UE4源代码分析】-005 Editor的起点-Main函数中,在分析UE4编辑器的启动过程中,发现WinMain函数在设置了windows环境之后,紧接着就进行的工作是GIsFirstInstance = MakeNamedMutex( CmdLine )。在MakeNamedMutex函数中,主要进行了以下操作:

bool MakeNamedMutex( const TCHAR* CmdLine )
{
    bool bIsFirstInstance = false;

    TCHAR MutexName[MAX_SPRINTF] = TEXT( "" );

    FCString::Strcpy( MutexName, MAX_SPRINTF, TEXT( "UnrealEngine4" ) );

    GNamedMutex = CreateMutex( NULL, true, MutexName );

    if( GNamedMutex && GetLastError() != ERROR_ALREADY_EXISTS && !FParse::Param( CmdLine, TEXT( "NEVERFIRST" ) ) )
    {
        // We're the first instance!
        bIsFirstInstance = true;
    }
    else
    {
        // Still need to release it in this case, because it gave us a valid copy
        ReleaseNamedMutex();
        // There is already another instance of the game running.
        bIsFirstInstance = false;
    }

    return( bIsFirstInstance );
}

  程序的流程如下:

Created with Raphaël 2.1.2 开始 设置内部变量状态 创建命名Mutex Handle是否有效 Mutex是否已经存在 带NEVERFIRST参数 设置返回值状态True 结束 释放Mutex Handle 设置返回值状态False yes no yes no yes no

图1 UE4启动过程CreateNamedMutex流程图

  Mutex的性质
- Mutex是系统内核的对象,可以用于线程或进程之间的同步;
- Mutex是互斥体,同一时刻只有多个线程或进程中的一个拥有该对象;
- Mutex分为命名互斥体和未命名互斥体两类;
- 未命名互斥体只能用于单个进程的多个线程之间同步资源;
- 命名的互斥体可以用于不同进程之间同步资源。
  从以上性质可以看出,当程序的第一个实例开始运行的时候,调用CreateMutex创建一个命名的互斥体,之后的程序实例视图调用CreateMutex创建命名互斥体的时候由于已经存在该名称的互斥体,所以CreateMutex调用会设置最近错误为ERROR_ALREADY_EXISTS,表示该互斥体已经存在。通过该错误即可检测程序是不是第一个运行的实例。

3、实现

  本文使用命名Mutex+计数变量的形式来记录程序的运行次数。
- 新建MFC对话框项目
  建议带上最大化最小化按钮,方便演示类似网易云音乐的第二次运行直接弹出第一次运行窗口的功能。
  本文中工程名为IsFirstRun,VS自动生成IsFirstRun.h,IsFirstRun.cpp,IsFirstRunDlg.h,IsFirstRunDlg.cpp等文件。
- 定义全局资源
  在IsFirstRunApp.cpp,定义全局变量:

    HANDLE g_hMutex = NULL;
    const CString g_cstrMutexName = L"IsFirstRunMutex";

  其中g_cstrMutexName表示我们要命名的Mutex对象名称。
  在窗口上放置一个static text控件,并将空间ID改为IDC_SHOW_MSG.
- 创建命名Mutex
  在CIsFirstRunApp()中添加代码:

    //尝试创建命名Mutex对象
    g_hMutex = CreateMutex(NULL, FALSE, g_cstrMutexName);
    if (g_hMutex && ERROR_ALREADY_EXISTS == GetLastError())
    {//命名Mutex已经存在
        //通过程序名称查找窗口
        CWnd* pWnd = CWnd::FindWindow(NULL, _T("IsFirstRun"));
        //向该窗口发送自定义消息
        SendMessage(pWnd->GetSafeHwnd(), WM_USER + 1001, 0, 0);
        //此时仍然需要释放该Mutex HANDLE
        ReleaseMutex(g_hMutex);
        //退出程序
        exit(0);
    }

  之所以在CIsFirstRunApp()构造函数中进行命名Mutex的创建,是为了对原有程序实例产生尽量小的影响,实际上这个创建过程可以放到Dlg的OnInitDlg中进行。
  此处创建失败的处理过程是向已经运行的程序实例发送了一个自定义消息WM_USER+1001。因此,需要添加对这个自定义消息的处理。
- 自定义消息的处理
  在IsFirstRunDlg.h中添加消息响应函数声明:
afx_msg LRESULT OnSencodRun(WPARAM, LPARAM);
并添加用于记录程序运行次数的成员变量:int m_nRunTimes;并初始化值为1.
BOOL CIsFirstRunDlg::OnInitDialog()中设置显示信息的初始值:

    CString str(L"本程序第1次运行!");
    GetDlgItem(IDC_SHOW_MSG)->SetWindowTextW(str);

  在IsFirstRunDlg.cpp中的消息映射表中添加自定义消息的消息映射:
ON_MESSAGE(WM_USER+1001,&CIsFirstRunDlg::OnSencodRun)
  实现CIsFirstRunDlg::OnSencodRun函数:

LRESULT CIsFirstRunDlg::OnSencodRun(WPARAM, LPARAM)
{
    //运行次数累加
    m_nRunTimes += 1;
    CString temp;
    temp.Format(L"本程序第%d次运行!", m_nRunTimes);
    //设置界面上程序运行次数提示
    GetDlgItem(IDC_SHOW_MSG)->SetWindowTextW(temp);
    //显示主窗口
    ShowWindow(SW_SHOWNORMAL);
    return 0;
}
  • 窗口关闭时释放Mutex
      由于命名Mutex是系统核心对象,如果程序退出时不关闭该对象,系统不会自动清理并关闭该对象,因此,需要在程序退出时手动关闭该对象。
      我们可以选择在窗口关闭的时候关闭该对象。
      为窗口添加WM_CLOSE消息响应函数,并在其中释放Mutex。
void CIsFirstRunDlg::OnClose()
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    if (g_hMutex)
    {
        ReleaseMutex(g_hMutex);
        g_hMutex = NULL;
    }
    CDialogEx::OnClose();
}
  • 运行效果
      程序第一次运行时效果图如下:
    这里写图片描述
      程序第二次运行时效果图如下:
    这里写图片描述
      如果第二次程序尝试启动时,第一次运行的程序已经处于最小化状态,尝试第二次运行程序会将第一次运行程序的主界面显示出来。

4、总结

  检测程序是否是第一个运行实例或者是检测程序是第几个运行实例本质上是一个进程间通信的问题,只需要在程序启动的时候去检测特定位置的资源状态即可。
  实际上,除了使用系统的命名核心对象之外,我们还可以采用诸如硬盘文件、系统注册表等手段记录程序运行次数,在程序启动时检测该值得出程序的运行次数。

PS:WIN10 + VS2015工程,
代码地址:https://github.com/freehawkzk/IsFirstRun.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值