2012-02-12 139 views
9

我正在使用C++編寫一個使用舊版C API的庫。我的庫的客戶端可以指定回調函數,這些回調函數通過我的庫通過C API調用而間接調用。這意味着必須處理客戶端回調中的所有異常。跨C API邊界傳遞異常

我的問題是這樣的:如何在邊界的一邊捕獲異常並在C++邊界重新執行後重新拋出它,然後執行返回到C++ land,以便可以通過處理異常客戶端代碼?

回答

5

用C++ 11我們可以使用:

std::exception_ptr active_exception; 

try 
{ 
    // call code which may throw exceptions 
} 
catch (...) 
{ 
    // an exception is thrown. save it for future re-throwing. 
    active_exception = std::current_exception(); 
} 

// call C code 
... 

// back to C++, re-throw the exception if needed. 
if (active_exception) 
    std::rethrow_exception(active_exception); 

之前C++ 11這些仍然可以通過Boost Exception使用。

+0

如果異常被拋出值,將'exception_ptr'保持活力? – 2012-02-12 20:04:37

+0

@SethCarnegie:按照標準,是的。 (§18.8.5/ 8「引用的對象應保持有效,只要至少有一個'exception_ptr'對象引用它。」)升壓執行應做同樣的。 – kennytm 2012-02-12 20:09:25

+0

這是真棒,好像委員會與我腦海中設計C++ 11。這也是該VS2010實現'exception_ptr','current_exception'和'rethrow_exception'我能接受這個答案的原因。謝謝。 – 2012-02-12 20:22:56

0

您可能可以通過C接口傳遞一個結構,以便在發生異常時用錯誤信息填充,然後在客戶端接收到該結構時,根據數據檢查並在客戶端中引發異常從結構。如果您只需要極少的信息來重新創建異常,則可能只需使用32位/ 64位整數作爲錯誤代碼。例如:

typedef int ErrorCode; 

... 

void CMyCaller::CallsClient() 
{ 
    CheckResult (CFunction (...)); 
} 

void CheckResult (ErrorCode nResult) 
{ 
    // If you have more information (for example in a structure) then you can 
    // use that to decide what kind of exception to throw.) 
    if (nResult < 0) 
     throw (nResult); 
} 

... 

// Client component's C interface 

ErrorCode CFunction (...) 
{ 
    ErrorCode nResult = 0; 

    try 
    { 
     ... 
    } 
    catch (CSomeException oX) 
    { 
     nResult = -100; 
    } 
    catch (...) 
    { 
     nResult = -1; 
    } 

    return (nResult); 
} 

如果你需要比單INT32/Int64的詳細信息,那麼你可以在呼叫前分配的結構,並將其地址傳遞給C函數,這將反過來,捕捉異常內部,如果他們發生,在它自己的一面拋出一個例外。

+0

如果可能的話,我不想丟失異常,例如,如果用戶的回調拋出了他們自己的異常類型,那麼我也想拋出異常 – 2012-02-12 20:07:16

+0

@SethCarnegie如果你越過模塊邊界(我假設你做了,否則這整個事情不會是一個問題),那麼我沒有看到一種方法來保存實際的異常信息。更復雜的是,C++允許任何類型成爲一個異常,所以如果你想保留它,你需要知道要處理的類型。如果所有的異常都保證是'std :: exception'衍生物,它可能不是很難,但如果允許任何類型,那就改變了事情。 – xxbbcc 2012-02-12 20:14:54

3

一些環境或多或少地直接支持這個。例如,如果通過/EH編譯器開關啓用structured exception handling和C++異常,則可以通過Microsoft的結構化異常處理實現C++異常(C的「異常」)。如果在編譯所有代碼時設置了這些選項(C++在每一端,C在中間),堆棧展開將「工作」。

但是,這幾乎總是一個壞主意(TM)。爲什麼,你問?考慮到中間的一塊C代碼是:

WaitForSingleObject(mutex, ...); 
invoke_cxx_callback(...); 
ReleaseMutex(mutex); 

而且該invoke_cxx_callback()(....擊鼓...)調用你的C++代碼拋出異常。你會泄漏一個互斥鎖。哎喲。

你看,事情是大多數C代碼不被寫入處理C++ - 風格棧在函數的執行任何時候平倉。此外,它沒有析構函數,所以它沒有保護自身免受異常的影響。

Kenny TM有一個針對C++ 11和Boost項目的解決方案。對於一般情況,xxbbcc有一個更一般的解決方案,雖然比較單調乏味。

+0

這是Kenny TM的一個非問題,因爲'invoke_cxx_callback'會正常返回(將異常存儲在'exception_ptr'中),並且當C代碼返回到C++代碼時,C++代碼將檢查是否引發了異常如果是的話重新拋出它。 – 2012-02-12 20:31:13

+0

@SethCarnegie:我知道,這就是爲什麼我提到「解決方案」的其他答案。這個答案只解釋了爲什麼不通過C代碼「異常」通過例外。 – 2012-02-12 20:46:22

+0

*「您可以通過Microsoft的結構化異常處理實現C++異常」* - 這是不正確的。微軟的編譯器一直在SEH異常方面實現C++異常。沒有選擇。編譯器開關僅控制由MSC實現的異常處理關鍵字的語義。 – IInspectable 2016-05-14 22:11:29