730 likes | 866 Views
面向对象分析与设计. 第九章:组件思想. 基于 UML 描述的组件软件研究. 一、组件概述 二、 COM 思想 三、 CORBA 思想 四、 EJB 思想. 一、组件概述. 面向对象编程的软件重用如果仅限于程序源代码级别的重用,这是一种低层次的重用,真正的代码重用意味着代码必须用以足够通用的方式为重建更大型的代码而编写。 传统的解决方案:把一个庞大的程序分割成许多小模块,每个模块保持一定的功能独立性。建立 API 方式实现模块间接口。 存在的问题: API 的更新;版本问题;模块间的通信;实现语言的兼容性。. 一、组件概述.
E N D
面向对象分析与设计 第九章:组件思想
基于UML描述的组件软件研究 一、组件概述 二、COM思想 三、CORBA思想 四、EJB思想
一、组件概述 • 面向对象编程的软件重用如果仅限于程序源代码级别的重用,这是一种低层次的重用,真正的代码重用意味着代码必须用以足够通用的方式为重建更大型的代码而编写。 • 传统的解决方案:把一个庞大的程序分割成许多小模块,每个模块保持一定的功能独立性。建立API方式实现模块间接口。 • 存在的问题:API的更新;版本问题;模块间的通信;实现语言的兼容性。
一、组件概述 • 组件复用作为一种现实有效的软件开发方法正受到越来越多的重视和研究。近年来,随着DNA,中间件等三层体系结构和分布式对象的兴起,为基于可复用组件的软件体系结构赋予了更大的内涵。从而使我们在评价构件复用的意义时,不应只局限于开发周期的缩短,软件质量的可靠提高,更要认识到构件组装式的开发在系统维护(如版本更新,功能增删等),分布式计算等方面所带来的极大便利。由于维护阶段占软件开发周期总成本的67%左右,因此构件重用对整个软件产业的重要意义就不言而喻了.从维护和分布计算的角度考虑,构件复用就是要实现“即插即用”。
一、组件概述 最终解决方案:组件软件。 组件化程序设计思想,同样是将庞大的系统分割成许多功能对立的小模块,不再是一个简单的代码集,而是一个自给自足的组件(component),这些组件模块可以运行在同一台机器,也可运行在不同的网络环境中。
一、组件概述 组件软件特点: • 组件易替换 • 适应业务需求更改 • 可实现二进制代码重用 • 有助于并行开发
一、组件概述 组件与框架: • 组件与框架关联 • 组件 Vs. 框架 • 组件 • 框架/组件结合
一、组件概述 • 组件 Vs. 框架 职责的分配,都使用策略 Generalized Ancestor Service Provider Components are based on Delegation Specialized Descendent Frameworks are based on Specialization
一、组件概述 • 组件 Vs. 框架 区别 • 组件 • “黑盒” 重用 • 客户只知道公用接口 • 对客户接口影响不大 • 框架 • “白盒” 重用 • 客户知道保护和公用的接口 • 对客户的结构影响大
一、组件概述 Client Application Component Library
一、组件概述 成功组件的生命周期 拒绝 认可 推荐 评价 设计 & 构造 改变需求 发布 标注删除 Deleted 不允许继续使用 分布
一、组件概述 框架与组件技术结合 Class Library Client Application
一、组件概述 • 框架和组件结合,概念: 泛化和委托 • 使用继承简单化设计 • 提供基本对象类型用于泛化 • 简化复杂组件的耦合 • 提供模板包装标准和复杂的结构和算法 • 但要注意减少对客户应用结构的影响
基于UML描述的组件软件研究 一、组件概述 二、COM思想 三、CORBA思想 四、EJB思想
2.1:COM思想--从C++到DLL再到COM • 1. C++ • 如某一软件厂商发布一个类库(CMath四则运算),此时类库的可执行代码将成为客户应用中不可分割的一部分。假设此类库的所产生的机器码在目标可执行文件中占有4MB的空间。当三个应用程序都使用CMath库时,那么每个可执行文件都包含4MB的类库代码(见图1.1)。当三个应用程序共同运行时,他们将会占用12MB的虚拟内存。问题还远不于此。一旦类库厂商发现CMath类库有一个缺陷后,发布一个新的类库,此时需要要求所有运用此类库的应用程序。此外别无他法了。
2.1:COM思想--从C++到DLL再到COM • 2. DLL • 解决上面问题的一个技术是将CMath类做成动态链接库(DLL ,Dynamic Link Library)的形式封装起来 。在使用这项技术的时候,CMath的所有方法都将被加到 CMath dll 的引出表(export list)中,而且链接器将会产生一个引入库(import library)。这个库暴露了CMath的方法成员的符号 。当客户链接引入库时,有一些存根会被引入到可执行文件中,它在运行时通知装载器动态装载 CMath Dll。当 CMath 位于dll中时,他的运行模型见图1.2
2.1:COM思想--从C++到DLL再到COM • 3. COM • "简单地把C++类定义从dll中引出来"这种方案并不能提供合理的二进制组件结构。因为C++类那既是接口也是实现。这里需要把接口从实现中分离出来才能提供二进制组件结构。此时需要有二个C++类,一个作为接口类另一个作为实现类。
2.2 COM基础 • 1、 COM基本知识 • 1.1 返回值HRESULTCOM要求所有的方法都会返回一个HRESULT类型的错误号。HRESULT 其实就一个类型定义: • typedef LONG HRESULT; • 我们一般下面的宏来判断方法是否成功: • #define SUCCEEDED(hr)(long(hr)>=0) • #define FAILED(hr)(long(hr)<0)
2.2 COM基础 • 1.2 初识 IDL每个标准的COM组件都需要一个接口定义文件,文件的扩展名为IDL。让我们看IUnknow接口的定义文件是怎样的。 • [local]属性禁止产生网络代码。 • [object]属性是表明定义的是一个COM接口。 • [uuid]属性给接口一个GUID。 • [unique]属性表明null(空)指针为一个合法的参数值。 • [pointer_defaul]属性所有的内嵌指针指定一个默认指针属性 typedef [unique] IUnknown *LPUNKNOWN;这是一个类型定义 • cpp_quote这个比较有趣,这是一个在idl文件写注解的方法。这些注解将保存到***.h和***_i.c文件中 • [in]表示这个参数是入参 • [out]表示这个参数是出参 • [iid_is(riid)]表示这个参数需要前一个的riid 参数。 • 注意:所有具有out属性的参数都需要是指针类型。
[ • local, • object, • uuid(00000000-0000-0000-C000-000000000046), • pointer_default(unique) • ] • interface IUnknown • { • typedef [unique] IUnknown *LPUNKNOWN; • cpp_quote("//////////////////////////////////////////////////////////////////") • cpp_quote("// IID_IUnknown and all other system IIDs are provided in UUID.LIB") • cpp_quote("// Link that library in with your proxies, clients and servers") • cpp_quote("//////////////////////////////////////////////////////////////////") • HRESULT QueryInterface( [in] REFIID riid, [out, iid_is(riid)] void **ppvObject); ULONG AddRef(); ULONG Release(); }
1.3 IUnkown接口在整个例子除了IUnkown这个东西,COM要求(最基本的要求)所有的接口都需要从IUnknown接口直接或间接继承。 • IUnkown接口定义了三个方法。 • HRESULT QueryInterface([in] REFIID riid,[out] void **ppv); • ULONG AddRef(); • ULONG Release(); • 其中 AddRef和Release()负责对象引用计数用的,而 QueryInterface()方法是用于查询所实现接口用的。每当COM组件被引用一次就应调用一次AddRef()方法。而当客户端在释放COM组件的某个接口时就需要调用Release()方法。这里所讲的请在下面的例子仔细体会。
2、一个比较简单的COM • 此例子共有四个文件组成: • Interface.h 接口类定义文件 • Math.h和Math.cpp实现类文件 • Simple.cpp 主函数文件这里用来当作COM的客户端
interface.h 文件 • #ifndef INTERFACE_H • #define INTERFACE_H • #include <unknwn.h> • //{7C8027EA-A4ED-467c-B17E-1B51CE74AF57} • static const GUID IID_ISimpleMath = { 0x7c8027ea, 0xa4ed, 0x467c, { 0xb1, 0x7e, 0x1b, 0x51, 0xce, 0x74, 0xaf, 0x57 } }; • //{CA3B37EA-E44A-49b8-9729-6E9222CAE84F} static const GUID IID_IAdvancedMath = { 0xca3b37ea, 0xe44a, 0x49b8, { 0x97, 0x29, 0x6e, 0x92, 0x22, 0xca, 0xe8, 0x4f } }; • interface ISimpleMath : public IUnknown • { public: virtual int Add(int nOp1, int nOp2) = 0; • virtual int Subtract(int nOp1, int nOp2) = 0; • virtual int Multiply(int nOp1, int nOp2) = 0; • virtual int Divide(int nOp1, int nOp2) = 0; }; • interface IAdvancedMath : public IUnknown • { public: virtual int Factorial(int nOp1) = 0; • virtual int Fabonacci(int nOp1) = 0; • }; • #endif
2、一个比较简单的COM • Interface.h 文件首先 #include <unknwn.h> 将 IUnknown 接口定义文件包括进来。接下来定义了两个接口,GUID(Globally Unique Identifier全局唯一标识符)它能保证时间及空间上的唯一。ISmipleMath接口里定义了四个方法, • 而IAdvancedMath接口里定义了二个方法。这些方法都是虚函数,而整个 ISmipleMath 与 IAdvancedMath 抽象类就作为二进制的接口。
math.h文件 • #include "interface.h" • class CMath : public ISimpleMath, public IAdvancedMath { private: ULONG m_cRef; • private: • int calcFactorial(int nOp); • int calcFabonacci(int nOp); • public: • // IUnknown Method • STDMETHOD(QueryInterface)(REFIID riid, void **ppv); • STDMETHOD_(ULONG, AddRef)(); • STDMETHOD_(ULONG, Release)(); • // ISimpleMath Method • int Add(int nOp1, int nOp2); • int Subtract(int nOp1, int nOp2); • int Multiply(int nOp1, int nOp2); • int Divide(int nOp1, int nOp2); • // IAdvancedMath Method • int Factorial(int nOp); • int Fabonacci(int nOp); • };
math.cpp文件 • #include "interface.h“ • #include "math.h" • STDMETHODIMP CMath::QueryInterface(REFIID riid, void **ppv) • {// 这里这是实现dynamic_cast的功能,但由于dynamic_cast与编译器相关 • if(riid == IID_ISimpleMath) *ppv = static_cast(this); else if(riid == IID_IAdvancedMath) *ppv = static_cast(this); else if(riid == IID_IUnknown) *ppv = static_cast(this); else { *ppv = 0; return E_NOINTERFACE; } reinterpret_cast(*ppv)->AddRef(); //这里要这样是因为引用计数是针对组件的 return S_OK; } STDMETHODIMP_(ULONG) CMath::AddRef() { return ++m_cRef; }
STDMETHODIMP_(ULONG) CMath::Release() • { ULONG res = --m_cRef; // 使用临时变量把修改后的引用计数值缓存起来 • if(res == 0) // 因为在对象已经销毁后再引用这个对象的数据将是非法的 • delete this; • return res; • } • 。。。。。。。 • 此类为实现类,他实现了ISmipleMath和IAdvancedMath两个接口类(当然也可以只实现一个接口类)。请注意:m_cRef 是用来对象计数用的。当 m_cRef 为0组件对象应该自动删除。
simple.cpp文件 • #include "math.h" • #include <iostream> • using namespace std; • int main(int argc, char* argv[]) { • ISimpleMath *pSimpleMath = NULL;//声明接口指针 • IAdvancedMath *pAdvMath = NULL; • //创建对象实例,我们暂时这样创建对象实例,COM有创建对象实例的机制 CMath *pMath = new CMath; • //查询对象实现的接口ISimpleMath • pMath->QueryInterface(IID_ISimpleMath, (void **)&pSimpleMath); • if(pSimpleMath) • cout << "10 + 4 = " << pSimpleMath->Add(10, 4) << endl; • //查询对象实现的接口 IAdvancedMath • pSimpleMath->QueryInterface(IID_IAdvancedMath, (void **)&pAdvMath); if(pAdvMath) • cout << "10 Fabonacci is " << pAdvMath->Fabonacci(10) << endl; • pAdvMath->Release(); • pSimpleMath->Release(); • return 0; } • 此文件相当于客户端的代码,首先创建一个CMath对象,再根据此对象去查询所需要的接口,如果正确得到所需接口指针,再调用接口的方法,最后再将接口的释放掉。
二、COM思想 面向对象的组件模型-COM • 按照组件模型搭建起来的软件称为组件软件。软件组件按照一定的接口模式组织成组件软件。组件接口方式保证软件组件之间的通信。 • COM除提供组件间的接口标准外,还引用了面向对象的思想。 • 在WINDOWS平台上,一个COM可以是一个DLL,也可是一个EXE。一个组件程序可以包含多个COM对象,一个COM对象也可实现多个接口。
二、COM思想 面向对象的组件模型-COM • 由于COM对象和使用COM对象的客户之间建立C/S模式,因此通常称COM对象为COM服务器。 • COM的发展是经过DDE,OLE1,VBX,OLE2,DCOM,ACTIVEX,COM+。 • COM组件软件组成:COM对象,COM接口,COM客户,COM库(框架)。
二、COM思想 COM具有的特点 • 二进制特性 • 接口不变性 • 继承性:类继承说明继承,同时也实现继承;接口继承只说明继承。 • 多态性:COM对象也具有多态性,通过COM接口体现。多态性使得客户用以同样的方法处理不同的对象,或不同类型的对象,只要它们实现了同样的接口。
二、COM思想 COM内容 • COM接口与对象 1、IUnknown HResult QueryInterface(iid,**ppv) ULONG AddRef(void) ULONG Release(void) 2、全局唯一标识符GUID 3、COM接口定义
二、COM思想 COM内容 3、COM接口定义 pVtable vtable 接口指针 vtable指针 指针函数1 对象实现 指针函数2 指针函数3 …
二、COM思想 COM内容 3、COM接口定义 class IDB: public IUnknown { public: virtual HRESULT _stdcall read(,,,)=0; virtual HRESULT _stdcall write(,,,)=0; … }
二、COM思想 COM内容 3、COM接口定义 pVtable vtable 客户使用的 接口指针 vtable指针 QueryInterface地址 对象实现 m_arrTable AddRef地址 m_arrName Release地址 Read地址 Write地址 …
二、COM思想 COM内容 4、接口描述语言IDL import “my.h”,”unknown.h”; { object, uuid( - - - - - ), } interface IFace: IUnknown { HRESULT MethodA([in],[out]); } 比如用以下编译: midl example.idl example.h : 包含接口说明文件 example_p.h: 实现了接口IFace的代理和存根 example_c.h: 定义了所以用到的GUID,包括IID dlldata.c: 包含代理/存根的入口和代理类厂所要的数据结构
对象B 客户程序 对象B 聚合 对象A 客户程序 对象A 包容 二、COM思想 COM内容 • COM应用模型 1、客户服务器模型:包容与聚合,容器与服务器的概念
二、COM思想 COM内容 • COM应用模型 2、进程内组件: DLL、LPC 一个进程内组件通常是DLL方式存在的,客户程序需要找到DLL。客户程序得到的指针指向组件程序中接口的vtable,vtable包含了该结构所有成员函数的入口地址。
客户程序 组件程序 代理DLL 存根DLL LPC 二、COM思想 COM内容 • COM应用模型 3、进程外组件: DLL、LPC、RPC、代理DLL、存根DLL
二、COM思想 COM内容 • COM应用模型 4、COM库(框架) 初始化函数:CoInitialize GUID相关函数:IsEqualGUID 对象创建函数:CoGetClassObject:获得对象类厂 CoCreateInstance:创建COM对象 内存管理函数:CoTaskMemAlloc
二、COM思想 COM内容 • COM应用模型 5、COM与注册表 Regsrv32
客户 类厂 对象 二、COM思想 COM内容 • COM组件 1、实现类工厂对象
二、COM思想 COM内容 • COM组件 1、实现类工厂对象 类厂本身就是一个COM对象,支持一个特殊的接口IClassFactory IClassFactory:public IUnknown { public : void HRESULT CreateInstance( /* [in] */ Iunknown *pUnknwon; /* [in] */ REFIID riid, /* [out] */ void **ppvObject() = 0;}
二、COM思想 COM内容 • COM组件 1、实现类工厂对象 Class CDBSrvFactory:public IClassFactory{ public : HRESULT CreateInstance(,,,); HRESULT QueryInterface(,,,); ULONG AddRef(,,,); ULONG Release(,,,);
二、COM思想 COM内容 • COM组件 1、实现类工厂对象 Class CDBSrvFactory::CreateInstance(,riid,**ppObject) { CDB * pDb= new CDB; if( FAILED(pDB->QueryInterface( riid,ppObject ) ) { delete pDB; … }
二、COM思想 COM内容 • COM组件 2、实现自动注册 3、实现自动卸载
二、COM思想 COM内容 • COM客户 1、COM对象创建函数:包含了工厂模式的思想,封装了类厂,使用户使用起来更简单。 CoGetClassObject : 获得类厂 CoCreateInstance: 直接创建对象 { IClassFactory *pFactory; CoGetClassObject( ,, *pFactory); pFactory->CreateInstance( , , *ppv) }