1: COM 技术内幕学习笔记
COM 基础实现方式( 方式 1 ) - 第一、二章 | 使用 CreateInstance 及 QueryInterface 实现的 COM (方式 2 ) - 第三章 | 加入引用计数 ( 方式三 ) - 第四章 |
#include "stdafx.h" #include "iostream.h"
#include <objbase.h>
interface IX { virtual void FX1()=0; };
interface IY { virtual void FY1()=0; };
class CA : public IX, public IY { public: BOOL _QueryInterface(const ID, void **pV);
virtual void FX1() { cout<<"FX1()"<<endl; }
virtual void FY1() { cout<<"FY1()"<<endl; }
};
int main(int argc, char* argv[]) { CA *pA = new CA();
IX *pX = pA; pX->FX1();
IY *pY = pA; pY->FY1();
delete pA;
return 0; } | #include "stdafx.h" #include "iostream.h"
#include <objbase.h>
class CUnknown { public: virtual BOOL _QueryInterface(const ID, void **pV) = 0; };
class IX : public CUnknown { public: virtual void FX1()=0; };
class IY : public CUnknown { public: virtual void FY1()=0; };
class CA : public IX, public IY { public: BOOL _QueryInterface(const ID, void **pV);
virtual void FX1() { cout<<"FX1()"<<endl; }
virtual void FY1() { cout<<"FY1()"<<endl; }
};
enum {IID_IX, IID_IY}; BOOL CA::_QueryInterface(const ID, void **pV) { if (ID == IID_IX) { *pV = static_cast<IX *>(this);
return TRUE; } else if (ID == IID_IY) { *pV = static_cast<IY *>(this);
return TRUE; }
return FALSE; }
CUnknown* CreateInstance() { CUnknown *pI = static_cast<IX *>(new CA());
return pI; }
int main(int argc, char* argv[]) { CUnknown *pUnknown = CreateInstance();
IX *pX = NULL; pUnknown->_QueryInterface(IID_IX, (void **)&pX); pX->FX1();
IY *pY = NULL; pX->_QueryInterface(IID_IY, (void **)&pY); pY->FY1();
delete pUnknown;
return 0; } | #include "stdafx.h" #include "iostream.h"
#include <objbase.h>
class CUnknown { public: virtual BOOL _QueryInterface(const ID, void **pV) = 0; virtual void _AddRef() = 0; virtual void _Release() = 0; };
class IX : public CUnknown { public: virtual void FX1()=0; };
class IY : public CUnknown { public: virtual void FY1()=0; };
class CA : public IX, public IY { public: CA(); virtual ~CA();
public: virtual BOOL _QueryInterface(const ID, void **pV); virtual void _AddRef(); virtual void _Release();
virtual void FX1() { cout<<"FX1()"<<endl; }
virtual void FY1() { cout<<"FY1()"<<endl; }
private: int m_nRef; };
enum {IID_IX, IID_IY}; BOOL CA::_QueryInterface(const ID, void **pV) { BOOL bReturn = FALSE;
if (ID == IID_IX) { *pV = static_cast<IX *>(this); bReturn = TRUE; } else if (ID == IID_IY) { *pV = static_cast<IY *>(this); bReturn = TRUE; } if (*pV != NULL) static_cast<CUnknown *>(*pV)->_AddRef();
return (bReturn) ? TRUE : FALSE; }
void CA::_AddRef() { ++m_nRef; }
void CA::_Release() { if (--m_nRef == 0) { delete this; } }
CA::CA() { m_nRef = 0; }
CA::~CA() { }
CUnknown* CreateInstance() { CUnknown *pI = static_cast<IX *>(new CA); pI->_AddRef();
return pI; }
int main(int argc, char* argv[]) { CUnknown *pUnknown = CreateInstance();
IX *pX = NULL; pUnknown->_QueryInterface(IID_IX, (void **)&pX); pX->FX1(); pX->_Release();
IY *pY = NULL; pX->_QueryInterface(IID_IY, (void **)&pY); pY->FY1(); pY->_Release();
pUnknown->_Release();
return 0; } |
结论: 1 : COM 接口在 c++ 中是用纯抽象基类来实现的 2 :一个 COM 组件可以提供多个接口 3 : C+ +类通过多重继承来实现提供多接口组件
不足之处: 1 :客户,组件之间通过接口通信来实现没有突出。 2 :客户端要使用到组件的声明( new CA() ) | 结论: 1 :通过使用 CreateInstance 来摆脱组件类声明 ( 使用不再使用指向 CA 的指针 ) 2 :可以看出客户,组件通过接口通信的锥形
不足之处: 1 :没有添加引用计数及释放引用计数 | 结论 : ( 在方式二的基础上 ) 1: 通过使用引用计数来完成删除组件方法 |
动态连接 ( 方式四 ) – 第五章 |
| |
Client program // CallCreate.cpp : Defines the entry point for the console application. //
#include "stdafx.h"
/* reference to the dll head file and copy from .dll file into the client program under file that cann't be found by it //*/ #include "../Create/cmpnt1.h"
CUnknown* CallInstance(char *name);
typedef CUnknown* (*PCREATEINSTANCE)();
CUnknown* CallInstance(char *name) { HINSTANCE hInst = ::LoadLibrary(name); if (hInst == NULL) { cout<<"fail to loadLibrary"<<endl; return NULL; }
PCREATEINSTANCE pCreateInstance = (PCREATEINSTANCE)::GetProcAddress(hInst, "CreateInstance"); if (pCreateInstance == NULL) { cout<<"cann't find the function"<<endl; return NULL; }
return pCreateInstance(); }
int main(int argc, char* argv[]) { CUnknown *pUnknown = CallInstance("Create.dll");
IX *pX = NULL; pUnknown->_QueryInterface(IID_IX, (void **)&pX); pX->FX1(); pX->_Release();
IY *pY = NULL; pX->_QueryInterface(IID_IY, (void **)&pY); pY->FY1(); pY->_Release();
pUnknown->_Release();
return 0; }
| Dll // #head file cmpnt1.h #define DLL_CREATE_INSTANCE extern "C" __declspec(dllexport) #include "cmpnt1.h"
BOOL CA::_QueryInterface(const ID, void **pV) { BOOL bReturn = FALSE;
if (ID == IID_IX) { *pV = static_cast<IX *>(this); bReturn = TRUE; } else if (ID == IID_IY) { *pV = static_cast<IY *>(this); bReturn = TRUE; } if (*pV != NULL) static_cast<CUnknown *>(*pV)->_AddRef();
return (bReturn) ? TRUE : FALSE; }
void CA::_AddRef() { ++m_nRef; }
void CA::_Release() { if (--m_nRef == 0) { delete this; } }
CA::CA() { m_nRef = 0; }
CA::~CA() { }
CUnknown* CreateInstance() { CUnknown *pI = (CUnknown*)(void*)new CA(); pI->_AddRef(); return pI; }
#implement file(cmpnt.h) #ifndef CMPNT1_H #define CMPNT1_H
#ifdef DLL_CREATE_INSTANCE
#else #define DLL_CREATE_INSTANCE extern "C" __declspec(dllimport) #endif
#include <windows.h> #include "iostream.h" #include <objbase.h>
class CUnknown; DLL_CREATE_INSTANCE CUnknown* CreateInstance(); enum {IID_IX, IID_IY};
class CUnknown { public: virtual BOOL _QueryInterface(const ID, void **pV) = 0; virtual void _AddRef() = 0; virtual void _Release() = 0; };
class IX : public CUnknown { public: virtual void FX1()=0; };
class IY : public CUnknown { public: virtual void FY1()=0; };
class CA : public IX, public IY { public: CA(); virtual ~CA();
public: virtual BOOL _QueryInterface(const ID, void **pV); virtual void _AddRef(); virtual void _Release();
virtual void FX1() { cout<<"FX1()"<<endl; }
virtual void FY1() { cout<<"FY1()"<<endl; }
private: int m_nRef; }; #endif //CMPNT1_H |
|
结论: 由于组件中客户所需要的所有函数都可以通过一个接口指针而访问到,因此需要 在 DLL 中输出 CreateInstance() 函数以便客户调用它。 不足之处: 客户和组件之间还是存在一种紧密的连接,客户必须要知道实现组件的 DLL 名称。 |
| |
第六章主要介绍如何通过CLSID 来查找相应DLL 名字的方法, 避免了客户通过DLL 文件名来锁定组件的依赖(第五章没有解决的问题) 。 由于安装程序及REGSVR32.EXE 可以调用实现组件的DLL 中输出的DllRegisterServer 函数来注册相应的组件,注册时相应的CLSID 和DLL 文件名也记录到注册表中,window 通过查找HKEY_CLASSES_ROOT 根目录下的CLSID 关键字,查找其下面的InproServer32( 进程中服务器,DLL 就是) ,在这个关键字里面记录了DLL 文件的名字。
第七章总结: ( 本章主要讲述类厂 )
1: 几个重要的函数及其内部关系 CoCreateInstance , CoGetClassObject
STDAPI CoCreateInstance(
REFCLSID rclsid , //Class identifier (CLSID) of the object
LPUNKNOWN pUnkOuter , // 表明创建的组件是否为聚合 (具体第八章进行讨论)
DWORD dwClsContext , // 表明组件,客户是否在不同的上下文进行工作(进程中,本地,远程) (具体第十章进行讨论)
REFIID riid , //Reference to the identifier of the interface
LPVOID * ppv //Address of output variable that receives
// the interface pointer requested in riid
);
STDAPI CoGetClassObject(
REFCLSID rclsid , //CLSID associated with the class object
DWORD dwClsContext , //Context for running executable code
COSERVERINFO * pServerInfo , // 用于控制对远程组件的访问 (具体第十章进行讨论) Pointer to machine on which the object is to be instantiated
REFIID riid , //Reference to the identifier of the interface
LPVOID * ppv //Address of output variable that receives the
// interface pointer requested in riid
);
就像我们在第五章看到的一样: CallCreateInstance 调用 Dll 中 CreateInstancee 来创建所需组件, CoGetClassObject ( COM 库) 需要 DLL 中一个特定的函数来 创建组件的类厂 。此函数就是 DllGetClassObject ( DLL )-可以在同一个 DLL 中实现多个组件 ( 第九章:可复用的类厂 )
STDAPI DllGetClassObject(
REFCLSID rclsid , //CLSID for the class object
REFIID riid , //Reference to the identifier of the interface
// that communicates with the class object
LPVOID * ppv //Address of output variable that receives the
// interface pointer requested in riid
);
某个组件的类厂一般而言都将实现 IclassFactory 接口,但也有特别的如 IclassFactory2.
HRESULT CreateInstance(
IUnknown * pUnkOuter , // 表明创建的组件是否为聚合 , 该参数是由 CoCreateInstance 来传递的 (具体第八章进行讨论) Pointer to whether object is or isn't part of an aggregate
REFIID riid , //Reference to the identifier of the interface
void ** ppvObject //Address of output variable that receives the
// interface pointer requested in riid
);
以上这些函数在客户, COM 库, DLL, 类厂,组件 之间的调用关系
DllGetClassObject 实现的是 factory 组件,
IClassFactory::CreateInstance() 实现的是 CA 组件
组件的注册:
实现组件注册需要在 DLL 文件中输出四个如下文件:
DllRegisterServer, DllUnRegisterServer,DllGetClassObject,
· DllRegisterServe r 、 DllUnRegisterServer :作用是在 windows 的注册表中登记或取消某个组 件登记— 需要包含 DLL 文件的模块句柄 ( 通过 DllMain 传递 ) ,以获取 DLL 的文件名登记或取消它。
· DllGetClassObject 作用是用于 COM 库创建类厂
· DllCanUnloadNow() : 由 COM 库调用来检查是否服务器被从内存中卸载 。
定义在 .def 文件中
EXPORTS
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
每一行都包含函数名和 PRIVATE 关键字。这个关键字的意思是:此函数是输出函数,但不包含在输入库( import lib )中。也就是说客户端不能直接从代码中调用这个函数,即使是链接了输入库也不行。这个关键字时必须要用的,否则链接器会出错。
因为 STDAPI 的扩展。输出必须使用 .DEF 文件
第八章 主要介绍内容:
为什么组件变化对客户无影响:
原因是: COM 支持接口继承而不支持实现继承 ;这也就是 基于抽象基类构建应用程序 而不用担心基类有时发生变化造成派生类不能正常使用的好处。
包容作用:
由于 COM 没有实现继承,所以使用 组件包容来完全模拟实现继承 。
包容特点:
1 :外部组件是内部组件的客户。
2 :外部组件使用内部组件接口来完成自己接口的实现
3 : 释放: 在将外部组件释放之前先将内部组件进行释放。
接口是基于组件的: 要想获得接口,首先要考虑到如何创建组件。
包容与聚合是 COM 用以实现组件复用的技术或者说是一种机制,它们类似于继承。
当一个组件 ( 外部组件 ) 包容另一个组件 ( 内部组件 ) 时,外部组件包含一个内部组件接口的指针,并重新实现此接口供其客户调用。在重新实现的这个接口中,外部组件只是简单的将调用请求转发给内部组件。包容的实现比较简单,
当外部组件聚合内部组件时,外部组件没有重新实现内部组件的接口和进行调用转发,而是把内部组件接口的指针直接返回给客户,从而使客户能够直接与内部组件打交道。但是,必须保证客户不能访问内部组件的 IUnknown 接口,不能也没有必要让客户知道它正在与另一个组件 -- 即所谓的内部组件交互,否则一切将变得十分混乱
包容实现的主要思想 :
组件 CA(funA, funB) 包含了组件 CB.(funB)
Virtual void CA::funB()
{
… .
m_pB->funB();
… .
}
创建 CA 的时候 , 在 CA::CreateInstance 的时候再创建 CB(). 同事获得 m_pB 接口
CA::CreateInstance
{
Init()
}
CA::Init()
{
CoCreateInstance(COMPLENT_2, … , m_pB)
}
聚合实现的主要思想 ( 被聚合的组建要完成实现两个接口 , 一个是 IUnknown 接口 , 另一个是实现 INondelegatingUnknown 接口 )
聚合实现的重要过程及其原理 :
为了实现组件 A 聚合组件 B 的某个接口 ISomeInterface ;对 A 初始化的时候,创建内部组件 B ,并把 A 的 this 指针传给 B ,内部组件返回 INondelegatingUnknown 接口并传给 A 的成员变量 m_pUnknownInner ,最后在 A 中查询 ISomeInterface 时,使用了 m_pUnknownInner->QueryInterface(...) ;可是 INondelegatingUnknown 的定义中没有 QueryInterface 啊,只有 NondelegatingQueryInterface 。 这是为什么?
其实组件之间是靠vtable 来通信的的 ,而不是C++ 的类定义, 他会去调用 NondelegatingQueryInterface 方法.
第九章 主要介绍内容:
Q: 把 IY 接口指针赋值给 IZ, 结果会怎样呢 .
IZ *pIZ;
pIX->QueryInterface( IID_IY , (void **)& pIZ );
A: 两种方法可以解决 ( 其一智能指针来封装接口指针 , 其二通过包装类来对接口进行封装 )
智能指针来封装接口指针 :
通过包装类来对接口进行封装 :