COM元件設計與應用(十二)——錯誤與異常處理
程式設計中,錯誤處理必不可少,而且通常要佔用很大的篇幅。本回書著落在 COM 中的錯誤(異常)的處理方法。
在元件程式中,如果遇到錯誤,一般有兩個方式進行處理。
二、簡單返回
對於比較簡單的錯誤,直接返回表示錯誤原因的 HRESULT。比如下面幾個就是常見的錯誤值:
E_INVALIDARG | 0x80070057 | 引數錯誤 |
E_OUTOFMEMORY | 0x8007000E | 記憶體錯誤 |
E_NOTIMPL | 0x80004001 | 未實現 |
E_POINTER | 0x80004003 | 無效指標 |
E_HANDLE | 0x80070006 | 無效控制代碼 |
E_ABORT | 0x80004004 | 終止操作 |
E_ACCESSDENIED | 0x80070005 | 拒絕訪問 |
E_NOINTERFACE | 0x80004002 | 不支援介面 |
引數 | 含義 | 值(二進位制) |
sev 嚴重程度 |
成功 | 00 |
成功,但有一些報告資訊 | 01 | |
警告 | 10 | |
錯誤 | 11 | |
fac 裝置資訊 |
FACILITY_AAF | 00000010010 |
FACILITY_ACS | 00000010100 | |
FACILITY_BACKGROUNDCOPY | 00000100000 | |
FACILITY_CERT | 00000001011 | |
FACILITY_COMPLUS | 00000010001 | |
FACILITY_CONFIGURATION | 00000100001 | |
FACILITY_CONTROL | 00000001010 | |
FACILITY_DISPATCH | 00000000010 | |
FACILITY_DPLAY | 00000010101 | |
FACILITY_HTTP | 00000011001 | |
FACILITY_INTERNET | 00000001100 | |
FACILITY_ITF | 00000000100 | |
FACILITY_MEDIASERVER | 00000001101 | |
FACILITY_MSMQ | 00000001110 | |
FACILITY_NULL | 00000000000 | |
FACILITY_RPC | 00000000001 | |
FACILITY_SCARD | 00000010000 | |
FACILITY_SECURITY | 00000001001 | |
FACILITY_SETUPAPI | 00000001111 | |
FACILITY_SSPI | 00000001001 | |
FACILITY_STORAGE | 00000000011 | |
FACILITY_SXS | 00000010111 | |
FACILITY_UMI | 00000010110 | |
FACILITY_URT | 00000010011 | |
FACILITY_WIN32 | 00000000111 | |
FACILITY_WINDOWS | 00000001000 | |
FACILITY_WINDOWS_CE | 00000011000 | |
code 唯一錯誤碼 |
16位(bit) 你自己定義去吧 |
呼叫者得到返回的 HRESULT 值後,也可以使用巨集 HRESULT_SEVERITY()、HRESULT_FACILITY()、HRESULT_CODE() 來取得sev錯誤程度、fac裝置資訊和 code 錯誤程式碼。
三、錯誤資訊介面
既然 COM 是靠各種各樣的介面來提供服務的,於是很自然地就會想到,是否有一個介面能夠提供更豐富的錯誤資訊報告那?答案是:ISupportErrorInfo。下面這段程式碼是使用 ISupportErrorInfo 的一般方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
STDMETHODIMP Cxxx::fun()
{
... ... ... ...
CComQIPtr<
ICreateErrorInfo > spCEI;
::CreateErrorInfo( &spCEI );
spCEI->SetGUID( IID_Ixxx ); // 發生錯誤的介面IID
spCEI->SetSource( L"xxx.xxx" ); // ProgID
// 如果你的元件同時提供了幫助檔案,那麼就可以:
spCEI->SetHelpContext( 0 ); // 設定幫助檔案的主題號
spCEI->SetHelpFile( L"xxx.hlp" ); // 設定幫助檔案的檔名
spCEI->SetDescription( L"錯誤描述資訊" );
CComQIPtr <
IErrorInfo > spErrInfo = spCEI;
if( spErrInfo )
::SetErrorInfo( 0, spErrInfo ); // 這時呼叫者就可以得到錯誤資訊了
return E_FAIL;
}
|
上面是原理性程式碼,在我們寫的程式中,不用這麼麻煩。因為 ATL 已經把上述的程式碼給我們包裝成 CComCoClass::Error() 的6個過載函數了。如此,我們可以非常簡單的改寫為:
1 2 3 4 5 |
STDMETHODIMP Cxxx::fun()
{
... ... ... ...
return Error( L"錯誤描述資訊" );
}
|
四、關於 try/catch
學習了 C++ 後,很多人都喜歡使用 try/catch 的異常處理結構。如果你使用 vc6.0 的ATL,編譯器預設是不支援異常處理的,編譯後會報告“warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify -GX”,解決方法是手工加上編譯開關:
圖一、加上編譯開關,支援C++的異常處理結構
在vc.net 2003 中,編譯器預設是支援異常處理結構的,所以不用特別進行設定。如果想減小目標檔案的尺寸,你也可以決定不使用 C++ 異常處理,那麼在專案屬性中
圖二、在vc.net中修改是否支援C++異常結構的編譯開關
五、客戶端接收元件的錯誤資訊
1、如果使用 API 方式呼叫元件,接收錯誤的方法是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
HRESULT hr = spXXX->fun() // 呼叫元件功能
if( FAILED( hr ) ) // 如果發生了錯誤
{
CComQIPtr <
ISupportErrorInfo > spSEI = spXXX; // 元件是否提供了 ISupportErrorInfo 介面?
if( spSEI ) // 如果支援,那麼
{
hr = spSEI->InterfaceSupportsErrorInfo( IID_Ixxx ); // 是否支援 Ixxx 介面的錯誤處理?
if( SUCCEEDED( hr ) )
{ // 支援,太好了。取出錯誤資訊
CComQIPtr <
IErrorInfo > spErrInfo; // 宣告 IErrorInfo 介面
hr = ::GetErrorInfo( 0, &spErrInfo ); // 取得介面
if( SUCCEEDED( hr ) )
{
CComBSTR bstrDes;
spErrInfo->GetDescription( &bstrDes ); // 取得錯誤描述
...... // 還可以取得其它的資訊
}
}
}
}
|
2、如果使用 #import 等包裝方式呼叫元件,接收錯誤的方法是:
1 2 3 4 5 6 7 8 9 |
try
{
...... // 呼叫元件功能
}
catch( _com_error &e )
{
e.Description(); // 取得錯誤描述資訊
...... // 還可以呼叫 _com_error 函式取得其它資訊
}
|
六、編寫支援錯誤處理的元件程式
非常簡單,只要在增加 ATL 元件物件的時候選中 ISupportErrorInfo 即可。
圖三、vc6.0 中,選中元件支援錯誤處理介面
圖四、vc.net 2003 中,選中元件支援錯誤處理介面
七、小結
閱讀文章後,請下載本回的示例程式。示例程式中演示了三種錯誤處理方法和三種接收錯誤的方法,同時程式中也有比較詳細的註釋。
http://tech.ddvip.com/2006-04/11447206804882_2.html