COM学习笔记.docx
- 文档编号:11744678
- 上传时间:2023-03-31
- 格式:DOCX
- 页数:39
- 大小:131.06KB
COM学习笔记.docx
《COM学习笔记.docx》由会员分享,可在线阅读,更多相关《COM学习笔记.docx(39页珍藏版)》请在冰豆网上搜索。
COM学习笔记
COM学习笔记
(一)初识COM
COM--ComponentObjectModel,组件对象模型。
一向以难学著称,有人曾说过这样的话:
世界上只有两个程序员真正理解COM,他们都在微软工作。
这句话虽然有点过,但基本上说出了COM确实有些难理解。
不过,不用担心,本文并不探求多深多高的技术领域,而是带领大家浏览一下我们的COM,就像本文的题目一样:
初识COM。
首先,我们先来了解一下有关COM的概念:
COM(ComponentObjectModel,组件对象模型)是微软公司的最高级的、包罗万象的二进制通信规范,用于软件组件间跨越多个进程、机器、硬件和操作系统进行互操作。
下面我们来看看COM的一些特点:
在COM中,应用程序不是通过诸如ShowWindow()的API函数进行操纵。
程序是由对象组成的,对象向外提供一个或多个接口。
接口是一组相关的函数,函数操作他们所属的对象。
不能直接访问对象中的数据,而只能通过对象的接口函数访问。
学过C++和数据结构的人应该对上述说法并不陌生。
在COM中,没有指向对象的指针这种东西,有的只是指向对象接口的指针。
实际上,是指向另一个指针的指针。
第二个指针指向一个指针表,表中的指针指向接口成员函数。
该指针表称为VTBL。
将指针指向对象后,就可以通过调用接口中的成员函数与该对象通信。
如何将指针指向第一个对象呢?
可以调用一个返回指向对象指针的COM函数如:
CoCreateInstace()。
COM对象都提供一个叫IUnknown的接口,该接口包含方法AddRef()、Release()和QueryInterface()。
每个接口都是从IUnknown接口派生出来的。
前两个方法操纵一个控制对象使用期限的内部引用计数。
当对象第一次被创建时,创建者必须调用该对象的AddRef(),将计数加1。
每当其他的用户将一个指针指向该对象时,必须再次调用该对象的AddRef()方法。
当用户不再使用对象时,它调用对象的Release()方法,将引用计数减1。
当最后一个用户调用对象的Release()方法后,计数值变为0,导致对象释放自己。
下面是AddRef()和Release()方法的简单实现:
ULONGIUnknown:
:
AddRef(void)
{
m_RefCount++;
returnm_RefCount;
}
ULONGIUnknown:
:
Release(void)
{
m_RefCount--;
if(m_RefCount==0)
{
deletethis;
return0;
}
returnm_RefCount;
}
由于每一个对象都支持IUnknown接口,因此可以通过QueryInterface()来询问对象是否支持您感兴趣的其它接口。
接口通过接口ID来标识。
HRESULTIUnknown:
:
QueryInterface(REFIIDriid,LPVOIDFAR*ppv)
{
if(riid==IID_IUnknown||riid==IID_IDropTarget)
{
*ppv=(LPVOID)this;
AddRef();
ReturnS_OK;
}
else{
*ppv=NULL;
returnE_NOINTERFACE;
}
用于唯一地区分COM中条目的标识符是一个被称为GUID(全局唯一标识符)或UUID(通用唯一标识符)。
Typedefstruct_GUID
{
unsignedlongData1;
unsignedshortData2;
unsignedshortData3;
unsignedcharData[8];
}GUID;
GUID的取值范围非常大,16个字节可能形成的不同组合为3.4ⅹ10^38。
在COM中传输格式化数据的工作是通过数据对象处理的,数据对象是支持IDataObject接口的对象。
IDataObject接口支持以下方法:
IDataObject:
:
GetData
IDataObject:
:
GetDataHere
IDataObject:
:
QueryGetData
IDataObject:
:
GetCanonicalFormatEtc
IDataObject:
:
SetData
IDataObject:
:
EnumFormatEtc
IDataObject:
:
DAdvise
IDataObject:
:
DUnadvise
IDataObject:
:
EnumDAdvise
决定了设计方案以及接口中需要包括的方法和参数后,必须使用接口描述语言(IDL)编写接口的抽象定义。
编写好.IDL文件后,使用VC++和PlatformSDK自带得MicrosoftIDL编译器编译,并生成头文件、勇于构建调度借口调用的代理和占为程序的代码以及实现开发工具和调用接口所必需的类型库。
自动化,通过它COM对象可以将其功能提供给解释型客户(如脚本编写语言)而不是编译型客户使用。
在开发阶段,当COM接口客户被编译时,编译器读取源代码,通过查阅头文件或类型库中的接口定义将方法名称解析为VTBL条目,并生成目标代码,以便将必须的参数压入堆栈并跳到接口VTBL中相应条目保存的地址。
编译过程可能需要很长时间,但编译后运行二进制代码的速度相对较快。
而解释型客户在真正执行之前,不会将源代码解析为机器代码。
自动化对象通过一个叫做IDispatch的标准接口暴露其所有的功能,而不是将每项功能作为自定义接口的VTBL中的条目来暴露。
对对象的任何内部方法的调用都是通过该接口进行处理的。
IDispatch接口的方法包括:
IDispatch:
:
Invoke
IDispatch:
:
GetIDsOfNames
IDispatch:
:
GetTypeInfo
IDispatch:
:
GetTypeInfoCount
当客户想调用自动化对象的内部方法时,它调用对象的IDispatch:
:
Invoke()。
客户可使用IDispatch:
:
GetIDsOfNames获得想要做事情的ID。
类型库是对象厂商提供的静态数据结构的集合,通过ItypeLib接口进行访问,包含关于单个对象、接口或类的信息。
类型库可以包含描述下述内容的信息:
服务器支持的对象类型
· 每个对象方法及其参数和类型
· 每个对象属性及其类型
· 枚举常量值
· 到在线文档中特定条目的引用
COMLanguageRequirements
TheonlylanguagerequirementforCOMisthatcodeisgeneratedinalanguagethatcancreatestructuresofpointersand,eitherexplicitlyorimplicitly,callfunctionsthroughpointers.Object-orientedlanguagessuchasC++andSmalltalk?
provideprogrammingmechanismsthatsimplifytheimplementationofCOMobjects,butlanguagessuchasC,Pascal,Ada, Java,andevenBASICprogrammingenvironmentscancreateanduseCOMobjects.
以上摘自MSDN,重要的是C,Pascal,Ada,Java,andevenBASIC都可以用来编写COM。
以上初步介绍了关于COM的一些东西,不多也很浅,理解以上的部分就够花费很长一段时间的了,如果真地对COM感兴趣的话,最好有一定的基础,包括:
数据结构,面向对象的程序设计,Windows编程等,不过也不一定都学,只是这些会对你学习COM并在短时间内掌握并深入理解COM会有相当的好处。
COM学习笔记
(二)CoCreateInstance具体内部实现
[cpp] viewplaincopy
1. CoCreateInstance(....)
2. {
3. //.......
4. IClassFactory *pClassFactory=NULL;
5. CoGetClassObject(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pClassFactory);
6. pClassFactory->CreateInstance(NULL, IID_IUnknown, (void**)&pUnk);
7. pClassFactory->Release();
8. //........
9.}
这段话的意思就是先得到类厂对象,再通过类厂创建组件从而得到IUnknown指针。
继续深入一步,看看CoGetClassObject的内部伪码:
[cpp] viewplaincopy
1. CoGetClassObject(.....)
2.{
3. //通过查注册表CLSID_Object,得知组件DLL的位置、文件名
4. //装入DLL库
5. //使用函数GetProcAddress(...)得到DLL库中函数DllGetClassObject的函数指针。
6. //调用DllGetClassObject
7.}
8./// DllGetClassObject是干什么的,它是用来获得类厂对象的。
只有先得到类厂才能去创建组件.
9./// 下面是DllGetClassObject的伪码:
10. DllGetClassObject(...)
11. {
12. //......
13. CFactory* pFactory= new CFactory; //类厂对象
14. pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory);
15. //查询IClassFactory指针
16. pFactory->Release();
17. //......
18. }
19./// CoGetClassObject的流程已经到此为止,现在返回CoCreateInstance,看看CreateInstance的伪码:
20. CFactory:
:
CreateInstance(.....)
21. {
22. //...........
23. CObject *pObject = new CObject; //组件对象
24. pObject->QueryInterface(IID_IUnknown, (void**)&pUnk);
25. pObject->Release();
26. //...........
27. }
这部分我们将构造一个创建COM组件的最小框架结构,然后看一看其内部处理流程是怎样的
[cpp] viewplaincopy
1.IUnknown *pUnk=NULL;
2.IObject *pObject=NULL;
3.CoInitialize(NULL);
4.CoCreateInstance(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, (void**)&pUnk);
5.pUnk->QueryInterface(IID_IOjbect, (void**)&pObject);
6.pUnk->Release();
7.pObject->Func();
8.pObject->Release();
9.CoUninitialize();
[cpp] viewplaincopy
1.CoCreateInstance(....)
2. {
3. .......
4. IClassFactory *pClassFactory=NULL;
5. CoGetClassObject(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pClassFactory);
6. pClassFactory->CreateInstance(NULL, IID_IUnknown, (void**)&pUnk);
7. pClassFactory->Release();
8. ........
9. }
这就是一个典型的创建COM组件的框架,不过我的兴趣在CoCreateInstance身上,让我们来看看它内部做了一些什么事情。
以下是它内部实现的一个伪代码:
[cpp] viewplaincopy
1.CoGetClassObject(.....)
2. {
3. //通过查注册表CLSID_Object,得知组件DLL的位置、文件名
4. //装入DLL库
5. //使用函数GetProcAddress(...)得到DLL库中函数DllGetClassObject的函数指针。
6. //调用DllGetClassObject
7. }
8. DllGetClassObject是干什么的,它是用来获得类厂对象的。
只有先得到类厂才能去创建组件.
9. 下面是DllGetClassObject的伪码:
10. DllGetClassObject(...)
11. {
12. ......
13. CFactory* pFactory= new CFactory; //类厂对象
14. pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory);
15. //查询IClassFactory指针
16. pFactory->Release();
17. ......
18. }
19. CoGetClassObject的流程已经到此为止,现在返回CoCreateInstance,看看CreateInstance的伪码:
20. CFactory:
:
CreateInstance(.....)
21. {
22. ...........
23. CObject *pObject = new CObject; //组件对象
24. pObject->QueryInterface(IID_IUnknown, (void**)&pUnk);
25. pObject->Release();
26. ...........
27. }
这段话的意思就是先得到类厂对象,再通过类厂创建组件从而得到IUnknown指针。
继续深入一步,看看CoGetClassObject的内部伪码:
上图是从COM+技术内幕中COPY来的一个例图,从图中可以清楚的看到CoCreateInstance的整个流程。
(7)一个典型的自注册的COMDLL所必有的四个函数
DllGetClassObject:
用于获得类厂指针
DllRegisterServer:
注册一些必要的信息到注册表中
DllUnregisterServer:
卸载注册信息
DllCanUnloadNow:
系统空闲时会调用这个函数,以确定是否可以卸载DLL
DLL还有一个函数是DllMain,这个函数在COM中并不要求一定要实现它,但是在VC生成的组件中自动都包含了它,它的作用主要是得到一个全局的实例对象。
(8)注册表在COM中的重要作用
首先要知道GUID的概念,COM中所有的类、接口、类型库都用GUID来唯一标识,GUID是一个128位的字串,根据特制算法生成的GUID可以保证是全世界唯一的。
COM组件的创建,查询接口都是通过注册表进行的。
有了注册表,应用程序就不需要知道组件的DLL文件名、位置,只需要根据CLSID查就可以了。
当版本升级的时侯,只要改一下注册表信息就可以神不知鬼不觉的转到新版本的DLL。
COM学习笔记(三)IUnknown接口
一:
接口定义
任何一个接口都是继承于IUnknown接口。
客户同组件的交互都是通过一个接口完成的。
在客户查询组件的其他接口时,也是通过接口完成的。
这个接口就是Iunkown,它的定义包含在Win32SDK中的UNKOWN.h头文件中。
[cpp] viewplaincopy
1.interface IUnkown
2.
3.{
4.
5. virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) = 0;
6.
7. virtual ULONG __stdcall AddRef() = 0;
8.
9. virtual ULONG __stdcall Release() = 0;
10.
11.};
2. 获取IUnkown指针
有一个叫CreateInstance的函数,它可以建立一个组件并返回一个IUkown指针:
IUnkown*CreateInstance();
在此,客户不必再使用new操作符。
3. QueryInterface函数
客户可使用IUnkown中的成员函数QueryInterface查询组件是否支持某个特定的接口,如果支持,将返回一个指向此接口的指针;如果不支持,则返回一个错误代码。
HRESULT__stdcallQueryInterface(constIID&iid,void**ppv);
其中,第一个参数iid标志客户所需的接口,第二个参数ppv存放所请求接口的指针地址。
4. 使用QueryInterface
[cpp] viewplaincopy
1.IUnkown* pI;
2.//define a pointer for the interface
3.IX* pIX = NULL;
4.//ask for interface IX
5.HRESULT hr = pI->QueryInterface(IID.IX, (void**) &pIX);
6.//check return value
7.if(SUCCEEDED(hr))
8.{
9. //use interface
10. pIX->fx();
11.}
在这段代码中,我们查询了pI是否支持由IID.IX所标识的接口。
若成功返回,那么我们就可以使用它返回的指针了。
5. QueryInterface的实现
QueryInterface需要根据某个给定的IID返回指向相应接口的指针。
如果组件支持客户指定的接口,那么返回S_OK及相应的指针,否则,返回E_NOINTERFACE并将相应的指针返回值设为NULL。
例如我们有类CA,及其接口的继承关系:
[cpp] viewplaincopy
1.HRESULT __stdcall CA:
:
QueryInterface(const IID& iid, void** ppv)
2.{
3. if(iid == IID.IUnkown)
4. {
5.
6. //the client wants the IUnkown interface
7. *ppv = static_cast
8. }
9. else if(iid == IDD.IX)
10. {
11. //the client wants the IX interface
12. *ppv = static_cast
13. }
14. else if (iid == IDD.IY)
15. {
16. //the client wants the IY interface
17. *ppv = static_cast
18. }
19. else
20. {
21. //we don't support the interface the client wants.
22. //be sure to set the resulting pointer to NULL
23. *ppv = NULL;
24. return E_NOINTERFACE;
25. }
26. static_cast
27. return S_OK;
28.}
在这里,QueryInterface使用一个简单的if-then-else语句实现。
当然用其他任何一种可以将一种类型映射成另外一种类型的结构也是可以实现的。
如可用数组、哈希表或树来实现。
6. QueryInterface的实现规则
(1) QueryInterface必须总是返回同一IUnkown指针
可以通过查询两个接口的IUnkown接口,并比较其返回值来确定这两个接口是否指向同一组件。
(2) 若客户曾经获取过某个接口,那么它将总能
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- COM 学习 笔记