BREW平台主要技术的分析与总结
摘要:2003年,美国高通公司在中国推出了BREW平台,在中国获得了快速的发展和应用。时间虽然不短,但是由于高通公司的种种约束以及BREW平台本身的诸多限制,使得许多开发人员对在BREW平台下进行开发不得要领。本文深入分析了BREW平台的主要技术以及实现机制,包括模块加载机制、事件处理机制、接口机制和回调机制,以期对BREW平台有深入透彻的理解,并对BREW的主要技术进行了总结。
关键词:BREW;模块加载;事件处理;接口;回调机制
中图分类号:TP311
1 引言
如今,手机已不仅是语音通信的工具,而且逐步成为数据业务开发与应用的平台,随着科学技术的不断更新和发展,如今手机已不仅仅是满足打电话的功能,而是更多地成为人们在工作、学习和生活中的助手。在这种情况下CDMA手机产生了一种新的应用平台,也就是由美国高通公司研发出的BREW平台[1]。
BREW 是Binary Runtime Environment for Wireless(无线二进制运行环境)的缩写,是美国高通(QUALCOMM)公司为无线数据应用程序开发和执行提供的通用接口平台。它为制造商和开发人员提供了一套应用程序接口(API),可以随时对运行环境进行扩展,提供应用程序需要的各种附加性能模块[2]。
BREW 包含两个系统,一个是在运营商的发布系统,也就是平时用mobile shop 访问的系统;另一个是在手机端的BREW 运行环境系统。不同的厂商对手机所配备的操作系统一般都是独立开发的(智能手机除外),之间可能是千差万别,然而它们都共同需要运行BREW程序,这就需要依赖一个的中间层存在,这个中间层对BREW 程序提供调用的接口,而把程序的调用事实上转化为对操作系统的系统调用或对底层硬件的直接访问,由于这个中间层基本上是一个调用的中转关系,因此它本身的体积是很小的,并在BREW 运行期间一直驻留内存为BREW 程序提供服务,这也就是为什么即使操作系统在BREW 运行期间不占用内存,应用程序也无法完全的使用内存。
2 BREW平台主要技术
2.1 模块加载机制
在BREW中,Module是基本的执行单位,一个Module可以包含一个或多个Applet,或者多个Extension Class[3]。下面具体分析BREW是如何动态加载模块的。
1) Module的信息
原则上来说,每个Module都需要有标识自身的MIF文件。
2) 枚举Module信息
这一步是在BREW环境初始化的时候进行的,由于通常在开机时就初始化BREW环境,所以枚举Module信息在开机时就进行。对于BREW通过检索各个MIF文件来获得各个Module的必要信息,比如ClassID等。ClassID是一个4字节的无符号整数,作为Module的唯一标识。
3) Module加载
Module的加载是在运行时才进行的,即执行该Module的时候。Module的加载是通过调用函数AEEMod_Load()实现的,而AEEMod_Load()实际是调用AEEStaticMod_New(),该函数的部分代码如下:
int AEEStaticMod_New(int16 nSize, IShell *pIShell, void *ph, IModule **ppMod,
PFNMODCREATEINST pfnMC,PFNFREEMODDATA pfnMF)
{
AEEMod *pMe = NULL;
VTBL(IModule) *modFuncs;
... ...
// 初始化函数指针
modFuncs->AddRef = AEEMod_AddRef;
modFuncs->Release = AEEMod_Release;
modFuncs->CreateInstance = AEEMod_CreateInstance;
modFuncs->FreeResources = AEEMod_FreeResources;
// 初始化虚函数列表
INIT_VTBL(pMe, IModule, *modFuncs);
... ...
return SUCCESS;
}
在该代码片段中AEEStaticMod_New()向BREW运行系统注册了四个函数,其中就有一个AEEMod_CreateInstance()函数,这样在每次BREW系统加载Module时都会去调用该函数,所有函数的源头调用点,就在这里[4]。
4) Module 创建
这是通过AEEMod_CreateInstance()函数来创建的。该函数的实现如下:
static int AEEMod_CreateInstance(IModule *pIModule,IShell *pIShell,AEECLASSID ClassID,void **ppObj)
{
AEEMod *pme = (AEEMod *)pIModule;
int nErr = EFAILED;
if (pme->pfnModCrInst) {
nErr = pme->pfnModCrInst(ClassID, pIShell, pIModule, ppObj);
#if !defined(AEE_STATIC)
} else {
nErr = AEEClsCreateInstance(ClassID, pIShell, pIModule, ppObj);
#endif
}
return nErr;
}
而通过分析AEEMod_CreateInstance()的代码,我们又可以发现Module指定了通过调用通用函数AEEClsCreateInstance()来创建Module包含的每个Applet。
5) Applet创建
AEEClsCreateInstance()通过AEEApplet_New()来最终创建Applet,AEEApplet_New()无非是具体分配Applet内存,初始化Applet的回调函数列表,并实例化Applet_HandleEvent(),然后返回IApplet指针,供运行时使用。
6) 程序运行
通常就是在Applet_HandleEvent()中进行各种事件处理。处理前一般将传入的IApplet指针先转换为AEEApplet或者用户自身的结构,以便可以访问其中的数据成员。
通过对模块加载流程的跟踪分析,给出模块加载的主要过程如下:
首先BREW允许系统调用AEEMod_Load ()函数,该函数不做任何事情将任务完全委托给函数AEEStaticMod_New()进行处理; AEEStaticMod_New()函数会加载一个Module,并注册必要的函数,其中就注册了函数AEEMod_CreateInstance();AEEMod_CreateInstance()函数创建模块实例并调用函数AEEClsCreateInstance();这个函数的功能就是依据ClassID 来创建该Module中所含有的所有Applet。当该模块要退出时,BREW会调用在AEEStaticMod_New()注册的函数AEEMod_FreeResources()释放资源。
2.2 消息处理机制
BREW应用程序的模型是基于一个事件驱动的协作式多任务模型[5]。事件处理机制的核心问题是程序应该只处理需要的事件,对于不需要处理的事件,需要返回给系统处理。应用在加载之后可以通过HandleEvent()函数接收所有输入的事件,然后会通过返回TRUE或FALSE指示是否处理事件。AEE层存在一个全局的事件队列,所有的事件都存储在该队列中,如果队列中的事件在分发后处理完毕或者无人处理,该事件将被从事件队列中删除。
BREW环境要求及时地处理事件。简而言之,如果应用执行处理事件HandleEvent()调用后没能在合适的时间返回,AEE可能就会关闭应用以保护设备响应其他请求。有些操作,例如,从网络套接字中读取数据,也许耗时过长,无法一次调用事件处理期就可以完成。这时就需要采用回调机制,以便在操作完成之后通知该应用。
BREW 中的消息处理函数都是Boolean返回类型的,这是为了实现事件处理的层次机制,当该层上的消息处理函数没有处理该事件时,应该返回FALSE,以便上层对该事件感兴趣的消息处理函数来处理。如果处理了,应该返回TRUE,说明该事件已被处理,无需其他层再处理。
当BREW 运行后,首先捕捉到各种事件,并将事件分发至BREW 环境中,BREW 环境再通过消息发送模块具体分发消息至目的地。接着在两种不同的情况下将走不同的流程。
如果当前没有活动对话框,则紧接着IAPPLET_Handleevent被BREW 自动调用来处理事件,从而实现了允许用户程序捕捉到事件并处理的机制。在用户的程序在处理消息过程中,用户程序可以将事件继续下发。
如果当前有活动对话框,则紧接着这个对话框的事件被BREW 自动调用,从而使得事件被对话最先截获,而对话框之后的处理是检查这个对话框包含的控件中哪个处于激活状态,并将事件下发给它的消息处理函数来处理,同时根据其返回值来判断其是否已经处理了该事件,当其返回FALSE 后,对话将该事件继续转发至其他对话框注册的消息处理函数,如果该消息处理函数仍然返回FALSE,BREW 继续将该事件转发至其他消息处理函数。这种机制使得以对话框方式来创建应用时,各种事件被自动的处理,从而简化了代码量.
除了IAPPLET具有HandleEvent外,所有继承IControl的接口也具有事件处理函数,允许处理事件。
例如:
Static Boolean IAPPLET_HandleEvent(AEEApplet * pMe, AEEEvent e, uint16 wp, uint16 dw)
{
case EVT_APP_START: //模块启动时
…
case EVT_APP_STOP: //模块停止时
…
case EVT_KEY: //响应按键事件
…
case EVT_COMMAND: //响应控件事件
IMENU_HandleEvent(); //控件事件处理函数
ITEXTCTL_HandleEvent();
case EVT_DIALOG_INIT: //响应对话框初始化事件
…
}
2.3 接口机制
BREW中所提供的服务实际上是一些小的二进制可执行程序形成的组件,它们可以给应用程序、操作系统以及其他组件提供服务[6]。开发自定义的BREW组件(如BREW 扩展类)类似于开发动态的、面向对象的API。多个BREW对象可以连接起来形成应用程序或组件系统。并且,组件可以在运行时刻,在不被重新链接或编译应用程序的情况下被卸下或替换掉。
BREW可以在运行时刻同其他组件连接起来构成某个应用程序,可以动态地加载或卸载应用,是动态链接的。BREW组件隐藏(封装)其内部实现细节,基于BREW的应用以二进制的形式发布,可以在不妨碍已有用户的情况下升级BREW应用或者BREW组件(如扩展类)。BREW的自定义扩展类按照一种标准的方式来宣布它们的存在。
BREW组件之间通过接口进行交互。接口是包含了一组函数的数据结构,通过这组数据结构,代码可以调用组件对象的功能。接口定义了一组成员函数,这组成员函数是组件对象暴露给客户的所有可用信息,客户可以利用这些信息取得组件提供的服务。
BREW提供了一组固定的接口,即使以后实现的方式出现了变化,只要应用程序和组件程序之间的接口不变,就不需要任何改变。BREW使用基于接口对象的引用计数来控制接口的生存期,并且为多个程序之间共享统一接口对象提供了有效的控制手段。BREW接口的引用计数使用方式是针对每个接口类或者应用程序采用引用计数[1]。
应用程序用一个指向接口数据结构的指针来调用接口成员函数,如图2.2所示。
图1 BREW应用程序的接口指针示意
BREW平台支持C和C++开发语言,但是一般的BREW应用程序都是用C写的,而对于C++的使用,BREW平台则需要做更多的事情,比如定义符重载等。而C语言是不支持面向对象的,只有C++支持面向对象的程序设计,因此BREW中必须使用C语言模拟实现C++语言面向对象机制。而面向对象技术中的关键技术多态是基于以上的内存模型和函数指针实现的,一般来说,如果使用类C语言描述多态,它相当于增加了一个间接层,在这个间接层拦截对于方法的调用,然后根据具体的指针指向实际对象调用相应的方法实现[1]。
下面结合具体的代码,对BREW接口和多态机制进行解释。
C结构体中内存对象的布局是采用按照定义顺序排列的,如果结构体定义如下:
struct A
{
int a;
int b;
int b;
};
在内存中的各个变量的布局为:
int a;
int b;
int c;
而如果结构体的定义如下:
struct B
{
int a;
int b;
int c;
int d;
};
在内存中的布局为:
int a;
int b;
int c;
int d;
这样可以看出完全是按照定义的顺序布局的,而如果采用如下的方式定义结构体C:
struct C
{
struct A a;
int d;
};
其在内存中的布局则为:
struct a;
int d;
这样把结构体成员a全部展开,就与结构体B的内存布局相同,存取结构体C中的a的成员a,虽然采用C.a.a才可以,但是内存中的布局与直接访问A.a效果是相同的,只不过增加了一个编译中间层,因此,在BREW中可以依照此技术实现继承机制。而把父结构体当作子结构体的第一个成员变量时,可以在运行时通过使用父结构的成员访问方式直接访问实际子结构体的成员变量,并且可以使用类型转换,转换为实际的子结构体,这样的技术正好是面向对象程序设计中继承和多态技术的基础。
BREW接口中定义了大量的函数指针,并且BREW规定所有BREW的模块必须实现IBase接口,而IBase接口的实质为如下的定义:
struct IBase
{
uint32 (*AddRef) (iname*);
uint32 (*Release) (iname*);
};
为了能够顺利从IBase接口派生特别定义了宏INHERIT_IBase,它就是如上的接口定义,而且BREW的定义模型,为了防止在接口之间转换造成混乱,只支持单根继承,也就是说用户每次只能够从一个接口派生,并不能同时派生多个接口,派生时使用如下的方式:
struct IInherit
{
INHERIT_IBase(IInherit);
void (*func1)();
void (*func2)();
};
这样根据前面介绍的内存模型,可以使用IBase接口来访问他的成员,而实际上访问的是IInherit接口的成员。
以上是继承的实现,相应的多态机制也就可以实现了。派生接口可以添加自己的成员变量和成员函数,不同的派生接口因而有了不同的特性,从而实现了接口的多态机制。
2.4 回调机制
回调机制是BREW平台中最关键的机制之一,很多接口类都是通过回调机制呈现在开发人员的面前[7]。相关操作函数为Callback_init()、Ishell_resume()和Callback_cancel().
BREW平台的回调机制,通常是异步的,举例来说,在网络通信的时候,如果是发送数据,普通的方式是发送一快数据,判断是否发送成功,如果发送不成功,则空闲一段时间,在调用发送数据的函数,如此往复,但是如果在BREW平台上,调用发送函数发送数据,如果发送成功,函数会返回发送成功的字节数,如果返回的是-2,表示当前发送不成功,接下来,需要注册一个回调函数,BREW会根据网络状况调用你注册的回调函数,继续发送需要发送的数据,直到所有数据发送成功。同样的道理,在接收数据的时候,如果数据量比较大,不是一次能够接收完,需要注册接收回调函数,当有数据到的时候就会调用你注册的回调函数接收数据,直到所有数据接收成功。
BREW平台回调机制的实现有几个关键的部分下面一一介绍。
Callback机制中的核心数据结构[8]:
typedef struct _AEECallback AEECallback;
struct _AEECallback
{
AEECallback *pNext;
void *pmc;
PFNCBCANCEL pfnCancel;
void *pCancelData;
PFNNOTIFY pfnNotify;
void *pNotifyData;
void *pReserved;
};
该结构体,前两个参数pNext和pmc都是不可修改的,是系统维护其值;pfnCancel和pCancelData是在取消回调时使用的;pfnNotify和pNotifyData是在回调时使用的;最后一个pReserved是保留参数,可以存储任何数据。
CALLBACK_Init和CALLBACK_Cancel是两个关键函数。CALLBACK_Init负责对AEECallback结构体进行初始化,该函数的参数列表第二个参数是PFNNOTIFY类型,就是需要回调的函数的函数指针类型,第三个参数是传递给该回调函数的参数指针,类型是void型指针。CALLBACK_Cancel函数是取消回调函数,也就是调用AEECallback结构体中的pfnCancel指针指向的函数来取消回调。ISHELL_Resume()函数允许向 AEE 外壳注册回调。 它可以向 AEE 外壳的待处理操作列表中添加回调。 AEE 外壳将在下一次调用事件循环时调用此回调函数,以使应用程序或对象协同处理多任务。如果已注册回调,则会先将其注销,然后再重新注册。
如果要使用BREW平台的回调机制,则只要初始化适当的AEECallback结构体,再在需要回调的时候调用ISHELL_Resume来启动回调函数 。回调函数是由系统调用用户的函数,由系统回调的函数,而非用户自行调用,这样用户就可以根据需要动态改变系统的功能,但是要求用户设计的函数必须遵循系统的函数声明。而从技术上讲,回调函数就是函数指针,可以达到运行时动态调用函数的作用[9]。
BREW中的回调函数除了使用在事件处理之外,定时器、复杂的数据加密算法和网络数据发送与接收等都使用回调函数技术,回调函数在基于事件处理的系统中还可以避免使用轮询技术导致应用程序陷入长时间的循环等待中。当把需要等待的事件或者是处理的任务作为回调函数注册到系统中,这样当系统接受到指定的消息或者是当系统处理完当前的任务,将会调用已经注册的回调函数。
在BREW程序设计中,特别需要避免的是长时间的单一函数或者方法执行,或者是使用如下的技术:
while(1)
{
DoSomeThing();
}
这种技术在游戏设计中是经常采用的,一旦进入游戏,将进入用户自己的循环,只从系统得到需要的数据。但是在BREW中,一旦陷入这种无限循环而无法及时响应系统消息,将会导致手持设备的强制性重新启动。因此在BREW中需要进行的周期性工作,一般是使用定时器来执行的,在设置定时器的时候,一般会同时初始化一个回调函数,这样在定时器结束后会调用这一个回调函数。
回调函数在BREW中的另外一个作用就是对于复杂任务的分解执行,对于十分复杂的任务或者功能,可能执行时间较长,由于这将导致设备的重新启动,因此必须修改算法,把复杂算法分步执行,没有执行完的程序也需要立刻返回,并且把没有完成的工作注册为回调函数,等下次执行调用。因此BREW的协作式多任务由此可以产生,由于把复杂任务进行了分解,导致每次只执行一部分,并且各个任务都可以得到执行的机会,因此可以产生模拟的多任务效果。但是这里的多任务是协作的,需要由用户在程序设计中进行保证,必须及时出让占用的系统资源,这个是协作式与抢先式多任务的明显区别,虽然这样增加了用户进行程序设计的难度,但是却最大限度的节约了系统资源,最大限度地增加了系统的执行和运行效率。因此在嵌入式程序设计以及BREW中被广泛采用。
3 总结
本文主要分析了BREW平台的主要技术,包括模块加载机制、事件处理机制、接口机制和回调机制,并结合具体的代码进行了深入的阐述。这四个机制并不是独立的,而是有机组成为BREW的技术核心,其中模块加载时会运用到事件处理机制和接口机制,事件处理过程同样也会运用到回调机制。对这四个机制进行深入的分析和了解,能更好更全面地理解BREW平台以及基于BREW平台的应用。