// FindNextFile requires checking for success of each call
while (FindNextFile(hFind, &ffd) != 0);
dwError = GetLastError();
if (dwError != ERROR_NO_MORE_FILES)
ErrorHandler(TEXT("FindFirstFile"));
FindClose(hFind);
return dwError;
从包含意外的情况,给该函数的调用方函数只能传递错误条件。 异常具有强大功能来传递函数的执行结果超出当前函数范围给堆栈上的每个帧至知道如何处理意外的情况的框架。 CLR 的异常系统 (称为两次通过异常系统) 将异常传递到线程的调用堆栈上, 每个前置以调用方开头,并继续直到某些功能说它会处理的异常 (这称为第一轮) 中。
然后,异常系统将展开其中引发异常和则为处理 (称为第二步中) 之间的调用堆栈上的每个框架的状态。 为堆栈 unwinds,CLR 将 finally 子句和 fault 子句以运行每个框架按原样 DPN。 然后,执行处理框架中的到 catch 子句。
因为 CLR 检查调用堆栈上的每个前置任务的调用方具有 catch 块不需要,可以被堆栈上任何位置捕获该异常。 而不是让代码以立即检查每个函数调用的结果,程序员可以处理远从引发异常的一个位置中的错误。 使用错误代码,将需要程序员可以检查和错误代码在每个堆栈帧传递至可进行处理错误条件的位置。 异常处理释放从检查在堆栈上的每个帧异常程序员。
有关详细引发自定义的异常类型,请参阅" 错误处理: 从托管 COM+ 服务器应用程序中引发自定义异常类型".
Win 32 SEH 异常和 System.Exception
还有来自能够捕获远从引发异常的异常的有趣副作用。 程序线程可以接收来自任何活动的框架,它调用堆栈上的程序例外不知道引发异常。 但例外不始终代表程序检测到的错误条件: 程序线程也可能导致程序之外的异常。
如果一个线程执行导致错误的处理器,然后将控制转到操作系统内核提供了到为 SEH 例外线程错误。 正如您的 catch 块不知道其中引发了异常的线程的堆栈上, 它不需要知道完全什么时候操作系统内核引发 SEH 异常。
Windows 将通知有关使用 SEH OS 例外的程序线程。 托管的代码的程序员很少看这些因为 CLR 通常会阻止该类型的由 SEH 异常的错误。 但是,如果 Windows 引发 SEH 异常,CLR 将提供它为托管代码。 尽管在托管代码中的 SEH 异常是极少,不安全的托管的代码可以生成一个 STATUS_ACCESS_VIOLATION 表示程序试图访问无效内存。
有关 SEH 的详细信息,请参阅 Matt Pietrek 文章" 在 Win 32 的深度的崩溃课程结构化异常处理"1997 年 1 月发布的 Microsoft Systems Journal .
SEH 异常都不同的类从由程序引发这些异常。 某个程序可能会引发异常,因为它试图中弹出空牌叠中的某个项目或尝试打开一个文件,不存在。 所有这些异常的意义的程序的执行上下文中。 SEH 异常指向外部的程序上下文。 访问冲突 (AV),例如,表示无效的内存的尝试的写入。 与程序错误不同 SEH 异常指示运行库的过程的完整性可能已被破坏。 但即使 SEH 异常是不同于 CLR 将 SEH 异常传递到托管的线程时,从 System.Exception 中, 派生的异常它可以被捕获使用 catch (Exception e) 语句。
某些系统尝试分隔这些两种类型的异常。 Microsoft Visual C++ 编译器区分 C++ throw 语句和 Win 32 SEH 异常如果编译使用 /EH 开关程序引发的异常。 这种分离是有用的因为一个普通的程序不知道如何处理没有不引发的错误。 如果 C++ 程序尝试将元素添加到一个 std::vector,应的操作可能不足内存,无法由于但正确的程序使用编写良好的库不应该处理的访问冲突。
这种分离,则适合程序员。 AV 是一个严重的问题: 关键的系统内存的意外的写入时可能产生不可预料的结果会影响过程的任何一部分。 但一个零除错误,导致的错误和未检查用户输入,等某些 SEH 错误是严重。 正确使用一个由零除的某个程序时就不这会影响系统的任何其他部分。 实际上,很可能 C++ 程序可以处理不 destabilizing 系统的其余部分的一个零除错误。 因此有用这种分离时它不会很表示托管的语义程序员需要。
托管代码和 SEH
CLR 有总会 SEH 异常发送到使用相同的机制为由程序引发的异常的托管代码。 这不是问题,只要代码不会尝试处理无法合理处理的异常情况。 大多数程序不能安全地继续访问冲突后的执行。 遗憾的是,CLR 的异常处理模型具有始终鼓励用户要允许程序捕获顶部 System.Exception 层次结构的任何异常捕获这些严重错误。 但这是很少要做正确的事情。
编写 catch (Exception e) 是一个常见的编程错误,因为未处理的异常有严重的后果。 但是,您可能认为如果您不知道由一个函数,将产生错误,应保护对所有可能的错误在程序调用该函数时。 这似乎类似的操作的合理过程直到您对的含义继续执行,当您的流程可能处于损坏的状态。 有时中止,尝试再次是最佳选择: 没有人喜欢看到一个 Watson 对话框但最好重新启动您的程序比要有数据损坏。
程序捕捉异常由他们不了解是一个严重的问题的上下文。 但是您不能使用异常规范或某些其他合同机制解决此问题。 和重要托管的程序能够接收 SEH 异常的通知,因为 CLR 是一个平台,许多种类的应用程序和主机。 有些主机,such as SQL Server,需要总控制应用程序的过程。 与本机代码有时托管的代码的相互操作必须处理本机 C++ 异常或 SEH 异常。
但大多数程序员编写 catch (Exception e) 确实不希望捕获访问冲突。 他们更愿意发生灾难性错误时其程序停止而不是让处于未知状态 limp 以及该程序的执行。 对于程序尤其如此该主机托管外接程序如 Visual Studio 或 Microsoft Office。 如果加载项会导致访问冲突并且然后 swallows 异常,主机可能会执行损坏自己州 (或用户文件) 不过意识到出现问题。
在 CLR 的版本 4,产品团队进行表示不同于所有其他异常的损坏的进程状态的异常。 我们将以指示已损坏的进程的状态指定有关个 SEH 异常。 指定与的上下文相对于异常类型本身引发异常。 这意味着从 Windows 收到访问冲突将被标记为一个损坏的状态异常 (CSE),但由新 System.AccessViolationexception 将未标记为一个 CSE 编写的 throw 引发用户代码中的一个。 如果您参加了 PDC 2008,您将收到一个社区技术预览的 Visual Studio 2010 包括这些更改。
一定要注意该异常不会损坏进程: 进程状态中检测到损坏后,引发异常。 是例如通过不安全的代码中的指针写入引用不属于该程序的内存时, 引发访问冲突。 非法写入实际上没有发生,操作系统检查内存的所有权并阻止该操作从记录的位置。 访问冲突表示本身的指针已损坏在一个线程的执行。
损坏的状态异常
在版本 4 及更高版本,CLR 异常系统将不提供 CSEs 为托管代码除非代码明确指出它可以处理损坏的进程状态异常。 这意味着在托管代码中的 catch (Exception e) 的实例不会有提供给它的 CSE。 中的 CLR 异常系统更改,从而您不必更改异常层次结构,或更改任何托管的语言的异常处理语义。
出于兼容性的原因 CLR 团队提供可以运行您的旧代码旧行为下的一些方法:
如果要重新编译代码在 Microsoft.NET Framework 3.5 中创建并运行它在.NET Framework 4.0 而不必更新源,可以在应用程序配置文件中添加一个条目: legacyCorruptedStateExceptionsPolicy = true。
针对.NET Framework 3.5 或更早版本运行库的编译的程序集将能够处理损坏的状态异常 (换句话说,维护旧行为).NET Framework 4.0 上运行时。
如果您希望在代码中来处理 CSEs,您必须指定您需要通过标记包含带有新的属性在异常子句 (catch,最后或错误),函数: System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions。 如果引发一个 CSE,CLR 将执行的搜索匹配的 catch 子句,但将仅搜索用 HandleProcessCorruptedStateExceptions 属性标记的函数中 (参见 图 2 )。
图 2 使用 HandleProcessCorruptedStateExceptions
// This program runs as part of an automated test system so you need
// to prevent the normal Unhandled Exception behavior (Watson dialog).
// Instead, print out any exceptions and exit with an error code.
[HandledProcessCorruptedStateExceptions]
public static int Main()
// Catch any exceptions leaking out of the program
CallMainProgramLoop();
catch (Exception e) // We could be catching anything here
// The exception we caught could have been a program error
// or something much more serious. Regardless, we know that
// something is not right. We'll just output the exception
// and exit with an error. We won't try to do any work when
// the program or process is in an unknown state!
System.Console.WriteLine(e.Message);
return 1;
return 0;
如果找到合适的 catch 子句,CLR 将展开堆栈,正常,但只将最后执行和 fault 块 (和 C# 中,隐式最后阻止在使用的语句) 函数标记为属性中。 遇到在部分受信任或透明的代码中,因为受信任的宿主不希望的不受信任外接程序捕获和忽略这些严重异常时,都忽略该 HandleProcessCorruptedStateExceptions 属性。
很仍错误使用 Catch (Exception e)
即使 CLR 异常系统将最坏的例外情况标记为已 CSE,它最好仍不在您在代码中写 catch (Exception e)。 异常表示意外情况的整个的系列。 CLR 可以检测最坏的异常,指示可能已损坏的进程状态的 SEH 异常。 但其他意外的情况仍可以有害如果忽略或常规处理。
在进程损坏缺少,CLR 提供了一些非常强程序的正确性和内存的安全保证。 执行以安全的 Microsoft 中间语言 (MSIL) 代码您可以确定某个程序时在程序中的所有指令将正确都执行。 但执行程序说明假设为是通常不同执行,程序员想。 根据给 CLR 完全正确的程序可能会损坏如程序文件以写入到磁盘之类的持久的状态。
以一个简单的示例为管理一个高中的考试分数的数据库的程序。 程序使用面向对象的设计原则封装数据,并引发托管的异常,以指示意外的事件。 学校 Secretary 太点击 Enter 键一的一天,很多时间生成成绩文件时。 程序尝试将值从一个空队列中弹出,并引发一个 QueueEmptyException 在调用堆栈上是通过框架处理的。
某处靠近堆栈的顶部是函数,GenerateGrades(),使用 try/catch 子句捕捉异常的。 遗憾的是,GenerateGrades() 有学生存储在队列中,不会有任何想法如何处理一个 QueueEmptyexception 不知道。 但编写 GenerateGrades() 的程序员不想而不保存的计算到目前为止的数据的程序崩溃。 所有内容将安全地写入到磁盘并退出程序。
使用此程序问题是它使许多可能不正确的假设。 什么是说学生队列中缺少的条目位于末尾? 可能是第一个学生记录清楚跳过,或最十分位。 该异常只告诉程序员程序不正确。 采取任何操作,将数据保存到磁盘或"恢复"和继续执行,是只是普通的错误。 不不可能上下文中引发异常的不知情的情况下任何正确的操作。
如果该程序已捕获接近于引发该异常的特定异常,它可能已能够采取适当措施。 该程序知道一个 QueueEmptyException 试图出列学生的函数中的含义。 如果函数捕获该异常类型,而不是捕捉整个类异常类型,它将尝试进行正确的程序状态的更好位置中。
一般情况下,捕获特定异常是正确做,因为它提供异常处理程序在大多数上下文。 如果您的代码可能可以捕获两种例外情况,然后它必须能够同时处理。 编写代码,指出 catch (Exception e) 必须为能够处理真正任何异常的情况。 这是一个很难保留的承诺。
某些语言尝试阻止程序员捕获异常一大类。 例如,C++ 有一机制,允许程序员指定哪些异常可引发的函数中的异常规范。 Java 采用这进一步但选中的情况例外编译器强制要求指定异常类。 在这两个的语言列表超出函数声明中的此函数可以流动的异常,并调用方需要处理这些异常。 异常规范是一个不错的主意,但它们具有了混合模式操作的结果。
还有为为了任何托管的代码应能够处理 CSEs vigorous 争论。 这些异常通常表示一个系统级错误,和只应了解系统级上下文的代码处理。 虽然大多数人不需要能够处理 CSEs,有几个方案,有必要。
一个方案是当您已经非常接近于在异常发生。 例如,考虑调用的已知 buggy 的本机代码的程序。 调试您了解它有时零的指针访问它之前的代码,这会导致访问冲突。 可以调用本机代码,因为您知道指针损坏的原因,并认为是维护进程完整性安全使用 P/Invoke 函数上使用 HandleProcessCorruptedStateExceptions 属性。
使用此属性可能会调用的其他方案时您就可以从错误。 实际上,您就几乎可以退出您的进程。 假设您已编写一个主机或一个框架,想要执行错误的一些自定义日志记录。 可以封装您的 Main 函数与 try/catch / finally 块,并将其标记与 HandleProcessCorruptedStateExceptions。 错误意外使得一直上程序的主函数,您是否写入一些数据您的日志作为少地,必须并退出此过程。 任何工作当过程的完整性为有问题可能是危险,但如果自定义日志记录失败有时可接受。
请看一下 图 3 所示的图表。 此函数 1 (fn1()) 以便其 catch 子句捕获访问冲突属性与 [HandleProcessCorruptedStateExceptions]。 在 finally 块函数 3 不会执行即使该异常将被捕获函数 1 中。 在堆栈底部函数 4 引发访问冲突。
图 3 异常和访问冲突
没有在这些情况下以及您正操作是完全安全,但还有就终止进程的不可接受的方案任一保证。 但是,如果您决定处理一个 CSE 没有大的负担对您作为程序员正确地执行该操作。 请记住 CLR 异常系统不会甚至交给一个 CSE 没有标记为新的属性是在第一轮搜索匹配的 catch 子句) 时或第二步 (时 unwinds 每个框架的状态和执行 finally 和 fault 块) 过程中的任何函数。
在 finally 块存在为了保证该代码始终运行,有是否异常。 (fault 块只时运行异常发生,但是它们具有类似保证始终正在执行)。 这些构造用于清理如释放文件句柄或反转模拟环境的重要资源。
甚至编写使用可靠的代码限制的执行,除非它是已被标记为 HandleProcessCorruptedStateExceptions 属性的函数中引发一个 CSE 时不会执行区域 (CER)。 很难非常编写正确的代码来处理一个 CSE 并继续安全地运行该进程。
仔细查看 图 4 查看内容可以转错误代码。 如果此代码不能处理 CSEs 的函数中则 finally 块不能运行发生访问冲突时。 很好在进程终止,将发布打开的文件句柄。 但如果某个其他代码捕获访问冲突,并尝试还原状态,需要知道它必须关闭该文件,以及恢复该程序已更改任何其他外部状态。
图 4 </a0>-finally 块可能不运行
void ReadFile(int index)
System.IO.StreamReader file =
new System.IO.StreamReader(filepath);
file.ReadBlock(buffer, index, buffer.Length);
catch (System.IO.IOException e)
Console.WriteLine("File Read Error!");
finally
if (file != null)
file.Close()
如果您决定您要处理一个 CSE,您的代码需要预期没有的未 DPN 的关键状态的大量。 最后,fault 块不具有已运行。 不执行受约束的执行区域。 该程序) 和过程处于未知状态。
如果您知道您的代码将执行正确的操作时,就会知道如何操作。 但如果您不确定状态的程序在内,执行,则最好只是让您退出的进程。 或如果您的应用程序承载,调用您的主机已指定该升级策略。 请参阅 Alessandro Catorcini 和 Brian Grunkemeyer 从 2007 年 12 月的 CLR 透彻列 有关编写可靠代码和 CER 的更多信息。
明智地代码
尽管 CLR 阻止您 naively 捕获 CSEs,是仍不一个好主意要捕获的异常的过大类。 但 catch (Exception e) 显示在大量代码中,,就不可能这将更改。 通过不提供表示一个损坏的进程状态 naively 捕捉所有异常的代码的异常,可防止此代码进行严重情况更糟糕。
在下次您编写或维护捕捉异常的代码时考虑该异常的含义。 捕获哪些程序 (和它使用的库) 都会引发的匹配类型? 您知道如何处理该异常,以便程序可以正确并安全地继续执行吗?
异常处理是一个功能强大的工具,应仔细和 thoughtfully 使用。 如果您确实要使用此功能,如果您真正需要处理可能已损坏的进程的异常,CLR 将信任您并可以执行此操作。 只需小心,并正确地执行该操作。
将您的问题和提出的意见发送至 [email protected].
Andrew Pardoe 是 Microsoft 的 CLR 的程序经理。 他适用于桌面和 Silverlight 的运行库执行引擎的许多方面。 在可以访问他 [email protected].