Windows的异常处理

Windows的异常处理

Windows系统的异常处理有两种:

  • 结构化异常处理(SEH,Structured Exception Handling)
  • 向量化异常处理(VEH,Vectored Exception Handling)

SEH

Windows定义了SEH机制来规范异常处理代码的设计(对程序员)和编译(对编译器)。
SEH 提供了 终结处理异常处理 两种功能。

  • 终结处理:用于保证终结处理块始终可以得到执行,无论被保护的代码块如何结束。
  • 异常处理:用于接收和处理被保护块中的代码所发生的异常;

__try__except__finallyVisual C++ 编译器为了支持SEH专门定义的关键字。

按照惯例:没有下划线的关键字通常是编程语言所定义的,如上一篇文章提到的C++异常处理,用的就是try、catch;
而加双下划线的关键字通常是编译器定义的。

SEH的终结处理

代码结构如下:

__try
{
	// 要保护的代码块
}
__finally
{
	// 终结处理块
}

只要保护的代码块被执行,终结处理块就会被执行。除非被保护的代码块终止了当前线程,如使用ExitProcess、ExitThread、TerminateProcess、TerminateThread等。因此终结处理块非常适合做 状态恢复资源释放 等工作。

SEH把被保护块的退出分为正常退出和非正常退出。在终结处理块中可以通过AbnormalTermination函数来获得被保护块的退出方式。

  • 正常退出:被保护块得到自然执行并顺序进入终结处理块。函数返回FALSE。
  • 非正常退出:被保护块因为发生异常或由于returngotobreakcontinue等流程控制语句离开被保护块。函数返回TRUE。

除了__try和__finally,终结处理还有个关键字__leave。它的作用是立即停止执行被保护块,使用__leave关键字的退出也属于正常退出。

SEH的异常处理

代码结构如下:

__try
{
	// 要保护的代码块
}
__except(过滤表达式)
{
	// 异常处理块
}

除了__try和__except,Visual C++编译器还提供了两个宏来辅助编写异常处理代码:
GetExceptionCode():返回异常代码,只能在过滤表达式或异常处理块中使用该宏。
GetExceptionInformation():返回一个指向EXCEPTION_POINTER结构的指针,只能在过滤表达式中使用该宏。

