September 20, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Writing BREW Extensions

  • April 21, 2005
  • By Ray Rischpater
  • Send Email »
  • More Articles »

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;
}




Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel