COM中的HRESULT, CLISD,ProgID, DLL注册,COM库函数的知识(COM技术内幕笔记之三)
1.COM中的HRESULT:
COM函数中返回值一般是HRESULT类型,这是因为COM是一套二进制标准,而并不是针对某个语言的,所以,定义了一个所有语言都认可的HRESULT类型。
HRESULT占32位,可以分成三个部分,0~15为返回代码16~30为设备代码,31为重要程度.
常用的HRESULT的值有:S_OK, NOERROR,S_FALSE,E_UNEXPECTED,E_NOIMPLE,E_NOINTERFACE等.
关于它们的定义,可以见winerror.h头文件.
如果HRESULT返回出现异常了,我们可以打印出hr的值,如cout<<hr<<endl;然后根据这个16位进制的值在winerror.h中找到相应的宏定义.
一个函数在各种情况下返回的状态代码通常包含多个成功代码及多个失败代码,这就是我们为什么要用SUCCEEDED及FAILED宏的原因.一般不能直接将HRESULT值同某个成功代码(如S_OK)进行比较以决定函数是否成功.
2.COM中的GUID:
GUID(IID)是一个128比特(16进制)的一个结构.其名是全局唯一标识符.定义GUID是为了避免COM接口标识的重复定义.
可以用VC++ tools目录下自带的GUIDGEN生成IID结构.如:
static const IID IID_IX =
{ 0xb5d1b386, 0xe796, 0x4a6b, { 0xaa, 0xd, 0x36, 0x89, 0x72, 0x86, 0x90, 0x78 } };
GUID的比较:其实在objbase.h中已经实现了guid的比较,直接用==操作符就可以进行两个guid之间的比较.如果不喜欢用操作符,则可以使用IsEqualGUID,ISEqualIID,IsEqualCLSID函数进行比较.
除了用GUID来唯一标识接口外,还用GUID来唯一的标识组件.同接口一样,所有的组件都有一个不同的ID,两个组件可以实现相同的接口集,但每一个组件必须具有各不相同的CLSID.
由于GUID结构较大,所以一般不用值传递GUID参数,而用引用传递.这就是为什么QueryInterface接收一个常量引用参数的原因.如果不想输出const IID&,则可以用REFIID类型代替.
3.组件在注册表中的结构:
在注册表中保存了组件的详细信息,其中HKEY_CLASSES_ROOT\CLSID保存了每个注册组件的GUID值.在这个GUID值下面有一个子关键字:InprocServer32,保存了DLL的文件名称及保存位置.还有个子关键字ProgID,保存组件的CLSID指定的一个易记名称.在VB中,将用ProgID而不是CLSID来标识组件.
另外,ProgID不止保存在HKEY_CLASSES_ROOT\CLSID的子关键字中,也另存一份保存在HKEY_CLASSES_ROOT的子关键字中.为什么呢?因为ProgID的主要作用是获得相应的CLSID,在每一个CLSID项中查找某个ProgID是非常低效的,所以在HKEY_CLASSES_ROOT下面直接列出ProgID.这样,根据ProgID来查找其CLSID将非常方便.
注意,用ProgID标识组件不能保证ProgID是唯一的.名字冲突会是一个潜在的问题.但因其处理比较容易,所以经常用它.
用ProgID有如下约定:
<Program>.<Component>.<Version>
例如:Visio.Application.3 Office.Binder.95
4.ProgID和CLSID之间的转换 / CLSID和字符串的转换:
有两个函数,可以在两者之间转换:CLSIDFromProgID / ProgIDFromCLSID,如:
CLSID clsid;
CLSIDFromProgID(L"Helicopter.TailRotor",&clsid);
CLSID和字符串的转换在COM库函数中实现,具体的函数有:
StringFromCLSID / StringFromIID / StringFromGUID2 / CLSIDFromString / IIDFromString
5.Dll的注册.
如果想注册DLL(用RegSvr32 -s命令注册),必须在DLL中输出以下两个函数:
STDAPI DllRegisterServer();和 STDAPI DllUnregisterServer();
regsvr32.exe命令就是通过调用dll函数中的这两个函数来完成组件的注册的.
6.COM库函数
所以的COM组件和客户都需要完成一些相同的操作,为保证这些操作是按标准并且兼容的方法完成的,COM定义了一个函数库以实现所有的这些操作
此函数库是在OLE32.DLL中实现的.
COM库的初始化:在使用COM库的函数之前,进程必须先调用CoInitialize来初始化COM库函数.当进程不再需要使用COM库函数中,必须调用CoUninitialize.
函数原型定义如下:
HRESULT CoInitialize(void* reserved);
void CoUninitialize();
对每一个进程,COM库函数只需初始化一次.由于COM库是用于创建组件的,因此进程中组件无需初始化COM库.COM库初始化一般只是在EXE中进行,而在DLL中则无需进行.
7.在组件中如果分配内存,在客户中怎么释放?
在组件中分配一块内存,然后将其通过一个输出参数传递给客户是一种非常常见的方式,但带来一个问题是:谁来释放它?如何释放?COM库实现了一个方便的函数,如CoTaskMemAlloc和CoTaskMemFree.用来释放组件中分配的内存:
如:
::StringFromCLSID(CLSID_Component1,&string); //组件中给string分配了内存存放GUID的字符串表示
//Use String
::CoTaskMemFree(string); //释放组件分配的内存