第6章
在前五章,作者将细节问题隐藏起来。本章主要将讨论COM的细节问题--HRESULT,GUID,注册表,最后介绍了COM库中一些函数。
HRESULT:
是QueryInterface的返回值。在设计组件的时候,可以用它来返回争取及错误代码。
HRESULT值实际上是一个可分成3个域的32位值,
HRESULT的低16位(0-15位)是函数返回值;16到30bit位返回的是设备值(操作系统代码),可以使用HRESULT_FACILITY宏来查看HRESULT值的设备;31bit返回严重级别。
WIN32的API中的FormatMessage函数可以将HRESULT错误信息打印出来,判断成功和失败使用SUCCEEDED和FAILED宏。
用户还可以自定义跟接口相关的HRESULT返回值,其中设备值部分必须是FACILITY_ITF,使用MAKE_HRESULT宏。还有几条规则:
(1)不能使用0x0000到0x01FF范围内的值。这是COM定义的FACILITY_ITY保留值。
(2)不要传播FACILITY_ITY错误代码。
(3)尽可能使用通用的COM成功和失败代码。
(4)避免定义自己的HRESULT,可以在函数中使用一个输出参数。
HRESULT其实是一个LONG型的值。预定义的HRESULT值在WINERROR.H,打开该文件看了一下。第31bit是一个Severity - indicates success/fail 0 - Success
1 - Fail (COERROR),SUCCEEDED和FAILED宏是根据这个bit来判断是成功还是失败。
GUID:
GUID可以用来标识组件和接口的ID。
前几章使用的IID是一个16字节(128bit)的GUID。GUID是Globally Unique Identifier的简称,在COM组件开发过程中主要是为了保证IID即接口ID的唯一性。Microsoft提供了两个生成GUID的程序,一个是UUIDGEN.EXE(命令行形式),一个是GUIDGEN.EXE(对话框形式)。
GUID的结构是
#ifndef GUID_DEFINED #define GUID_DEFINED typedef struct _GUID { // size is 16 DWORD Data1; WORD Data2; WORD Data3; BYTE Data4[8]; } GUID; #endif // !GUID_DEFINED
在OBJBASE.H文件中,定义了operator==操作符
__inline BOOL operator==(const GUID& guidOne, const GUID& guidOther) { return !memcmp(&guidOne,&guidOther,sizeof(GUID)); }
GUID除了可以唯一的标识接口以外,还可以用来唯一标识组件,称为类标识符CLSID。在第六章将会用到COM库的CoCreateInstance函数就是使用CLSID来将要创建的组件。
IID的引用const IID &可以使用REFIID,CLSID是REFCLSID,GUID是REFGUID(都定义在OBJBASE.H文件中)。
Windows注册表
对于组件的使用者来说,怎样找到组件所在的文件,并且能够正确的调用它们,就要用到注册表,注册表中保存了组件所在的文件及其他信息。
CLSID在注册表中作为索引在注册表中发布包含它的DLL文件的名称,作为CoCreateInstance函数的传入参数,CoCreateInstance使用CLSID作为关键字在注册表中查找所需的文件名称。
注册表中是由许多元素组成的层次结构,每一个元素被称作一个关键字。每一个关键字可以包含一系列子关键字、一系列命名的值、一个未命名的值。
在此,我们关心CLSID和DLL文件的名称。
COM使用了注册表的一个分支HKEY_CLASSES_ROOT,在这个关键字下有一个CLSID的子关键字,在CLSID关键字下列有系统中安装的所有组件的CLSID,每一个CLSID关键字的缺省值是组件有意义的名称,子关键字InproServer32的缺省值是组件所在的DLL文件名称。如下图所示
HKEY_CLASSES_ROOT
注册表HKEY_CLASSES_ROOT关键字的开头,列出的是各种应用程序注册的文件扩展名。在扩展名之后,有许多其他的名字,大多数是ProgID,ProgID是给某个CLSID指定的容易记忆的名称,Visual Basic使用ProgID而不是CLSID来标识组件。还有AppID,此关键字下子关键字的作用是将某个APPID映射为某个远程服务器的名称,将在第10章讨论。组件类别,讲CATID(组件类别ID)映射为某个特定的组件类别。Interface,用于讲IID映射为某个接口的相关信息,用于在跨进程使用接口的情况,将在第10章讨论。TypeLib,保存接口成员函数使用的参数信息,将在第11章讨论。
ProgID
ProgID是为CLSID起的容易记忆的名字,只是没办法保证ProgID的唯一性。ProgID的命名约定<Program>.<Component>.<Version>,<Version>字段可以没有,ProgID的主要作用是获取相应的CLSID,ProgID关键字的缺省值是组件用户易记的名称,它下面有一个CLSID的子关键字,缺省值是组件的CLSID。<Version>字段没有的ProgID也被列在HKEY_CLASSES_ROOT下,除了CLSID子关键字,还有一个CurVer的子关键字,缺省值是组件当前版本的ProgID。如下图所示
注册表的其他细节
(1)CLSIDFromProgID和ProgIDFromCLSID,可以进行CLSID和ProgID之间的转换。
(2)组件注册----重要
写完了组件,怎样在注册表中对它进行注册呢,以方便使用者来调用它。
在包含组件的DLL中需要输出STDAPI DllRegisterServer()和STDAPI DllUnregisterServer()函数,其中STDAPI在OBJBASE.H中定义为extern "C" HRESULT __stdcall,使用regsvr32.exe来注册某个组件的过程,其实就是调用DllRegisterServer函数进行注册,在DllRegisterServer和DllUnregisterServer中实际上使用WIN32 API的注册表编辑函数RegCreateKeyEx,RegSetValueEx等来完成组件的注册或取消(使用Reg函数需要链接ADVAPID32.LIB导入库)。
(3)组件类别
组件类别实际上是一个接口集合,也是用GUID进行识别,只是被称作CATID。如果某个组件能够实现该组件类别的所有接口,它就可以是该组件类别的一个成员。组件的开发人员不需要自己完成将组件加入组件类别的注册工作,Windows系统的Component Category Manager会完成这个工作。
(4)OleView
Win32 SDK提供的OleView提供了查看组件的另外一种方式。
COM库函数
COM组件的使用者和创建者,都需要使用一些相同的操作,例如对使用者来说,用CLSID来调用组件的函数等等。这些都在COM库中实现,统一用户的调用方法。
COM库函数在OLE32.DLL中实现
(1)初始化
除了CoGetMalloc外,在使用COM库函数之前,必须调用CoInitialize函数来初始化,当不使用时,调用CoUninitialize函数。这两个函数的原型是HRESULT CoInitialize(void *reserved)和void CoUninitialize(),reserved参数必须为NULL。对于每个进程,COM库函数只需初始化一次。OLE是建立在COM库基础上的,当需要对类型库、剪贴板、拖放、ActiveX文档、自动化及ActiveX控件的支持时,需要调用OleInitialize及OleUninitialize函数。
(2)内存管理
任务内存分配器,考虑到了多线程同步的问题,可以再多线程应用程序中使用。
CoGetMalloc返回IMalloc接口,IMalloc接口的Alloc,Free函数来分配,释放内存。更方便的方式是使用void* CoTaskMemAlloc(ULONG cb //size in bytes)和void CoTaskMemFree(void* pv)。
(3)字符串转化成GUID
StringFromCLSID,StringFromIID,StringFromGUID2,CLSIDFromString,IIDFromString,可以完成字符串与GUID之间的转换。详细的用法,查看MSDN。