C++ API design and error handling
我需要编写C ++ API,它包含几个使用.lib文件(MSVC)从Dll公开的导出C ++类。从我理解的另一个问题的答案来看,导出的类方法不能使用异常,如果C ++ API是在一个VC ++版本中构建的(比如2010),而客户端代码是用另一个VC ++版本编写的。由于异常不能成为公共API接口的一部分,我正在寻找另一种错误处理策略。我的限制:我不想使用COM,而丰富的错误代码系统(如HRESULT)对我来说还不够。我希望有类似异常的类,其中包含错误代码,错误消息和我需要的任何其他信息。另外,我不想为每个VC ++版本单独构建。
我目前的做法如下。每个公共类方法都返回枚举值(如ErrorCode)。在方法失败的情况下,像GetLastErrorInfo这样的静态函数返回指向C ++类的指针(比如说ErrorInfo),它包含到达错误信息。 ErrorInfo保留为特定于线程的数据,并包含当前线程中最后一次调用的错误信息。如果最后一次API调用成功,则GetErrorInfo返回NULL。
请考虑以下代码:
1 2 3 4 5 6 7 8 9 10 | try { classPtr->DoSomething(); cout << classPtr->GetData() << endl; } catch(const MyException& ex) { cout << ex.GetErrorMessage() << endl; return; } |
没有例外,它看起来像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ErrorCode result; int data; result = classPtr->DoSomething(); if ( result != Success ) { cout << MyClass::GetLastErrorInfo()->GetErrorMessage() << endl; return; } result = classPtr->GetData(data); if ( result != Success ) { cout << MyClass::GetLastErrorInfo()->GetErrorMessage() << endl; return; } cout << data << endl; |
这看起来不太好。类接口很乱:每个函数现在都有ErrorCode返回类型。返回值成为输出参数。是否有更好的方法,允许达到错误信息,并保持干净的公共API接口?
您可能忽略了一个简单的解决方案。唯一的限制是异常不能跨越模块边界。客户端代码本身抛出异常没有问题。因此,在标题中提供内联函数,比如CheckReturn(),它会抛出丰富的异常。
有关灵感,请查看COM IErrorInfo接口及其关联的_com_error类。他们解决了完全相同的问题。还要注意MSVC中可用的#import指令,它会自动生成调用的小包装函数,并在失败返回值上抛出异常。但是你不想使用COM,因此不能直接使用。
如果从.dll返回C ++对象,则必须非常小心,因为调用者可能会尝试以涉及复制它们或删除它们的方式使用这些对象。如果调用者没有使用相同的堆(或相同的标准库),那么整个地方都会发生各种崩溃和内存泄漏。根据我的经验,在DLL边界上传递C ++对象是个坏主意。
我尝试以某种方式创建API,要求调用者管理所有内存(分配和释放),以便始终清楚指针的所有权。这会创建更多的工作,因为您需要接受指针然后用数据填充这些缓冲区而不是返回C ++对象。我还没有看到在.dll之间安全地传递C ++对象的工作实现。 (但那时我的经验可能有限。)
此外,当您从.dll导出C ++类时,除了C ++之外的其他任何东西都不可能使用该.dll,因为其他语言对于对象具有不同的内存布局。甚至不同的C ++编译器也可能在使用这些类时遇到问题。
重新阅读你的约束,我会说创建COM对象是你最好的选择之一,因为它为你提供了灵活性和返回具有复杂数据的对象的能力(尽管不是C ++对象)。
这个怎么样:
基本错误数据 - 您可以扩展lib的"其他东西":
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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; }; } |
基本错误容器。包含与描述相关的所有内容,这是所有客户直接处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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&); }; } |
基本的lib调用:
1 2 3 4 5 6 7 8 9 10 | 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; } } |
客户来电:
1 2 3 4 5 6 7 | MON::t_error err; if (!MON::DoSomething(err)) { log <<"cannot do anything! Error:"; err.description(log); return; } |