mfc异常崩溃处理

本文将完成一个监控和处理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 表示错误已经被修复,请从异常发生处继续执行 


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
}

1.3 模拟一次崩溃,例如本例中,新建一个按钮,并处理一次异常操作:
void CtestDlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	int* ptr = NULL;
	*ptr = 3;
}

编译运行,当test.exe程序执行了异常代码后,会试图打开同级目录下的restart.exe程序。
本例的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");

2.2 重启一个程序,需要用到关闭一个进程、一个启动程序的程序(相关头文件里的声明以下均省略):
//启动进程
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
}

2.6 编译运行,本例结构如下:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值