2012-01-29 72 views
4

我需要编写C++ API,它由从DLL的暴露几个出口的C++类,使用.lib文件(MSVC)。从我的另一个问题的答案我明白,导出的类方法不能使用异常,如果C++ API内置在一个VC++版本(比如说2010),并且客户端代码写入另一个VC++版本。由于异常不能成为公共API接口的一部分,我正在寻找另一个错误处理策略。我的限制:我不想使用COM,而丰富的错误代码系统(如HRESULT)对我来说还不够。我想拥有包含错误代码,错误消息和我需要的任何其他信息的异常类。另外,我不想为每个VC++版本单独构建。C++ API设计和错误处理

我目前的做法如下。每个公共类方法都返回枚举值(如ErrorCode)。在该方法失败的情况下,像GetLastErrorInfo这样的静态函数将返回指向C++类的指针(假设ErrorInfo),该指针包含覆盖范围错误信息。 ErrorInfo保留为线程特定数据,并包含当前线程中最后一次调用的错误信息。如果最后一次API调用成功,则GetErrorInfo返回NULL。

考虑以下代码有例外:

 
try 
{ 
    classPtr->DoSomething(); 
    cout << classPtr->GetData() << endl; 
} 
catch(const MyException& ex) 
{ 
    cout << ex.GetErrorMessage() << endl; 
    return; 
} 

没有例外,它看起来像这样:

 
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; 

这看起来并不好。 Class接口很麻烦:现在每个函数都有ErrorCode返回类型。返回值成为输出参数。是否有更好的方法,允许达到错误信息,并保持清洁的公共API接口?

+0

你可以使用boost :: tuple来获得多个返回类型并避免整个输出参数。但这只是真正的问题的破解 – Lalaland 2012-01-29 15:18:02

+0

也如何创建内联包装函数来解释错误代码并抛出异常。我可以想到两种方法来做到这一点。 1.客户端包装(myFunction)。 2.客户端执行myFunction,并且myFunction是封装(internalMyFunction)。使用元组和模板元编程通过包装器传递其他返回类型。 – Lalaland 2012-01-29 15:21:31

+0

可能的解决方案是在内部保留最后的错误,每种方法都会更新它。因此,类方法不需要返回错误。还有一种方法可以访问最后一个错误代码。当然,在多线程访问的情况下,解决方案会更复杂 – Eugene 2012-01-29 15:47:04

回答

6

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

为了获得灵感,请查看COM IErrorInfo接口及其关联的_com_error class。他们解决了完全相同的问题。还要注意MSVC中提供的#import指令,它会自动生成一些小的包装函数,这些包装函数会在调用失败返回值时抛出异常。但是你不想使用COM,因此不能直接使用。

2

你必须非常小心,如果你从一个.dll返回C++对象,因为您的来电者可能会尝试使用的方式,包括让他们的拷贝或删除它们的对象。如果调用者没有使用相同的堆(或相同的标准库),则会在各处发生各种崩溃和内存泄漏。根据我的经验,将C++对象传递给DLL边界是一个坏主意。

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

另外,从.dll导出C++类时,除C++之外的其他任何东西都不可能使用该.dll,因为其他语言对于对象具有不同的内存布局。即使不同的C++编译器也可能在使用这些类时遇到问题。

重读你的约束,我会说创建COM对象是你最好的选择之一,因为它给你的灵活性和复杂的数据恢复对象的能力(尽管不是C++对象)。

+0

..而且微软又搞得一团糟! :-( – 2012-01-29 15:50:59

1

如何:

基本误差数据 - 你可以延长你的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&); 
    }; 
} 

基本LIB电话:

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; 
}