在COM编程中正确的处理COM异常,是增强程序鲁棒性的基础。在delphi中有safecall的概念,在vb中虽然没有提及此概念,但仍然在使用。在vc中,需要手工支持。那什么是safecall,与COM异常又有什么关系呢?
safecall其实是约定了COM组件的提供者与调用者之间调用的方式。 safecall首先是一个stdcall,也就是说COM组件暴露的所有接口方法采用与windows API一致的stdcall调用约定。其次,safecall还要求接口方法均返回HRESULT错误代码,不能引发COM异常;调用者在调用接口方法后,均要检查HRESULT,对错误进行相应的处理。也就是说,safecall保证在方法内部封装了所有COM异常,调用者不必处理异常。
例如:IProvider提供一个方法Echo,我们看不同语言不同范式的区别。
vb
'provider 'vb封装了COM异常和HRESULT错误代码 function Echo(sMessage as String) as String Echo=sMessage end function 'caller 'vb封装了对HRESULT错误代码的处理,如有错误引发COM异常 on error resume next MsgBox Echo("Hello World!")
vc++ -- 一般方式
//provider //需要手工用try catch封装异常,保证所有错误均通过HRESULT返回 HRESULT Echo(BSTR bstrMessage,BSTR* pbstrValue) { try { *pbstrValue=::SysAllocString(L"Hello World!"); } catch(_com_error& e) { return e.Error(); } return S_OK; } //caller //一般方式 _bstr_t bstrValue; HRESULT hr=pProvider->Echo(_bstr_t(L"Hello World!"),bstrValue.GetAddress()); if(FAILED(hr)) { //这里有两种处理方式, //第一种,直接返回错误 return hr; //第二种,返回异常,就像vb的方式一样 _com_util::CheckError(hr); } //如果provider组件是通过#import导入的,可有如下的方式调用 try { _bstr_t bstrValue=pProvider->Echo(L"Hello World!"); } catch(_com_error& e) { return e.Error(); }
vb般的vc++
//provider //采用自定义宏封装 HRESULT Echo(BSTR bstrMessage,BSTR* pbstrValue) { __SAFECALL_BEGIN; *pbstrValue=::SysAllocString(L"Hello World!"); __SAFECALL_END; } //caller //通过#import导入 //如果是内部调用的方法,不必再检测com异常; //因为向外暴露的方法均通过safecall对com异常进行了封装 _bstr_t bstrValue=pProvider->Echo(L"Hello World!");
自定义宏
#define __SAFECALL_BEGIN / try{/ #define __SAFECALL_END / / }/ catch(_com_error& e){/ return e.Error();/ }/ catch(...){/ return E_FAIL;/ }/ return S_OK