zl程序教程

您现在的位置是:首页 >  系统

当前栏目

Windows核心编程 第2 4章 异常处理程序和软件异常

Windows软件异常编程 核心 处理程序
2023-09-11 14:14:00 时间

异常处理程序和软件异常

    C P U引发的异常,就是所谓的硬件异常(hardware exception)。操作系统和应用程序

也可以引发相应的异常,称为软件异常(software exception)。

当出现一个硬件或软件异常时,操作系统向应用程序提供机会来考察是什么类型的异常被引发,并能够让应用程序自己来处理异常。下面就是异常处理程序的文法:


注意- - e x c e p t关键字。每当你建立一个t r y块,它必须跟随一个f i n a l l y块或一个e x c e p t块。一个try 块之后不能既有f i n a l l y块又有e x c e p t块。但可以在t r y - e x c e p t块中嵌套t r y - f i n a l l y块,反过来也可以。

   与结束处理程序(__try{}__finally{})不同,异常过滤器( exception filter)和异常处理程序是通过操作系统直接执行的,编译程序在计算异常过滤器表达式和执行异常处理程序方面不做什么事。

尽管在结束处理程序的t r y块中使用r e t u r ng o t oc o n t i n u eb r e a k语句遭到强烈地反对,但在异常处理程序的t r y块中使用这些语句不会产生速度和代码规模方面的不良影响。这样的语句出现在与e x c e p t块相结合的t r y块中不会引起局部展开的系统开销。

例子1(可以直接捕获到异常,不会内存您访问故障的窗体,__try{}__finally{}会弹窗)


解释__except()里面的三种参数:


EXCEPTION_EXECUTE_HANDLER

这个值的意思是要告诉系统:“我认出了这个异常。即,我感觉这个异常可能在某个时候发生,我已编写了代码来处理这个问题,现在我想执行这个代码。”


FuncOren1/0导致硬件中断,然后开始回溯到FuncOstimpy1发现有except(同时参数是EXCEPTION_EXECUTE_HANDLER),然后就开始继续执行finallyfinally开始全局展开,执行完成finally代码之后回溯到上一层 FuncOstimpy1里去执行__except代码。结果是先输出finally然后输出except

暂停全局展开:书上说是在类似上面的例子中,如果finally里面有return语句就会终止全局展开,然而没编译过去。

 

EXCEPTION_CONTINUE_EXECUTION

 

    这里,首先遇到的问题是在我们试图向 p c h B u ff e r所指向的缓冲区中放入一个字母‘ J’时发生的。因为这里没有初始化p c h B u ff e r,使它指向全局缓冲区g _ s z B u ff u rp c h B u ff e r实际指向N U L LC P U将产生一个异常,并计算与异常发生的 t r y块相关联的e x c e p t块的异常过滤器。在e x c e p t块中,对O i l F i l t e r函数传递了p c h B u ff e r变量的地址。

    当O i l F i l t e r获得控制时,它要查看* p p c h B u ff e r是不是N U L L,如果是,把它设置成指向全局缓冲区g _ s z B u ff e r。然后这个过滤器返回E X C E P T I O N _ C O N T I N U E _ E X E C U T I O N。当系统看到过滤器的值是E X C E P T I O N _ C O N T I N U E _ E X E C U T I O N时,系统跳回到产生异常的指令,试图再执行一次。这一次,指令将执行成功,字母‘J’将放在g _ s z B u ff e r的第一个字节。

    随着代码继续执行,我们又在 t r y块中碰到除以0的问题。系统又要计算过滤器的值。这一次,O i l F i l t e r看到* p p c h B u ff e r不是N U L L,就返回E X C E P T I O N _ E X E C U T E _ H A N D L E R,这是告诉系统去执行e x c e p t块中的代码。这会显示一个消息框,用文本串报告发生了异常。

实际执行结果是输出了一次Function completed 但第一个MessageBoxA(NULL ,pchBuffer ,"tit" ,MB_OK);  并没有执行

EXCEPTION_CONTINUE_SEARCH

