【问题标题】:Throwing a Win32Exception抛出 Win32Exception
【发布时间】:2009-03-23 15:57:52
【问题描述】:

我最近一直在编写大量涉及与 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 属性。但是,我一直认为最好修改/包装至少一些(如果不是全部)这些异常,以便它们提供稍微更面向上下文的错误消息,即在本机代码的任何情况下更有意义的错误消息是正在使用。为此,我考虑了几种替代方案:

  1. Win32Exception 的构造函数中指定自定义错误消息。

    throw new Win32Exception(Marshal.GetLastWin32Error(), "My custom error message.");
    
  2. Win32Exception 包装在另一个异常对象中,以便保留原始错误代码和消息(Win32Exception 现在是父异常的InnerException)。

    throw new Exception("My custom error message.", Win32Exception(Marshal.GetLastWin32Error()));
    
  3. 与2相同,只是使用另一个Win32Exception作为包装异常。

  4. 与 2 相同,但使用从 Exception 派生的自定义类作为包装异常。

  5. 与 2 相同,但在适当时使用 BCL(基类库)异常作为父异常。不确定在这种情况下将InnerException 设置为Win32Exception 是否合适(可能是低级包装器,但不是更高级别/抽象的接口,这并不能清楚地表明Win32 互操作发生在后面场景?)

基本上我想知道的是:在 .NET 中处理 Win32 错误的推荐做法是什么?我看到它以各种不同的方式在开源代码中完成,但我很好奇是否有任何设计指南。如果没有,我会在这里对您的个人喜好感兴趣。 (也许你甚至没有使用上述方法?)

【问题讨论】:

    标签: c# winapi exception win32exception


    【解决方案1】:

    这并不是 Win32 异常所特有的;问题是,两个不同的异常派生类型应该在什么时候识别两个不同的错误情况,什么时候应该抛出存储在其中的不同值的相同类型?

    不幸的是,如果不事先知道您的代码将被调用的所有情况,这是不可能回答的。:) 这是只能按类型过滤异常的问题。概括地说,如果您强烈认为以不同方式处理两种错误情况会很有用,请抛出不同的类型。

    否则,通常情况下 Exception.Message 返回的字符串只需要记录或显示给用户。

    如果有其他信息,请使用您自己的更高级的东西来包装 Win32Exception。例如,您正在尝试对文件执行某些操作,而您正在运行的用户没有执行此操作的权限。捕获 Win32Exception,将其包装在您自己的异常类中,其消息给出文件名和正在尝试的操作,然后是内部异常的消息。

    【讨论】:

    • +1 获取更多信息 - 没有什么比“找不到文件”异常更令人沮丧的了。
    • @Earwicker:你说得很对......另外,我问这个问题是专门与 Win32Exception 相关的,因为它们通常(尽管并非总是)往往相当模糊和无益,正如 Harper笔记。
    • 为了澄清你的最后一段,你的意思是最好附加内部错误消息以及作为内部异常包装它?
    • @Noldorin - 很遗憾,是的,除非您知道调用者将遵循构建复合消息的模式(请参阅上面的更新)。
    【解决方案2】:

    我一直认为,处理此问题的适当方法取决于目标受众以及您的课程将如何被使用。

    如果您的类/方法的调用者将意识到他们正在以一种或另一种形式调用 Win32,我将使用您指定的选项 1)。这对我来说似乎是最“清楚”的。 (但是,如果是这种情况,我会以清楚表明将直接使用 Win32 API 的方式命名您的类)。话虽如此,BCL 中有一些异常实际上是 Win32Exception 的子类,以便更清楚地了解,而不仅仅是包装它。例如,SocketException 派生自 Win32Exception。我从未亲自使用过这种方法,但它似乎是一种潜在的干净方式来处理这个问题。

    如果您的类的调用者不知道您正在直接调用 Win32 API,我将处理该异常,并使用您定义的自定义、更具描述性的异常。例如,如果我正在使用您的课程,并且没有迹象表明您正在使用 Win32 api(因为您出于某些特定的、非显而易见的原因在内部使用它),我没有理由怀疑我可能需要处理 Win32Exception。您总是可以记录这一点,但对我来说,将其捕获并给出一个在您的特定业务环境中具有更多意义的异常似乎更合理。在这种情况下,我可能会将初始 Win32Exception 包装为内部异常(即:您的情况 4),但根据导致内部异常的原因,我可能不会。

    此外,本机调用会引发 Win32Exception 很多时候,但 BCL 中还有其他更相关的异常。当您调用未包装的本机 API 时就是这种情况,但也有类似的函数被包装在 BCL 中。在这种情况下,我可能会捕获异常,确保它是我所期望的,然后抛出标准的 BCL 异常。一个很好的例子是使用 SecurityException 而不是抛出 Win32Exception。

    不过,总的来说,我会避免使用您列出的选项 2 和 3。

    选项二会抛出一个通用的异常类型——我非常建议完全避免这种情况。将特定的异常包装成更通用的异常似乎是不合理的。

    选项 3 似乎多余 - 与选项 1 相比确实没有优势。

    【讨论】:

    • 感谢您的建议。我认为在我的情况下直接抛出 Win32Exception 可能是最好的,因为包含代码的类实际上是操作其他进程/线程/共享内存的东西的包装器。
    • (续)抛出 BCL 异常(并包装 Win32 异常?)也可能是合适的。当人们想要隐藏正在使用 Win32 互操作的事实(即相对高级的接口)时,选项 4 确实看起来是最好的。
    【解决方案3】:

    我个人会做#2 或#4...最好是#4。将 Win32Exception 包装在上下文相关的异常中。像这样:

    void ReadFile()
    {
        if (!WinApi.NativeFunction(param1, param2, param3))
            throw MyReadFileException("Couldn't read file", new Win32Exception());
    }
    

    这样,如果有人捕捉到异常,他们就会很清楚问题发生在哪里。我不会做#1,因为它需要捕获来解释您的文本错误消息。而#3 并没有真正提供任何额外的信息。

    【讨论】:

    • 同意。当我想隐藏 Win32 异常时,我可能会使用选项 4 - 可能使用 BCL 异常而不是适当的自定义异常。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-03
    • 1970-01-01
    • 1970-01-01
    • 2010-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多