您的位置:首页 > 其它

mfc异常崩溃处理

2014-01-03 10:14 323 查看
本文将完成一个监控和处理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 编译运行,本例结构如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: