【摘要】组件对象模型(Component Object Model,COM) 是微软公司首推的编程模型。它将软件分为接口和实现两个独立的部分,由接口定义抽象的功能,一个实现模块则可能实现多个接口,来提供实际的功能。这样在一个大型软件中,可以将模块之间的耦合大幅度降低,降低模块管理的难度。有的嵌入式操作系统中也引入了COM,比如vxWorks。但是,COM为了兼顾分布式(DCOM),实现二进制级的对象模型交换,显得比较复杂,基本代码量比较大。本文试图引入一种轻量级的组件对象模型,完全用c实现,核心部分非常小,甚至可以在51单片机这种级别的计算平台上运行,有利于实现多种平台下的代码共享,这里将这个模型称为轻量级组件对象模型(LCOM:Light Component Object Model)。
1.概述
所谓接口,可以理解为一组相关的函数指针,用来描述某一组功能。用一组函数来表达一组功能,这种方式在编程实践中是经常用的,比如Linux中Mesa的OpenGL的Dispatch API,就是一组较为复杂的函数指针,Windows下的DirectX的相关功能,包括Linux下的块设备驱动,都是定义好的一组一组的函数指针。
LCOM与COM一样,用一个128位的ID号标识一组函数指针,也就是一个接口。接口一经发布,以后接口中的函数个数,声明顺序,函数名称,参数表,返回值等就不再修改,这样可以让应用程序能够有效兼容不同版本的实现,不同版本实现时只要提供版本的升级库,应用程序甚至都不用重新连接,就能够直接使用。
接口的稳定性其实是非常重要的,目前开源社区的一个问题是兼容性问题,一个软件模块的版本前后往往不兼容,甚至既不向前兼容,也不向后兼容,每个版本神出鬼没地改了点东西,给使用者带来很大的困惑。Windows下的接口相对稳定性要好很多,从上个世纪80年代开始的Win32编程模型,到现在基本上还能用,都到64位系统了,还叫Win32。这对在Windows下提供服务的软件团队,减少了很多维护的费用,也减少了很多软件人员的培训开销。
基于LCOM的应用程序可以通过一个公共的所有对象都规定要实现的接口来查询对象是否实现指定的接口,并通过这个接口来管理对象的生存周期(实际上用引用计数来实现),对象的生成,销毁,引用,是否有效(内部一致性),以及一些附加的比如对象实现的版本号,都由对象统一接口实现。
比如对于OpenGL的系统,可以定义OpenGL 1.1接口,OpenGL 1.3接口,OpenGL 1.5接口,OpenGL 2.1接口, OpenGL 3.2接口,OpenGL 4.0接口等等,这样一个驱动实际上就是实现一个对象,它支持什么接口就可以通过一个公共的接口来询问。可以设计专门的测试系统来测试一个驱动对象对各个版本的API是否支持。
LCOM虽然只是一组函数指针,我们还需要支持从接口直接访问对象,这样减轻应用程序的负担,不需要分别维护数据和函数组。应用程序只要维护一个接口,就可以访问到对象的所有功能,通过任意一个接口都可以查询到对象支持的其他接口,并能访问对象的所有数据。
LCOM还提供一组公共的函数和宏,来实现对象的管理,将应用程序从复杂的对象生命周期管理中解脱出来。
LCOM在对象的创建方面提供统一的办法,这样可以将对象的描述放在一个比如xml的文件中,用一个简单的解析软件就可以将应用系统需要的对象模块全部创建出来,实现软件模块的灵活管理。这样处理给对象的存储与传输也带来了便利,对象只要存储对象的类ID,以及实例化参数和对象,就能够存在存储器中,并通过通信系统传递,然后用统一的对象生成接口重新在远程生成。
LCOM用形式化的方法定义接口,定义的接口可读性,无二义性,完备性,稳定性都有保证,便于团队之间的交流,可以让软件的设计,实现,测试,验证,调试,评估等工作能够由不同的专业团队来完成。特别对一些通用的接口,可以由各种专业团队来发布各自专业的服务软件。
比如定义一个I2C接口,这样不同的硬件环境下面用不同的对象来实现同一个I2C接口,但是它的测试,验证,评估(性能评估?)由另外的专业团队来维护,基于I2C的上层应用则稳定地依靠这个接口运行。这样做减少了团队之间的交流开销,让团队之间的信任变得更加简单,甚至可能出现网络上根本不认识的团队的高效协作。
如果有个中心化的组件交易组织,可以提供基于网络组件或接口的需求发布,设计,实现,测试等团队,提供组件级别的交易,解决应用软件的整合工作量。
LCOM用ANSI C编程,以便于用于一些小型的系统中(比如51单片机,其编译器可能不支持c++),其最小系统占用的存储器可以剪裁到非常小,尽可能减轻系统开销。
2.GUID
LCOM跟COM一样,用GUID来标识LCOM内部的接口(IID),对象(CLSID),参数(PARAMID),一般而言,一个GUID标识的目标一旦发布,其意义以后就不再做任何修改,这样有助于应用的兼容性,保护用户的先期投入。你可以不支持某个接口,只要声称支持,就应该满足这个接口发布时的所有功能和逻辑关系。如果要支持新的特征,那只能用另外一个接口来表达。Windows下提供的DirectX系列模块,比如DIrectPlay,就有DIrectPlay2, DirectPlay3等接口。
GUID定义如下:
#undef DEFINE_GUID
#ifdef IMPLEMENT_GUID
#if _BYTE_ORDER == _BIG_ENDIAN
#define DEFINE_GUID(name, L, s1, s2, b1, b2, b3, b4, b5, b6, b7, b8) \
const uint32_t name[4] = {L, \
((s2) << 0) | (s1 << 16), \
((b1) << 0) | ((b2) << 8) | ((b3) << 16) | ((b4) << 24), \
((b5) << 0) | ((b6) << 8) | ((b7) << 16) | ((b8) << 24), \
};
#else
#define DEFINE_GUID(name, L, s1, s2, b1, b2, b3, b4, b5, b6, b7, b8) \
const uint32_t name[4] = {L, \
((s1) << 0) | (s2 << 16), \
((b4) << 0) | ((b3) << 8) | ((b2) << 16) | ((b1) << 24), \
((b8) << 0) | ((b7) << 8) | ((b6) << 16) | ((b5) << 24), \
};
#endif /*_BYTE_ORDER == _BIG_ENDIAN*/
#else
#define DEFINE_GUID(name, L, s1, s2, b1, b2, b3, b4, b5, b6, b7, b8) \
extern const uint32_t name[4];
#endif
可以看到GUID由128位组成,放在4个32位的无符号整数中。
GUID的生成有很多工具,windows下推荐使用微软的GUID生成工具guidgen.exe,可以根据运行的计算机硬件状态和运行的时间,生成一个128位的数字,据说可以保证任何两次生成(包括不同的计算机同时生成)的ID号都不相同。这样你可以放心的生成一个独一无二的ID号来命名你的接口或者对象。
这个工具生成后可以选择下面的格式:
// {290C21DB-06A6-4752-A90C-75DF92A5CF48}
DEFINE_GUID(<<name>>,
0x290c21db, 0x6a6, 0x4752, 0xa9, 0xc, 0x75, 0xdf, 0x92, 0xa5, 0xcf, 0x48);
把其中的名字换成想定义的名字即可,比如一般在c头文件中定义如下:
DEFINE_GUID(IID_XXXX, 0x290c21db, 0x6a6, 0x4752, 0xa9, 0xc, 0x75, 0xdf, 0x92, 0xa5, 0xcf, 0x48);
DEFINE_GUID这个宏定义实际上由两个实现,一个是实际将GUID的128值存储在一个全局变量中的实现,在外部定义IMPLEMENT_GUID时实现,一个只是声明一个外部变量,供其他模块引用。在引用模块中直接包含c头文件即可,在对象实现或者接口相关的唯一一个文件中则先定义IMPLEMENT_GUID,然后包含声明该GUID的头文件,然后马上取消IMPLEMENT_GUID定义,这样确保每个GUID能够由唯一的模块来实现。
在实际使用中,我们大量使用指向GUID的指针,因此定义一个指针类型,实际上很多情况下都存储或者传输指向GUID的指针,同一模块内部比较GUID是否相等时,由于都引用同一个值,可以直接比较指针是否相等即可,当然对外部传入的GUID指针,不能假设是直接的指针引用,因此定义一个宏来判断两个GUID是否相等:
typedef const uint32_t* IIDTYPE;
#define isGUIDEqual(n1, n2) (((n1)[0] == (n2)[0]) \
&& ((n1)[1] == (n2)[1]) \
&& ((n1)[2] == (n2)[2]) \
&& ((n1)[3] == (n2)[3]))
3.基本公共接口
3.1公共接口定义
LCOM定义所有对象必须实现的公共接口如下:
typedef struct sIObject {
int __thisoffset;
/*
功能:对象是否支持指定的接口
参数:
object -- 对象数据指针
iid -- 接口编号
pInterface -- 存放返回的接口指针
返回值:
EIID_OK: 成功
EIID_INVALIDOBJECT:object 是无效对象
EIID_INVALIDPARAM: 参数错误
EIID_UNSUPPORTEDINTERFACE : 对象不支持该接口
*/
int (*QueryInterface)(HOBJECT object, IIDTYPE iid, const void **pInterface);
/*
功能:增加对象的引用计数
参数:
object -- 对象数据指针
返回值:
>=0: 增加后的引用计数
EIID_INVALIDOBJECT:object 是无效对象
*/
int (*AddRef)(HOBJECT object);
/*
功能:减少对象的引用计数,引用计数为0时删除该对象
参数:
object -- 对象数据指针
返回值:
>= 0: 减少后的引用计数
EIID_INVALIDOBJECT:object 是无效对象
*/
int (*Release)(HOBJECT object);
/*
功能:判断对象是否是一个有效对象
参数:
object -- 对象数据指针
返回值:
0 -- 对象是无效的
1 -- 对象是有效的
*/
int (*IsValid)(HOBJECT object);
}IObject;
并规定每个对象的0偏移处必须是一个指向IObject的指针,这样指向对象的指针实际上是指向一个指向接口的指针。也就是说,LCOM中所谓的对象,其实是指向接口指针的一个指针,这个指针存放在对象的实现数据结构中。定义
typedef void * HOBJECT;
表示一个接口,该指针指向某个接口的指针,由于对象偏移值为0的地方存放的是指向IObject的一个指针,因此偏移值0的接口指针也是对象的指针。每个接口实现中记录了从接口对象到接口指针的偏移值,因此可以从接口得到得到对象指针。
#define objectThis(_obj) (((uint8_t *)(_obj))-((*(IObject **)(_obj))->__thisoffset))
为了实现对象的生存周期管理,还应该有一个引用计数变量,对象还应该保存一个对象类型的GUID,以便应用确认对象是否是它所想要的对象类型。
每一个接口的前面几个成员,都应该与IObject完全一样,这样每个一个接口都可以当作IObject使用,确保通过每个接口都能够调用对象的公共接口功能。
一个对象实现某个接口时,必须在对象中存储该接口的指针,注意到接口的第一个成员是__this_offset,这个成员记录该接口的指针到对象开始的偏移值。这样如果我们有对象中这个指针成员的指针,就可以根据这个偏移值得到对象的首地址,从而访问到对象本身。
3.2 公共接口实现样例
这样如果要实现一个对象xxxx,头文件中只要定义对象的CLSID即可,在实现c文件中定义结构如下:
typedef struct _sXXXX {
const IObject * __IObject_ptr;
int __object_refcount;
IIDTYPE __object_clsid;
}sXXXX;
static int xxxxQueryInterface(HOBJECT object, IIDTYPE iid, const void **pInterface);
static int xxxxAddRef(HOBJECT object);
static int xxxxRelease(HOBJECT object);
static int xxxxIsValid(HOBJECT object);
static void xxxxDestroy(HOBJECT object);
static int xxxxCreate(const PARAMITEM * pParams, int paramcount, HOBJECT* pObject);
static int xxxxValid(HOBJECT object);
static const IObject xxxx_object_interface = {
0, /* IObject的指针必须在对象的0偏移处 */
xxxxQueryInterface,
xxxxAddRef,
xxxxRelease,
xxxxIsValid
};
static const char * xxxxModuleInfo()
{
return "1.0.0 20210423.2118 xxxx module by RAOXIANHONG";
}
static int xxxxRegister()
{
return objectCreateRegister(CLSID_XXXX, xxxxCreate, xxxxModuleInfo);
}
FUNCPTR A_u_t_o_registor_xxxx = (FUNCPTR)xxxxRegister;
static int xxxxQueryInterface(HOBJECT object, IIDTYPE iid, const void **pInterface)
{
if (!objectIsClass(object, CLSID_XXXX))
return EIID_INVALIDOBJECT;
if (!objectIsValid(object))
return EIID_INVALIDOBJECT;
if (pInterface == 0)
return EIID_INVALIDPARAM;
*pInterface = 0;
if (isGUIDEqual(iid ,IID_OBJECT)) {
*pInterface = objectThis(object);
objectAddRef(object);
return EIID_OK;
}
return EIID_UNSUPPORTEDINTERFACE;
}
static int xxxxAddRef(HOBJECT object)
{
sXXXX * pD;
if (!objectIsValid(object))
return EIID_INVALIDOBJECT;
pD = (sXXXX *)objectThis(object);
pD->__object_refcount++;
return pD->__object_refcount;
}
static int xxxRelease(HOBJECT object)
{
sXXXX * pD;
int ret;
if (!objectIsValid(object))
return EIID_INVALIDOBJECT;
pD = (sXXXX *)objectThis(object);
pD->__object_refcount--;
ret = pD->__object_refcount;
if (pD->__object_refcount <= 0) {
pD->__object_refcount = 1;
/*为了保证在Destroy过程中不出现递归调用,这里将引用记数设置为1*/
xxxxDestroy(object);
}
return ret;
}
static int xxxxIsValid(HOBJECT object)
{
sXXXX * pD;
if (object == 0)
return 0;
pD = (sXXXX *)objectThis(object);
if (pD->__object_clsid != CLSID_XXXX)
return 0;
if (pD->__object_refcount < 1)
return 0;
return xxxxValid(object);
}
部分宏和函数在后面的章节中会做详细解释。
4.对象管理
LCOM提供了对对象类的管理功能,包括类的注册,类实例生成,类模块信息输出等。可以提供统一的类对象实例生成函数,应用程序只要持有类的CLSID,以及对应的实例化参数,就可以通过objectCreate或者objectCreateEx两个函数生成对象实例。
类模块信息输出函数可以将系统中注册的类的模块信息打印出来,这样由助于在系统调试,维护过程中方便地得到每个模块的版本信息,版本不匹配也是软件维护工程师的噩梦,前台维护工程师与后台开发工程师其实首先需要确认的往往就是版本信息,确保两个团队工作在一个版本上。
这样每个对象实现时除了实现LCOM的公共接口外,还实现了对象管理所需要的几个函数:
static void xxxxDestroy(HOBJECT object);
static int xxxxCreate(const PARAMITEM * pParams, int paramcount, HOBJECT* pObject);
static const char * xxxxModuleInfo();
static int xxxxValid(HOBJECT object);
以及一个函数指针:
FUNCPTR A_u_t_o_registor_xxxx = (FUNCPTR)xxxxRegister;
这个指针支持在某些能访问符号表的操作系统(比如vxWorks)下,可以在调入模块的同时直接运行所有的A_u_t_o_registor_开始的函数,完成模块中包含的所有类的注册。
其中xxxxDestroy实现对象的删除操作,应用程序中并不直接调用这个函数删除对象,而是通过AddRef增加引用计数(QueryInterface也增加引用计数),通过Release减少引用计数,当引用计数为0时,调用该函数释放对象占用的资源。
xxxxValid则返回对象内部是否构成一个合法的对象,一般可以通过一些数据是否一致来判断。在调用该函数之前已经确保对象确实时模块中实现的类,并且指针是合法的。
xxxxCreate和xxxxModuleInfo与对象类ID CLSID_XXXX被传到objectCreateRegister函数中,该函数将每个类的对象生成函数和实现模块的版本信息函数记录在一个表中。
对象管理模块提供了如果管理函数如下:
/*下面是对象生成的一致性实现支持*/
typedef struct _sParamItem {
IIDTYPE name; /*参数编号,由对象实现规定每个编号的含义,对象实现文档中应该说明对象生成参数表的详细内容*/
uint32_t i32value; /*32位一般参数*/
double fvalue; /*浮点参数*/
void * pvalue;
}PARAMITEM;
/*
对象生成函数
功能:对象实现模块生成一个新的对象
参数:pParams -- 对象生成参数表,当paramcount>0是,pParams必须不为NULL
paramcount -- 参数表中的参数个数,必须不小于零,为零时,pParams可以为NULL
pObject -- 存放返回的对象句柄的指针,不能为NULL
返回值:
EIID_OK -- 成功
EIID_INVALIDPARAM -- 参数无效
EIID_MEMORYNOTENOUGH -- 内存不足
EIID_GENERALERROR -- 其他错误
*/
typedef int (*FObjectCreate)(const PARAMITEM * pParams, int paramcount, HOBJECT * pObject);
/*
对象信息函数
功能:返回对象实现模块的版本信息
参数:无
返回值:
对象实现模块的版本信息
*/
typedef const char * (*FModuleVersionInfo)();
/*
对象类管理模块初始化
功能:初始化内部资源,这个函数只在初始化时调用一次
参数:
返回值:
EIID_OK -- 成功
*/
int objectRegisterInit();
/*
对象类注册
功能:注册一个对象类到对象生成支持模块中
参数:clsid -- 对象类编号
objectCreator -- 对象生成函数
moduleinfo -- 对象版本信息函数
返回值:
EIID_OK -- 成功
EIID_INVALIDPARAM -- 参数非法
EIID_MEMORYNOTENOUGH -- 内存不足
EIID_GENERALERROR -- 该对象类已经注册
*/
int objectCreateRegister(IIDTYPE clsid, FObjectCreate objectCreator, FModuleVersionInfo moduleinfo);
/*
功能:打印所有注册的对象实现模块的版本信息
参数:data -- 传到funcoutput函数的第一个参数,
funcoutput -- 字符串输出函数,如果为0,则调用printf输出到标准输出上。
返回值:
*/
int objectPrintInfo(PTR data, OFUNCPTR funcoutput);
/*
对象类注销
功能:注销所有的注册类,应用程序结束时应调用此例程以释放资源
参数:
返回值:
EIID_OK -- 成功
*/
int objectCreateUnRegisterAll(void);
/*
对象生成
功能:查询已经注册的对象类的对象生成函数,生成对象
参数:clsid -- 对象类编号
pParams -- 对象生成参数表,如果该类已经注册,将传到注册的对象生成函数中
paramcount -- 对象生成参数个数
pObject -- 存放返回的对象句柄的指针
返回值:
EIID_OK -- 成功
EIID_INVALIDPARAM -- 参数非法
EIID_MEMORYNOTENOUGH -- 内存不足
EIID_GENERALERROR -- 对象生成出错
EIID_INVALIDCLSID -- 该类没有注册
*/
int objectCreate(IIDTYPE clsid, const PARAMITEM * pParams, int paramcount, HOBJECT * pObject);
int objectCreateEx(IIDTYPE clsid, const PARAMITEM * pParams, int paramcount, IIDTYPE iid, const void **pInterface);
/*
查询对象生成函数
功能:返回指定类编号的对象生成函数
参数:clsid -- 对象类编号
pOBjectCreator -- 存放对象生成函数的指针
返回值:
EIID_OK -- 成功
EIID_INVALIDPARAM -- 参数非法
EIID_INVALIDCLSID -- 该类没有注册
*/
int objectQueryCreator(IIDTYPE clsid, FObjectCreate * pObjectCreator);
这样,应用可以用统一的接口来生成对象,甚至可以将生成对象的CLSID和参数存放在一个软件构成描述文件中,通过一个简单的解析函数来实现整个软件中各个对象的生成。
5.定义接口定义和实现
LCOM最终还是要实现用户的功能,首先要能够定义用户接口,然后能够在对象中实现指定接口。
5.1 接口定义
这里举个例子来说明如何定义接口。前面经常做OpenGL的应用,为了在一个应用平台中运行不同的应用,我们将一个OpenGL的应用抽象成一个接口如下:
typedef struct sIGLApp {
int __thisoffset;
int (*QueryInterface)(HOBJECT object, IIDTYPE iid, const void **pInterface);
int (*AddRef)(HOBJECT object);
int (*Release)(HOBJECT object);
int (*IsValid)(HOBJECT object);
int (*Render)(HOBJECT object, int x, int y, int width, int height);
int (*SwitchIn)(HOBJECT object);
int (*SwitchOut)(HOBJECT object);
int (*GetOption)(HOBJECT object, int option);
int (*MouseLeftDoubleClick)(HOBJECT object, int x, int y, int ctrlkey);
int (*MouseLeftDown)(HOBJECT object, int x, int y, int ctrlkey);5 int (*MouseLeftUp)(HOBJECT object, int x, int y, int ctrlkey);
int (*MouseRightDoubleClick)(HOBJECT object, int x, int y, int ctrlkey);
int (*MouseRightDown)(HOBJECT object, int x, int y, int ctrlkey);
int (*MouseRightUp)(HOBJECT object, int x, int y, int ctrlkey);
int (*MouseMove)(HOBJECT object, int x, int y, int ctrlkey);
int (*KeyDown)(HOBJECT object, int key, int ctrlkey);
int (*KeyUp)(HOBJECT object, int key, int ctrlkey);
}IGLApp;
这个接口分几个部分:最前面是LCOM规定的公共接口函数和偏移值变量。后面是绘制函数Render,再往后是鼠标消息和键盘消息的处理函数。另外还有一个函数来返回OpenGL应用的比如是否需要外面定时调用(实现动画效果)等。这个接口只是示范性的,因此定义的比较随意。但是它仍然足够抽象,可以在各个平台下运行。
5.2 接口实现
比如我们要实现一个前面文章讲过的绘制MandelBrot集合的应用(用OpenGL 4.0绘制MandelBrot集合),可以这样定义对象(mandelbrot_app.h):
#ifndef __GLAPP_MANDELBROT_H
#define __GLAPP_MANDELBROT_H
#ifdef __cplusplus
extern "C" {
#endif
#include "guid.h"
DEFINE_GUID(CLSID_MANDELBROT, 0xdc03ff75, 0x27f1, 0x4330, 0x82, 0xab, 0x3, 0x5c, 0x5, 0x66, 0x81, 0xa2);
int glappMandelbrotCreate(IGLApp ***pApp);
#ifdef __cplusplus
}
#endif
#endif
这里还声明了一个全局函数glappMandelbrotCreate,用它可以生成MandelBrot应用对象,其中调用了objectCreateObejctEx函数生成了一个类为CLSID_MANDELBROT的对象,并返回它实现的IGLApp的接口。应用程序其实不关心对象,只关心IGLApp接口。
该对象实现如下,这个实现代码没有任何辅助代码,因此显得非常复杂,冗长,读起来是比较费劲的,不想尝试的可以跳过去,不过在代码注释中给出了实现的一些考虑,深究者还是可以回看一下的:
#include "stdio.h"
#include "object.h"
#include "glapp.h"
#include "gl/glew.h"
#include "loadprogram.h"
/*
这里定义IMPLEMENT_GUID,是将glapp_mandelbrot.h中定义的
CLSID_MANDELBROT在本文件中实现
*/
#define IMPLEMENT_GUID
#include "glapp_mandelbrot.h"
#undef IMPLEMENT_GUID
typedef struct _sMandelBrotApp {
/*Object接口指针*/
const IObject * __IObject_ptr;
/*引用计数*/
int __object_refcount;
/*类ID指针*/
IIDTYPE __object_clsid;
/*IGLApp接口指针*/
const IGLApp* __IGLApp_ptr;
/*类实现变量*/
GLuint m_program;
double psize;
double pfromx;
double pfromy;
int psizeLoc;
int pfromLoc;
int vertexLoc;
int inited;
int x, y, w, h;
}sMandelBrotApp;
/*下面声明公共接口的函数和对象管理所需要的函数,这对每个对象实现都是必须的*/
static int mandelbrotappQueryInterface(HOBJECT object, IIDTYPE iid, const void **pInterface);
static int mandelbrotappAddRef(HOBJECT object);
static int mandelbrotappRelease(HOBJECT object);
static int mandelbrotappIsValid(HOBJECT object);
static void mandelbrotappDestroy(HOBJECT object);
static int mandelbrotappCreate(const PARAMITEM * pParams, int paramcount, HOBJECT* pObject);
static int mandelbrotappValid(HOBJECT object);
/*定义一个对象实现IObject接口的IObject对象,
MandelBrotApp对象的第一个成员必须是指向该数据的指针*/
static const IObject mandelbrotapp_object_interface = {
0, /*IObject的指针必须作为对象实现的第一个成员,因此偏移为0*/
mandelbrotappQueryInterface,
mandelbrotappAddRef,
mandelbrotappRelease,
mandelbrotappIsValid
};
static const char * mandelbrotappModuleInfo();
/*实现对象类注册函数,其实就是将对象的类ID,
对象生成函数和对象实现模块的信息函数提交到对象管理模块*/
static int mandelbrotappRegister() {
return objectCreateRegister(CLSID_MANDELBROT, mandelbrotappCreate, mandelbrotappModuleInfo);
}
OFUNCPTR A_u_t_o_registor_mandelbrotapp = (OFUNCPTR)mandelbrotappRegister;
/*下面声明IGLApp接口的函数*/
static int mandelbrotapp_glapp_Render(HOBJECT object, int x, int y, int width, int height);
static int mandelbrotapp_glapp_SwitchIn(HOBJECT object);
static int mandelbrotapp_glapp_SwitchOut(HOBJECT object);
static int mandelbrotapp_glapp_GetOption(HOBJECT object, int option);
static int mandelbrotapp_glapp_MouseLeftDoubleClick(HOBJECT object, int x, int y, int ctrlkey);
static int mandelbrotapp_glapp_MouseLeftDown(HOBJECT object, int x, int y, int ctrlkey);
static int mandelbrotapp_glapp_MouseLeftUp(HOBJECT object, int x, int y, int ctrlkey);
static int mandelbrotapp_glapp_MouseRightDoubleClick(HOBJECT object, int x, int y, int ctrlkey);
static int mandelbrotapp_glapp_MouseRightDown(HOBJECT object, int x, int y, int ctrlkey);
static int mandelbrotapp_glapp_MouseRightUp(HOBJECT object, int x, int y, int ctrlkey);
static int mandelbrotapp_glapp_MouseMove(HOBJECT object, int x, int y, int ctrlkey);
static int mandelbrotapp_glapp_KeyDown(HOBJECT object, int key, int ctrlkey);
static int mandelbrotapp_glapp_KeyUp(HOBJECT object, int key, int ctrlkey);
/*下面定义一个IGLApp的指针,对象中应该由一个指针成员指向这个结构*/
static const IGLApp mandelbrotapp_glapp_interface = {
(int)&(((const sMandelBrotApp*)0)->__IGLApp_ptr),
/*这个接口的偏移就是指向这个结构的指针在对象中的偏移*/
mandelbrotappQueryInterface,
mandelbrotappAddRef,
mandelbrotappRelease,
mandelbrotappIsValid,
mandelbrotapp_glapp_Render,
mandelbrotapp_glapp_SwitchIn,
mandelbrotapp_glapp_SwitchOut,
mandelbrotapp_glapp_GetOption,
mandelbrotapp_glapp_MouseLeftDoubleClick,
mandelbrotapp_glapp_MouseLeftDown,
mandelbrotapp_glapp_MouseLeftUp,
mandelbrotapp_glapp_MouseRightDoubleClick,
mandelbrotapp_glapp_MouseRightDown,
mandelbrotapp_glapp_MouseRightUp,
mandelbrotapp_glapp_MouseMove,
mandelbrotapp_glapp_KeyDown,
mandelbrotapp_glapp_KeyUp,
};
static int mandelbrotappAddRef(HOBJECT object)
{
sMandelBrotApp* pD;
if (!objectIsValid(object))
return EIID_INVALIDOBJECT;
pD = (sMandelBrotApp*)objectThis(object);
pD->__object_refcount++;
return pD->__object_refcount;
}
static int mandelbrotappRelease(HOBJECT object)
{
sMandelBrotApp* pD;
int ret;
if (!objectIsValid(object))
return EIID_INVALIDOBJECT;
pD = (sMandelBrotApp*)objectThis(object);
pD->__object_refcount--;
ret = pD->__object_refcount;
if (pD->__object_refcount <= 0) {
pD->__object_refcount = 1; /*为了保证在Destroy过程中不出现递归调用,这里将引用记数设置为1*/
mandelbrotappDestroy(object);
}
return ret;
}
static int mandelbrotappIsValid(HOBJECT object)
{
sMandelBrotApp* pD;
if (object == 0)
return 0;
pD = (sMandelBrotApp*)objectThis(object);
if (pD->__object_clsid != CLSID_MANDELBROT)
return 0;
if (pD->__object_refcount < 1)
return 0;
return mandelbrotappValid(object);
}
/*QueryInterface的实现,该对象实现了两个接口IObject和IGLApp,
可以看到,实际返回的所谓接口,其实是对象中指向该接口定义的一个指针,
在系统传递HOBJECT其实就是这个指针。这个指针指向的地址的第一个数据是
该执政在对象中的偏移值,因此该指针减去这个偏移值就是对象的地址:
#define objectThis(_obj) (((Ouint8_t *)(_obj))-((*(IObject **)(_obj))->__thisoffset))
*/
static int mandelbrotappQueryInterface(HOBJECT object, IIDTYPE iid, const void **pInterface)
{
/*本模块只提供CLSID_MANDELBROT类对象的支持*/
if (!objectIsClass(object, CLSID_MANDELBROT))
return EIID_INVALIDOBJECT;
/*确保对象是有效的*/
if (!objectIsValid(object))
return EIID_INVALIDOBJECT;
if (pInterface == 0)
return EIID_INVALIDPARAM;
*pInterface = 0;
if (isGUIDEqual(iid ,IID_OBJECT)) {
*pInterface = objectThis(object);
objectAddRef(object);
return EIID_OK;
} else if (isGUIDEqual(iid, IID_GLAPP)) {
/*查询IID_GLAPP接口时,返回的时对象中__IGLApp_ptr的地址*/
*pInterface = &((sMandelBrotApp*)(objectThis(object)))->__IGLApp_ptr;
/*查询一个接口等于引用一次对象,因此增加引用计数,
查询出来的接口不用的时猴要调用Release释放,避免内存泄漏*/
objectAddRef(object);
return EIID_OK;
}
return EIID_UNSUPPORTEDINTERFACE;
}
static int mandelbrotappCreate(const PARAMITEM * pParams, int paramcount, HOBJECT * pObject)
{
sMandelBrotApp * pobj;
pobj = (sMandelBrotApp *)malloc(sizeof(sMandelBrotApp));
if (pobj == NULL)
return -1;
pobj->m_program = 0;
pobj->psize = 4.0;
pobj->pfromx = -0.75;
pobj->pfromy = 0;
pobj->inited = 0;
*pObject = 0;
pobj->__IGLApp_ptr = &mandelbrotapp_glapp_interface;
/*返回生成的对象*/
pobj->__IObject_ptr = &mandelbrotapp_object_interface;
pobj->__object_refcount = 1;
pobj->__object_clsid = CLSID_MANDELBROT;
*pObject= (HOBJECT)&pobj->__IObject_ptr;
return EIID_OK;
}
static const char *mandelbrotappModuleInfo()
{
/*建议的模块信息格式:版本号-日期时间 模块说明*/
return "1.0.0-20210408.1108 MandelBrot App";
}
static void mandelbrotappDestroy(HOBJECT object)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
free(pApp);
}
/*
功能:判断对象是否是一个有效对象
参数:
object -- 对象数据指针
返回值:
0 -- 对象是无效的
1 -- 对象是有效的
*/
static int mandelbrotappValid(HOBJECT object)
{
return 1;
}
static ShaderItem shaders[] = {
{GL_VERTEX_SHADER, "d:/openglshader/mandelbrot.vert", 0},
{GL_FRAGMENT_SHADER, "d:/openglshader/mandelbrot.frag", 0},
{0, 0},
};
static int mandelbrotapp_glapp_Init(HOBJECT object)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
pApp->m_program = glLoadProgram(shaders);
pApp->psizeLoc = glGetUniformLocation(pApp->m_program, "psize");
pApp->pfromLoc = glGetUniformLocation(pApp->m_program, "pfrom");
pApp->vertexLoc = glGetAttribLocation(pApp->m_program, "vertex");
pApp->inited = 1;
return 0;
}
static int mandelbrotapp_glapp_DeInit(HOBJECT object)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
return 0;
}
static float squad[] = {
-1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
-1.0f, -1.0f,
1.0f, 1.0f,
1.0f, -1.0f
};
extern int OutputString(int append, const char * info);
static int mandelbrotapp_glapp_Render(HOBJECT object, int x, int y, int width, int height)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
if (pApp->inited == 0) {
mandelbrotapp_glapp_Init(object);
}
pApp->x = x;
pApp->y = y;
pApp->w = width;
pApp->h = height;
glViewport(0, 0, (GLint) width, (GLint) height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
glUseProgram(pApp->m_program);
glUniform1d(pApp->psizeLoc, pApp->psize);
glUniform2d(pApp->pfromLoc, pApp->pfromx, pApp->pfromy);
glEnableVertexAttribArray(pApp->vertexLoc);
glVertexAttribPointer(pApp->vertexLoc, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)&squad[0]);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(pApp->vertexLoc);
{
char buf[90];
sprintf(buf, "%20.17lle %20.17lle %20.17lle", pApp->pfromx, pApp->pfromy, pApp->psize);
OutputString(0, buf);
}
return 0;
}
static int mandelbrotapp_glapp_SwitchIn(HOBJECT object)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
return 0;
}
static int mandelbrotapp_glapp_SwitchOut(HOBJECT object)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
return 0;
}
static int mandelbrotapp_glapp_GetOption(HOBJECT object, int option)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
return 0;
}
static int mandelbrotapp_glapp_MouseLeftDoubleClick(HOBJECT object, int x, int y, int ctrlkey)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
return 0;
}
static int mandelbrotapp_glapp_MouseLeftDown(HOBJECT object, int x, int y, int ctrlkey)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
if (ctrlkey & GLAPP_SPECIAL_KEY_CONTROL) {
pApp->psize *= 2;
} else if (ctrlkey & GLAPP_SPECIAL_KEY_SHIFT) {
pApp->psize /= 2;
} else {
int cx, cy;
double diffx, diffy;
cx = pApp->x + pApp->w / 2;
cy = pApp->y + pApp->h / 2;
diffx = cx - x;
diffy = cy - y;
diffx /= pApp->w;
diffy /= pApp->h;
diffx *= pApp->psize;
diffy *= -pApp->psize;
pApp->pfromx -= diffx;
pApp->pfromy -= diffy;
}
return 1;
}
static int mandelbrotapp_glapp_MouseLeftUp(HOBJECT object, int x, int y, int ctrlkey)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
return 0;
}
static int mandelbrotapp_glapp_MouseRightDoubleClick(HOBJECT object, int x, int y, int ctrlkey)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
return 0;
}
static int mandelbrotapp_glapp_MouseRightDown(HOBJECT object, int x, int y, int ctrlkey)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
return 0;
}
static int mandelbrotapp_glapp_MouseRightUp(HOBJECT object, int x, int y, int ctrlkey)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
return 0;
}
static int mandelbrotapp_glapp_MouseMove(HOBJECT object, int x, int y, int ctrlkey)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
return 0;
}
static int mandelbrotapp_glapp_KeyDown(HOBJECT object, int key, int ctrlkey)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
return 0;
}
static int mandelbrotapp_glapp_KeyUp(HOBJECT object, int key, int ctrlkey)
{
sMandelBrotApp * pApp;
pApp = (sMandelBrotApp *)objectThis(object);
return 0;
}
int glappMandelbrotCreate(IGLApp ***pApp)
{
int ret;
A_u_t_o_registor_mandelbrotapp();
ret = objectCreateEx(CLSID_MANDELBROT, NULL, 0, IID_GLAPP, (const void **)pApp);
return ret;
}
5.3实现的存储器开销
实现一个对象,对象中得有一个指向IObject的指针,一个引用计数,一个指向类ID的指针,然后额外每实现一个接口,得增加一个指向该接口定义的指针。总的开销还可以接受。
6 用宏来辅助实现
你要是能够有耐心看完5.2中的代码,一个感觉是什么?太冗余了,感觉重重复复,啰啰嗦嗦是吧,另外就是太精细了,各种名称交织在一起,一不小心写错一个下划线或者什么的,整个程序就乱套。这么多行代码,有一大半是用来应付LCOM的,感觉不实用啊,一个实用的模型应该是让程序员集中精力在需要实现的算法上,而不是用大部分精力来对付符合管理要求的繁文缛节。
因此,LCOM定义了一系列宏,来辅助实现。这些宏包括:
/*这个宏用来声明每个对象的基本成员,包括指向IObject的指针
引用计数,和CLSID,直接放在对象实现的结构最前面即可
*/
#define OBJECT_HEADER \
const IObject * __IObject_ptr; \
int __object_refcount; \
IIDTYPE __object_clsid; \
/*
这个宏声明指定接口的一个指针,能够做得比较规范,保持与其他部分的一致性。
*/
#define INTERFACE_DECLARE(__interface) \
const __interface * __##__interface##_ptr;
/*
这个宏定义每个接口必须实现的Object接口变量和函数,放在接口定义的最前面
*/
#define OBJECT_INTERFACE \
int __thisoffset; \
int (*QueryInterface)(HOBJECT object, IIDTYPE iid, const void **pInterface); \
int (*AddRef)(HOBJECT object); \
int (*Release)(HOBJECT object); \
int (*IsValid)(HOBJECT object); \
/*这个宏初始化指定接口的指针变量,跟INTERFACE_DECLARE对应,
放在对象Create函数中
*/
#define INTERFACE_INIT(__interface, objptr, _obj, interfacename) \
objptr->__##__interface##_ptr = &_obj##_##interfacename##_interface;
/*这个宏声明公共对象接口,声明对象管理相关的函数,
实现其中的Register函数,并定义公共对象接口的数据*/
#define OBJECT_FUNCDECLARE(_obj, _clsid)\
static int _obj##QueryInterface(HOBJECT object, IIDTYPE iid, const void **pInterface); \
static int _obj##AddRef(HOBJECT object); \
static int _obj##Release(HOBJECT object); \
static int _obj##IsValid(HOBJECT object); \
static void _obj##Destroy(HOBJECT object); \
static int _obj##Create(const PARAMITEM * pParams, int paramcount, HOBJECT* pObject); \
static int _obj##Valid(HOBJECT object);\
static const IObject _obj##_object_interface = { \
0, \
_obj##QueryInterface, \
_obj##AddRef, \
_obj##Release, \
_obj##IsValid \
}; \
static const char * _obj##ModuleInfo();\
static int _obj##Register() {\
return objectCreateRegister(_clsid, _obj##Create, _obj##ModuleInfo); \
}\
OFUNCPTR A_u_t_o_registor_##_obj = (OFUNCPTR)_obj##Register;
/*这个宏定义公共对象的函数和变量,放在接口定义的最前面*/
#define INTERFACE_HEADER(_obj, __interface, _localstruct) \
(int)&(((const _localstruct*)0)->__##__interface##_ptr), \
_obj##QueryInterface, \
_obj##AddRef, \
_obj##Release, \
_obj##IsValid,
/*这个宏定义QueryInterface函数实现的开始部分,实现了对IObject接口的查询*/
#define QUERYINTERFACE_BEGIN(_obj, _clsid) \
static int _obj##QueryInterface(HOBJECT object, IIDTYPE iid, const void **pInterface) \
{ \
if (!objectIsClass(object, _clsid)) \
return EIID_INVALIDOBJECT; \
if (!objectIsValid(object)) \
return EIID_INVALIDOBJECT; \
if (pInterface == 0) \
return EIID_INVALIDPARAM; \
*pInterface = 0; \
if (isGUIDEqual(iid ,IID_OBJECT)) { \
*pInterface = objectThis(object); \
objectAddRef(object); \
return EIID_OK; \
}
/*这个宏跟在QUERYINTERFACE_BEGIN宏后面,每实现一个接口就跟一项,
实现对该接口的查询功能*/
#define QUERYINTERFACE_ITEM(_iid, __interface, _localstruct) \
else if (isGUIDEqual(iid, _iid)) { \
*pInterface = &((_localstruct *)(objectThis(object)))->__##__interface##_ptr; \
objectAddRef(object); \
return EIID_OK; \
} \
/*QueryInterface函数实现的结束部分*/
#define QUERYINTERFACE_END \
return EIID_UNSUPPORTEDINTERFACE; \
}
/*这个宏初始化对象中的公共对象接口,并返回生成的对象,
作为Create函数的最后部分*/
#define OBJECT_RETURN_GEN(_obj, _objptr, _retvar, _sid) \
do \
{ \
_objptr->__IObject_ptr = &_obj##_object_interface; \
_objptr->__object_refcount = 1; \
_objptr->__object_clsid = _sid; \
*_retvar = (HOBJECT)&_objptr->__IObject_ptr; \
}while (0)
/*这个宏提供了公共对象接口中AddRef,
Release, IsValid函数的实现*/
#define OBJECT_FUNCIMPL(_obj, _localstruct, _sid) \
static int _obj##AddRef(HOBJECT object) \
{ \
_localstruct * pD; \
if (!objectIsValid(object)) \
return EIID_INVALIDOBJECT; \
pD = (_localstruct *)objectThis(object); \
pD->__object_refcount++; \
return pD->__object_refcount; \
} \
\
static int _obj##Release(HOBJECT object) \
{ \
_localstruct * pD; \
int ret; \
if (!objectIsValid(object)) \
return EIID_INVALIDOBJECT; \
pD = (_localstruct *)objectThis(object); \
pD->__object_refcount--; \
ret = pD->__object_refcount; \
if (pD->__object_refcount <= 0) { \
pD->__object_refcount = 1; /*为了保证在Destroy过程中不出现递归调用,这里将引用记数设置为1*/ \
_obj##Destroy(object); \
} \
return ret; \
} \
\
static int _obj##IsValid(HOBJECT object) \
{ \
_localstruct * pD; \
if (object == 0) \
return 0; \
pD = (_localstruct *)objectThis(object); \
if (pD->__object_clsid != _sid) \
return 0; \
if (pD->__object_refcount < 1) \
return 0; \
return _obj##Valid(object); \
}
利用这些宏,改写5.1中的接口定义,并同时定义IGLApp实现的辅助宏:
typedef struct sIGLApp {
OBJECT_INTERFACE
int (*Render)(HOBJECT object, int x, int y, int width, int height);
int (*SwitchIn)(HOBJECT object);
int (*SwitchOut)(HOBJECT object);
int (*GetOption)(HOBJECT object, int option);
int (*MouseLeftDoubleClick)(HOBJECT object, int x, int y, int ctrlkey);
int (*MouseLeftDown)(HOBJECT object, int x, int y, int ctrlkey);
int (*MouseLeftUp)(HOBJECT object, int x, int y, int ctrlkey);
int (*MouseRightDoubleClick)(HOBJECT object, int x, int y, int ctrlkey);
int (*MouseRightDown)(HOBJECT object, int x, int y, int ctrlkey);
int (*MouseRightUp)(HOBJECT object, int x, int y, int ctrlkey);
int (*MouseMove)(HOBJECT object, int x, int y, int ctrlkey);
int (*KeyDown)(HOBJECT object, int key, int ctrlkey);
int (*KeyUp)(HOBJECT object, int key, int ctrlkey);
}IGLApp;
#define GLAPP_VARDECLARE
#define GLAPP_VARINIT(_objptr, _sid)
#define GLAPP_FUNCDECLARE(_obj, _clsid, _localstruct) \
static int _obj##_glapp_Render(HOBJECT object, int x, int y, int width, int height); \
static int _obj##_glapp_SwitchIn(HOBJECT object); \
static int _obj##_glapp_SwitchOut(HOBJECT object); \
static int _obj##_glapp_GetOption(HOBJECT object, int option); \
static int _obj##_glapp_MouseLeftDoubleClick(HOBJECT object, int x, int y, int ctrlkey); \
static int _obj##_glapp_MouseLeftDown(HOBJECT object, int x, int y, int ctrlkey); \
static int _obj##_glapp_MouseLeftUp(HOBJECT object, int x, int y, int ctrlkey); \
static int _obj##_glapp_MouseRightDoubleClick(HOBJECT object, int x, int y, int ctrlkey); \
static int _obj##_glapp_MouseRightDown(HOBJECT object, int x, int y, int ctrlkey); \
static int _obj##_glapp_MouseRightUp(HOBJECT object, int x, int y, int ctrlkey); \
static int _obj##_glapp_MouseMove(HOBJECT object, int x, int y, int ctrlkey); \
static int _obj##_glapp_KeyDown(HOBJECT object, int key, int ctrlkey); \
static int _obj##_glapp_KeyUp(HOBJECT object, int key, int ctrlkey); \
static const IGLApp _obj##_glapp_interface = { \
INTERFACE_HEADER(_obj, IGLApp, _localstruct) \
_obj##_glapp_Render, \
_obj##_glapp_SwitchIn, \
_obj##_glapp_SwitchOut, \
_obj##_glapp_GetOption, \
_obj##_glapp_MouseLeftDoubleClick, \
_obj##_glapp_MouseLeftDown, \
_obj##_glapp_MouseLeftUp, \
_obj##_glapp_MouseRightDoubleClick, \
_obj##_glapp_MouseRightDown, \
_obj##_glapp_MouseRightUp, \
_obj##_glapp_MouseMove, \
_obj##_glapp_KeyDown, \
_obj##_glapp_KeyUp, \
};
改写5.2中的实现代码,结果是:
#include "stdio.h"
#include "object.h"
#include "glapp.h"
#include "gl/glew.h"
#include "loadprogram.h"
#define IMPLEMENT_GUID
#include "glapp_mandelbrot.h"
#undef IMPLEMENT_GUID
typedef struct _sMandelBrotApp {
OBJECT_HEADER
INTERFACE_DECLARE(IGLApp)
GLAPP_VARDECLARE
GLuint m_program;
double psize;
double pfromx;
double pfromy;
int psizeLoc;
int pfromLoc;
int vertexLoc;
int inited;
int x, y, w, h;
}sMandelBrotApp;
OBJECT_FUNCDECLARE(mandelbrotapp, CLSID_MANDELBROT);
GLAPP_FUNCDECLARE(mandelbrotapp, CLSID_MANDELBROT, sMandelBrotApp);
OBJECT_FUNCIMPL(mandelbrotapp, sMandelBrotApp, CLSID_MANDELBROT);
QUERYINTERFACE_BEGIN(mandelbrotapp, CLSID_MANDELBROT)
QUERYINTERFACE_ITEM(IID_GLAPP, IGLApp, sMandelBrotApp)
QUERYINTERFACE_END
static int mandelbrotappCreate(const PARAMITEM * pParams, int paramcount, HOBJECT * pObject)
{
sMandelBrotApp * pobj;
pobj = (sMandelBrotApp *)malloc(sizeof(sMandelBrotApp));
if (pobj == NULL)
return -1;
pobj->m_program = 0;
pobj->psize = 4.0;
pobj->pfromx = -0.75;
pobj->pfromy = 0;
pobj->inited = 0;
*pObject = 0;
GLAPP_VARINIT(pobj, CLSID_MANDELBROT);
INTERFACE_INIT(IGLApp, pobj, mandelbrotapp, glapp);
/*返回生成的对象*/
OBJECT_RETURN_GEN(mandelbrotapp, pobj, pObject, CLSID_MANDELBROT);
return EIID_OK;
}
static const char *mandelbrotappModuleInfo()
{
return "1.0.0-20210408.1108 MandelBrot App";
}
/*后面略去*/
这个要简单多了吧,对象定义之后到Create之前的代码原来是130行,现在变成6行宏定义了,而且不会出现前后不呼应的情况,程序大部分精力可以集中在需要实现的算法上。
这次篇幅有点长了,后面有机会再介绍如何用实现诸如链表,二叉树等数据结构的通用接口。