本文将完成一个监控和处理mfc程序异常崩溃后自动重启的实例,同时建议所有异常都应查找原因(例如内存溢出、野指针操作等等,他们在编译时都无法发现),本着人性化的处理流程,你也需要一个对异常崩溃的处理工作。以下是详细步骤:
1.新建一个基于对话框的dialog(本例test.exe)
1.1 在testDlg.cpp中添加一个我们自定义的
回调函数,用来处理mfc程序的异常崩溃:
// CtestDlg 对话框
LONG WINAPI FreeEIM_UnhandledExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo)
{
AfxMessageBox(L"Exception"); //调用回调函数成功
//调用重启/错误提交程序(视情况而定)
CString strPath = _T("restart.exe");
USES_CONVERSION;
LPCSTR lpcs = NULL;
lpcs = T2A(strPath.GetBuffer(strPath.GetLength()));
WinExec(lpcs, SW_SHOWNORMAL);
return EXCEPTION_EXECUTE_HANDLER; //返回本回调函数的处理结果
}
返回值可以有以下几种情况,视具体情况而定:
EXCEPTION_EXECUTE_HANDLER equ 1 表示我已经处理了异常,可以优雅地结束了
EXCEPTION_CONTINUE_SEARCH equ 0 表示我不处理,其他人来吧,于是windows调用默认的处理程序显示一个错误框,并结束
EXCEPTION_CONTINUE_EXECUTION equ -1 表示错误已经被修复,请从异常发生处继续执行
EXCEPTION_CONTINUE_SEARCH equ 0 表示我不处理,其他人来吧,于是windows调用默认的处理程序显示一个错误框,并结束
EXCEPTION_CONTINUE_EXECUTION equ -1 表示错误已经被修复,请从异常发生处继续执行
1.2 在程序初始化的时候,例如OnInitDialog中, 声明当程序异常崩溃时调用我们自定义的回调函数:
BOOL CtestDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
SetUnhandledExceptionFilter(FreeEIM_UnhandledExceptionFilter); //声明调用回调函数
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
void CtestDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
int* ptr = NULL;
*ptr = 3;
}
本例的restart.exe将试图重启发生的异常程序。
2.新建一个基于对话框的dialog(本例restart.exe)
2.1 本例将用一个 ini文件记录下每次重启发生的时间,声明一下ini文件名和需要重启的程序名,添加一个引用,本例默认他们都在 同级目录下:#include <Tlhelp32.h>
const CString PRO_NAME = _T("test_restart.exe");//_T("360Desktop.exe");
const CString PRO_PATH = _T("test_restart.exe");//_T("..\\..\\Bin\\Release\\360Desktop.exe");
const CString INI_PATH = _T("restart.ini");
//启动进程
void CrestartDlg::CreateProPath(CString sProPath)
{
USES_CONVERSION;
LPCSTR lpcs = NULL;
lpcs = T2A(sProPath.GetBuffer(sProPath.GetLength()));
WinExec(lpcs, SW_SHOWNORMAL);
}
//关闭进程
BOOL CrestartDlg::KillProName(CString sProName)
{
//创建进程快照(TH32CS_SNAPPROCESS表示创建所有进程的快照)
HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
//PROCESSENTRY32进程快照的结构体
PROCESSENTRY32 pe;
//实例化后使用Process32First获取第一个快照的进程前必做的初始化操作
pe.dwSize = sizeof(PROCESSENTRY32);
//下面的IF效果同:
//if(hProcessSnap == INVALID_HANDLE_VALUE) 无效的句柄
if(!Process32First(hSnapShot,&pe))
{
return FALSE;
}
//将字符串转换为小写
sProName.MakeLower();
//如果句柄有效 则一直获取下一个句柄循环下去
while (Process32Next(hSnapShot,&pe))
{
//pe.szExeFile获取当前进程的可执行文件名称
CString scTmp = pe.szExeFile;
//将可执行文件名称所有英文字母修改为小写
scTmp.MakeLower();
//比较当前进程的可执行文件名称和传递进来的文件名称是否相同
//相同的话Compare返回0
if(!scTmp.Compare(sProName))
{
//从快照进程中获取该进程的PID(即任务管理器中的PID)
DWORD dwProcessID = pe.th32ProcessID;
HANDLE hProcess = ::OpenProcess(PROCESS_TERMINATE,FALSE,dwProcessID);
::TerminateProcess(hProcess,0);
CloseHandle(hProcess);
return TRUE;
}
scTmp.ReleaseBuffer();
}
sProName.ReleaseBuffer();
return FALSE;
}
2.3 对ini文件的操作,以及时间戳的操作,需要用到以下方法:
//时间戳比较time1 - time2
int CrestartDlg::CheckTime(int time1,int time2)
{
return (time1%100 + time1/100*60) - (time2%100 + time2/100*60);
}
//获取程序路径
CString CrestartDlg::GetExPath()
{
TCHAR exeFullPath[MAX_PATH]; // MAX_PATH
GetModuleFileName(NULL,exeFullPath,MAX_PATH);//得到程序模块名称,全路径
CString strdir,tmpdir=exeFullPath;
strdir=tmpdir.Left(tmpdir.ReverseFind('\\'));
return strdir;
}
//获取当前系统时间
CString CrestartDlg::GetSysTime()
{
CTime tm;
tm = CTime::GetCurrentTime();
CString sTime;
sTime = tm.Format("%H%M%S"); //%Y%m%d
return sTime;
}
//初始化ini
BOOL CrestartDlg::InitINI(CString sINIPath)
{
CString sPath = this->GetExPath() + L"\\" + sINIPath; //ini文件全路径
CFileFind finder;
BOOL find = finder.FindFile(sPath);
if(!find)
{
this->WriteINI(sINIPath,L"TIME1",this->GetSysTime());
}
return find;
}
//写入ini节点
void CrestartDlg::WriteINI(CString sINIPath,CString key,CString val)
{
CString sPath = this->GetExPath() + L"\\" + sINIPath; //ini文件全路径
::WritePrivateProfileStringW(_T("360Desktop Restart"),key,val,sPath);
}
//读取ini节点
CString CrestartDlg::ReadINI(CString sINIPath,CString key)
{
CString val;
CString sPath = GetExPath() + L"\\" + sINIPath; //ini文件全路径
::GetPrivateProfileStringW(_T("360Desktop Restart"),key,_T("-1"),val.GetBuffer(MAX_PATH),MAX_PATH,sPath);
val.ReleaseBuffer();
return val;
}
2.4 需要用到的方法准备就绪,下面完成一个重启程序流程(本例将尝试在30秒内重启异常2次,读者应视具体情况自定流程):
//判断重启
void CrestartDlg::CheckRestart()
{
if(this->InitINI(INI_PATH)) //初始化ini
{
CString stNow = this->GetSysTime();
int ntNow = _ttoi(stNow);
int ntPre1 = _ttoi(this->ReadINI(INI_PATH,L"TIME1"));
if(ntPre1>0)
{
if(this->CheckTime(ntNow,ntPre1) < 30)
{
//重启间隔短
int ntPre2 = _ttoi(this->ReadINI(INI_PATH,L"TIME2"));
if(ntPre2>0)
{
//第三次重启
if(this->CheckTime(ntPre2,ntPre1) < 30)
{
//重启间隔短
//补救失败,不处理
//...
this->KillProName(PRO_NAME);
}
else
{
//重启间隔长,重新计数
this->KillProName(PRO_NAME);
this->CreateProPath(PRO_PATH);
}
this->WriteINI(INI_PATH,L"TIME1",L"-1");
this->WriteINI(INI_PATH,L"TIME2",L"-1");
}
else
{
//第二次重启
this->WriteINI(INI_PATH,L"TIME2",stNow);
//第一次补救
//删除database
//DeleteFile(L"dabase.db");
this->KillProName(PRO_NAME);
this->CreateProPath(PRO_PATH);
}
}
else
{
//重启间隔长,重新计数
this->WriteINI(INI_PATH,L"TIME1",L"-1");
this->WriteINI(INI_PATH,L"TIME2",L"-1");
this->KillProName(PRO_NAME);
this->CreateProPath(PRO_PATH);
}
}
else
{
//第一次重启
this->WriteINI(INI_PATH,L"TIME1",stNow);
this->KillProName(PRO_NAME);
this->CreateProPath(PRO_PATH);
}
}
else
{
//没有ini文件,第一次重启
this->KillProName(PRO_NAME); //结束进程
this->CreateProPath(PRO_PATH); //启动进程
}
}
2.5 最后在该dialog的OnInitDialog初始化中调用一下自定义的重启流程:
BOOL CrestartDlg::OnInitDialog()
{
CDialog::OnInitDialog();
#pragma region
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
#pragma endregion
// TODO: 在此添加额外的初始化代码
this->CheckRestart();//重启流程
this->OnOK();//关闭自己
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}