错误与异常处理---组件


在组件程序中,如果遇到错误,一般有两个方式进行处理。 
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表中时,客户端才可以截获异常。但并不影响其在组件实现中抛出异常。 

为什么要这么作呢?为了效率?

posted @ 2009-07-27 18:23  山涧鸟鸣  阅读(2248)  评论(0编辑  收藏  举报