泛读笔记-COM原理与应用---1

1.COM模型中,客户请求服务时,只能通过接口进行。
每个接口都由一个128位的全局唯一标识符(GUID)标识。

// C/C++语言中,可用如下结构来描述
typedef struct_GUID
{
	DWORD Data1;
	WORD Data2;
	WORD Data3;
	BYTE Data4[8];
}GUID;

// 例子:
extern "C" const GUID CLSID_MYSPELLCHECKER = 
{
	0x54bf6567,
	0x1007,
	0x11d1,
	{
		0xb0, 0xaa, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00
	}
};

客户通过GUID获得接口的指针
通过接口指针,客户可调用相应的成员函数

客户用一个128位的GUID来标识对象,称为CLSID。
系统中有这类COM对象的信息,包括COM对象所在的模块文件,COM对象在代码中的入口点,
客户程序可由CLSID来创建COM对象。

客户创建对象后,得到的是一个指向对象某个接口的指针。
可从某个接口得到该对象的任意其它接口。

COM规范使用GUID来标识COM对象。
// 产生GUID

// 成功,S_OK
HRESULT CoCreateGuid(GUID* pguid);

CLSID是用来标识COM对象的GUID

COM对象 接口成员

2.从API到COM接口
查字典
API
Initialize
LoadLibrary
InsertWord
DeleteWord
LookupWord
RestoreLibrary
FreeLibrary

接口定义了一组成员函数,这组成员函数是组件对象暴露出来的所有信息,客户程序用这些函数获得组件对象的服务。

指向接口的指针
指向接口函数表,表中每一项为4字节长的函数指针。又称虚函数表。

// 用C语言描述的字典接口
struct IDictionaryVtbl;
struct IDictionary
{
	IDictionaryVtbl* pVtbl;
};

struct IDictionaryVtbl
{
	BOOL (* Initialize)(IDictionary* this);
	BOOL (* LoadLibrary)(IDictionary* this, String);
	...
	void (* FreeLibrary)(IDictionary* this);
};

// 1.每个接口成员函数的第一个参数为指向IDictionary的指针。接口必定存在于某个COM对象上。
// 2.接口成员函数中,字符串变量须用Unicode字符指针。
// 3.windows平台两种调用习惯
// _cdecl和_stdcall
// _cdecl可实现C语言的函数可变参数特性
// COM规范,COM API使用_stdcall调用习惯
// 除非要使用可变参数特性,否则就使用_stdcall调用习惯
// 4.对客户程序,只需要如上的描述
// 对组件程序,须提供实现。
// 5.一个COM对象可支持很多个接口
// 客户程序如何标识一个接口?
// 类似COM对象的标识方法。COM接口也采用了全局唯一标识符,称为接口标识符。
// 例子:
extern "C" const IID IID_IUnknown = 
{
	0x00000000,
	0x0000,
	0x0000,
	{
		0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46
	}
};

// 用C++类重新定义的IDictionary

class IDictionary
{
	virtual BOOL Initialize() = 0;
	virtual BOOL LoadLibrary(String) = 0;
	virtual BOOL InsertWord(String, String) = 0;
	...
	virtual void FreeLibrary() = 0;
};

class定义中隐藏了虚函数表 vtable。
每个成员函数隐藏了第一个指针,this指针指向类的实例。
this指向地址内容为 pVtable
pVtable指向地址处内容为 虚函数表

// 接口只是一种描述,不提供实现。
// COM对象要实现接口IDictionary,COM对象须以某种方式把它自身与类IDictionary联系,然后把IDictionary的指针暴露给客户程序。
// 客户程序获得接口指针pIDictionary后,可调用接口的成员函数。
如:
// C++版本
pIDictionary->LoadLibrary(“Eng…Ch.dict”);
// C语言版本
pIDictionary->pVtbl->LoadLibrary(pIDictionary, “End…Ch.dict”);

3.接口描述语言IDL
示例:

// .idl
interface IDictionary
{
	HRESULT Initialize();
	HRESULT LoadLibrary([in] string);
	HRESULT InsertWord([in]string, [in]string);
	...
	HRESULT FreeLibrary();
};

// 利用MIDL编译工具可生成对应的idict.h

内存接口
客户使用的接口指针pIDictionary指向处内容为pVtable
pVtable指向处内容为vtable第一个元素
vtable第一个元素指向CDictionary类中虚函数的具体实现

所有接口必须从IUnknown派生。

客户程序只能通过接口与COM对象通信。
...

1
2

3

5
6

QueryInterface实现
10

11

12

客户程序
组件对象/COM对象,可实现多个接口对象
21

