2012-01-29 67 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; 
}