资源预览内容
第1页 / 共68页
第2页 / 共68页
第3页 / 共68页
第4页 / 共68页
第5页 / 共68页
第6页 / 共68页
第7页 / 共68页
第8页 / 共68页
第9页 / 共68页
第10页 / 共68页
亲,该文档总共68页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
第 1 页 共 68 页简单接口实现规范作者:Softit增补:小小企鹅,StoneLee最新更新:2003-5-27预备知识: C+的基础概念,特别是虚函数和多态 COM,建议参考书籍COM 本质论 (ISBN:7-5083-0611-2)第一章 整体概念第一节 概要说明基于组件的软件设计方法是软件工业实践的一个基本成功经验,在软件设计过程中要考虑模块的少耦合少依赖,这是模块重用的基础。C+虚函数为接口提供了理论基础。之所以称之为“简单接口” ,是相对于 COM 和 CORBA 组件而言,大部分小组件不需要支持引用计数、多语言开发、跨网络运行等特性。运用简单接口还可以很容易写出模块化的插件,例如,可以将棋牌类客户端做成插件形式,但外观可以使用公用的界面框架,也可以嵌入到游戏大厅里。简单接口实现的组件将来改造成 ActiveX 组件也很容易。第二节 名词解释一、 图示第 2 页 共 68 页二、 说明1、 接口 一组纯虚函数的集合。实现时,是个头文件,里面全部是纯虚函数,从 C+观点讲,就是一个函数指针表(vfnTable) ,详细可参考 COM 有关书籍。例如,上图中的 IFoo 部分。2、 服务实现接口的组件,供客户应用程序调用,我们称此组件提供了一个支持接口的服务,或简单理解成 Server 也可以。服务一般以 DLL 或 lib 库和接口的头文件一起提供。 (当然:最好还应该有一个说明文档) 。例如,上图中的 CFoo 部分。3、 客户使用接口的程序,一般是调用接口的具体应用程序,也可理解为 Client。一般客户都是独立成为一个应用程序。如上图所示,为 CExtern 部分。4、 回调接口有的时候,客户通过接口调用服务的相关方法后,需要知道这些方法是否执行成功。但是存在下面两种可能:1) 由于服务可能是异步模式,所以客户并不能马上通过方法的返回值获得。2) 或则,为了程序的结构清晰,服务并不想通过接口的调用的返回值,而是希望通过调用客户的一些固定的函数来通知客户事件发生。这时,就需要用到回调接口。第 3 页 共 68 页和接口不同,回调接口实际是服务发起的,由客户实现的。而接口却是有客户发起的,由服务实现的。接口的使用,是为了把定义与实现分离,这样能提高程序各模块之间的独立性。类似于 COM 组件的连接点。 “服务”有些事件要通知“客户” ,通过回掉接口实现。回调接口也是用头文件实现,里面是纯虚函数。客户必须继承和实现这个回调接口。然后将实现回调接口的对象的指针传给服务(一般在服务创建函数中) ,这样服务就可以在一些约定好的事件中回调客户的程序代码。如上图所示,CFoo 通过回调接口(IFooSink)调用外部模块 CExtern 的函数,CExtern 实现 IFooSink。CExtern 实例化后,将实例后的指针传给 CFoo。一般定义为以 Sink 为结束的接口,如 IFooSink,表明此接口是供 IFoo 回调的。旁注:Sink 的英文意思是 “接收器” 。5、 多接口和多回调接口I、 多接口任何一个客户,都可能用到多个服务。比如:我们有一个自动下载的客户程序,CAutoDownLoad ,它要使用以下接口为其服务,包括通信接口 ICommunicate、资源接口 IResMgr。这样,我们在 CAutoDownLoad 里,只有获得 ICommunicate 和 IResMgr 两个指针,就可以通过其实例(即服务对象)实现相关的接口功能。II、 多回调接口同时,一个客户,也可能实现多个回调接口。比如:一个游戏服务器 CGameServer,被多个回调接口触发,比如:通信回调接口ICmmSink,数据库完成通知 IDBSink。这时,CGameServer 只要简单地继承这几个回调接口,如下:Class CGameServer:public ICmmSink,IDBSink 第 4 页 共 68 页然后,CGameServer 将自己的实例化后的指针,分别传给对应的通信和数据库服务,让他们回调自己。第三节 模块之间通信方式比较:基于接口和基于类共享的方法的比较简单接口间当一个模块 IFoo 需要回调其它模块(IExtern)的函数时,在 IExtern 模块中实现回调接口(IFooSink,IExtern 继承接口 IFooSink 即可),将 IFooSink 传送给 IFoo,这种方式是两个接口间交流的方式,约束简明。接口方式调用的另一个极重要的好处是多态性,即一个接口可以有多种不同的实现方法,如一个 CArchieve 所包含的月报表数据,通过显示接口 IView,可以对应三种不同的显示实现方式(柱状、饼状、数字),而且用户可以在运行时刻动态选择其中的一种或几种显示方式。相反,如果要调用 C+类 CFoo 的一个方法,需包含 CFoo 的头定义,头文件中可能有大量私有的,与类之间通讯无关的东西,如 CFoo 可能包含了很多其它头文件、很多自定义宏等。CFoo 不知有哪些个方法被别人调用、有哪些 public 属性被引用,因此不能轻易修改自已函数,则将两个类的实现绑定在一起,因无法知道一个类调用了另一个类的哪些方法和引用了哪一些 public 成员,因而类的定义不能轻易修改,否则可能会影响很多模块。第 5 页 共 68 页第二章 接口的具体实现第一节 接口定义样板文件:IFoo.hclass IFoovirtual void Release() = NULL; / 释放对象,见下面的说明 “接口对象的销毁”virtual BOOL Add(LPCSTR szName, DWORD dwReserved=0) = NULL;virtual BOOL Delete(LPCSTR szName) = NULL;接口是一经发布尽可能少修改,所以一些将来可能会修改或一些重要的虚函数定义时要加一个 dwReserved 参数,便于将来扩充第 6 页 共 68 页第二节 接口对象的创建:即服务的实例化一、 方法接口对象的创建即 new 一个实现此接口的对象,将此对象的接口指针返回即可。创建将来可能更改接口实现时,需要支持版本控制,如互联网应用程序一般要求后期发布的程序与早期发布的模块接口兼容。二、 范例一般我们将接口实现放在一个 DLL 中,然后,通过 DLL 的输出函数来实例化接口对象。如下面所示意:1、 DLL 实例化一般写在 IFoo.cpp 中extern C _cdecl _declspec(dllexport) BOOL CreateFooObject(/*out*/IFoo* ppFoo)if(ppFoo = NULL) / 先判断传入指针是否为空,是编码的好习惯return FALSE;CFoo *pFoo = new CFoo(); / 这里实例化服务 CFooif(pFoo = NULL) / 好习惯return FALSE;/ 注意:通过类型转换,将 CFoo 转为 IFoo 返回给客户*ppFoo = static_cast(pFoo);return TRUE;第 7 页 共 68 页2、 服务的实现:Server.dspI、 Foo.hclass CFoo: public IFoo private:CNameList m_listName; / 假设 CNameList 类是一个动态数组,/ 支持 Insert、RemoveCurrent、Top 、GetCurrent、Next 等方法public:virtual void Release();virtual BOOL AddName(LPCSTR szName, DWORD dwReserved);virtual BOOL DeleteName(LPCSTR szName);第 8 页 共 68 页II、 Foo.cpp#include “IFoo.h”#include “Foo.h”BOOL CFoo:AddName(LPCSTR szName, DWORD dwReserved)return(m_listName.Insert(szName);BOOL CFoo:DeleteName(LPCSTR szName)m_listName.Top();BOOL bFind = FALSE;char *szListName;while(szListName = m_listName.GetCurrent()if (strcmp(szListName, szName) = 0) bFind = TRUE;m_listName.RemoveCurrent();elsem_listName.Next();return bFind;void CFoo:Release()m_listName.Top();while(m_listName.GetCurrent() m_listName.RemoveCurrent();delete this;第 9 页 共 68 页3、 客户的使用:Client.dspI、 Extern.hclass CExternprivate:IFoo* m_pFoo;public:CExtern() m_pFoo = NULL;void CreateIFoo();void UseIFoo();void NotUserIFooNever();第 10 页 共 68 页II、 Extern.cpp#include “./Include/IFoo.h” / 后面有一章“ 一些规范”里会解释这个路径#include “Extern.h”void CExtern:CreateIFoo()。 。 。/ 为阅读,省略了一些加栽 dll 的代码if (!CreateFoolObject(m_pFoo = NULL;void CExtern:UseIFoo()if (m_pFoo)m_pFoo-AddName(“Test”);void CExtern:NotUseIFooNever()if (m_pFoo)m_pFoo-Release();m_pFoo = NULL;三、 先实例化服务,再实例化客户有时,先实例化服务,然后再实例化客户。例如:我们的游戏服务器和游戏框架,就是采用这种方式。此时,由服务调用客户的创建函数,并将自己的接口指针做为参数传给客户。第 11 页 共 68 页第三节 接口对象的销毁接口对象的销毁,不应该由客户用 delete 来完成。一般,接口对象都提供一个 Release(或 close)虚函数,自己负责销毁自己。这个可以从上面例子可以看出,如果用下面的下法:void CExtern:NotUseIFooNever()if (m_pFoo)delete m_pFoo;m_pFoo = NULL;那么,对于 CExtern,它只看到了 IFoo 这片内存区,是不知道有 m_listName 这样一个动态列表的对象的,所以当调用 delete m_pFoo 时,m_listName 是无法删除的,这样只能产生内存泄露。所以,必须而且只能由对象自己完成销毁工作,就如上例中实现的 NotUserIFooNever()中调用的一样。同时,还有一个范例说明,销毁应该由对象自己完成:即 DLL 的运行期库问题。参看:Windows 核心编程 (ISBN :7-111-07945-0 )P465 和 P131。下面有一个简单的程序,大家可以调试以下: 包括不调用 Release()函数 去掉 Release()里的 delete this 将 main()里的 pInterface-Release()注释,换成下面的 delete pInterface看一下内存和
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号