取值 E X C E P T I O N _CONTINUE_ SEARCH。这个标识符是告诉系统去查找前面与一个 e x c e p t块相匹配的t r y块,并调用这个t r y块的异常处理器。

GetExceptionCode
    一个异常过滤器在确定要返回什么值之前,必须分析具体情况。例如,异常处理程序可能知道发生了除以0引起的异常时该怎么做,但是不知道该如何处理一个内存存取异常。异常过滤器负责检查实际情况并返回适当的值。

 

软件异常

迄今为止,我们一直在讨论硬件异常,也就是 C P U捕获一个事件并引发一个异常。在代码中也可以强制引发一个异常。这也是一个函数向它的调用者报告失败的一种方法。传统上,失败的函数要返回一些特殊的值来指出失败。函数的调用者应该检查这些特殊值并采取一种替代的动作。通常,这个调用者要清除所做的事情并将它自己的失败代码返回给它的调用者。这种错误代码的逐层传递会使源程序的代码变得非常难于编写和维护。

另外一种方法是让函数在失败时引发异常。用这种方法,代码更容易编写和维护,而且也执行得更好,因为通常不需要执行那些错误测试代码。实际上,仅当发生失败时也就是发生异常时才执行错误测试代码。

但令人遗憾的是,许多开发人员不习惯于在错误处理中使用异常。这有两方面的原因。第一个原因是多数开发人员不熟悉S E H。即使有一个程序员熟悉它,但其他程序员可能不熟悉它。如果一个程序员编写了一个引发异常的函数,但其他程序员并不编写S E H框架来捕获这个异常,那么进程就会被操作系统结束。

    开发人员不使用S E H的第二个原因是它不能移植到其他操作系统。许多公司的产品要面向多种操作系统,因此希望有单一的源代码作为产品的基础,这是可以理解的。 S E H是专门针对Wi n d o w s的技术。

本段讨论通过异常返回错误有关的内容。首先,看一看 Windows Heap函数,例如H e a p C r e a t eh e a p A l l o c等。回顾第1 8章的内容,我们知道这些函数向开发人员提供一种选择。通常当某个堆( h e a p)函数失败,它会返回 N U L L来指出失败。然而可以对这些堆函数传递H E A P _ G E N E R AT E _ E X C E P T I O N S标志。如果使用这个标志并且函数失败,函数不会返回N U L L,而是由函数引发一个 S TAT U S _ N O _ M E M O RY软件异常,程序代码的其他部分可以用S E H框架来捕获这个异常。

如果想利用这个异常,可以编写你的 t r y块,好像内存分配总能成功。如果内存分配失败,可以利用e x c e p t块来处理这个异常,或通过匹配 t r y块与f i n a l l y块,清除函数所做的事。这非常方便。

程序捕获软件异常采取的方法与捕获硬件异常完全相同。也就是说,前一章介绍的内容可同样适用于软件异常。

本节重讨论如何让你自己的函数引发软件异常,作为指出失败的方法。实际上,可以用类似于微软实现堆函数的方法来实现你的函数:让函数的调用者传递一个标志,告诉函数如何指出失败。引发一个软件异常很容易,只需要调用R a i s e E x c e p t i o n函数:

 

    第一个参数 d w E x c e p t i o n C o d e是标识所引发异常的值。 H e a p A l l o c函数对这个参数设定S TAT U S _ N O _ M E M O RY。如果程序员要定义自己的异常标识符,应该遵循标准 Wi n d o w s错误代码的格式,像Wi n E r r o r. h文件中定义的那样。参阅表2 4 - 1

如果要建立你自己的异常代码,要填充D W O R D4个部分:

 

