首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
80 阅读
2
nvim番外之将配置的插件管理器更新为lazy
58 阅读
3
2018总结与2019规划
54 阅读
4
PDF标准详解(五)——图形状态
33 阅读
5
为 MariaDB 配置远程访问权限
30 阅读
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
Java
emacs
linux
文本编辑器
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
308
篇文章
累计收到
27
条评论
首页
栏目
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
页面
归档
友情链接
关于
搜索到
3
篇与
的结果
2019-06-30
Java 异常处理
异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。Java中的异常主要分为下列几类:检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。所有的异常类是从 java.lang.Exception 类继承的子类。 Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。它们之间的关系入下图:从Exception继承的类都是异常,异常可以被处理,处理完后程序仍然可以继续运行。从Error继承来的类都是错误,在运行时错误无法被处理,只能修改代码逻辑。从Runtime中继承的类都是运行时异常,这类异常在程序中可以处理,也可以不处理。而非运行时异常在代码中必须处理。不然编译会报错。Java中异常处理的方式Java中的异常处理主要有下列几种:使用 throw 在指定方法中抛出指定异常。比如 throw IOException(); 在方法中抛出了一个IO异常使用 throws 将异常抛出给调用者处理。在函数声明时使用。方法声明时可以抛出多个异常,如果多个异常有继承关系,那么只需要抛出父类异常即可。如果父类的方法没有抛出异常,子类在重写父类方法时也不能使用这种方式抛出异常try...catch 处理异常。在使用try 处理异常时需要注意:如果catch 中捕获的有多个异常,且异常间有继承关系,那么必须把子类写在前面,父类在后面异常中的常用方法Throwable 中定义了3个异常处理的方法:String getMessage(): 返回异常的详细信息String toString() : 返回异常的简短信息void printStackTrace(): 打印异常的调用信息这些异常信息一般在try...catch 中使用,例如try { //do something }catch(Exception e){ e.printStackTrace(); }finally 关键字无论异常是否发生,finally中的代码都会执行。一般finally中编写释放资源的代码,比如释放文件对象。需要注意的是,finally中会改变return的执行顺序,不管return在哪,都会最后执行finally中的returntry{ //do some thing return; }catch(Exception e) { return; } finally{ return; //会执行这个 } return;自定义异常类自定义异常时需要注意:异常类都必须继承自 Throwable类,如果要定义检查性异常,需要继承 Exception,要定义运行时异常,需要继承 RuntimeException。class MyException extends Exception{ }假设我们定义一个异常类,表示取钱的异常,当取钱数少于1000时报异常,提示用户去ATM取,可以这样写class TooLittleMoneyException extends Exception { private int money; private String message; TooLittleMoneyException(int money){ message = "" + money + "太少,请到ATM自助取款机去取"; } String getMessage(){ return message; } } //取钱方法 //打开交易通道 //校验账户是否合法 try{ if (money < 1000){ throw TooLittleMoneyException(money); } //取钱,并在对应账户中减少相应的金额 }catch(TooLittleMoneyException e){ System.out.println(e.getMessage()); }finally{ //关闭交易通道 }
2019年06月30日
5 阅读
0 评论
0 点赞
2018-08-28
VC++ 崩溃处理以及打印调用堆栈
我们在程序发布后总会面临崩溃的情况,这个时候一般很难重现或者很难定位到程序崩溃的位置,之前有方法在程序崩溃的时候记录dump文件然后通过windbg来分析。那种方法对开发人员的要求较高,它需要程序员理解内存、寄存器等等一系列概念还需要手动加载对应的符号表。Java、Python等等语言在崩溃的时候都会打印一条异常的堆栈信息并告诉用户那块出错了,根据这个信息程序员可以很容易找到对应的代码位置并进行处理,而C/C++则会弹出一个框告诉用户程序崩溃了,二者对比来看,C++似乎对用户太不友好了,而且根据它的弹框很难找到对应的问题,那么有没有可能使c++像Java那样打印异常的堆栈呢?这个自然是可能的,本文就是要讨论如何在Windows上实现类似的功能异常处理一般当程序发生异常时,用户代码停止执行,并将CPU的控制权转交给操作系统,操作系统接到控制权后,将当前线程的环境保存到结构体CONTEXT中,然后查找针对此异常的处理函数。系统利用结构EXCEPTION_RECORD保存了异常描述信息,它与CONTEXT一同构成了结构体EXCEPTION_POINTERS,一般在异常处理中经常使用这个结构体。异常信息EXCEPTION_RECORD的定义如下:typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; //异常码 DWORD ExceptionFlags; //标志异常是否继续,标志异常处理完成后是否接着之前有问题的代码 struct _EXCEPTION_RECORD* ExceptionRecord; //指向下一个异常节点的指针,这是一个链表结构 PVOID ExceptionAddress; //异常发生的地址 DWORD NumberParameters; //异常附加信息 ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; //异常的字符串 } EXCEPTION_RECORD, *PEXCEPTION_RECORD;Windows平台提供的这一套异常处理的机制,我们叫它结构化异常处理(SEH),它的处理过程一般如下:如果程序是被调试运行的(比如我们在VS编译器中调试运行程序),当异常发生时,系统首先将异常信息交给调试程序,如果调试程序处理了那么程序继续运行,否则系统便在发生异常的线程栈中查找可能的处理代码。若找到则处理异常,并继续运行程序如果在线程栈中没有找到,则再次通知调试程序,如果这个时候仍然不能处理这个异常,那么操作系统会对异常进程默认处理,这个时候一般都是直接弹出一个错误的对话框然后终止程序。系统在每个线程的堆栈环境中都维护了一个SEH表,表中是用户注册的异常类型以及它对应的处理函数,每当用户在函数中注册新的异常处理函数,那么这个信息会被保存在链表的头部,也就是说它是采用头插法来插入新的处理函数,从这个角度上来说,我们可以很容易理解为什么在一般的高级语言中一般会先找与try块最近的catch块,然后在找它的上层catch,由里到外依次查找。与try块最近的catch是最后注册的,由于采用的是头插法,自然它会被首先处理。在Windows中针对异常处理,扩展了__try 和 __except 两个操作符,这两个操作符与c++中的try和catch非常相似,作用也基本类似,它的一般的语法结构如下:__try { //do something } __except(filter) { //handle }使用 __try 和 __except 的时候它主要分为3个部分,分别为:保护代码体、过滤表达式、异常处理块保护代码体一般是try中的语句,它值被保护的代码,也就是说我们希望处理那个代码块产生的异常过滤表达式是 except后面扩号中的值,它只能是3个值中的一个,EXCEPTION_CONTINUE_SEARCH继续向下查找异常处理,也就是说这里的异常处理块不处理这种异常,EXCEPTION_CONTINUE_EXECUTION表示异常已被处理,这个时候可以继续执行直线产生异常的代码,EXCEPTION_EXECUTE_HANDLER表示异常已被处理,此时直接跳转到except里面的代码块中,这种方式下它的执行流程与一般的异常处理的流程类似.异常处理块,指的是except下面的扩号中的代码块.注意:我们说过滤表达式只能是这三个值中的一个,但是没有说这里一定得填这三个值,它还支持函数或者其他的表达式类型,只要函数或者表达式的返回值是这三个值中的一个即可。上述的方式也有他的局限性,也就是说它只能保护我们指定的代码,如果是在 __try 块之外的代码发生了崩溃,可能还是会造成程序被kill掉,而且每个位置都需要写上这么些代码实在是太麻烦了。其实处理异常还有一种方式,那就是采用 SetUnhandledExceptionFilter来注册一个全局的异常处理函数来处理所有未被处理的异常,其实它的主要工作原理就是往异常处理的链表头上添加一个处理函数,函数的原型如下:LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(__in LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);它需要传入一个函数,以便发生异常的时候调用这个函数,这个回调函数的原型如下:LONG WINAPI UnhandledExceptionFilter( __in struct _EXCEPTION_POINTERS* ExceptionInfo );回调函数会传入一个表示当前堆栈和异常信息的结构体的指针,结构的具体信息请参考MSDN, 函数会返回一个long型的数值,这个数值为上述3个值中的一个,表示当系统调用了这个异常处理函数处理异常之后该如何继续执行用户代码。SetUnhandledExceptionFilter 函数返回一个函数指针,这个指针指向链表的头部,如果插入处理函数失败那么它将指向原来的链表头,否则指向新的链表头(也就是注册的这个回调函数的地址)而这次要实现这么一个能打印异常信息和调用堆栈的功能就是要使用这个方法。打印函数调用堆栈关于打印堆栈的内容,这里不再多说了,请参考本人之前写的博客windows平台调用函数堆栈的追踪方法这里的主要思路是使用StackWalker来根据当前的堆栈环境来获取对应的函数信息,这个信息需要根据符号表来生成,因此我们需要首先加载符号表,而获取当前线程的环境,我们可以像我博客中写的那样使用GetThreadContext来获取,但是在异常中就简单的多了,还记得异常处理函数的原型吗?异常处理函数本身会带入一个EXCEPTION_POINTERS结构的指针,而这个结构中就包含了异常堆栈的信息。还有一些需要注意的问题,我把它放到实现那块了,请小心的往下看^_^实现实现部分的源码我放到了github上,地址这个项目中主要分为两个类CBaseException,主要是对异常的一个简单的封装,提供了我们需要的一些功能,比如获取加载的模块的信息,获取调用的堆栈,以及解析发生异常时的相关信息。而这些的基础都在CStackWalker中。使用上,我把CBaseException中的大部分函数都定义成了virtual 允许进行重写。因为具体我还没想好这块后续会需要进行哪些扩展。但是里面最主要的功能是OutputString函数,这个函数是用来进行信息输出的,默认CBaseException是将信息输出到控制台上,后续可以重载这个函数把数据输出到日志中。CBaseException 类CBaseException 主要是用来处理异常,在代码里面我提供了两种方式来进行异常处理,第一种是通过 SetUnhandledExceptionFilter 来注册一个全局的处理函数,这个函数是类中的静态函数UnhandledExceptionFilter,在这个函数中我主要根据异常的堆栈环境来初始化了一个CBaseException类,然后简单的调用类的方法显示异常与堆栈的相关信息。第二种是通过 _set_se_translator 来注册一个将SEH转化为C++异常的方法,在对应的回调中我简单的抛出了一个CBaseException的异常,在具体的代码中只要简单的用c++的异常处理捕获这么一个异常即可CBaseException 类中主要用来解析异常的信息,里面提供这样功能的函数主要有3个ShowExceptionResoult: 这个函数主要是根据异常码来获取到异常的具体字符串信息,比如非法内存访问、除0异常等等GetLogicalAddress:根据发生异常的代码的地址来获取对应的模块信息,比如它在PE文件中属于第几个节,节的地址范围等等,它在实现上首先使用 VirtualQuery来获取对应的虚拟内存信息,主要是这个模块的首地址信息,然后解析PE文件获取节表的信息,我们循环节表中的每一项,根据节表中的地址范围来判断它属于第几个节,注意这里我们根据它在内存中的偏移计算了它在PE文件中的偏移,具体的计算方式请参考PE文件的相关内容.3.ShowRegistorInformation:获取各个寄存器的值,这个值保存在CONTEXT结构中,我们只需要简单打印它就好CStackWalker类这个类主要实现一些基础的功能,它主要提供了初始化符号表环境、获取对应的调用堆栈信息、获取加载的模块信息在初始化符号表的时候尽可以多的遍历了常见的几种符号表的位置并将这些位置中的符号表加载进来,以便能更好的获取到堆栈调用的情况。在获取到对应的符号表位置后有这样的代码if (NULL != m_lpszSymbolPath) { m_bSymbolLoaded = SymInitialize(m_hProcess, T2A(m_lpszSymbolPath), TRUE); //这里设置为TRUE,让它在初始化符号表的同时加载符号表 } DWORD symOptions = SymGetOptions(); symOptions |= SYMOPT_LOAD_LINES; symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS; symOptions |= SYMOPT_DEBUG; SymSetOptions(symOptions); return m_bSymbolLoaded;这里将 SymInitialize的最后一个函数置为TRUE,这个参数的意思是是否枚举加载的模块并加载对应的符号表,直接在开始的时候加载上可能会比较浪费内存,这个时候我们可以采用动态加载的方式,在初始化的时候先填入FALSE,然后在需要的时候自己枚举所有的模块,然后手动加载所有模块的符号表,手动加载需要调用SymLoadModuleEx。这里需要提醒各位的是,这里如果填的是FALSE的话,后续一定得自己加载模块的符号表,否则在后续调用SymGetSymFromAddr64的时候会得到一堆的487错误(也就是地址无效)我之前就是这个问题困扰了我很久的时间。在获取模块的信息时主要提供了两种方式,一种是使用CreateToolhelp32Snapshot 函数来获取进程中模块信息的快照然后调用Module32Next 和 Module32First来枚举模块信息,还有一种是使用EnumProcessModules来获取所有模块的句柄,然后根据句柄来获取模块的信息,当然还有另外的方式,其他的方式可以参考我的这篇博客 枚举进程中的模块在枚举加载的模块的同时还针对每个模块调用了 GetModuleInformation 函数,这个函数主要有两个功能,获取模块文件的版本号和获取加载的符号表信息。接下来就是重头戏了——获取调用堆栈。获取调用堆栈首先得获取当前的环境,在代码中进行了相应的判断,如果当前传入的CONTEXT为NULL,则函数自己获取当前的堆栈信息。在获取堆栈信息的时候首先判断是否为当前线程,如果不是那么为了结果准确,需要先停止目标线程,然后获取,否则直接使用宏来获取,对应的宏定义如下:#define GET_CURRENT_THREAD_CONTEXT(c, contextFlags) \ do\ {\ memset(&c, 0, sizeof(CONTEXT));\ c.ContextFlags = contextFlags;\ __asm call $+5\ __asm pop eax\ __asm mov c.Eip, eax\ __asm mov c.Ebp, ebp\ __asm mov c.Esp, esp\ } while (0)在调用StackWalker时只需要关注esp ebp eip的信息,所以这里我们也只简单的获取这些寄存器的环境,而其他的就不管了。这样有一个问题,就是我们是在CStackWalker类中的函数中获取的这个线程环境,那么这个环境里面会包含CStackWalker::StackWalker,结果自然与我们想要的不太一样(我们想要的是隐藏这个库中的相关信息,而只保留调用者的相关堆栈信息)。这个问题我还没有什么好的解决方案。在获取到线程环境后就是简单的调用StackWalker以及那堆Sym开头的函数来获取各种信息了,这里就不再详细说明了。至此这个功能已经实现的差不多了。库的具体使用请参考main.cpp这个文件,相信有这篇博文以及源码各位应该很容易就能够使用它。据说这些函数不是多线程安全的,我自己没有在多线程环境下进行测试,所以具体它在多线程环境下表现如何还是个未知数,如果后续我有兴趣继续完善它的话,可能会加入多线程的支持。
2018年08月28日
9 阅读
0 评论
0 点赞
2016-08-16
windows 异常处理
为了程序的健壮性,windows 中提供了异常处理机制,称为结构化异常,异常一般分为硬件异常和软件异常,硬件异常一般是指在执行机器指令时发生的异常,比如试图向一个拥有只读保护的页面写入内容,或者是硬件的除0错误等等,而软件异常则是由程序员,调用RaiseException显示的抛出的异常。对于一场处理windows封装了一整套的API,平台上提供的异常处理机制被叫做结构化异常处理(SEH)。不同于C++的异常处理,SEH拥有更为强大的功能,并且采用C风给的代码编写方式。异常处理机制的流程简介一般当程序发生异常时,用户代码停止执行,并将CPU的控制权转交给操作系统,操作系统接到控制权后,将当前线程的环境保存到结构体CONTEXT中,然后查找针对此异常的处理函数。系统利用结构EXCEPTION_RECORD保存了异常描述信息,它与CONTEXT一同构成了结构体EXCEPTION_POINTERS,一般在异常处理中经常使用这个结构体。异常信息EXCEPTION_RECORD的定义如下typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; //异常码 DWORD ExceptionFlags; //标志异常是否继续,标志异常处理完成后是否接着之前有问题的代码 struct _EXCEPTION_RECORD* ExceptionRecord; //指向下一个异常节点的指针,这是一个链表结构 PVOID ExceptionAddress; //异常发生的地址 DWORD NumberParameters; //异常附加信息 ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; //异常的字符串 } EXCEPTION_RECORD, *PEXCEPTION_RECORD;当系统在用户程序中查找异常处理代码时主要通过查找当前的这个链表。下面详细说明异常发生时操作系统是如何处理的:如果程序是被调试运行的(比如我们在VS编译器中调试运行程序),当异常发生时,系统首先将异常信息交给调试程序,如果调试程序处理了那么程序继续运行,否则系统便在发生异常的线程栈中查找可能的处理代码。若找到则处理异常,并继续运行程序如果在线程栈中没有找到,则再次通知调试程序,如果这个时候仍然不能处理这个异常,那么操作系统会对异常进程默认处理,比如强制终止程序。SEH的基本框架结构化异常处理一般有下面3个部分组成:保护代码体过滤表达式异常处理块其中保护代码体:是指有可能发生异常的代码,一般在SEH中是用__try{}包含的那部分过滤表达式:是在__except表达式的括号中的部分,一般可以是函数或者表达式,过滤表达式一般只能返回3个值:EXCEPTION_CONTINUE_SEARCH表示继续向下寻找异常处理的程序,也就是说本__exception不能处理这个异常;EXCEPTION_CONTINUE_EXECUTION表示异常已被处理,继续执行当初发生异常的代码;EXCEPTION_EXECUTE_HANDLER:表示异常已被处理,直接跳转到__exception(){}代码块中执行,这个时候就有点像C++中的异常处理了。一般一个__try块可以跟随多个__except块异常处理块:是指__except大括号中的代码块另外可以在过滤表达式中调用GetExceptionCode和GetExceptionInformagtion函数取得正在处理的异常信息,这两个函数不能再过滤表达式中使用,但是可以作为过滤表达式中的函数参数。下面是一个异常处理的简单的例子:#define PAGELIMIT 1024 DWORD dwPageCnt = 0; LPVOID lpPrePage = NULL; DWORD dwPageSize = 0; INT FilterFunction(DWORD dwExceptCode) { if(EXCEPTION_ACCESS_VIOLATION != dwExceptCode) { return EXCEPTION_EXECUTE_HANDLER; } if(dwPageCnt >= PAGELIMIT) { return EXCEPTION_EXECUTE_HANDLER; } if(NULL == VirtualAlloc(lpPrePage, dwPageSize, MEM_COMMIT, PAGE_READWRITE)) { return EXCEPTION_EXECUTE_HANDLER; } lpPrePage = (char*)lpPrePage + dwPageSize; dwPageCnt++; return EXCEPTION_CONTINUE_EXECUTION; } int _tmain(int argc, TCHAR *argv[]) { SYSTEM_INFO si = {0}; GetSystemInfo(&si); dwPageSize = si.dwPageSize; char* lpBuffer = (char*)VirtualAlloc(NULL, dwPageSize * PAGELIMIT, MEM_RESERVE, PAGE_READWRITE); lpPrePage = lpBuffer; for(int i = 0; i < PAGELIMIT * dwPageSize; i++) { __try { lpBuffer[i] = 'a'; } __except(FilterFunction(GetExceptionCode())) { ExitProcess(0); } } VirtualFree(lpBuffer, dwPageSize * PAGELIMIT, MEM_FREE); return 0; }这段代码我们通过结构化异常处理实现了内存的按需分配,首先程序保留了4M的地址空间,但是并没有映射到具体的物理内存,接着向这4M的空间中写入内容,这个时候会造成非法的内存访问异常,系统会执行过滤表达式中调用的函数,在函数中校验异常的异常码,如果不等于EXCEPTION_ACCESS_VIOLATION,也就是说这个异常并不是读写非法内存造成的,那么直接返回EXCEPTION_EXECUTE_HANDLER,这个时候会执行__exception块中的代码,也就是结束程序,如果是由于访问非法内存造成的,并且读写的范围没有超过4M那么就提交一个物理页面供程序使用,并且返回EXCEPTION_CONTINUE_EXECUTION,让程序接着从刚才的位置执行也就是说再次执行写入操作,这样保证了程序需要多少就提交多少,节约了物理内存。终止处理块终止处理块是结构化异常处理特有的模块,它保证了当__try块执行完成后总会执行终止处理块中的代码。一般位于__finally块中。只有当线程在__try中结束,也就是在__try块中调用ExitProcess或者ExitThread。由于系统为了保证__try块结束后总会调用__finally所以某些跳转语句如:goto return break等等就会添加额外的机器码以便能够跳入到__try块中,所以为了效率可以用__leave语句代替这些跳转语句。另外需要注意的一点是一个__try只能跟一个__finally块但是可以跟多个__except块。同时__try块后面要么跟__except要么跟__finally这两个二选一,不能同时跟他们两个。抛出异常在SEH中抛出异常需要使用函数:RaiseException,它的原型如下:void WINAPI RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR* lpArguments);第一个是异常代码,第二个参数是异常标志,第三个是异常参数个数,第四个是参数列表,这个函数主要是为了填充EXCEPTION_RECORD结构体并将这个节点添加到链表中,当发生异常时系统会查找这个链表,下面是一个简单的例子:DWORD FilterException() { wprintf(_T("1\n")); return EXCEPTION_EXECUTE_HANDLER; } int _tmain(int argc, TCHAR *argv[]) { __try { __try { RaiseException(1, 0, 0, NULL); } __finally { wprintf(_T("2\n")); } } __except(FilterException()) { wprintf(_T("3\n")); } _tsystem(_T("PAUSE")); return 0; }上面的程序使用RaiseException抛出一个异常,按照异常处理的流程,程序首先会试着执行FilterException,以便处理这个异常,所以首先会输出1,然后根据返回值EXCEPTION_EXECUTE_HANDLER决定下一步会执行异常处理块__except中的内容,这个时候也就表示最里面的__try块执行完了,在前面说过,不管遇到什么情况,执行完__try块,都会接着执行它对应的__finally块,所以这个时候会首先执行__finally块,最后执行外层的__except块,最终程序输出结果为1 2 3win32下的向量化异常处理为什么向量化异常要强调是win32下的呢,因为64位windows不支持这个特性理解这个特性还是回到之前说的操作系统处理异常的顺序上面,首先会交给调试程序,然后再由用户程序处理,根据过滤表达式返回的值决定这个异常是否被处理,而这个向量化异常处理,就是将异常处理的代码添加到这个之前,它的代码会先于过滤表达式之前执行。我们知道异常是由内层向外层一层一层的查找,如果在内层已经处理完成,那么外层是永远没有机会处理的,这种情况在我们使用第三方库开发应用程序,而这个库又不提供源码,并且当发生异常时这个库只是简单的将线程终止,而我们想处理这个异常,但是由于内部处理了,外层的try根本捕获不到,这个时候就可以使用向量化异常处理了。这样我们可以编写异常处理代码先行处理并返回继续执行,这样库中就没有机会处理这个异常了。使用这个机制通过AddVectoredExceptionHandler函数可以添加向量化异常处理过滤函数,而调用RemoveVectoredExceptionHandler可以移除一个已添加的向量化异常处理过滤函数。下面是一个简单的例子:int g_nVal = 0; void Func(int nVal) { __try { nVal /= g_nVal; } __except(EXCEPTION_EXECUTE_HANDLER) { printf("正在执行Func中的__try __except块\n"); ExitProcess(0); } } LONG CALLBACK VH1(PEXCEPTION_POINTERS pExceptionInfo) { printf("正在执行VH1()函数\n"); return EXCEPTION_CONTINUE_SEARCH; } LONG CALLBACK VH2(PEXCEPTION_POINTERS pExceptionInfo) { printf("正在执行VH2()函数\n"); if (EXCEPTION_INT_DIVIDE_BY_ZERO == pExceptionInfo->ExceptionRecord->ExceptionCode) { g_nVal = 25; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; } LONG CALLBACK VH3(PEXCEPTION_POINTERS pExceptionInfo) { printf("正在执行VH3()函数\n"); return EXCEPTION_CONTINUE_SEARCH; } LONG SEH1(EXCEPTION_POINTERS *pEP) { //除零错误 if (EXCEPTION_INT_DIVIDE_BY_ZERO == pEP->ExceptionRecord->ExceptionCode) { g_nVal = 34; return EXCEPTION_EXECUTE_HANDLER; } return EXCEPTION_CONTINUE_SEARCH; } int _tmain(int argc, TCHAR *argv[]) { LPVOID lp1 = AddVectoredExceptionHandler(0, VH1); LPVOID lp2 = AddVectoredExceptionHandler(0, VH2); LPVOID lp3 = AddVectoredExceptionHandler(1, VH3); __try { Func(g_nVal); printf("Func()函数执行完成后g_nVal = %d\n", g_nVal); } __except(SEH1(GetExceptionInformation())) { printf("正在执行main()中的__try __except块"); } RemoveVectoredExceptionHandler(lp1); RemoveVectoredExceptionHandler(lp2); RemoveVectoredExceptionHandler(lp3); return 0; }上述的程序模拟了调用第三方库的情况,比如我们调用了第三方库Func进行某项操作,我们在外层进行了异常处理,但是由于在Func函数中有异常捕获的代码,所以不管外层如何处理,总不能捕获到异常,外层的异常处理代码总是不能执行,这个时候我们注册了3个向量处理函数,由于VH1返回的是EXCEPTION_CONTINUE_SEARCH,这个时候会在继续执行后面注册的向量函数——VH2,VH2返回EXCEPTION_CONTINUE_SEARCH,会继续执行VH3,VH3还是返回EXCEPTION_CONTINUE_SEARCH,那么它会继续执行库函数内层的异常处理,内层的过滤表达式返回EXCEPTION_EXECUTE_HANDLER,这个时候会继续执行异常处理块中的内容,结束程序,如果我们将3个向量函数中的任何一个的返回值改为EXCEPTION_CONTINUE_EXECUTION,那么库中的异常处理块中的内容将不会被执行。函数AddVectoredExceptionHandler中填入的处理函数也就是上述代码中的VH1 VH2 VH3只能返回EXCEPTION_CONTINUE_EXECUTION和EXCEPTION_CONTINUE_SEARCH,对于其他的值操作系统不认。将SEH转化为C++异常C++异常处理并不能处理所有类型的异常而将SEH和C++异常混用,可以达到使用C++异常处理处理所有异常的目的要混用二者需要在项目属性->C/C++->代码生成->启动C++异常的选项中打开SEH开关。在混用时可以在SEH的过滤表达式的函数中使用C++异常,当然最好的方式是将SEH转化为C++异常。通过调用_set_se_translator这个函数指定一个规定格式的回调函数指针就可以利用标准C++风格的关键字处理SEH了。下面是它们的定义:_set_se_translator(_se_translator_function seTransFunction); typedef void (*_se_translator_function)(unsigned int, struct _EXCEPTION_POINTERS* );使用时,需要自定义实现_se_translator_function函数,在这个函数中通常可以通过throw一个C++异常的方式将捕获的SEH以标准C++EH的方式抛出下面是一个使用的例子:class SE_Exception { public: SE_Exception(){}; SE_Exception(DWORD dwErrCode) : dwExceptionCode(dwErrCode){}; ~SE_Exception(){}; private: DWORD dwExceptionCode; }; void STF(unsigned int ui, PEXCEPTION_POINTERS pEp) { printf("执行STF函数\n"); throw SE_Exception(); } void Func(int i) { int x = 0; int y = 5; x = y / i; } int _tmain(int argc, TCHAR *argv[]) { try { _set_se_translator(STF); Func(0); } catch(SE_Exception &e) { printf("main 函数中捕获到异常 \n"); } return 0; }程序首先调用_set_se_translator函数定义了一个回掉函数,当异常发生时,系统调用回掉函数,在函数中抛出一个自定义的异常类,在主函数中使用C++的异常处理捕获到了这个异常并成功输出了一条信息。
2016年08月16日
5 阅读
0 评论
0 点赞