BREW Up a Better Way to Synch Data Between Apps Using a Singleton

BREW Up a Better Way to Synch Data Between Apps Using a Singleton

s a BREW developer, you’re probably intimately familiar with the process of writing applications, but perhaps not as familiar with the process of writing extensions. In BREW, an application is a class that can only have one instance. This means that a BREW application is a singleton, an example of a class which ensures that only one instance of the class can be created (for an in-depth examination of the singleton design pattern, see Design Patterns, by Gamma, et al.).

Occasionally, however, you may need to create a BREW extension, often to manage shared resources or a shared interface: a good place in which to use a singleton. For example, say you have a private extension between two applications that controls access to data in /brew/shared on the file system. In a case like this, you want to ensure that the extension accurately represents the state of the file system; if both applications are running (say one in background and one in foreground mode), a singleton can greatly simplify synchronization.

The typical patterns for implementing a singleton require either a global variable or a class member variable. Unfortunately, neither of these constructs is available in BREW when compiling for Over-the-Air (OTA) provisioning. This has driven many developers to experiment with the local thread storage API available in later versions of BREW, bizarre tricks like writing pointers to active data in memory to configuration files, or simply to despair.

Fortunately, there is a better way: leveraging the nature of BREW’s IModule interface, which itself is a singleton enforced by BREW at creation time and using it to implement the singleton contract. To do this, you must first understand a little about how BREW instantiates components.The Lifecycle of a BREW Component
Before just subclassing BREW’s IModule interface in the hopes of conjuring up a singleton, it’s important to understand exactly for what a BREW IModule is responsible. Unfortunately, the existing documentation in this area is fuzzy at best.

BREW modules?such as applications and extensions?are the fundamental unit of code loading. In BREW, a module can contain the implementation for multiple classes, such as an application or multiple extensions. Your module is accompanied by a module information file, which provides information regarding the classes your module contains. When BREW loads a module from disk, it represents the module with a unique instance of the IModule class. During execution, this IModule instance is a factory, creating instances of the classes it contains when requested by BREW or other applications. Once all instances of these classes in the module are released, the module itself is released, freeing the memory it used to contain its code and data.

Using the IModule Interface
Normally, you don’t have to worry about the implementation of IModule. QUALCOMM BREW provides a helper file, AEEModGen.c, which contains reference source code for modules. You implement a global function, AEEClsCreateInstance, which the AEEModGen.c file invokes in a timely manner when loading your component. Internally, however, AEEModGen.c implements AEEMod_Load, which is the entry point for all modules (a quick peek at QUALCOMM make files confirms this, too?the make file instructs the linker to ensure that AEEMod_Load is the first function in the compiled module). AEEModGen.c also implements the four methods of IModule:

  • CreateInstance: BREW invokes this method when it needs an instance of a class provided by the module.
  • FreeResources: this method frees additional resources consumed by the module prior to its destruction.
  • AddRef: this method increments the module’s reference count.
  • Release: this method decrements the module’s reference count and cleans up after the module when its reference count reaches 0.

Implementing Singletons
A little-known fact makes the implementation of a singleton quick and easy: BREW only loads and creates a module once?regardless of how many times it is required. This makes sense, because loading a module is an expensive process?it requires updates to system tables about what classes are available and also brings in the executable from disk and allocates ancillary support structures. By implementing your class that implements the IModule contract, you can add support for singletons, class variables, or anything else you want to ensure only exists once per module. This is a little trickier than simply writing a normal BREW class, because instead of just writing a class and its methods, you must also write the module load function.

The IModule Subclass Representation
The code below creates the data structure that represents the IModule subclass instance. But first, take a look at the in-core representation of the singleton’s IModule subclass:

