COM技术6

    这一章关注的细节问题,包括HRESULT,GUID,还有注册表和一些系统函数.先来看HRESULT,COM中鼓励函数返回的值都为HRESULT类型,这样可以方便的监控组件的行为.如果接口确实需要返回一个值,可以使用一个用于输出的行参.我想,这应该是基于返回值在COM中的重要性要大于通常的C和C++编程,COM编程由于把接口的实现者和使用者分开了,所以使用者在使用时必须要查询,而以查询的结果做为能否继续进行的依据,这样,返回值就显得尤为重要.HRESULT比一般的FALSE和TRUE要复杂,它采取了32bit保存返回的状态信息,其中最高位表示是否成功,0表示成功,1表示失败.后15位为设备代码,用以对不同的错误进行了分类.例如FACILITY_RPC,FACILITY_STORATE等.FACILITY_NULL为0,表示没有指定设备.低16位表示返回代码.除了设备代码为FACILITY_ITF之外的所有HRESULT值都是通用的,它们跟一般windows错误码一样,都有唯一的含义.但如果设备代码是 FACILITY_ITF,则该HRESULT是特定于返回该代码的接口的.用户自定义的HRESULT采用的设备代码都是FACILITY_ITF.不同的接口的返回值部分(低16位)可以相同,HRESULT所需了解的就是这么多,它包含一些宏,方便对32位的整型做不同的操作.可以用MAKE_HRESULT自定义错误码,比如可以这样定义: 

MAKE_HRESULT(SEVERITY_ERROR,FACILITY_ITF,100);

    但出于通用性考虑,尽量使用COM自带的成功和失败码.MAKE_HRESULT的定义如下:

