COM的错误与异常处理

在组件程序中,如果遇到错误,一般有两个方式进行处理。 
1.简单返回HRESULT  
对于比较简单的错误,直接返回表示错误原因的   HRESULT。 

2.抛出COM异常---调用Error(...)  
既然   COM   是靠各种各样的接口来提供服务的,于是很自然地就会想到,是否有一个接口能够提供更丰富的错误信息报告那? 答案是:IErrorInfo (调用SetErrorInfo(0,   pErrorInfo);)。 

ATL   把SetErrorInfo包装成   CComCoClass::Error()   的6个重载函数了。 

函数调用过程如下: 
Error   -->   AtlReportError   -->   AtlSetErrorInfo   -->   SetErrorInfo(0,   pErrorInfo); 

template    < class    T,    const    CLSID *    pclsid    =     & CLSID_NULL >  
class    CComCoClass 

public
    DECLARE_CLASSFACTORY() 
    DECLARE_AGGREGATABLE(T) 
    typedef   T   _CoClass; 
    
static     const    CLSID &    WINAPI   GetObjectCLSID()   { return     * pclsid;} 
    
static    LPCTSTR   WINAPI   GetObjectDescription()   { return    NULL;} 
    
static    HRESULT   WINAPI   Error (LPCOLESTR   lpszDesc,  <===    Error 
    
const    IID &    iid    =    GUID_NULL,   HRESULT   hRes    =     0
    { 
        
return    AtlReportError (GetObjectCLSID(),   lpszDesc,   iid,   hRes); 
    } 
    
static    HRESULT   WINAPI   Error (LPCOLESTR   lpszDesc,   DWORD   dwHelpID,  <===    Error 
    LPCOLESTR   lpszHelpFile,   
const    IID &    iid    =    GUID_NULL,   HRESULT   hRes    =     0
    { 
        
return    AtlReportError (GetObjectCLSID(),   lpszDesc,   dwHelpID,   lpszHelpFile, 
    iid,   hRes); 
    } 
    
static    HRESULT   WINAPI   Error (UINT   nID,    const    IID &    iid    =    GUID_NULL,  <===    Error 
    HRESULT   hRes   
=     0 ,   HINSTANCE   hInst    =    _AtlBaseModule.GetResourceInstance()) 
    { 
        
return    AtlReportError (GetObjectCLSID(),   nID,   iid,   hRes,   hInst); 
    } 
    
static    HRESULT   WINAPI   Error (UINT   nID,   DWORD   dwHelpID, 
    LPCOLESTR   lpszHelpFile,   
const    IID &    iid    =    GUID_NULL, 
    HRESULT   hRes   
=     0 ,   HINSTANCE   hInst    =    _AtlBaseModule.GetResourceInstance())  <===    Error 
    { 
        
return    AtlReportError (GetObjectCLSID(),   nID,   dwHelpID,   lpszHelpFile, 
    iid,   hRes,   hInst); 
    } 
    
static    HRESULT   WINAPI   Error (LPCSTR   lpszDesc, 
    
const    IID &    iid    =    GUID_NULL,   HRESULT   hRes    =     0 <===    Error 
    { 
        
return    AtlReportError (GetObjectCLSID(),   lpszDesc,   iid,   hRes); 
    } 
    
static    HRESULT   WINAPI   Error (LPCSTR   lpszDesc,   DWORD   dwHelpID,  <===    Error 
    LPCSTR   lpszHelpFile,   
const    IID &    iid    =    GUID_NULL,   HRESULT   hRes    =     0
    { 
        
return    AtlReportError (GetObjectCLSID(),   lpszDesc,   dwHelpID, 
    lpszHelpFile,   iid,   hRes); 
    } 

    ... 
}; 

-->   AtlReportError:  
inline   HRESULT   WINAPI   AtlReportError ( const    CLSID &    clsid,   LPCOLESTR   lpszDesc, 
const    IID &    iid    =    GUID_NULL,   HRESULT   hRes    =     0

       
return    AtlSetErrorInfo(clsid,   lpszDesc,    0 ,   NULL,   iid,   hRes,   NULL); 


-->   AtlSetErrorInfo: 
ATLINLINE   ATLAPI   AtlSetErrorInfo( const    CLSID &    clsid,   LPCOLESTR   lpszDesc,   DWORD   dwHelpID, 
LPCOLESTR   lpszHelpFile,   
const    IID &    iid,   HRESULT   hRes,   HINSTANCE   hInst) 

    USES_CONVERSION; 
    TCHAR   szDesc[
1024 ]; 
    szDesc[
0 ]    =    NULL; 
    
//    For   a   valid   HRESULT   the   id   should   be   in   the   range   [0x0200,   0xffff] 
     if    (IS_INTRESOURCE(lpszDesc))    // id 
    { 
        UINT   nID   
=    LOWORD((DWORD_PTR)lpszDesc); 
        ATLASSERT((nID   
>   =     0x0200     &&    nID    <=     0xffff )   ¦ ¦   hRes    !=     0 ); 
        
if    (LoadString(hInst,   nID,   szDesc,    1024 )    ==     0
        { 
        ATLASSERT(FALSE); 
        lstrcpy(szDesc,   _T( 
" Unknown   Error  " )); 
        } 
        lpszDesc   
=    T2OLE(szDesc); 
        
if    (hRes    ==     0
        hRes   
=    MAKE_HRESULT( 3 ,   FACILITY_ITF,   nID); 
    } 

    CComPtr 
< ICreateErrorInfo >    pICEI; 
    
if    (SUCCEEDED(CreateErrorInfo( & pICEI))) 
    { 
        CComPtr 
< IErrorInfo >    pErrorInfo; 
        pICEI
->  SetGUID(iid); 
        LPOLESTR   lpsz; 
        ProgIDFromCLSID(clsid,   
& lpsz); 
        
if    (lpsz    !=    NULL) 
        pICEI
->  SetSource(lpsz); 
        
if    (dwHelpID    !=     0     &&    lpszHelpFile    !=    NULL) 
        { 
        pICEI
->  SetHelpContext(dwHelpID); 
        pICEI
->  SetHelpFile(const_cast  < LPOLESTR >  (lpszHelpFile)); 
        } 
        CoTaskMemFree(lpsz); 
        pICEI
->  SetDescription((LPOLESTR)lpszDesc); 
        
if    (SUCCEEDED(pICEI ->  QueryInterface(__uuidof(IErrorInfo),   ( void ** ) & pErrorInfo))) 
        SetErrorInfo(
0 ,   pErrorInfo);  <======    抛出COM异常  
    } 
    
return    (hRes    ==     0 )    ?    DISP_E_EXCEPTION   :   hRes; 

最终,通过SetErrorInfo抛出COM异常


 二、错误与异常处理--客户端 

客户端接收组件的错误信息,有两个方式 
1.返回HRESULT 

2.截获COM异常---   GetErrorInfo()  

而截获COM异常,也有两个方式: 
2.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   );  <========    截获COM异常  
            
if (   SUCCEEDED(   hr   )   ) 
            { 
                CComBSTR   bstrDes; 
                spErrInfo
->  GetDescription(    & bstrDes   );  //    取得错误描述 
                ......  //    还可以取得其它的信息 

2.2   使用   #import   等包装方式调用组件,然后抛出C++异常,再然后由客户端截获  
1.使用   #import   等包装组件 
比如: 
为什么可以使用try/catch结构就可以截获COM异常呢? 
因为,当你使用   #import   引入组件类型库后,编译器帮你包装为一个接口的C++类,而成员函数中,使用了throw,所以你就能catch了。 具体它如何包装的,你编译后,打开   tlh   文件去看。 

如:

inline   _variant_t   ISet::GetValue   (   _bstr_t   Name   )   { 
    VARIANT   _result; 
    VariantInit(
& _result); 
    HRESULT   _hr   
=    get_Value(Name,    & _result); 
    
if    (FAILED(_hr))   _com_issue_errorex(_hr,    this ,   __uuidof( this ));  <== 抛出C ++ 异常 
    
return    _variant_t(_result,    false ); 

其中: 
void   __stdcall   _com_issue_errorex(HRESULT,   IUnknown*,   REFIID)   throw(_com_error); 
_com_issue_errorex的实现MS没有开放源码,但应该就是使用COM异常信息来填充_com_error信息(比如m_hresult,   IErrorInfo* 等),然后抛出C++异常。  

*注意:_com_issue_errorex是如何巧妙实现COM异常到C++异常转换的。  

3.   客户端截获C++异常  
try 

...... //   调用组件功能 

catch(   _com_error   &e    ) 

e.Description(); //   取得错误描述信息 
...... //   还可以调用   _com_error   函数取得其它信息 


_com_error的定义如下:

class    _com_error   { 
public
        
//    Constructors 
        
//  
        _com_error(HRESULT   hr, 
                              IErrorInfo
*    perrinfo    =    NULL, 
                              
bool    fAddRef    =     false )    throw (); 
        _com_error(
const    _com_error &    that)    throw (); 

        
//    Destructor 
        
//  
         virtual     ~ _com_error()    throw (); 

        
//    Assignment   operator 
        
//  
        _com_error &     operator = ( const    _com_error &    that)    throw (); 

        
//    Accessors 
        
//  
        HRESULT   Error()    const     throw (); 
        WORD   WCode()   
const     throw (); 
        IErrorInfo   
*    ErrorInfo()    const     throw (); 

        
//    IErrorInfo   method   accessors 
        
//  
        _bstr_t   Description()    const     throw (_com_error); 
        DWORD   HelpContext()   
const     throw (); 
        _bstr_t   HelpFile()   
const     throw (_com_error); 
        _bstr_t   Source()   
const     throw (_com_error); 
        GUID   GUID()   
const     throw (); 

        
//    FormatMessage   accessors 
         const    TCHAR    *    ErrorMessage()    const     throw (); 

        
//    EXCEPINFO.wCode   <->   HRESULT   mappers 
         static    HRESULT   WCodeToHRESULT(WORD   wCode)    throw (); 
        
static    WORD   HRESULTToWCode(HRESULT   hr)    throw (); 

private
        
enum    { 
                WCODE_HRESULT_FIRST   
=    MAKE_HRESULT(SEVERITY_ERROR,   FACILITY_ITF,    0x200 ), 
                WCODE_HRESULT_LAST   
=    MAKE_HRESULT(SEVERITY_ERROR,   FACILITY_ITF + 1 ,    0 )    -     1  
        }; 
        
const    HRESULT                       m_hresult;  
        IErrorInfo   
*                          m_perrinfo;  
        mutable   TCHAR   
*                    m_pszMsg;  
}; 


*注意:其实_com_error就是对IErrorInfo的包装,用截获的COM异常来填充;

总之,客户端接收组件错误信息就两种方式 
1。返回HRESULT 
2。组件:SetErrorInfo(...,   IErrorInfo   *)       抛出COM异常 
      客户端:GetErrorInfo(...,   IErrorInfo   *)       截获COM异常 

而客户端之所以可以截获C++异常,主要是应为编译时对“返回HRESULT”和GetErrorInfo进行了包装。

如果不对返回错误信息进行C++异常包装(_com_error)然后抛出,会很繁的。 
就像这样,处理每个可能的错误,都要一大串代码   ---晕!

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   );  <========    截获COM异常  
            
if (   SUCCEEDED(   hr   )   ) 
            { 
                CComBSTR   bstrDes; 
                spErrInfo
->  GetDescription(    & bstrDes   );  //    取得错误描述 
                ......  //    还可以取得其它的信息 

 还好,微软聪明的工程师帮我们通过C++异常的方式,很巧妙地处理了这个问题。

使得程序变得如此简明!^_^ 

try 

spXXX-> fun() //   调用组件功能 

catch(   _com_error   &e   ) 

e.Description(); //   取得错误描述信息 
...... //   还可以调用   _com_error   函数取得其它信息 
}

对组件错误的处理,根据返回HRESULT,可以获得基本错误信息。 
如果你认为足够了,OK,这就行了。 

但如果你要向错误信息中加入其它一些信息,这时就需要抛出COM异常---调用Error(...)。 
然后由客户端截获异常,获得额外的信息。

其实在客户端可以得到的信息就是:HRESULT(_hr)和GetErrorInfo    
经MS包装后,抛出异常的过程是这样的: 
if   (FAILED(_hr){ 
  使用GetErrorInfo得到的信息,来填充_com_error 
  然后抛出   _com_error 
}

这些都是编译器作的手脚。十分巧妙! 
PF!

另外,使用   #import   等包装组件,组件有些方法通过返回值来传递[out,   retval]参数,这样就不会返回HRESULT。 所以,当使用   #import   等包装组件时,必须通过try{}catch{},来捕获错误。

对于支持抛出COM异常的组件,需要ISupportErrorInfo接口

class    ATL_NO_VTABLE   CSample   :   
public    CComObjectRootEx  < CComSingleThreadModel >  , 
public    CComCoClass  < CParser,    & CLSID_Parser >  , 
public    ISupportErrorInfo, 
public    IDispatchImpl  < IParser,    & IID_IParser,    & LIBID_sampleLib,    /* wMajor   = */     1 ,    /* wMinor   = */     0 >  

public

    组件的ISupportErrorInfo接口其实就一个方法InterfaceSupportsErrorInfo,用来判断组件中某一接口是否支持截获异常。  
    STDMETHODIMP   CParser::InterfaceSupportsErrorInfo(REFIID   riid) 
    { 
        
static     const    IID *    arr[]    =    
        { 
        
& IID_IParser 
        }; 

        
for    ( int    i = 0 ;   i    <     sizeof (arr)    /     sizeof (arr[ 0 ]);   i ++
        { 
        
if    (InlineIsEqualGUID( * arr[i],riid)) 
        
return    S_OK; 
        } 
        
return    S_FALSE; 
    } 

    ...


也就是说:只有接口位于arr表中时,客户端才可以截获异常。 

static   const   IID*   arr[]   =   

&IID_IParser 
}; 

总结:只有接口位于arr表中时,客户端才可以截获异常。但并不影响其在组件实现中抛出异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值