我最近编写了很多涉及与Win32 API互操作的代码,并且开始想知道处理由Windows API函数调用引起的本机(非托管)错误的最佳方法是什么。
目前,对本机函数的调用如下所示:
// NativeFunction returns true when successful and false when an error
// occurred. When an error occurs, the MSDN docs usually tell you that the
// error code can be discovered by calling GetLastError (as long as the
// SetLastError flag has been set in the DllImport attribute).
// Marshal.GetLastWin32Error is the equivalent managed function, it seems.
if (!WinApi.NativeFunction(param1, param2, param3))
throw new Win32Exception();
引发异常的行可以等同地重写,我相信:
throw new Win32Exception(Marshal.GetLastWin32Error());
现在,这很好,它会抛出一个异常,包含设置的Win32错误代码以及错误的(通常)人类可读的描述,作为Exception对象的Message
属性。但是,我一直认为修改/包装至少一些(如果不是全部)这些异常是明智的,这样它们会给出一个稍微更多的面向上下的错误消息,即在本机代码的任何情况下都更有意义。正在使用。我已经考虑了几个替代方案:
在Win32Exception
的构造函数中指定自定义错误消息。
throw new Win32Exception(Marshal.GetLastWin32Error(), "My custom error message.");
将Win32Exception
包装在另一个Exception对象中,以便保留原始错误代码和消息(Win32Exception
现在是父异常的InnerException
。< / p>
throw new Exception("My custom error message.", Win32Exception(Marshal.GetLastWin32Error()));
与2相同,只是使用另一个Win32Exception
作为包装器例外。
与2相同,只是使用从Exception
派生的自定义类作为包装器异常。
与2相同,但在适当时使用BCL(基类库)例外作为父项。在这种情况下,不确定它是否适合将InnerException
设置为Win32Exception
(可能对于低级包装器而不是更高级/抽象的接口,这使得Win32不明显互操作发生在幕后?)
基本上我想知道的是:在.NET中处理Win32错误的推荐做法是什么?我看到它以各种不同的方式在开源代码中完成,但我很好奇是否有任何设计指南。如果没有,我会对您的个人偏好感兴趣。 (也许你甚至不使用上述方法?)
答案 0 :(得分:4)
这并不是特定于Win32异常;问题是,两个不同的错误情况应该何时由两个不同的Exception派生类型识别,何时它们应该抛出相同的类型,并在其中存储不同的值?
不幸的是,如果事先不知道您的代码将被调用的所有情况,这是不可能回答的。:)这是只能按类型过滤异常的问题。从广义上讲,如果您有强烈的感觉,以不同的方式处理两个错误情况会有用,请抛出不同的类型。
否则,通常需要将Exception.Message返回的字符串记录或显示给用户。
如果有其他信息,请使用更高级别的内容包装Win32Exception。例如,您正在尝试对文件执行某些操作,而您正在运行的用户无权执行此操作。捕获Win32Exception,将其包装在您自己的异常类中,其消息提供文件名和正在尝试的操作,然后是内部异常的消息。
答案 1 :(得分:3)
我一直认为,处理此问题的合适方法取决于目标受众以及您的课程将如何使用。
如果您的类/方法的调用者意识到他们以某种形式调用Win32,我会使用您指定的选项1)。这对我来说似乎是最“清晰”的。 (但是,如果是这种情况,我会以明确表示将直接使用Win32 API的方式命名您的类)。话虽这么说,BCL中有一些例外,实际上将Win32Exception子类化得更清楚,而不是仅仅包装它。例如,SocketException派生自Win32Exception。我从来没有亲自使用过这种方法,但它看起来似乎是一种处理这个问题的潜在干净方法。
如果你的类的调用者不知道你直接调用Win32 API,我会处理异常,并使用你定义的自定义,更具描述性的异常。例如,如果我正在使用你的类,并且没有迹象表明你正在使用Win32 api(因为你在内部使用它是出于某种特定的,非显而易见的原因),我没有理由怀疑我可能需要处理Win32Exception。你可以随时记录这一点,但是对我来说似乎更合理的是捕获它并给出一个在你的特定业务环境中具有更多意义的异常。在这种情况下,我可能会将初始Win32Exception包装为内部异常(即:您的案例4),但是根据导致内部异常的原因,我可能不会。
此外,很多时候从本机调用中抛出Win32Exception,但BCL中还有其他更相关的例外。当您调用未包装的本机API时会出现这种情况,但是在BCL中包含了类似的功能。在那种情况下,我可能会捕获异常,确保它是我所期待的,但随后抛出标准的BCL异常。一个很好的例子就是使用SecurityException而不是抛出Win32Exception。
一般来说,我会避免你列出的选项2和3。
选项2抛出一般的异常类型 - 我非常建议完全避免这种情况。将特定异常包装成更广义的异常似乎是不合理的。
选项三似乎是多余的 - 与选项1相比确实没有优势。
答案 2 :(得分:2)
我个人会做#2或#4 ...最好是#4。在您的异常中包装Win32Exception,这是对上下文敏感的。像这样:
void ReadFile()
{
if (!WinApi.NativeFunction(param1, param2, param3))
throw MyReadFileException("Couldn't read file", new Win32Exception());
}
这样一来,如果有人抓住异常,他们就会很清楚问题出在哪里。我不会做#1因为它需要catch来解释你的文本错误信息。并且#3并没有真正提供任何其他信息。