#define  MAKE_HRESULT(sev,fac,code)  
((HRESULT) (((unsigned 
long )(sev) << 31 |  ((unsigned  long )(fac) << 16 |  ((unsigned  long )(code))) ) 

    VC有一个查看错误代码的工具叫Error Lookup,可以通过错误代码返回相应的字符串,当然我们也可以使用FormatMessage获得某个错误码的具体信息.

    下面来看GUID,它是保证组件和接口唯一的标志,GUID的精华是唯一,为此它使用网卡地址和时间戳来保证这个值的时空唯一性,一般可以利用vc自带的工具GUIDGEN.exe产生这个GUID,默认路径在C:/Program Files/Microsoft Visual Studio/Common/Tools下.接口的GUID称为IID.组件的称为CLSID.因为GUID比较大(16个字节)所以一般采用引用传递方式.现在来看看程序中是如何使用GUID.

    通常的做法是用GUIDGEN.exe产生一个唯一的GUID,然后copy到代码中.有几种格式可以选择,书中推荐使用第二种,将生成一个如下格式的:

//  {57A649F8-00E4-4367-B7E4-972268AD6BE9}
DEFINE_GUID( << name >>
0x57a649f8 0xe4 0x4367 0xb7 0xe4 0x97 0x22 0x68 0xad 0x6b 0xe9 );

 

    我们用自己接口的名字,如IID_IX来代替<<name>>.DEFINE_GUID在OBJBASE.H中定义:

#ifndef INITGUID
#define  DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) 
    EXTERN_C 
const  GUID FAR name
#else
#define  DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) 
        EXTERN_C 
const  GUID name 
                
=   { l, w1, w2, { b1, b2,  b3,  b4,  b5,  b6,  b7,  b8 } }
#endif   //  INITGUID

    在这里INITGUID是没有定义的所以这里只是是一个声明.
如果在OBJBASE.H后包含了INITGUID.H,那么产生的代码就是定义了.在INITGUID.H中对DEFINE_GUID做了重定义:


    这里IFace.h包含了GUID的extern声明,而GUID.cpp包含了对GUID的定义,在需要使用這個GUID的地方如CMPNT.cpp,只要包含IFace.h就可以直接使用这个GUID了,当然要避免重复定义,INITGUID.H只能在一个文件包含,因为变量可以声明很多次,但只能定义一次.
在OBJBASE.H有这方面的说明:

//  macros to define byte pattern for a GUID.
//       Example: DEFINE_GUID(GUID_XXX, a, b, c, ...);
//
//  Each dll/exe must initialize the GUIDs once.  This is done in one of
//  two ways.  If you are not using precompiled headers for the file(s) which
//  initializes the GUIDs, define INITGUID before including objbase.h.  This
//  is how OLE builds the initialized versions of the GUIDs which are included
//  in ole2.lib.  The GUIDs in ole2.lib are all defined in the same text
//  segment GUID_TEXT.
//
//  The alternative (which some versions of the compiler don't handle properly;
//  they wind up with the initialized GUIDs in a data, not a text segment),
//  is to use a precompiled version of objbase.h and then include initguid.h
//  after objbase.h followed by one or more of the guid defintion files.

    下面来看注册表,注册表实际上可以看成一个树形结构,内容是一些{键:值}的对.COM只使用了注册表的一个分支:HKEY_CLASSES_ROOT.在次关键字下,有一个CLSID关键字.CLSID下的子关键字InproServer32保存了组件所在的DLL文件的名称.下图展示了一个注册表CLSID分支的结构:   

    从图中可以看出来,Tail Rotor Simulator组件的CLSID被保存在HKEY_CLASSES_ROOT/CLSID下.组件的这个有意义的名称被注册为组件的CLSID缺省值.在此CLSID下的InprocServer32子关键字包含实现Tail Rotor Simulator组件的DLL名称C:/Helicopter/TailRotor.dll.

    现在来简略看看HKEY_CLASSES_ROOT下的一级子键,首先列出的主要是各种程序所注册的文件扩展名.在文件名之后,是各种其他名字.这类名字大多是ProgID,表示程序员定义的标志符.还有一些特殊的关键字,如CLSID,AppID,CATID(组件类别),Interface,Licenses,TypeLib.这些将GUID映射为其他某块信息.如CLSID将GUID映射为一个文件名称.其他特殊关键字将再后面章节介绍.

    下面我们将详细讨论下ProgID,注册表中HKEY_CLASSES_ROOT分支的大多数子关键字都是ProgID,所谓ProgID指的是程序员给某个CLSID制定的一个程序员易记的名字.它的命名约定一般是如下所示:

 

<Program>.<Component>.<Version>

    注意这只是约定而不是规则.

    ProgID关键字是在CLSID关键字的下面(和InprocServer32同级),ProgID的主要作用是获得相应的CLSID.如果在某个CLSID下面找ProgID将非常低效,所以在HKEY_CLASSES_ROOT下也将列出ProgID的值.在每个ProgID下都有一个CLSID的关键字,包含了组件的CLSID.下图是6.4图的扩展,注意ProgID和CLSID都包含了对方的信息.


   
  

    ProgID和CLSID的转换COM提供了两个函数CLSIDFromProgID和ProgIDFromCLSID.如:

    CLSID clsid;
    CLSIDFromProgID(L
" Helicopter.TailRotor " , & clsid);

    随着组件的增多,让用户挑选组件或者以此装载每个组件,然后查询所需的接口都显得过于低效.组件类别提供了一个方案.它其实就是一个接口集合.该集合将分配给一个GUID,此GUID此时此为CATID.如果一个组件实现了某个组件类别的所有接口,那么它就可以将其注册成该组件类别的一个成员.组件类别实现的方法和C++中的抽象基类是类似的.一个抽象基类是一组派生类所必须实现的函数的集合,所以可以说此派生类是相应抽象基类的一个特定实现.同理,一个组件类别是属于此类别的组件所必须实现的接口集合.属于某组件类别的组件是该组件类别的一个特定实现.
  
    组件类别最吸引人的地方可能是开发人员无需自己完成注册表的处理.这些工作可以由windows系统附带的一个名为Component Category Manager来完成.Component Category Manager,CLSID_StdComponentCategoriesMgr是一个实现了两个主要接口的COM组件:ICatRegister(完成组件类别的登记和取消注册)及ICatInformation(获取系统中某个组件类别的信息,如系统注册的所有组件类别,某个特定类别的所有组件,某个特定组件的所有类别). 

     由于Windows自带的regedit是按CLSID列表显示的,不方便我们查看,我们可以用OleView更好的观察组件类别.还可以用它来检查自己注册的正确性.

        所有的COM组件和客户都需要完成一些相同的操作.为保证这些操作是按照标准并且兼容的,COM定义了一个函数库以实现所有的操作.此函数库是在OLE32.DLL中实现的.在使用静态链接时,可以使用OLE32.LIB.下表显示了一些常用的函数调用:

 

 

下面是一些例子: 

// 使用StringFromGUID2
wchar_t szCLSID[ 39 ];
int  r  =  ::StringFromGUID2(CLSID.COmponent1,szCLSID, 39 );

/*
注意传给StringFromGUID2的参数是一个Unicode串(即一个宽字符wchar_t类型的数组).在非Unicode的系统中,
需要将结果转化为单字节字符(char).为此可以用ANSI的wcstombs函数,如下所示
*/

#ifndef _UNICODE
// covert from widechar to non-wide
char  szCLSID_single[ 39 ];
wcstombs(szCLSID_single,szCLSID,
39 );
#endif

/*
注意,有些函数需要使用内存任务分配器
*/

wchar_t
*   string ;
// Get String from CLSID
::StringFromCLSID(CLSID_Component1, & string );
// Use string
.
.
.
// Free string
::CoTaskMemFree( string );
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值