typedef struct _SSingletonModule                                     {   DECLARE_VTBL(IModule)    uint32           nRefs;			   IShell           *pIShell;		   PFNMODCREATEINST	pfnModCrInst;    PFNFREEMODDATA	pfnModFreeData;   /// Our singleton pointer.   SSingletonData   *pInstance;} SSingletonModule;

This is pretty basic stuff if you’ve built extensions before. The first line declares the virtual function table for this class, which implements IModule. The next lines are the typical baggage carried by all classes?a reference count and a pointer to the system shell. Next are two function pointers required for static extensions (those extensions compiled in-line with the ROM), but unused for OTA applications.

Author’s Note: I choose to keep these (and code you see in later listings) for static extensions in all of my code, because I’ve been fortunate enough to work in settings where I’ve needed to quickly port existing code to work in ROM for handset manufacturers.

Finally comes the secret sauce: the last line will contain a pointer to the singleton class when it’s created.

The IModule Entry Point
Next, you need to write the entry point for the module. This isn’t hard, both because there’s a reference implementation in AEEModGen.c and because with one notable exception, it’s the same as the first-stage constructor for other classes (Listing 1).

This code has two interesting chunks: the plethora of preprocessor directives in declaring the entry point (the first eighteen lines) and the simulator-specific bits in AEEStaticMod_New. These are necessary because the manner in which the module is loaded depends on the platform:

  • In the emulator, AEEMod_Load is exported from the module’s DLL.
  • In some production environments internal to QUALCOMM, the entry point is actually called module_main.
  • In all other cases, for OTA configuration, the entry point is AEEMod_Load, and it’s declared the entry point during the link process.

Regardless, AEEMod_Load is really just a wrapper function for AEEStaticMod_New, which is the function static builds invoke when creating static modules. This function is much like the AEEClsCreateInstance for any class, with two key differences: it must accept pointers to creation and destruction functions when invoked during static builds, and; it must tuck away the shared pointer to the BREW helper utilities on the emulator. This work occurs in the eight lines wrapped by an AEE_SIMULATOR preprocessor check in AEEStaticModNew, and tucks away the pointer to the helper function virtual table in the global variable used by the module. If you’re really curious about how this works, take a close look at AEEStdLib.h in any version of the QUALCOMM BREW SDK.

The IModule Subclass Method ImplementationsThe AddRef and Release methods for the new IModule class are just like any other AddRef and Release, so they’re not shown here. The FreeResources is an empty function, too, since there’s no special file or memory allocation on a per-module basis that needs to be tidied up.

What remains is CreateInstance, the method BREW invokes in the module to obtain class instances from the module. It’s what calls the class’ AEEClsCreateInstance function. The module’s CreateInstance functionm must do the same work as the existing IModule’s CreateInstance as well as enforce the singleton contract (see Listing 2).

The function shown in Listing 2 has two jobs: to enforce the singleton contract and to create the singleton instance if one is not already available. The code is quite straightforward. On entry, if the module is being asked for an instance of the singleton class and the module’s pInstance member has a pointer to the singleton class, it simply increments the reference count for the existing object and returns it. This guarantees that any client of the module always receives the same instance of the object. Otherwise, it creates the object in the usual way, either by calling the static-compiled factory method if one was handed to AEEStaticMod_Load, or by invoking your AEEClsCreateInstance (which is the usual case).

The rest of the singleton extension is coded just like any other extension: its entry point is CreateInstance, and it is responsible for building the public interface with its virtual table structure, and private data structure that’s cast-compatible with the public structure.

As you can see, it’s easy to create a module that can implement the singleton contract for one or more of its classes. While the example I show demonstrates a single class, you can extend this easily by adding additional slots in the module’s data for each singleton class instance, then adding the appropriate enforcement in your module’s CreateInstance method.

In addition to allowing you create singletons, the knowledge that the IModule instance for your module is only created once gives you the opportunity to share other data between class instances. This gives you an effective way to create variables with class-wide scope. The possibilities for this are virtually limitless, from sharing data between class instances to pooling shared resources for compression or other algorithms.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist