您的位置:首页 > 其它

[深入分析BREW机制]:动手写BREW扩展接口

2008-10-11 06:00 609 查看
动手写BREW扩展接口
——目录——
引言
1、写扩展接口最好要熟悉的几个宏
2、示例接口IStringParse
3、接口解析
3.1 头文件解析
3.2 .c文件解析
3.3接口实例的创建
3.4接口的实现函数
3.5接口的释放
结语

引言
扩展接口在BREW开发中很重要,使你的程序更健壮,维护工作更高效。一个程序应尽力将业务处理分离成功能类接口,这使得业务的实现和维护,与对业务的使用以及用户交互部分之间的耦合降到最低。对OEM(或运营商)而言,相当于扩展了自己平台的接口,这使得除了自已之外,第三方也可以在这些功能接口上独立开发应用,也就是说,有了这些接口后,oem就可以将应用委托给第三方开发,而不必每个具体应用都由自已开发;对第三方而言,如果善于利用扩展接口,可以降低软件开发周期,提高响应速度。
本篇通过示例一个提取电话号码和email地址的扩展接口来讲述如何写一个扩展接口。如001篇所述,扩展接口的实质就是BREW类,所以熟悉001篇后再读本篇,会使你如鱼得水。

1、写扩展接口最好要熟悉的几个宏
说“最好”,是因为这些宏不是必须的,你也可以直接使用展开式,但既然是BREW接口,就要体现一点BREW的风格,这也让你写出的接口更整结、简练。

//接口宏
#define QINTERFACE(iname) struct _##iname/
{
struct VTBL(iname) *pvt;/
};/
typedef struct VTBL(iname) VTBL(iname);/
struct VTBL(iname)

//虚函数表(结构体)宏
#define VTBL(iname) iname##Vtbl

//获取接口的虚函数表指针
#define GET_PVTBL(p, iname) ((iname *)p)->pvt

//定义一个iname类型的结构体vtiname, 带有VTBL字样,但跟虚函数表无关
#define DECLARE_VTBL(iname) iname vt##iname;

//必须继承的两个接口函数
#define DECLARE_IBASE(iname) /
uint32 (*AddRef) (iname *); /
uint32 (*Release) (iname *);

/*简单注释:_##iname 表示将_与iname简单相联; /表示下一行仍是宏的展开式*/

2 、示例接口IStringParse
下文所有BREW定义的宏在上文已经列出,在阅读时要充分利用这些宏帮助你理解接口句法。
/*文件: stringparse.h*/
typedef _IStringParse IStringParse;

typedef enum _eParseType
{
PARSE_TELNO,
PARSE_EMAIL,
}eParseType;

QINTERFACE(IStringParse)
{
INHERIT_IBASE(IStringParse);
int (*EnumInit) (IStringParse *po, const char *pBuf, eParseType ps);
char* (*EnumNext) (IStringParse *po);
}

#define ISTRINGPARSE_AddRef(po) /
GET_PVTBL(po, IStringParse)->AddRef(po)
#define ISTRINGPARSE_Release(po) /
GET_PVTBL(po, IStringParse)->Release(po)
#define ISTRINGPARSE_EnumInit(po, pb, ps) /
GET_PVTBL(po, IStringParse)->EnumInit(po, pb, ps)
#define ISTRINGPARSE_EnumNext(po) /
GET_PVTBL(po, IStringParse)->EnumNext(po)

/*文件: stringparse.c*/
#include "stringparse.h"

static int new (IStringParse **ppo);
static uint32 addref (IStringParse *po);
static uint32 release (IStringParse *po);
static int enuminit (IStringParse *po, const char *pBuf, eParseType ps);
static int enumnext (IStringParse *po);

static const VTBL(IStringParse) g_vtbl =
{
addref,
release,
enuminit,
enumnext,
}

typedef struct _CStringParse
{
DECLARE_VTBL(IStringParse)

uint32 m_nRef;
IShell *m_pIShell;
IModule *m_pIModule;
eParseType m_ps;
}CStringParse;

int AEEClsCreateInstance(AEECLSID ClsId, IShell *pIShell, IModule *pIModule, void **po)
{
if(AEECLSID_STRINGPARSE != ClsID)
{
return EFAILED;
}

return new(pIShell, pIModule, po);
}

static int new(IShell *pIShell, IModule *pIModule, IStringParse **po)
{
CStringParse *pme = NULL;

if(!po)
{
return EFAILED;
}

pme = (CStringParse *)MALLOC(sizeof(CStringParse));

GET_PVTBL(pme, IStringParse) = &g_vtbl;
pme->m_nRef = 1;
pme->m_pIShell = pIShell;
pme->m_pIModule = pIModule;
pme->m_ps = PARSE_TELNO;

IShell_AddRef(pme->m_pIShell);
IModule_AddRef(pme->m_pIModule);
*po = (IStringParse *)pme;

return SUCCESS;
}

static uint32 addref(IStringParse *po)
{
CStringParse *pme = (CStringParse *)po;

if(!pme)
{
return 0;
}
pme->m_nRef ++;

return pme->m_nRef;
}

static uint32 release(IStringParse *po)
{
CStringParse * pme = (CStringParse *)po;
if(!pme)
{
return 0;
}

if(pme->m_nRef > 0)
{
pme->m_nRef --;
}

if(0 == pme->m_nRef)
{
if(pme->m_pIShell)
{
IShell_Release(pme->m_pIShell);
pme->m_pIShell;
}
if(pme->m_pIModule)
{
IModule_Release(pme->m_pIModule);
pme->m_pIModule;
}
FREE(pme);
}

return pme->m_nRef;
}

