统一处理非托管API中的错误代码

我正在编写一个包含相当大的非托管API的包装器。 几乎每个导入的方法在失败时都会返回一个常见的错误代码。 现在,我这样做:

ErrorCode result = Api.Method(); if (result != ErrorCode.SUCCESS) { throw Helper.ErrorToException(result); } 

这很好用。 问题是,我有很多非托管方法调用,这会非常令人沮丧和重复。 所以,我尝试切换到这个:

 public static void ApiCall(Func apiMethod) { ErrorCode result = apiMethod(); if (result != ErrorCode.SUCCESS) { throw Helper.ErrorToException(result); } } 

这允许我将所有这些调用减少到一行:

 Helper.ApiCall(() => Api.Method()); 

然而,这有两个直接的问题。 首先,如果我的非托管方法使用out参数,我必须首先初始化局部变量,因为方法调用实际上是在委托中。 我希望能够简单地声明一个目的地而不初始化它。

其次,如果抛出exception,我真的不知道它来自何处。 调试器跳转到ApiCall方法,堆栈跟踪仅显示包含对ApiCall的调用而不是委托本身的方法。 由于我可以在单个方法中进行许多API调用,这使调试变得困难。

然后我考虑使用PostSharp来包装所有非托管调用以及错误代码检查,但我不确定如何使用extern方法。 如果最终只是为每个人创建一个包装器方法,那么我会ApiCallApiCall方法相同的exception问题,对吧? 另外,如果调试器只存在于已编译的程序集中,那么调试器如何知道如何在我的代码中向我显示抛出exception的站点?

接下来,我尝试实现一个自定义封送器,它将拦截API调用的返回值并检查那里的错误代码。 遗憾的是,您无法应用自定义封送程序来返回值。 但我认为如果有效的话,那将是一个非常干净的解决方案。

 [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(ApiMethod))] public static extern ErrorCode Method(); 

现在我完全没有想法。 还有什么其他方法可以解决这个问题?

遵循Visual Studio 2010 SDK中的ErrorHandler类。 它存在于早期版本中,但新版本具有CallWithCOMConvention(Action) ,根据您的API与其他托管代码的交互方式,这可能certificate是有价值的。

在可用的方法中,我建议实现以下内容:

  • Succeeded(int)
    Failed()只是!Succeeded() ,所以你可以跳过它)

  • ThrowOnFailure(int)
    (为您的返回代码引发正确的例外)

  • CallWith_MyErrorCode_Convention(Action)CallWith_MyErrorCode_Convention(Func)
    (比如CallWithCOMConvention ,但是你的错误代码)

  • IsCriticalException(Exception)
    (由CallWith_MyErrorCode_Convention

如果您不检查ErrorCode.SUCCESS会发生什么? 您的代码会快速失败并抛出exception吗? 如果您的托管代码抛出,您能告诉哪个非托管API失败了吗? 如果是这样,请考虑不检查错误,只是在非托管API失败时让运行时抛出。

如果情况并非如此,我建议咬紧牙关并遵循你的第一个想法。 我知道你称它为“令人沮丧和重复”,但是从一个带有“聪明”宏解决方案的项目来到类似问题之后,检查方法调用中的返回值和包装exception是疯狂的门户:exception消息和堆栈跟踪变为误导,您无法跟踪代码,性能受损,您的代码会针对错误进行优化,并在成功时脱轨。

如果特定的返回值是错误,那么就会产生一个唯一的exception。 如果它可能不是错误,那么让它去,如果变成错误就抛出。 你说你想把支票减到一行吗?

 if (Api.Method() != ErrorCode.SUCCESS) throw new MyWrapperException("Api.Method broke because ..."); 

如果任何方法返回相同的“常见错误代码”,您的提议也会抛出相同的exception。 这是另一个调试噩梦; 对于从多个调用返回相同错误代码的API,请执行以下操作:

 switch (int returnValue = Api.Method1()) { case ErrorCode.SUCCESS: break; case ErrorCode.TIMEOUT: throw new MyWrapperException("Api.Method1 timed out in situation 1."); case ErrorCode.MOONPHASE: throw new MyWrapperException("Api.Method1 broke because of the moon's phase."); default: throw new MyWrapperException(string.Format("Api.Method1 returned {0}.", returnValue)); } switch (int returnValue = Api.Method2()) { case ErrorCode.SUCCESS: break; case ErrorCode.TIMEOUT: throw new MyWrapperException("Api.Method2 timed out in situation 2, which is different from situation 1."); case ErrorCode.MONDAY: throw new MyWrapperException("Api.Method2 broke because of Mondays."); default: throw new MyWrapperException(string.Format("Api.Method2 returned {0}.", returnValue)); } 

详细? 对。 沮丧? 不,令人沮丧的是尝试调试一个应用程序,无论出现什么错误,都会从每一行抛出相同的exception。

我认为,简单的方法是添加aditional图层。

 class Api { .... private static ErrorCode Method();//changing Method to private public static void NewMethod()//NewMetod is void, because error is converted to exceptions { ErrorCode result = Method(); if (result != ErrorCode.SUCCESS) { throw Helper.ErrorToException(result); } } .... } 

创建一个私有属性来保存ErrorCode值,并从setter中抛出exception。

 class Api { private static ErrorCode _result; private static ErrorCode Result { get { return _result; } set { _result = value; if (_result != ErrorCode.SUCCESS) { throw Helper.ErrorToException(_result); } } } public static void NewMethod() { Result = Api.Method(); Result = Api.Method2(); } } 

写一个T4模板为你做代。

您现有的代码实际上非常非常接近。 如果使用表达式树来保存lambda而不是Func委托,那么你的Helper.ApiCall可以提取被调用函数的标识,并将其添加到它抛出的exception中。 有关表达树的更多信息和一些非常好的例子,Google Marc Gravell 。