接口函数的参数,返回值,内部实现用到字符的,一律采用Unicode字符串。

22

23

24

客户程序:
25

26
27

4.COM实现
DLL程序包含一个引出函数表
包含了函数名字,序号,地址。

windows平台上,不同进程间通信的方法很多,COM采用了本地过程调用和远程过程调用进行进程间通信。

客户程序只与同一进程中的代理对象打交道,组件程序只与同一进程中的存根DLL打交道。LPC调用只在代理对象和存根DLL之间进行。

应用A调用系统API,存根DLL通过LPC调用另一进程内的服务,服务通过LPC返回结果,存根DLL返回结果。

为了实现进程外组件,我们需要实现组件程序,代理DLL,存根DLL。

5.用注册表管理COM对象
客户程序通过COM库完成对象创建。
COM库通过系统注册表提供的信息进行组件创建。

组件程序把实现的COM对象信息,接口信息保存到注册表,称为组件的注册。

HKEY_CLASSES_ROOT/CLSID/
进程内组件:InprocServer32
进程外组件:LocalServer32
1
注册表是客户和组件程序共同访问的信息仓库,通常,组件程序安装到机器上后,须把它的信息注册到注册表中。
自注册组件程序/非注册组件程序。

自注册组件:
对进程内组件,
对进程外组件,

只要进程内组件通过了相应的入口函数,RegSvr32就可完成注册或注销工作。
组件程序用于注册,注销入口函数:
DllRegisterServer
DllUnregisterServer

用下面命令行方式:
RegSvr32 c:\DictComp.dll
会调用C盘下DictComp.dll的DllRegisterServer完成组件程序注册。
用下面命令行:
RegSvr32 /u c:\DictComp.dll
会调用C盘下DictComp.dll的DllUnregisterServer完成组件程序注销。

在函数体内把组件对象的信息写到系统注册表中或从系统注册表中把相关信息去掉。
COM规范要求,支持自注册的进程外组件须支持命令行参数 /RegServer /UnregServer。注册成功,返回0;失败,返回非0。

6.类厂
客户程序,调用COM库的函数进行组件对象创建,COM库的创建函数依据注册表的信息并调用组件程序的入口函数来创建组件对象。

组件程序须提供入口函数 DllGetObjectClass来提供组件程序组件信息。
COM库通过类厂创建COM对象,每个COM类,有一个类厂专门用于该COM类的对象创建操作。
类厂本身也是COM对象,支持IClassFactory接口。
11
类厂本身是COM对象,用于其它COM对象的创建。
类厂由DllGetClassObject引出函数创建。
111

COM库或客户一旦有了类厂的接口指针,就可通过类厂接口IClassFactory的CreateInstance创建相应的COM对象。

COM库中,三个API可用于对象的创建,CoGetClassObject,CoCreateInstance, CoCreateInstanceEx。
1111
CoGetClassObject找到clsid指定的COM类的类厂,连接到类厂对象,需要时装入组件代码。
对进程内组件对象,调用DLL模块的DllGetClassObject,传入clsid,iid,ppv,DllGetClassObject创建类厂,返回类厂对象接口指针。

dwClsContext指定组件类型:
进程内组件
进程外组件
进程内控制对象

pServerInfo指定创建远程对象时的服务器信息。创建进程内组件对象或本地进程外组件时,设为NULL。

如CoGetClassObject创建的类厂对象位于进程外组件。
CoGetClassObject启动组件进程,等待。
直到组件进程把它支持的COM类 对象的类厂注册到COM中,
CoGetClassObject把COM中相应的类厂信息返回。

组件外进程被COM库启动时【带命令行参数"/Embedding"】,须把所支持的COM类的类厂对象通过CoRegisterClassObject注册到COM。
进程退出时,须调用CoRevokeClassObject来通知COM它所注册的类厂对象不再有效。
1111
函数内部实际调用CoGetClassObject。
pUnknownOuter与类厂接口的CreateInstance中对应参数一致。

11111
1

如创建远程对象或希望一次获得对象的多个接口指针,用CoCreateInstanceEx。
如希望获取类厂对象或调用类厂某些成员函数,用CoGetClassObject。
其它情况,用CoCreateInstance。

类厂的实现:
1

1
1
1
1
1

类厂对组件生存期的控制:
锁计数对整个组件程序负责,如一个组件程序实现了多个类厂,则,所有类厂可共用一个锁计数器。
引入锁操作后,判断一个组件程序是否卸载出内存:
组件程序中是否还存在组件对象
组件程序的锁计数器是否为0

#COM库

发布了117 篇原创文章 · 获赞 84 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/x13262608581/article/details/85806410
今日推荐