static int enuminit (IStringParse *po, const char *pBuf, eParseType ps)
{
CStringParse *pme = (CStringParse *)po;
//略
}

static int enumnext (IStringParse *po)
{
CStringParse *pme = (CStringParse *)po;
//略
}

3、接口解析

3.1 头文件解析
用宏对头文件进行展开,可以看到,头文件里其实只typedef了两个数据类型,一个是IStringParse,另一个是IStringParseVtbl;
IStringParse只有一个成员变量,即IStringParseVtbl *pvt;
而IStringParseVtbl的成员是一系列函数指针,这一系列指针的前面两个必须是AddRef和Release, 通过DECLARE_IBASE(IStringParse)来继承,其它为该接口的功能函数指针。
如果一个变量po是IStringParse类型的,那么访问函数指针的方法为:
GET_VTBL(po, IStringParse)->func.
为了实现封装,在头文件里没有定义任何变量,调用者能知道的只有这个接口包含哪些功能, 接口的实例创建、函数实现、实例的数据成员都在.c文件里定义。

3.2 .c文件解析
.c文件里需重点关注两样东西,一是CStringParse类型,一是全局变量g_vtbl.
CStringParse由两个部分组成,第一部分是成员vtIStringParse,是一个IStringParse类型的结构体,由DECLARE_VTBL(IStringParse)展开得到。由于vtIStringParse只有一个成员pvt,一个虚函数表指针,所以CStringParse的第一部分的大小为固定的四个字节;CStringParse第二部分为私有成员,CStringParse有两个私有成员,m_nRef, m_ps,其中m_nRef是每个BREW类都必须有的数据成员,用于实现引用计数机制。
如001篇所述,基于CStringParse与IStringParse的这种位置关系,如果一个变量pme是CStringParse类型的,那么可以强转成IStringParse类型直接对IStringParse的成员进行访问而不会出错。下面是对虚函数表进行访问的两种方式:
pme->vtIStringParse.pvt;
((IStringParse *)pme)->pvt;
我们只希望接口的使用者能调用虚函数表里的函数指针,而不希望让使用者看到接口的私有成员,用第一种方式,就必须让CStringParse对使用者可见,无法满足我们的希望;而第二种方式,只需让IStringParse对使用者可见即可,完美地实现了对CStringParse私有成员的封装。所以我们创建的实例是IStringParse类型的,实际上实例指向的是一个sizeof(CStringParse)大小的空间。

3.3接口实例的创建
假设该接口的clsid为AEECLSID_STRINGPARSE,那么使用如下语句便可创建该实例:
IShell *pIShell = AEE_GetShell();
IStringParse *pIStringParse = NULL;
ISHELL_CreateInstance(pIShell, AEECLSID_STRINGPARSE, (void **)&pIStringParse);

查看.c文件,发现有一个函数AEECLSCREATEINSTANCE,该函数是所有动态接口和动态应用的入口,函数名固定。也就是说所有动态接口和动态应用都必须实现它,并且不能用static修饰它。对于静态的扩展接口或应用(一般只有oem才能写静态的),也有入口,但该入口函数要统一在某一个地方进行注册,所以名字可以随便取。

new函数用于具体的创建工作,供AEECLSCREATEINSTANCE函数调用。

new完成三件事,第一件是分配一个sizeof(CStringParse)大小的空间给pme; 第二件是对pme的成员进行初始化,初始化就是一个组装部件的过程,包括但不限于:将全局变量的地址&g_vtbl赋给pme的第一个成员虚函数表指针,初始化m_nRef,初始化其它成员。其中g_vtbl是将虚函数表中的函数指针与具体的实现函数进行关联的核心。第三件是将pme强转成IStringParse类型输出给调用者。

所以,创建一个接口实例,实际就是生成一个CStringParse类型的变量pme,然后强转成IStringParse类型输出给调用者。

3.4接口的实现函数
接口的实现函数有几个重要特征:
1)在.h文件里定义的虚函数表里的每一个函数指针,在.c文件里都有一个实现函数与其对应;
2)实现函数的声明与对应函数指针的声明必须完全一样(函数名子可以随便取),并且第一个参数总是IStringParse类型的;
3)在函数内部总要将IStringParse类型的参数po强转回CStringParse类型,不然就无法使用私有成员了。说“强转回”是因为po一开始就是CStringParse类型,只不过将它交到调用者手里时强转成了IStringParse类型。
4)实现函数作为g_vtbl的成员在new函数里与对应的函数指针挂钩,并且实现函数在g_vtbl中的位置必须与对应函数指针在虚函数表里的位置一样,否则就会出错。

3.5接口的释放
当你使用完接口IStringParse后,要调用ISTRINGPARSE_Release来释放该接口实例,ISTRINGPARSE_Release触发的现实函数是.c文件里的release函数,该函数的工作是将m_nRef减1,当m_nRef减到0时,便会彻底释放该实例所占用的所有资源,实例的生命期结束。

结语
读到这里,如里你已理解全篇,那么你便有了理解BREW其它知识的一个深入基础,同时你也对com机制有了一点见识,我们可以看到用来写BREW扩展接口的这一套方法可以用在任何c语言编程中,甚至其它编程语言中,所以这里讲的不只是BREW。
welcome to my blog: blog.csdn.net/chinesecoolman
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: