`
wangminshe89
  • 浏览: 667390 次
文章分类
社区版块
存档分类
最新评论

Writing BREW Extensions

 
阅读更多

Writing BREW Extensions
By Ray Rischpater

Introduction

One of the key differences between QUALCOMM BREW and other smart phone operating systems is its component-oriented approach to software development. Nearly every API you use when writing a BREW application is actually part of one or more software components, individually packaged by QUALCOMM or the handset OEM as part of the BREW runtime. Like most component-oriented environments such as the Component Object Model (COM), it's possible for you to create your own component, called an extension, that looks and behaves just like any other BREW interface, such as IShell or IDisplay. The extension you create can contain one or more BREW classes, and can be private to your application, or public for other applications (either yours or the world at large) to use.

Why use the extension model, when things like C modules or classes provide ample opportunities for modularity? BREW extensions provide several advantages over classes or modules for major functional areas of your application such as data storage, protocols, or codecs, including:

  • You can make an extension that other companies can license and from which you can make money using the same model as with other BREW applications, giving you additional opportunities for revenue.

  • Extensions are shared code outside your application; the handset downloads only one copy of an extension's code regardless of how many applications use the extension, letting you save valuable space on the handset's file system.

  • Because extensions live outside your application, they're an ideal way for multiple teams at your firm to isolate development to specific functional areas.

  • Extensions follow the same paradigm as other BREW interfaces, making it easier for new team members (or customers using your public extensions) to learn how to use your libraries.

  • Unlike modules, classes, or even libraries, BREW extensions are served by the BREW Delivery System (BDS) when needed by an application. Thus, if your application relies on an extension, when a customer purchases your application, the extension is downloaded automatically. More importantly, if a customer purchases a second application that uses the same extension, it's not downloaded again. Network and storage use is minimized by the fact that the extension is a shared component between multiple applications, in the same way that dynamically linked libraries save memory and disk space under Linux, Windows, and other desktop and server operating systems.

Crafting a BREW Extension

What makes a good BREW extension? Almost anything you identify as a candidate for reuse between applications that doesn't have a user interface, including:

  • Codecs for image, audio, or video presentation. Ideally, these implement the BREW IMedia interface (or IImage for static images), so that your customers can treat your codec as if it were bundled with BREW.

  • Data storage for data shared between applications, such as personal information managers.

  • Protocol handlers for Internet- or SMS-based protocols.

  • New user interface components that extend the BREW UI Toolkit's widgets, or even the older BREW IControl based hierarchy of components.

Some successful BREW developers have gone as far as breaking the bulk of their applications up into shared extensions, so the actual application you see in fact simply leverages a handful of extensions shared among their products. Other companies, such as M7 Networks and Truvideo, provide extensions to application developers, bringing high score management, video playback, and other features to other companies' applications. Finally, many Original Equipment Manufacturers (OEMs) use BREW extensions under the hood when building the applications that make up a cell phone's user interface.

A BREW extension has at least three components: its Module Information File (MIF), a header declaring its public interface, and its private implementation. Users of your extension include only its public interface header, whereas when you provide your interface to other applications through the BDS, you're making both the MIF and the private implementation (compiled as a conventional BREW module, a .MOD file) available.

Clients of your extension create new instances of the classes it contains using ISHELL_ClassCreateInstance, just as they would any other BREW class. To ensure this linkage works correctly, it's important to define your extension's class IDs correctly in your extension's MIF (see Figure 1). To do this:

  1. Launch the BREW MIF Editor.

  2. Select the "Extensions" tab.

  3. Click the "New..." button.

  4. Select the BID file for your extension's class, or enter the class ID.

  5. Continue steps 3-4 for each class in your extension.

  6. Save your MIF file.


<!--[if gte vml 1]><v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"> <v:stroke joinstyle="miter" /> <v:formulas> <v:f eqn="if lineDrawn pixelLineWidth 0" /> <v:f eqn="sum @0 1 0" /> <v:f eqn="sum 0 0 @1" /> <v:f eqn="prod @2 1 2" /> <v:f eqn="prod @3 21600 pixelWidth" /> <v:f eqn="prod @3 21600 pixelHeight" /> <v:f eqn="sum @0 0 1" /> <v:f eqn="prod @6 1 2" /> <v:f eqn="prod @7 21600 pixelWidth" /> <v:f eqn="sum @8 21600 0" /> <v:f eqn="prod @7 21600 pixelHeight" /> <v:f eqn="sum @10 21600 0" /> </v:formulas> <v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect" /> <o:lock v:ext="edit" aspectratio="t" /> </v:shapetype><v:shape id="_x0000_i1025" type="#_x0000_t75" alt="" href="http://www.developer.com/img/articles/2005/04/21/BREW1.gif" target="&quot;new&quot;" style='width:375pt;height:280.5pt' o:button="t"> <v:imagedata src="file:///C:/DOCUME~1/TIANHL~1/LOCALS~1/Temp/msohtml1/01/clip_image001.gif" o:href="http://www.developer.com/img/articles/2005/04/21/BREW1.gif" /> </v:shape><![endif]--><!--[if !vml]--><!--[endif]-->

Figure 1: Setting an extension's class IDs in the MIF.

The other thing you need to do is create the public interface for your extension. To do this, you need to create a header file that defines the virtual table (also called simply a vtable) for the classes in your extension and the macros to dispatch against the vtable, along with any other structures or macros required by your extension's classes. Creating the vtable is easy to do, even working in C, thanks to the AEEINTERFACE_DEFINE and AEEGETPVTABLE macros provided by Qualcomm in AEEInterface.h.

The AEEINTERFACE_DEFINE lets you define the vtable for a class. For example, consider an extension to manage a user's to do list, presumably as part of a simple personal information manager (PIM); the interface to the task database might resemble that of the IDatabase interface already provided by BREW. To define the vtable for the task database, named ITaskDatabase, I write:

#define INHERIT_ITaskDatabase( iname ) /
INHERIT_IQueryInterface( iname ); /
void (*Reset)( iname *); /
ITaskRecord * (*GetRecordByID)( iname *, uint32 ); /
ITaskRecord * (*GetNextRecord)( iname * ); /
uint32 (*GetRecordCount)( iname * ); /
ITaskRecord * (*CreateRecord)( iname * )
AEEINTERFACE_DEFINE( ITaskDatabase );

The first preprocessor instruction defines a new vtable INHERIT_ITaskDatabase for a class descended from the IQueryInterface class. This vtable has five methods, along with the three defined by IQueryInterface itself. In turn, other classes can declare themselves dependent on this vtable using this macro. The second directive, itself a preprocessor macro, creates a new BREW class named ITaskDatabase using the INHERIT_ITaskDatabase macro itself—in essence, defining ITaskDatabase as the interface that inherits from ITaskDatabase, which would a circular definition except for the first macro declaration.

All BREW classes should have a base class of either IBase or IQueryInterface, which itself descends from IBase. As a general rule, if you're developing for later versions of BREW it's almost always better to inherit from IQueryInterface, because doing so gives you a well-defined mechanism for extensibility while ensuring binary compatibility. The IQueryInterface contract requires you to provide three methods:

  • The AddRef method, which increments the reference count for an instance of an interface.

  • The Release method, which decrements the reference count for an instance of an interface, and releases the resources used by the interface and the interface itself if the reference count reaches zero.

  • The QueryInterface method, which returns the requested interface for the indicated interface ID if one is available.

Note: In point of fact, instead of inheriting from IQueryInterface, ITaskDatabase could have inherited from IDatabase, except for two things. First, it's good practice when extending BREW to always inherit from IQueryInterface to ensure compatibility across versions, and second, the IDatabase interface uses BREW's older QINTERFACE macro when defining its interface, which makes it harder to derive child classes.

Of course, no one wants to rummage through a vtable to dispatch against an interface, so QUALCOMM provides the AEEGETPVTBL method that you use to define macros to access each element of your class vtable. For the ITaskDatabase class just defined, I write:

#define ITASKDATABASE_AddRef(p) /
AEEGETPVTBL((p),ITaskDatabase)->AddRef( p )

#define ITASKDATABASE_Release(p) /
AEEGETPVTBL((p),ITaskDatabase)->Release( p )

#define ITASKDATABASE_QueryInterface( p, clsid, ppo ) /
AEEGETPVTBL((p),ITaskDatabase)->QueryInterface((p),(clsid),(pp))

#define ITASKDATABASE_Reset(p) /
AEEGETPVTBL((p),ITaskDatabase)->Reset( p )

#define ITASKDATABASE_GetRecordByID(p, dwID) /
AEEGETPVTBL((p),ITaskDatabase)->GetRecordByID((p),(dwID))

#define ITASKDATABASE_GetNextRecord(p) /
AEEGETPVTBL((p),ITaskDatabase)->GetNextRecord( p )

#define ITASKDATABASE_GetRecordCount(p) /
AEEGETPVTBL((p),ITaskDatabase)->GetRecordCount( p )

#define ITASKDATABASE_CreateRecord(p) /
AEEGETPVTBL((p),ITaskDatabase)->CreateRecord( p )

These macros are straightforward, but perhaps the most tedious part of writing the extension's interface, because you must keep the arguments in sync with the arguments of your class methods, and you won't find any errors you make until you actually invoke one of the macros in your code. For large projects, you could probably automate the process of generating the macros from the vtable definition, although I'm not aware of anyone who's gone to the trouble of doing so.

Implementing a BREW Extension

Under the hood, an extension isn't much different from an application—in fact, what separates an application from an extension is that an application has an event handler, registered when you initialize your application. Extensions, as faceless components, don't handle events, but must provide other methods, much as a C++ class does.

Like an application, your extension's entry point is AEEClsCreateInstance, invoked from within the QUALCOMM-provided AEEModGen.c file. A typical AEEClsCreateInstance simply examines the incoming class ID and invokes an extension-specific constructor, like this:

int AEEClsCreateInstance( AEECLSID clsID,
IShell * pIShell,
IModule *pIModule,
void ** ppMod)
{
switch( clsID )
{
case AEECLSID_TASKDATABASE:
return TaskDatabase_CreateInstance( clsID,
pIShell,
pIModule,
ppMod );
default:
return EBADCLASS;
}
}

It's always a good idea to separate your AEEClsCreateInstance method from your extension's constructor like this, even when you have only a single class in your extension, for three reasons. First, it's much easier to add additional classes later. Second, the AEEClsCreateInstance construct is only applicable when writing dynamically loaded code (code written and installed over the air); if you're writing an extension for OEMs that will be statically compiled into the handset ROM, each of your extension class constructors will be registered in a special table used by BREW to manage in-ROM extensions. Finally, if you decide not to ship your extension as an extension but instead link it directly into your application, you can simply pull the definition of AEEClsCreateInstance and use the constructor function it calls, and the rest of the work you've invested in making your extension, including the public interface header, is still useful.

Your constructor must allocate the memory necessary for the object itself and its vtable, as well as do any other necessary initialization work. As with a well-designed class in C++ or Java, it's safest to keep your constructor as simple as possible, doing as little setup work as is necessary. Whereas the QUALCOMM BREW interfaces don't have a lot of examples for two-phase object construction (in which you first create an object and then initialize it), it's a good bet that under the hood most complex classes like IMedia probably do something close to this, because two-phase object construction is a well-understood design pattern that provides good error handling facilities. A typical extension constructor looks like this:

typedef struct _STaskDatabaseData
{
/// The vtable (must be first)
AEEVTBL(ITaskDatbase) *pvt;

IModule *pIModule;
IShell *pIShell;
uint32 nRefs;

// Our member variables
...
} STaskDatabaseData;

int ITaskDatabase_CreateInstance( AEECLSID clsID,
IShell *pIShell, IModule *pIModule,
void **ppMod)
{
STaskDatabaseData *pThis = NULL;
AEEVTBL(ITaskDatbase) *modFuncs;
int16 nSize = sizeof( STaskDatabaseData ) +
sizeof( AEEVTBL(ITaskDatbase) ) ;

if( !ppMod || !pIShell || clsID != AEECLSID_TASKDATABASE )
return EFAILED;

pThis = (STaskDatabaseData *) MALLOC( nSize );
*ppMod = pThis;

if ( NULL == pThis ) return ENOMEMORY;
ZEROAT( pThis );

modFuncs = (AEEVTBL(ITaskDatbase) *)((byte *)pThis +
sizeof( STaskDatabaseData ));
modFuncs->AddRef = ITaskDatabase_AddRef;
modFuncs->Release = ITaskDatabase_Release;
modFuncs->QueryInterface = ITaskDatabase_QueryInterface;
modFuncs->Reset = ITaskDatabase_Reset;
modFuncs->GetRecordByID = ITaskDatabase_GetRecordByID;
modFuncs->GetNextRecord = ITaskDatabase_GetNextRecord;
modFuncs->GetRecordCount = ITaskDatabase_GetRecordCount;
modFuncs->CreateRecord = ITaskDatabase_CreateRecord;
INIT_VTBL(pThis, ITaskDatabase, *modFuncs);

// initialize the data members
pThis->nRefs = 1;
pThis->pIShell = pIShell;
pThis->pIModule = pIModule;

// Add References
ISHELL_AddRef( pThis->pIShell );
if ( pThis->pIModule ) IMODULE_AddRef( pThis->pIModule );

return AEE_SUCCESS;
}

The work done by the constructor leads to a lot of code, but as you see from the listing, other than a memory allocation it's really all bookkeeping. It begins by computing the size of the required result structure, which must be large enough to hold both private data for the object as well as its virtual table. Next, I allocate the necessary space, returning if the allocation fails or else zeroing the resulting memory (which isn't strictly necessary if your constructor is just going to fill out all of its member variables at this point). With the memory in hand, I find the location of the vtable, which sits after the static data (see Figure 2) in the newly created object, and then assign each of the method pointers in the vtable to the appropriate function. Finally, I initialize each of the data members and return the newly created object.

<!--[if gte vml 1]><v:shape id="_x0000_i1026" type="#_x0000_t75" alt="" style='width:205.5pt;height:194.25pt'> <v:imagedata src="file:///C:/DOCUME~1/TIANHL~1/LOCALS~1/Temp/msohtml1/01/clip_image003.gif" o:href="http://www.developer.com/img/articles/2005/04/21/BREW2.gif" /> </v:shape><![endif]--><!--[if !vml]--><!--[endif]-->

Figure 2: Identifying the vtable

Of course, every constructor deserves a well-balanced destructor, which falls into the Release method for the class:

static uint32 ITaskDatabase_Release(ITaskDatabase *p) 

{

STaskDatabaseData *pThis = (STaskDatabaseData *)p;

 if (!pThis ) return 0;

 pThis->nRefs--;

if (pThis->nRefs != 0) return pThis->nRefs;

 RELEASEIF( pThis->pIShell ); 

RELEASEIF( pThis->pIModule );

 FREE_VTBL(pThis, ITaskDatabase);

 FREEIF( pThis );

 return 0; 

}

This function—which need have only file scope, because it's always accessed though the vtable anyway—simply decrements the reference count and either returns the new reference count, or frees the members of the object before releasing the object's vtable and the object itself. You should note the casting skullduggery that occurs between the single argument to release—the interface pointer—and the underlying data structure that contains the data associated with the interface instance. It's a bit of a hassle, and many times in looking at extensions written by others you may see a macro like this:

#define THIS_FROM_INTERFACE(p, objtype) objtype *pThis = p;

I explicitly choose not to do that because I've found by coaching others that hiding the cast behind a macro only makes it harder to understand how an extension works for newcomers.

The remainder of your methods will look much like ITaskDatabase_Release: First convert the interface pointer into a pointer to the underlying representation, and then do whatever your method is supposed to do.

Summary

The BREW extension concept gives you a powerful mechanism with which to partition your code into components that can be shared between multiple applications. In addition to providing access to the same interface used by BREW components (making it easy for developers to learn to use your extensions), the platform provides the ability to offer extensions seamlessly through the BDS to both your own applications and those of other companies, letting you strike the perfect balance between internally sharing code and making your technologies available to other application developers in a profitable way.

For More Information

QUALCOMM BREW: http://www.qualcomm.com

QUALCOMM BREW Extensions: http://brew.qualcomm.com/brew/en/developer/resources/ad/extensions.html

About the Author

Ray Rischpater is the chief architect at Rocket Mobile, Inc., specializing in the design and development of messaging and information access applications for today's wireless devices. Ray Rischpater is the author of several books on software, including Development Including eBay Application Development and Software Development for the QUALCOMM BREW Platform, both available from Apress, and is an active Amateur Radio operator. Contact Ray at kf6gpe@lothlorien.com.

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics