com技术内幕读书笔记2

来源:互联网 发布:数据有效性检验 编辑:程序博客网 时间:2024/05/16 01:21

 

第六章 关于HRESULTGUID、注册表及其它细节

HRESULT

HRESULT是一个32位整型。最高位表示是否成功,0表示成功,1表示失败。然后15位为设备代码,表示可以返回HRESULT中返回代码(低16位)的操作系统部分。例如FACILITY_RPCFACILITY_STORATE等。FACILITY_NULL0,表示没有指定设备。低16位表示返回代码。

除了设备代码为FACILITY_ITF之外的所有HRESULT值都是通用的,它们跟一般windows错误码一样,都有唯一的含义。但如果设备代码是FACILITY_ITF,则该HRESULT是特定于返回该代码的接口的。用户自定义的HRESULT采用的设备代码都是FACILITY_ITF。不同的接口的返回值部分(低16位)可以相同,设备部分又都是FACILITY_ITF,所以HRESULT是相同的。但因为用户一般知道使用的接口,所以可以根据接口跟HRESULT配合获得具体返回信息。

winerror.h中定义了所有的com错误码。该文件存放了所有普通windows错误和com错误。如果一个HRESULT值的设备代码是FACILITY_WIN32,则它并不在HRESULT列表中存在。要获得其含义,只需寻找与低16位相等的windows错误码。这样做估计是为了减少winerror.h的内容。

可以通过FormatMessage获得某个错误码的具体信息。

正确码和错误码都可能有多个。要判断一个HRESULT是正确码还是错误码,应该使用SUCCEEDEDFAILED宏。其实前者就是判断是否大于等于0,后者判断是否小于0

如前所述,用户自定义HRESULT代码,其设备代码一定是FACILITY_ITF,返回代码不能使用0X00000x01FF。创建自定义的HRESULT可以使用MAKE_HRESULT宏,例如MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 100);参数1是正确/错误码,参数2是设备码,参数3是返回码。下面是该类的定义:

#define MAKE_HRESULT(sev,fac,code) /

 

((HRESULT) (((unsigned long)(sev)<<31) | ((unsigned long)(fac)<<16) | ((unsigned long)(code))) )

 

常见的HRESULT参考P80的表。

 

GUID

typedef struct _GUID {          // size is 16

 

    DWORD Data1;

 

    WORD   Data2;

 

    WORD   Data3;

 

    BYTE  Data4[8];

 

} GUID;

 

总共128比特(16字节),根据网卡地址和时间以及一些其它因素算法之后生成,保证全球唯一。可以使用vsguidgen.exe生成。

不仅每个接口有GUID,称为IID;每个组件也有自己的GUID,称为CLSID

因为GUID比较大,所以一般采用引用传递方式。

COMWindows注册表

注册表中存放了很多关于COM组件的信息,都放置在ROOT-HKEY_CLASSES_ROOT下面。

可以分为以下几类:

1.       CLSID关键字下面存放了所有注册过的COM组件的CLSID。每个CLSID作为一项。它们都包含一个子关键字InprocServer32,键值是组件所在的DLL文件名称。每个CLSID还有一个子关键字ProgID,是给每个CLSID起的一个易记的名字。格式是<program>.<Component>.<Version>,并不一定遵守。有的ProgID没有版本号,此ProgID被映射到所安装的最新版本的组件。例如MSDEV.APPLICATION

2.       HKEY_CLASSES_ROOT关键字下面直接列出了所有注册COM组件的ProgID。下面有CLSID关键字,为组件的CLSID;如果该ProgID不包含版本号,还可能有关键字CurVer,为组件的当前版本的ProgID

3.       其它信息

ProgIDCLSID可以通过CLSIDFromProgIDProgIDFromCLSID转换。

COM规范给定每个COM组件都必须有自注册功能,即必须在DLL中导出两个函数DllRegisterServer()DLLUnregisterServer()。前者用来注册,后者反注册。可以在客户程序中通过LoadLibrary配合GetProcaddress来获得函数的地址然后调用,也可以使用RegSvr32.exe来注册。DllRegisterServer的功能就是向注册表写入跟该COM组件相关的信息。

可以通过OleView来查看COM组件的信息,代替注册表。这样更清晰。

组件类别

      一个组件类别是一个接口集合。该集合将被分配给一个GUID,称为CATID。对某个组件而言,如果它实现了某个组件类别的所有接口,那它就可以注册称为该组件类别的一个成员。一个组件可以是多个组件类别的成员。

COM库函数

所有的COM组件和客户都需要完成一些相同的操作。为保证这些操作按标准进行,COM定义了函数来实现这些操作。正如c语言是一种编程规范,同时c语言库可以方便开发。COM库也是同样作用。它是在OLE32.DLL中实现的。包括四类

1.       初始化函数。一般CoInitialize来初始化,CoUninitialize反初始化。只有初始化之后才能使用COM库中的函数。

2.       GUID相关函数。包括前面的GUIDProgID交换的函数、GUID和字符串转换的函数等。

3.       对象创建函数。将在下一章介绍。

4.       内存管理函数。

因为客户程序、组件程序、COM库程序可能使用不同的语言,所以如果其中一个申请内存,交给另一个使用,然后释放,不能使用newdeleteCOM库提供了Imalloc接口来申请和释放内存。Imalloc::Alloc申请,Imalloc:Free释放。之前必须调用CoGetMalloc获取Imalloc接口指针。这样就可以客户申请内存,交给组件使用并释放了。或者相反也可以。

      为了方便使用,CoTaskMemAllocCoTaskMemFree可以只操作一步替代前面的先生成接口,然后再调用接口方法的两步过程。

每类的具体内容参考《COM原理与应用》的p80

第七章 类厂

CoCreateInstance

Com库中包含一个创建组件的名为CoCreateInstance的函数。声明是:

HRESULT _stdcall CoCreateInstance(

      Const CLSID& clsid,

      IUnknown* pIUnknownOuter,

      DWORD dwClsContext,

      Const IID& iid,

      Void **ppv

);

第一个参数是待创建组件的CLSID,第二个参数是用于聚合组件的,第八章介绍。第三个参数是限定所创建的组件的执行上下文的,第四个参数为组件上待使用的接口的IID,最后一个参数返回此接口的指针。

第三个参数指定的上下文可以是下列值的组合:

1.       CLSCTX_INPROC_SERVER:客户希望创建在同一进程中运行的组件。

2.       CLSCTX_INPROC_HANDLER

3.       CLSCTX_LOCAL_SERVER:本地EXE服务组件

4.       CLSCTX_REMOTE_SERVER:远程EXE服务组件

类厂

CoCreateInstance实际上并没有直接创建COM组件,而是创建了一个称为类厂的组件。类厂组件的唯一功能是创建其它组件。一个类厂组件可以对应多种普通COM组件,但每个类厂组件的实例只能创建一种COM组件。

COM库中CoGetClassObject可以获得类厂接口的指针。声明如下:

HRESULT _stdcall CoGetClassObject(

      Const CLSID& clsid,

      DWORD dwClsContext,

      COSERVERINFO* pServerInfo,

      Const IID& iid,

      Void **ppv);

ppv作为输出参数存放返回的类厂接口指针,类厂一般实现了IclassFactory指针。该接口继承自IUnknown,但多个两个方法:CreateInstance用来创建实际的COM对象,LockServer用来防止组件所在的dll被卸载。

IclassFactory::CreateInstance的声明是:

CreateInstance(IUnknown *PunknownOuter, const IID& iid, void **ppv);它接收一个接口GUID,返回该接口的指针。它并不接受组件的CLSID,所以一个类厂实例只能够创建一种COM组件,即传给CoGetClassObjectCLSID对应的组件。

在书上P109给出了CoCreateInstance是如何使用CoGetClassObjectIclassFactory::CreateInstance配合完成的。

客户、COM库、组件dll、类厂、组件之间的交互过程

1.       客户首先调用COM库的CoCreateInstance函数来创建COM组件。

2.       CoCreateInstance首先调用COM库的CoGetClassObject获取类厂。

3.       该函数具体是通过调用了组件DLL输出的DllGetClassObject来创建类厂。

4.       DllGetClassObject通过new函数产生一个Cfactory的对象,并通过QueryInterface获取其接口指针(一般是IclassFactory指针)。

5.       返回到COM库的CoCreateInstance调用刚才获得的接口指针(IclassFactory,类厂)的CreateInstance函数。

6.       该函数new指定的组件类,通过QueryInterface获得指定的接口

7.       CoCrateInstanse释放掉IclassFactory指针(通过Release),然后向客户程序返回获得的指针。

8.       可以在客户中使用获得的接口了。

可以参考书的P111p120的图。

在第6步中,根据不同的CLSID创建不同的组件,可以实现一个类厂供该DLL中多个组件共用。但只是类共用,不是实例共用。一旦在创建类厂时通过CoGetClassObject指定了CLSID,则只能创建该COM组件的实例。

DLL的装卸

1.       进程内组件的装载过程:客户程序调用COM库的CoCreateInstance。它调用CoGetClassObject。在该函数中,COM根据系统注册表中的信息,找到CLSID对应的组件程序(DLL文件)的全路径,然后调用CoLoadLibrary(间接调用LoadLibrary)导入COM组件DLL。下面就是调用DLL中的DllGetClassObject,之后的顺序就是前面一小节的内容。

2.       进程内组件的卸载过程:

如果一个DLL中的所有组件都不再使用,则应该释放该DLLCOM库提供了一个函数CoFreeUnusedLibraries,它可以检测当前不再使用的dll,然后释放掉。客户程序可以手动调用该函数来释放DLL。每个COM组件所在的DLL都应该输出一个DllCanUnloadNow函数,供CoFreeUnusedLibraries查询dll状态。只有某个dll符合以下两个条件时,才可以释放该DLL。即DllCanUploadNow返回S_OK

1)       dll中维护了一个全局变量。开始是0。每当创建(构造)一个COM组件,都会加1;析构则减1。只有该全局变量是0时才返回S_OK

2)       类厂并不跟一般COM组件共用1)中的全局变量。所有类厂共用一个全局变量。该变量不是在构造和析构函数中增减,而是有专门的函数LockServer来负责增减。从前面可以知道,该函数是Ifactory的成员。每次客户调用LockServer(TRUE)增加计数,FALSE减少计数。0时可以返回S_OK

参考本章例子和《COM原理与应用》P84,以及《ATL开发指南》第二章例子会有系统了解如何在一个一般dll中写com组件程序,以及如何写客户程序。

 

http://www.newsmth.net/pc/pccon.php?id=1363&nid=73478&pid=0&tag=0&tid=7568

原创粉丝点击