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 不支持接口

  另外,你还可以返回自己构造 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 中,选中组件支持错误处理接口

七、小结
  阅读文章后,请下载本回的示例程序。示例程序中演示了三种错误处理方法和三种接收错误的方法,同时程序中也有比较详细的注释。


最新评论 [发表评论] [文章投稿] 查看所有评论 推荐给好友 打印

obq0387_cn,哪里看的资料啊。错了,实际上正如杨老师所讲的严重程度有2位! ( beardog 发表于 2006-5-9 0:08:00)
 
不敢全部苟同,两个错误:

1.HRESULT是32位的,分三个部分,既严重程度,设备代码,错误代码, 其中严重程度其实只有一位(第31位),而不是两位, 而设备代码有15位,错误代码有16位,你所讲的是WINDOWS的错误代码格式,而非HRESULT的代码格式

2.对象支持不支持异常,并不要求要实现ISupportErrorInfo接口,也就是说不实现这个接口也可以SetErrorInfo,和GetErrorInfo,之所以实现这个接口主要的目的是用来检测某个接口的方法会不会抛出异常,而不是接口要抛出COM异常,就一定需要实现ISupportErrorInfo
( obq0387_cn 发表于 2005-10-3 11:44:00)
 
实在是很抱歉,话多了点,搞到超过1000字~请各位见谅! ( jAmEs_ 发表于 2005-9-3 11:28:00)
 

      IErrorInfo* ErrorInfo = NULL;
      if (SUCCEEDED (GetErrorInfo (0, &ErrorInfo)))
      {
        ErrorInfo->GetDescription (&Description);
        ErrorInfo->GetSource (&Source);
        ErrorInfo->GetHelpFile (&HelpFile);
        ErrorInfo->GetHelpContext (&HelpContext);
        ErrorInfo->Release ();
      }

      throw EOleException (Description, ErrorCode, Source, HelpFile, HelpContext);
    }
  }
}
通过它可以转换本机COM+组件的异常,但是不同机器的异常它又还是处理不到,只能返回HRESULT,但是不能取得具体的内容。
希望杨老师指点!非常感谢!!
( jAmEs_ 发表于 2005-9-3 11:26:00)
 
void CheckResult(HRESULT hr,IUnknown* Object,REFIID ErrorIID)
{
  if (FAILED (hr) && (HRESULT_FACILITY (hr) == FACILITY_ITF))
  {
    bool HasErrorInfo = false;

    if (Object && (!IsEqualGUID (ErrorIID, GUID_NULL)))
    {
      ISupportErrorInfo* SupportErrorInfo = NULL;
      HRESULT hr = Object->QueryInterface (IID_ISupportErrorInfo, (void**)&SupportErrorInfo);
      if (SUCCEEDED (hr))
      {
        if (SupportErrorInfo->InterfaceSupportsErrorInfo (ErrorIID) == S_OK)
          HasErrorInfo = true;
        SupportErrorInfo->Release ();
      }
    }
    else
      HasErrorInfo = true;

    if (HasErrorInfo)
    {
      int ErrorCode = hr;
      WideString Description, Source, HelpFile;
      ULONG HelpContext = 0;

待续。。。
( jAmEs_ 发表于 2005-9-3 11:26:00)
 
据我现在所知道的,好像是COM要实现ISuppertErrorInfo接口,才能返回异常,是不是这样?
然后COM调用SetErrorInfo,把HRESULT返回客户端,这个HRESULT我不知道怎么转换为字符,也就是对应的内容~
我在网上找到一个函数:

待续。。。
( jAmEs_ 发表于 2005-9-3 11:24:00)
 
杨老师,你好!
你时指.tli文件里面,调用_com_issue_errorex产生异常吗?
但是我不知道_com_issue_errorex的实现代码~BCB里面,我不能直接使用这个函数,编译会找不到它的实现,BCB里面带一个comutil.cpp,它里面有函数的实现:
void _stdcall _com_issue_errorex(HRESULT hr, IUnknown*, REFIID) throw(_com_error){

  /* what's the right thing to do here? probably *not* what we're doing, but
     without a test case ...  Can someone who is using this please send email
     to [email protected] explaining *how* it's being used and what you want
     from it? :) */

  throw _com_error(hr);
}
我用这个代码编译正常,但是,结果并没有取得COM抛出的具体内容,所以我认为这个函数的实现不太正确。

待续。。。 ( jAmEs_ 发表于 2005-9-3 11:23:00)
 
to[jAmEs_]:为什么可以使用try/catch结构那?因为,当你使用 #import 引入组件类型库后,编译器帮你包装为一个接口的C++类,而成员函数中,使用了throw,所以你就能catch了。具体它如何包装的,你编译后,打开 tlh 文件去看。 ( 杨老师 发表于 2005-9-2 20:59:00)
 
杨老师,你好!
感谢你的回复!
我想再问问,在COM(+)抛出异常的时候,如执行完SetErrorInfo以后,返回一个HRESULT,这个值不等于0,客户端为何能使用
try
{
}
catch(...)
{
}
的结构去截取错误的呢?实际上说,HRESULT也不过是个数值,它并没有抛出异常。
难道客户端程序自动在当返回不等于0的值时,就throw一个异常出来?那它依据什么来throw呢?机制又是什么呢?
不好意思,我的问题多了点,但是我觉得搞清楚这个,那在BCB上的问题应该就有思路了。 ( jAmEs_ 发表于 2005-8-30 16:08:00)
 
to [jAmes_] DCOM 当然可以这样处理错误。BCB 我不会,所以无法给你提供建议:(
to [zhao_dazhi] 大志您好:)
 

你可能感兴趣的:(C++,windows,exception,object,null,编译器)