typedef struct _EXCEPTION_POINTERS 
{
    PEXCEPTION_RECORD ExceptionRecord;	// 异常结构,记录了异常的详细信息
    PCONTEXT ContextRecord;				// 发生异常时的线程上下文
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

过滤表达式

可以是常量、函数调用、条件表达式或其他表达式,只要表达式的结果为0-11这三个值之一,即可。其代表的含义如下:

#define EXCEPTION_EXECUTE_HANDLER 1	// 是本保护块预计到的异常,让系统执行本块中的异常处理代码,执行完后会继续执行本异常处理块下面的代码,即__except后面的代码
#define EXCEPTION_CONTINUE_SEARCH 0	// 本保护块不处理该异常,让系统继续寻找其他保护块
#define EXCEPTION_CONTINUE_EXECUTION -1	// 已经处理异常,让程序回到异常发生点继续执行。如果导致异常的情况没被消除,很有可能还会发生异常

常用的过滤表达式:

  • __except(EXCEPTION_EXECUTE_HANDLER),或直接写__except(1)。
  • __except(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION?EXCEPTION_EXECUTE_HANDLER:EXCEPTION_CONTINUE_SEARCH):意思是如果发生的异常是非法访问异常,就执行异常处理块,否则继续搜索其它异常保护块。
  • 使用逗号表达式以实现一系列操作,如__except(printf(“In __Except block”),VAR_WATCH(), …)
  • 调用其他函数,如__except (UDFExceptionFilter(PtrIrpContext, GetExceptionInformation()))

嵌套使用异常处理和终结处理

一个异常保护块(__try块) 不能同时拥有 异常处理块(__except)和终结处理块(__finally),但是可以通过嵌套使一段代码同时得到异常处理和终结处理。
示例代码:

__try
{
	__try
	{
		int n = 0;
		int i = 1/n;
	}
	__finally
	{
		printf("__finally block\r\n");
	}
}
__except(printf("expression\r\n"), EXCEPTION_EXECUTE_HANDLER)
{
	printf("__except block\r\n");
}

程序运行结果为:
expression
__finally block
__except block

即,在该嵌套代码中,当发生除零异常时,会先执行__except块的 过滤表达式。然后运行__finally块,最后运行__except块。
为什么会这样呢?
在解释之前要先介绍两个概念:局部展开和全局展开。

SEH的实现机制

我们在使用SEH时,其实代码的执行是由编译器操作系统来控制的,为了让机制运行起来,编译器必须生成一些额外的代码,系统也必须执行一些额外工作。

局部展开

当__try块中的代码非正常退出,而执行__finally块时,会发生局部展开。

我们举个简单的例子,当在__try块中由return语句时,怎么保证在return之前先执行__finally块呢?
答:编译器在检查程序代码时,如果在__try块中发现了return语句,就会生成一些代码,先将返回值保存在一个局部变量中,然后去执行__finally块,这个过程就叫局部展开。当__finally块执行完毕后,编译器再将该临时变量的值返回给函数的调用者。

局部展开带来的额外开销,对应用程序的性能是有害的,应避免。
__try块正常退出,再进入__finally块,这种情况下,额外开销就是最小的

全局展开

当异常处理的过滤表达式的计算结果是EXCEPTION_EXECUTE_HANDLER(1)时,系统必须执行全局展开。全局展开导致所有已经开始执行但尚未完成的try-finally块得以继续执行

介绍完这两个概念,上面嵌套的例子就可以解释了
当里层的__try块发生除零异常时,会先看本层是否有对应的__except处理块,发现没有,则寻找外面一层的__try块,发现有,于是进入到了外层的__except块,首先执行过滤表达式,其结果是EXCEPTION_EXECUTE_HANDLER,此时发生全局展开
全局展开后,会先从里层到外层检查是否有未完成的__finally块,发现有,则执行(而这本身其实就是一个局部展开的过程)。
执行完之后,到外层,继续执行__except块。

我们再多加一层嵌套,如果理解了上面例子的原理,则应该能准确的知道这个例子的运行结果。
示例代码:

__try
{
	__try
	{
		__try
		{
			int n = 0;
			int i = 1 / n;
		}
		__finally
		{
			printf("__finally block3\r\n");
		}
	}
	__finally
	{
		printf("__finally block2\r\n");
	}	
}
__except (printf("expression\r\n"), EXCEPTION_EXECUTE_HANDLER)
{
	printf("__except block1\r\n");
}

程序运行结果为:
expression
__finally block3
__finally block2
__except block1

VEH

除了SEH,从XP开始,Windows还支持了向量化异常处理VEH。
VEH的基本思想:通过注册回调函数来接收和处理异常。
回调函数原型

LONG CALLBACK VertoredHandler(PEXCEPTION_POINTERS ExceptionInfo);

其返回值应该是EXCEPTION_CONTINUE_SEARCH(0,继续搜索) 或 EXCEPTION_CONTINUE_EXECUTION(-1,恢复执行)。

注册和注销回调函数

typedef LONG
(NTAPI *PVECTORED_EXCEPTION_HANDLER)(
    PEXCEPTION_POINTERS ExceptionPointers
);
// 注册成功后,返回一个指针,指向的是系统为该异常处理器分配的一个结构指针(VEH_REGISTRATION),注销时要用;注册失败则返回NULL
PVOID WINAPI AddVectoredExceptionHandler(
	_In_ ULONG FirstHandler, 	// 指定回调函数的被调用顺序,0表示希望最后被调用,1表示最先
	_In_ PVECTORED_EXCEPTION_HANDLER VectoredHandler);	// 回调函数
ULONG WINAPI RemoveVectoredExceptionHandler(_In_ PVOID);

目前我在编写代码时,还没有用过VEH,一般都是用SEH,因此就不详细介绍了,感兴趣的可以移步《软件调试》第十一章11.5节。

SEH和VEH的区别

  • SEH既可以用在用户态,也可以用在内核态;而VEH只能用在用户态中。
  • SEH无Windows版本限制;而VEH只能用在XP及更高版本的Windows中。
  • SEH的登记和注销依赖编译器编译时生成的数据结构和代码;而VEH的注册和注销都是通过调用系统API显式完成的。
  • 若同时注册了SEH和VEH,VEH优先级更高
  • SEH的注册信息是以固定的结构存储在线程栈中,因此SEH只对当前函数或其子函数有效;而VEH是存储在进程的内存堆中,因此具有全局有效性,对整个进程都有效。
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt是一款跨平台的应用程序开发框架,而MinGW是一种在Windows平台上使用的编译器套件。在使用Qt进行开发时,也可能会遇到一些异常情况需要进行处理。 首先,我们可以通过try-catch语句来捕获和处理异常。try语句块中包含有可能发生异常的代码,catch语句块用于处理并捕获这些异常。比如: ``` try { // 可能发生异常的代码 } catch (const std::exception& e) { // 处理异常的代码 } ``` 另外,在Qt中也提供了一些特定的异常处理类。比如,对于文件操作可能会出现的异常,可以使用QFile类的error()函数来判断文件操作是否发生了异常,然后使用QFile::errorString()函数获取异常信息。例如: ``` QFile file("example.txt"); if (!file.open(QIODevice::ReadOnly)) { QString errorMsg = file.errorString(); // 处理异常的代码 } ``` 此外,还可以使用Qt的信号和槽机制来进行异常处理。通过发射一个自定义的信号来传递异常信息,然后在槽函数中进行处理。例如: ``` class MyObject : public QObject { Q_OBJECT signals: void errorOccurred(const QString& errorMsg); public: void doSomething() { try { // 可能发生异常的代码 } catch (const std::exception& e) { emit errorOccurred(QString::fromUtf8(e.what())); } } }; class MyWidget : public QWidget { Q_OBJECT public slots: void handleError(const QString& errorMsg) { // 处理异常的代码 } }; // 在使用时连接信号和槽函数 MyObject* obj = new MyObject(); MyWidget* widget = new MyWidget(); connect(obj, &MyObject::errorOccurred, widget, &MyWidget::handleError); ``` 总之,Qt配合MinGW可以很好地处理异常情况。可以使用try-catch语句捕获和处理异常,利用Qt提供的一些异常处理类处理特定的异常,或者使用信号和槽机制传递异常信息进行处理。根据具体情况选择合适的方法来处理异常。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值