LCOM:带数据的接口
Posted 饶先宏
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LCOM:带数据的接口相关的知识,希望对你有一定的参考价值。
【摘要】本文试图在LCOM编程模型中引入面向接口的变量,为程序员提供更加灵活的编程模型。
1 问题的来由
前面的文章LCOM:轻量级组件对象模型引入了一种类似于COM的一种编程模式,可以理解为COM的简化版本。其中以接口作为编程关注的核心,所有的功能都用接口定义,一个软件模块实现一个或多个对象,每个对象实现一个或多个接口,对外提供服务。每个接口定义为一组函数的集合,在COM中,这点规定得比较死,接口中是不允许出现变量的,以致于要访问一个对象的内部变量,出现了var_Get, var_Set这样的接口函数(Visual Basic中可以自动生成)。
用函数控制对象内部的变量访问,具有很多优点,比如可以控制对变量的修改符合变量的逻辑,保持对象内部的数据一致性,访问时确保不出现非法访问,比如数组访问,如果下标超访问,那就是灾难,很多软件的bug都由此产生,还可以提供变量只读,只写等功能。读取变量时也可以提供一些特别的服务,比如虚拟化的服务,对象内部实际上不存在对应的变量,可以提供一个虚拟的变量Get,这样可以保持内部实现灵活的同时,保持外部接口符合用户逻辑,毕竟用户看到和内部实现是两回事嘛。
然而不允许接口中出现变量,也带来了很多不便,一个感觉就是太教条了,比如经典的双向链表数据结构,就是在每个数据对象中加入next和last指针,然后操控这两个指针,即可构成一个循环的双向链表。当然可以设计一个双向链表接口,用来提供next和last的访问,但是这样做总觉的有点怪怪的,限制了程序员的自由发挥了,原来data->pNext->pLast = data;这样的用法不好用了,程序员要适应一套新的东西,关键是没有带来实质性的好处。当然你可以争辩说通过接口函数访问变量可以增加代码安全性,比如验证链表节点的合法性什么的,但是不能流畅地编码,以及访问个变量就要调用一个函数,对于一些程序员会造成很大的心理负担。无论如何,函数调用的运行效率肯定不如直接的变量访问嘛,还想在C51下面用LCOM呢,这么一来CPU大量耗费在这些繁文缛节中。程序员需要自由,有权利在安全性和效率之间做平衡,要允许高手写出同样安全但是效率更高的代码,对专业拍照人士不能只提供傻瓜相机。因此本文在LCOM中试图引入一种在接口中定义变量的方法,就以双向链表为例,来演示这个方法的可行性。
2 接口数据存在什么地方
我们面临的第一个问题是,接口相关的数据如何表示,存储在什么地方?前面的LCOM接口中,接口函数声明在对象实现文件的一个静态变量中,来复习一下前面文章中实现的MandelBrotApp对象中实现的IObject接口和IGLApp接口:
下面声明公共接口的函数和对象管理所需要的函数,这对每个对象实现都是必须的*/
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
};
上面这段代码提供了mandelbrotapp对象中IObject接口的实现,在对象中则这些函数只是存储了一个指向mandelbrotapp_object_interface 这个变量的指针。同样:
/*下面声明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,
};
定义了IGLApp接口的实现函数,对象中也只存放了指向mandelbrotapp_glapp_interface 这个变量的指针__IGLApp_ptr,我们依靠这个指针在对象中的相对位置,从而计算处对象的地址,于是就可以正常访问这个对象中的各个数据对象。对象的QueryInterface接口函数实现时则返回这个指针的地址。然而如果要实现接口相关的变量,肯定不能放在这个静态的数据结构中,因此直接将变量写到接口中时不行的,因为这个静态的数据结构将被所有的对象实例共享,不可能为每个对象实例提供一个独立的变量存储空间。
让我们再仔细看看IObject的实现,上次讲通过宏来辅助每个对象实现IObject的函数时,其中AddRef和Release的实现依靠一个引用计数实现。对象中还有一个可以判别是否是某个类的CLSID的变量,为用户提供是否是某个对象类的支持,这个也是用对象中存储的CLSID实现的,这两个变量用来实现IObject接口,其实可以视为是IObject接口相关的变量吧,我们规定在每个对象中要这么声明:
typedef struct _sMandelBrotApp {
/*Object接口指针*/
const IObject * __IObject_ptr;
/*引用计数*/
int __object_refcount;
/*类ID指针*/
IIDTYPE __object_clsid;
/*IGLApp接口指针*/
const IGLApp* __IGLApp_ptr;
......
后面支持用宏来辅助实现,定义了这样的宏:
/*这个宏用来声明每个对象的基本成员,包括指向IObject的指针
引用计数,和CLSID,直接放在对象实现的结构最前面即可
*/
#define OBJECT_HEADER \\
const IObject * __IObject_ptr; \\
int __object_refcount; \\
IIDTYPE __object_clsid; \\
嗯,规则往往是被规则制定者打破的,微软公司有个著名的程序员制定的编程规则第一条就是:任何规则都是可以违反的,只要有足够的理由。程序员一方面要敬畏规则,另一方面要藐视它。不知道微软公司的c语言版本COM实现时如何做的,反正LCOM这样做是最顺的,所以也就不顾忌变量的对外公开了。
从这个可以看到,其实接口相关的变量是可以实现的,最简单的办法就是声明在接口指针的后面。这样一个接口IObject **变量同时可以当作一个指向
struct sObject {
const IObject * __IObject_ptr;
int __object_refcount;
IIDTYPE __object_clsid;
};
这个结构的指针使用,于是IObject接口就自带变量了。当然这仅仅适用于用我们提供的宏来实现IObject接口时的情况。事实上对象实现者可以选择自己实现自己的AddRef和Release,根本不是通过上面的宏或者类似的数据结构实现,这样上面的所谓IObject接口相关变量的说法其实只是宏辅助实现时的一个附加效果,不能作为LCOM中的规范性条文,使用LCOM的IObject接口时不能假定IObject**就是一个sObject的指针,万一有程序员不用我们提供的辅助宏来实现自己的对象呢。
不管怎么说,我们有了一个实现对象相关变量的办法,就是将接口指针变量和接口变量声明在一起,这样通过QueryInterface得到的接口指针变量的指针,同时也是接口相关变量的结构体指针(当然这个结构体必须以一个接口定义变量指针开始)。
3 双向链表接口
本节开始我们以双向链表为例,来演示接口相关变量的实现方法。事实上,我们走个极端,这个接口干脆没有函数(当然IObject相关的函数还是必须实现的,LCOM规定每个接口必须实现IObject的每个函数,这样通过任意接口都可以调用AddRef, Release和QueryInterface等函数),只有两个接口相关的变量,pNext和pLast。我们采用双向循环链表的方式来描述,用户自己记住表头,然后从表头开始通过pNext和pLast可以双向遍历链表,当然表头一般来说不是链表中的项。下面来定义双向链表接口,我们提供辅助宏:
DEFINE_GUID(IID_DLIST, 0xcdad8509, 0xb8, 0x4c3e, 0xa3, 0xcc, 0xbe, 0x7c, 0xf0, 0xc, 0x20, 0x8e);
struct sIDList;
struct sIDListVar;
typedef struct sIDList IDList;
typedef struct sIDListVar IDListVar, *IDListVarPtr;
struct sIDList {
OBJECT_INTERFACE
};
#define DLIST_VARDECLARE \\
INTERFACE_DECLARE(IDList) \\
IDListVar* __dlist_pLast; \\
IDListVar* __dlist_pNext;
struct sIDListVar {
DLIST_VARDECLARE
};
#define DLIST_VARINIT(_objptr, _obj) \\
INTERFACE_INIT(IDList, _objptr, _obj, dlist); \\
_objptr->__dlist_pLast = \\
_objptr->__dlist_pNext = (IDListVarPtr)&_objptr->INTERFACE_VAR(IDList);
#define DLIST_FUNCIMPL(_obj, _clsid, _localstruct) \\
static const IDList _obj##_dlist_interface = { \\
INTERFACE_HEADER(_obj, IDList, _localstruct) \\
};
可以看到,双向链表的接口没有任何函数,但是我们定义了DLIST_VARDECLARE宏,要求要支持双向链表的对象,在对象数据结构中用这个宏声明双向链表相关的变量,这样用IID_DLIST接口名QueryInterface出来的接口指针可以当做一个IDListVar的指针来用,反过来IDListVar也可以当一个接口或者HOBJECT来用,通过它也可以QueryInterface得到其他接口,或者用objectThis宏得到实现双向链表的对象的指针,总之就是一个正常的LCOM接口,但是没有任何DLIST相关的接口函数,可以直接访问接口变量__dlist_pNext和__dlist_pLast,这两个变量是IDListVar的指针,可以作为HOBJECT使用,当然也可以作为IDList**使用了。于是我们达到了目的,实现了一个双向链表接口,其中没有任何DLIST相关的函数,只有两个变量。这个实现似乎比传统的方法要方便一些,一般传统的办法是要求将pNext和pLast放在对象的最前面,在LCOM实现中,由于offset的支持,我们可以将这些变量放在对象的任何地方。这个做法可能的好处是,可以将一个对象放在不同的数据结构中了,比如放在两个不同的链表中(可能要重新定义一下接口),或者一方面放在一个双向链表中,另一方面放在一个二叉树中,不用考虑两个数据结构争着要占用对象数据结构的最前面(其实最前面的好处仅仅在于偏移固定为零,不需要存储一个额外的偏移量,LCOM的接口自带偏移量,可以解决这个问题)。
这样在一个对象中如果要支持双向链表,只要简单地在对象实现结构中声明双向链表接口即可。比如:
typedef struct _sMandelBrotApp {
OBJECT_HEADER
INTERFACE_DECLARE(IGLApp)
GLAPP_VARDECLARE
DLIST_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);
DLIST_FUNCIMPL(mandelbrotapp, CLSID_MANDELBROT, sMandelBrotApp};
OBJECT_FUNCIMPL(mandelbrotapp, sMandelBrotApp, CLSID_MANDELBROT);
QUERYINTERFACE_BEGIN(mandelbrotapp, CLSID_MANDELBROT)
QUERYINTERFACE_ITEM(IID_GLAPP, IGLApp, sMandelBrotApp)
QUERYINTERFACE_ITEM(IID_DLIST, IDList, sMandelBrotApp)
QUERYINTERFACE_END
加了三行代码,就实现了LCOM的双向链表接口,我们的MandelBrotApp可以放在一个双向链表中维护了。
4 双向链表接口操作函数
这就实现了吗?当然实现了,传统的双向链表也就是在对象的最前面增加两个指针嘛。如何操作这个双向链表呢,有三种方案,一种是仍然定义接口函数,来操作pNext和pLast变量完成链表操作,这种办法的好处是可以跟LCOM的传统做法一样,比如可以使用安全的objectCall宏等,但是缺点是代码冗余,每个对象类都实现了一套完全一样的代码,高手应该感觉心里不爽才是。一种是用宏来实现,一般的双向链表支持是通过宏来提供的,当然我们这里也可以支持。还有一种是通过一个函数库来支持,这样代码可以不冗余,同时可以做一些安全性验证,确保系统的安全性。三种办法使用者可以自行选择,这里建议用第三种方式。定义双向链表的操作函数如下,当然不够用的话仍然可以通过宏或者其他函数来实现:
typedef int (*dlist_traversan_func)(IDListVarPtr item, void *param);
/*初始化一个表头*/
int dlistInit(IDListVarPtr list);
/*在表的最后面追加一个项*/
int dlistAppendItem(IDListVarPtr list, HOBJECT item);
/*在表的最前面插入一个项*/
int dlistInsertItem(IDListVarPtr list, HOBJECT item);
/*将list2表中的项链接到表list中,list2则清空*/
int dlistCancat(IDListVarPtr list, IDListVarPtr list2);
/*遍历表list,对表list中的每一项调用func,param作为参数传到func中*/
int dlistTraversal(IDListVarPtr list, dlist_traversan_func func, void * param);
/*将item项插入到项before之前*/
int dlistInsertBefore(HOBJECT before, HOBJECT item);
/*将item项插入到项after之后*/
int dlistInsertAfter(HOBJECT after, HOBJECT item);
/*将项item从所在表list中拆下来*/
int dlistDetach(HOBJECT item);
/*得到表中项的个数*/
int dlistItemCount(IDListVarPtr list);
/*删除表中的每一项(每一项将调用Release)*/
int dlistRemoveAll(IDListVarPtr list);
下面是这些函数的实现,可以看出虽然是LCOM模式定义的,但是实现方法似乎又回到了经典的模式,这种感觉就美妙很多了,感觉很顺滑啊:
#include "object.h"
#define IMPLEMENT_GUID
#include "../include/dlist.h"
#undef IMPLEMENT_GUID
/*初始化一个表头*/
int dlistInit(IDListVarPtr plist)
{
if (plist==NULL)
return -1;
plist->__dlist_pNext = plist;
plist->__dlist_pLast = plist;
return 0;
}
/*在表的最后面追加一个项*/
int dlistAppendItem(IDListVarPtr plist, HOBJECT item)
{
IDListVarPtr pitem = NULL;
if (plist == NULL)
return -1;
objectQueryInterface(item, IID_DLIST, &pitem);
if (pitem == NULL) {
return -1;
}
pitem->__dlist_pNext = plist;
pitem->__dlist_pLast = plist->__dlist_pLast;
pitem->__dlist_pNext->__dlist_pLast = pitem;
pitem->__dlist_pLast->__dlist_pNext = pitem;
objectRelease(pitem);
return 0;
}
/*在表的最前面追加一个项*/
int dlistInsertItem(IDListVarPtr plist, HOBJECT item)
{
IDListVarPtr pitem = NULL;
if (plist == NULL)
return -1;
objectQueryInterface(item, IID_DLIST, &pitem);
if (pitem == NULL) {
return -1;
}
pitem->__dlist_pLast = plist;
pitem->__dlist_pNext = plist->__dlist_pNext;
pitem->__dlist_pLast->__dlist_pNext = pitem;
pitem->__dlist_pNext->__dlist_pLast = pitem;
objectRelease(pitem);
return 0;
}
/*将list2表中的项链接到表list中,list2则清空*/
int dlistCancat(IDListVarPtr plist, IDListVarPtr plist2)
{
if ( (plist == NULL) || (plist2 == NULL) ) {
return -1;
}
if (plist2->__dlist_pNext != plist2) {
plist ->__dlist_pLast->__dlist_pNext = plist2->__dlist_pNext;
plist2->__dlist_pNext->__dlist_pLast = plist ->__dlist_pLast;
plist2->__dlist_pLast->__dlist_pNext = plist;
plist ->__dlist_pLast = plist2->__dlist_pLast;
plist2->__dlist_pNext = plist2;
plist2->__dlist_pLast = plist2;
}
return 0;
}
/*遍历表list,对表list中的每一项调用func,param作为参数传到func中*/
int dlistTraversal(IDListVarPtr plist, dlist_traversan_func func, void * param)
{
IDListVarPtr pitem, pitemtemp;
if (plist == NULL)
return -1;
pitem = plist->__dlist_pNext;
while (pitem != plist) {
pitemtemp = pitem->__dlist_pNext;
if (func(pitem, param) != 0)
break;
pitem = pitemtemp;
}
return 0;
}
/*将item项插入到项before之前*/
int dlistInsertBefore(HOBJECT before, HOBJECT item)
{
IDListVarPtr pitem = NULL;
IDListVarPtr pbefore = NULL;
objectQueryInterface(item, IID_DLIST, &pitem);
objectQueryInterface(before, IID_DLIST, &pbefore);
if ( (pitem == NULL) || (pbefore==NULL) ) {
objectRelease(item);
objectRelease(before);
return -1;
}
pitem->__dlist_pNext = pbefore;
pitem->__dlist_pLast = pbefore->__dlist_pLast;
pitem->__dlist_pLast->__dlist_pNext = pitem;
pitem->__dlist_pNext->__dlist_pLast = pitem;
objectRelease(item);
objectRelease(before);
return 0;
}
/*将item项插入到项after之后*/
int dlistInsertAfter(HOBJECT after, HOBJECT item)
{
IDListVarPtr pitem = NULL;
IDListVarPtr pafter = NULL;
objectQueryInterface(item, IID_DLIST, &pitem);
objectQueryInterface(after, IID_DLIST, &pafter);
if ( (pitem == NULL) || (pafter==NULL) ) {
objectRelease(item);
objectRelease(after);
return -1;
}
pitem->__dlist_pLast = pafter;
pitem->__dlist_pNext = pafter->__dlist_pNext;
pitem->__dlist_pLast->__dlist_pNext = pitem;
pitem->__dlist_pNext->__dlist_pLast = pitem;
objectRelease(item);
objectRelease(after);
return 0;
}
/*将项item从所在表list中拆下来*/
int dlistDetach(HOBJECT item)
{
IDListVarPtr pitem = NULL;
objectQueryInterface(item, IID_DLIST, &pitem);
if (pitem == NULL) {
return -1;
}
pitem->__dlist_pLast->__dlist_pNext = pitem->__dlist_pNext;
pitem->__dlist_pNext->__dlist_pLast = pitem->__dlist_pLast;
pitem->__dlist_pLast = pitem;
pitem->__dlist_pNext = pitem;
objectRelease(item);
return 0;
}
/*得到表中项的个数*/
int dlistItemCount(IDListVarPtr plist)
{
int count;
IDListVarPtr pitem;
if (plist==NULL)
return 0;
count = 0;
while (pitem != plist) {
pitem = pitem->__dlist_pNext;
count++;
}
return count;
}
/*删除表中的每一项(每一项将调用Release)*/
int dlistRemoveAll(IDListVarPtr plist)
{
IDListVarPtr pitem, pnextitem;
if (plist==NULL)
return -1;
pitem = plist->__dlist_pNext;
while (pitem != plist) {
pnextitem = pitem->__dlist_pNext;
objectRelease((const IDList **)pitem);
pitem = pnextitem;
}
plist->__dlist_pNext = plist->__dlist_pLast = plist;
return 0;
}
5 双向链表的链表表头对象
前面说过,要使用循环双向链表,得记住一个表头,表头可以直接声明一个IDListVar对象,优点是不需要动态分配内存,这样少维护一个指针,缺点是这个表头不能作为一个LCOM对象使用。这里给出一个选项,直接实现一个表头类对象,使用时可以直接声明这个表头类对象,然后按照LCOM对象来管理,也可以用isClass宏来查询是否是表头,这样可以减少很多维护工作。
下面是定义:
DEFINE_GUID(CLSID_DLISTHEADER, 0x7ba10549, 0x8c42, 0x4301, 0xbb, 0xc7, 0x75, 0x5a, 0x7f, 0x8, 0x9, 0xae);
IDListVar * dlistheaderCreate();
#define IsDListHeader(obj) objectIsClass(obj, CLSID_DLISTHEADER)
实现如下:
/*表头对象*/
typedef struct _sDListHeader {
OBJECT_HEADER
DLIST_VARDECLARE
}sDListHeader;
OBJECT_FUNCDECLARE(dlistheader, CLSID_DLISTHEADER);
DLIST_FUNCIMPL(dlistheader, CLSID_DLISTHEADER, sDListHeader);
OBJECT_FUNCIMPL(dlistheader, sDListHeader, CLSID_DLISTHEADER);
QUERYINTERFACE_BEGIN(dlistheader, CLSID_DLISTHEADER)
QUERYINTERFACE_ITEM(IID_DLIST, IDList, sDListHeader)
QUERYINTERFACE_END
static const char以上是关于LCOM:带数据的接口的主要内容,如果未能解决你的问题,请参考以下文章
Android报错Multiple dex files define Lcom/ta/utdid2/c/a/c
Android报错Multiple dex files define Lcom/ta/utdid2/c/a/c
java.lang.NoClassDefFoundError:解析失败:Lcom/google/android/gms/common/R$string;