R a i s e E x c e p t i o n的第二个参数 d w E x c e p t i o n F l a g s,必须是 0E X C E P T I O N _N O N C O N T I N U A B L E。本质上,这个标志是用来规定异常过滤器返回 E X C E P T I O N _CONTINUE _EXECUTION来响应所引发的异常是否合法。如果没有向 R a i s e E x c e p t i o n传递EXCEPTION_ NONCONTINUABLE参数值,则过滤器可以返回 E X C E P T I O N _ C O N T I N U E _E X E C U T I O N。正常情况下,这将导致线程重新执行引发软件异常的同一 C P U指令。但微软已做了一些动作,所以在调用R a i s e E x c e p t i o n函数之后,执行会继续进行。

如果你向R a i s e E x c e p t i o n传递了E X C E P T I O N _ N O N C O N T I N U A B L E标志,你就是在告诉系统,你引发异常的类型是不能被继续执行的。这个标志在操作系统内部被用来传达致命(不可恢复)的错误信息。另外,当 H e a p A l l o c引发S TAT U S _ N O _ M E M O RY软件异常时,它使用E X C E P T I O N _ N O N C O N T I N U A B L E标志来告诉系统,这个异常不能被继续。意思就是没有办法强制分配内存并继续运行。

如果一个过滤器忽略E X C E P T I O N _ N O N C O N T I N U A B L E并返回E X C E P T I O N _ C O N T I N U E _E X E C U T I O N,系统会引发新的异常:E X C E P T I O N _ N O N C O N T I N U A B L E _ E X C E P T I O N

当程序在处理一个异常的时候,有可能又引发另一个异常。比如说,一个无效的内存存取有可能发生在一个f i n a l l y块、一个异常过滤器、或一个异常处理程序里。当发生这种情况时,系统压栈异常。回忆一下G e t E x c e p t i o n I n f o r m a t i o n函数。这个函数返回EXCEPTION_ POINTERS结构的地址。E X C E P T I O N _ P O I N T E R SE x c e p t i o n R e c o r d成员指向一个EXCEPTION_ R E C O R D结构,这个结构包含另一个 E x c e p t i o n R e c o r d成员。这个成员是一个指向另外的 E X C E P T I O N _R E C O R D的指针,而这个结构包含有关以前引发异常的信息。

通常系统一次只处理一个异常,并且E x c e p t i o n R e c o r d成员为N U L L。然而如果处理一个异常的过程中又引发另一个异常,第一个E X C E P T I O N _ R E C O R D结构包含有关最近引发异常的信息,并且这个E X C E P T I O N _ R E C O R D结构的E x c e p t i o n R e c o r d成员指向以前发生的异常的E X C E P T I O N _R E C O R D结构。如果增加的异常没有完全处理,可以继续搜索这个 E X C E P T I O N _ R E C O R D结构的链表,来确定如何处理异常。

R a i s e E x c e p t i o n的第三个参数n N u m b e r O f A rg u m e n t s和第四个参数p A rg u m e n t s,用来传递有关所引发异常的附加信息。通常,不需要附加的参数,只需对 p A rg u m e n t s参数传递N U L L,这种情况下, R a i s e E x c e p t i o n函数忽略 n N u m b e r O f A rg u m e n t s参数。如果需要传递附加参数,n N u m b e r O f A rg u m e n t s参数必须规定由p A rg u m e n t s参数所指向的U L O N G _ P T R数组中的元素数目。这个数目不能超过E X C E P T I O N _ M A X I M U M _ PA R A M E T E R SEXCEPTION_ MAXIMUM_

PARAMETERS Wi n N T. h中定义成1 5

在处理这个异常期间,可使异常过滤器参照 E X C E P T I O N _ R E C O R D结构中的 N u m b e rP a r a m e t e r sE x c e p t i o n I n f o r m a t i o n成员来检查n N u m b e r O f A rg u m e n t sp A rg u m e n t s参数中的信息。

你可能由于某种原因想在自己的程序中产生自己的软件异常。例如,你可能想向系统的事件日志发送通知消息。每当程序中的一个函数发现某种问题,你可以调用 R a i s e E x c e p t i o n并让某些异常处理程序上溯调用树查看特定的异常,或者将异常写到日志里或弹出一个消息框。你还可能想建立软件异常来传达程序内部致使错误的信息。