C++API设计和错误处理

C++ API design and error handling

本文关键字:错误 处理 C++API      更新时间:2023-10-16

我需要使用.lib文件(MSVC)编写C++API,它由几个从Dll公开的导出C++类组成。根据我对另一个问题的回答,我理解导出的类方法不能使用异常,如果C++API是在一个VC++版本中构建的(比如说2010),而客户端代码是在另一个VCC++版本中编写的。由于异常不能是公共API接口的一部分,我正在寻找另一种错误处理策略。我的限制:我不想使用COM,丰富的错误代码系统(如HRESULT)对我来说是不够的。我希望有一个类似异常的类,它包含错误代码、错误消息和我需要的任何其他信息。此外,我不想为每个VC++版本单独构建。

我目前的做法如下。每个公共类方法都返回枚举值(如ErrorCode)。在方法失败的情况下,像GetLastErrorInfo这样的静态函数会返回指向C++类(比如ErrorInfo)的指针,该类包含到达错误信息。ErrorInfo作为线程特定的数据保存,并包含当前线程中上次调用的错误信息。若上次API调用成功,GetErrorInfo将返回NULL。

考虑这个代码的例外:

尝试{classPtr->DoSomething();cout<lt;classPtr->GetData()<lt;endl;}catch(const MyException和ex){cout<lt;例如GetErrorMessage()<lt;endl;回来}

毫无例外,它看起来是这样的:

ErrorCode结果;int数据;result=classPtr->DoSomething();if(result!=成功){cout<MyClass::GetLastErrorInfo()->GetErrorMessage()<endl;回来}result=classPtr->GetData(data);if(result!=成功){cout<MyClass::GetLastErrorInfo()->GetErrorMessage()<endl;回来}cout<lt;数据<endl;

这看起来不太好。类接口很混乱:现在每个函数都有ErrorCode返回类型。返回值将成为输出参数。是否有更好的方法,允许访问错误信息,并保持干净的公共API接口?

您可能忽略了一个简单的解决方案。唯一的限制是异常不能跨越模块边界。客户端代码本身抛出异常没有问题。因此,在头中提供一个内联函数,比如CheckReturn(),它会抛出富异常。

有关灵感,请查看COM IErrorInfo接口及其相关的_COM_error类。他们解决了完全相同的问题。还要注意MSVC中提供的#import指令,它自动生成小包装函数,这些函数进行调用并在失败返回值上引发异常。但是你不想使用COM,所以它不能直接使用。

如果从.dll返回C++对象,则必须格外小心,因为调用方可能会尝试以复制或删除这些对象的方式使用这些对象。如果调用程序不使用相同的堆(或相同的标准库),那么到处都会出现各种崩溃和内存泄漏。根据我的经验,通过DLL边界传递C++对象是个坏主意。

我会尝试创建API,要求调用方管理所有内存(分配和释放),以便指针的所有权始终是明确的。这会产生更多的工作,因为您需要接受指针,然后用数据填充这些缓冲区,而不是返回C++对象。我还没有看到C++对象在.dll之间安全传递的工作实现。(但我的经验可能有限。)

此外,当您从.dll导出C++类时,C++以外的任何东西都不太可能使用该.dll,因为其他语言对对象有不同的内存布局。即使是不同的C++编译器也可能在使用这些类时出现问题。

重新阅读您的约束,我认为创建COM对象是最好的选择之一,因为它为您提供了灵活性,并能够返回具有复杂数据的对象(尽管不是C++对象)。

这个怎么样:

基本错误数据-你可以扩展你的lib的"其他东西":

namespace MON {
  class t_error_description {
  public:
    t_error_description(const int& code, const std::string& message);
    virtual ~t_error_description(); /* << allow any other info via subclass */
  public:
    virtual void description(std::ostream& stream) const;
    /* … */
  private:
    const int d_code;
    const std::string d_message;
  };
}

基本错误容器。包装与描述相关的所有内容,这就是客户直接处理的所有内容:

namespace MON {
  class t_error {
  public:
    t_error();
    ~t_error();
  public:
    /* or perhaps you'd favor a stream op? */
    void description(std::ostream&) const;
    /* sets the error - this is a take operation */
    void set(const t_error_description* const desc);
    void clear();
    /* … */
  private:
    /* trivial construction */
    t_auto_pointer<const t_error_description> d_errorDescription;
  private:
    /* verboten */
    t_error(const t_error&);
    t_error& operator=(const t_error&);
  };
}

基本库调用:

namespace MON {
  /* return false on error */
  bool DoSomething(t_error& outError) {
    if (Foo()) {
      outError.set(new t_error_description(ErrorCodeThingy, "blah blah"));
      return false;
    }
    return true;
  }
}

客户电话:

MON::t_error err;
if (!MON::DoSomething(err)) {
  log << "cannot do anything!nError: ";
  err.description(log);
  return;
}