下載原始碼
一、前言
程式設計中,錯誤處理必不可少,而且通常要佔用很大的篇幅。本回書著落在 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 |
不支援介面 |
另外,你還可以返回自己構造 HRESULT 錯誤值。方法是使用宏 MAKE_HRESULT(sev,fac,code)
| 參數 |
含義 |
值(二進位) |
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 的一般方法:
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個重載函數了。如此,我們可以非常簡單的改寫為:
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 方式調用組件,接收錯誤的方法是:
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 等封裝方式調用組件,接收錯誤的方法是:
try
{
......// 調用組件功能
}
catch( _com_error &e )
{
e.Description();// 取得錯誤描述資訊
......// 還可以調用 _com_error 函數取得其它資訊
}
六、編寫支援錯誤處理的組件程式
非常簡單,只要在增加 ATL 組件對象的時候選中 ISupportErrorInfo 即可。
圖三、vc6.0 中,選中組件支援錯誤處理介面
圖四、vc.net 2003 中,選中組件支援錯誤處理介面
七、小結
閱讀文章後,請下載本回的樣本程式。樣本程式中示範了三種錯誤處理方法和三種接收錯誤的方法,同時程式中也有比較詳細的注釋。