In Part I we mapped out the plan: We will build, test, and try to sell a small commercial BREW-based application. In Part II, we created our .MIF file and generated a ClassID for the app. What's next?
True BREW Testing (TBT) is no longer subsidized, so I've decided to use the converter to demonstrate and explain several technical issues that you will encounter while developing your own commercial application. I will not, however, submit it for TBTit costs too much for doing a tutorial. Instead, in future articles I'll use my own experience with the process to identify and describe the challenges associated with getting an application through True BREW Testing and onto a carrier's Application Download Server.
In this installment, I'll cover the code generated by the BREW AppWizard (which we introduced in Part II). We'll examine its hooks into the Application Execution Environment (AEE) and walk through the initial modifications required by practically every BREW application.
First, we need to define some terms. When I use the word "application," I am referring to the ultimate goal of a development effort. It is the conceptual end product with which the user interacts. For example, the "Converter" is an application.
A BREW "module" is a container for all of the application's functionality. When the user starts an application by selecting its icon in the BREW Application Manager, the AEE causes the associated module to be loaded into memory.
In BREW terms, an "applet" is a discrete unit of functionality that the module loads and the AEE executes. A module may consist of more than one applet, but only one applet can be active at any given time (because BREW is single-threaded). For example, if we added a calculator (with its own unique class ID) to the "Converter" application, the latter would consist of two applets: the currency converter and the calculator. In the context of a module that contains only one applet, an "applet" is the same as an "application."
In case I haven't confused you enough already, a BREW applet is often referred to as a "class"but not a C++ class. This makes sense when you consider the fact that a class ID, like the one generated using the BREW MIF Editor in the last article, uniquely identifies an applet.
If you look in "AEEAppGen.h," in the SDK's include directory, you will find that
AEEApplet is a
typedef for a
struct. Not too surprisingly, this
struct contains key information about the applet, including
IShell pointer that provides the applet with access to shell services;
IModule pointer that keeps track of the module that "owns" the applet;
IDisplay pointer that gives the applet the ability to write to the screen;
pAppHandleEvent, a pointer to the applet's event handling function; and
pFreeData, a pointer to a function that frees all of the applet's dynamically allocated data just before the applet closes. After you've laid the groundwork, including the creation of an event handler and a
m_pIDisplay are the only members that you'll use throughout the remainder of the application. The AEE will take care of the rest of the
Figure 1 shows Visual Studio, pretty much as it appears after the AppWizard has finished generating the BREW project. In the right-hand pane, you can see the source code generated by the AppWizard, with all of its comments removed.
Figure 1. The source generated by the AppWizard.
A few files have been #included at the top of the "converter.c" source file. "AEEModGen.h" and "AEEAppGen.h" contain declarations that
AEEClsCreateInstance() needs in order to instantiate our module and applet. "AEEShell.h" contains support for the
IShell services that the application will need throughout its lifetime. "AEEFile.h" is #included because we asked the wizard to provide file support back in Part II of this series.
#include lines, we come to the prototype for the application's event handler. At runtime, the AEE will dispatch all the application's events to this function. In most cases, our applet will return TRUE if it handles the event and FALSE if it does not. Returning FALSE gives the AEE a chance to perform default processing in response to the event.
As you can see, the wizard also included a couple of source files in our project, namely "AEEAppGen.c" and "AEEModGen.c". These files contain the source necessary to create the module and add the application's applets. When the user selects the application icon in the BREW Application Manager, the AEE kicks off the entire process by calling
AEEMod_Load(), contained in "AEEModGen.c". Also in "AEEModGen.c",
AEEMod_CreateInstance() calls the
AEEClsCreateInstance() function that an application's developer is obligated to provide, the latter function being the glue that binds our applet to the AEE.
This brings us to the definition of
AEEClsCreateInstance(). As you can see, the function's signature includes an
AEECLSID (AEE Class ID), a pointer to an
IShell instance, a pointer to an
IModule, and a pointer to a
When our application's
AEEClsCreateInstance() is called, the first parameter,
ClsId, will always contain the class ID from the application's module information file (.mif). Recall that, in Part II, the MIF Editor saved the ID we generated in a .bid (BREW ID) file. We have to #include this .bid file in "converter.c". This is where our source code looks for the class ID. In fact, if you open the "converter.bid" file, you'll notice that the hex class ID is defined as
AEECLSID_CONVERTER. By scanning ahead to the first if() in the
AEEClsCreateInstance() function, you can see that
AEEApplet_New() (more in a moment) is only executed if
ClsId == AEECLSID_CONVERTER. In plain English, we only get an instance of our applet if the class ID passed in from the AEE (obtained from the .mif file) is the same as that contained in the applet's .bid file.
If you switch from a locally generated class ID to a "real" one, created by the web-based generator, don't forget to update the class ID in the .mif. Otherwise when you attempt to run the application, it will fail to start, and the emulator will display the message "Unknown Error (1)". Remember that obtaining a new .bid file from the Extranet doesn't automatically update the class ID in the .mif. To resolve the issue, open the .mif in the MIF Editor, click the button marked "Browse for BID file", and select the updated file you downloaded from the extranet. After you've saved the updated .mif, your application should again function as expected.
Next, we arrive at the call to
AEEApplet_New(), defined for us in "AEEAppGen.c".
AEEAppletNew()'s main function is to allocate memory for, and populate, the
AEEApplet instance and then add it to the module by calling
AEEMod_ListAdd(), defined in "AEEModGen.c". The arguments
ClsId, pIShell, po, and
converter_HandleEvent are used to populate the corresponding members of the applet's
AEEApplet_New()'s first argument is the size of the memory chunk required to hold the data the applet must track throughout its lifetime. The wizard only knows that this chunk has to be big enough to hold the applet's
AEEApplet instance. It is up to the developer to specify any additional data requirements and ensure that
AEEApplet_New() allocates enough memory. We'll revisit this when we create Converter's applet-specific data structure. When
*ppObj will point to the new applet instance.
Note that the final argument passed to
AEEApplet_New() is NULL. If we take a peek at
AEEApplet_New()'s signature in "AEEAppGen.c", we see that this final parameter is of type
PFNFREEDATA. If we look in "AEEAppGen.h", we find that
PFNFREEDATA is a
typedef for a pointer to a
void function taking a parameter of type
IApplet*. If we supply the proper argument in the call to
AEEApplet_New(), the AEE will automatically call the function we name, before it terminates the applet. The
freeData() function (you can use any name you want) provides an opportunity to free all of the applet's dynamically allocated data.
Lastly, we have the definition for
converter_HandleEvent(). BREW applications are entirely event driven, thus a function of this ilk is an absolute necessity.
Figure 2. The main screen for the Converter application.
The wizard included two essential events in the handler, namely
EVT_APP_STOP. Note that, if
FALSE, the applet will close without the AEE generating an
EVT_APP_STOP. Thus, don't free memory in the
EVT_APP_STOP handler if the corresponding allocations take place in the
EVT_APP_START handler. The safest approach is to always release application-level dynamic objects in the
Filling in the Blanks
The wizard has supplied us with a very rudimentary framework. It's now time to create our application-specific data structure and code the application's
Figure 3. Additional types that are needed in the applet data structure.
Figure 2 shows the main screen for the Converter application. It consists of two list controls, a text control, a static text control (labeled "Equivalent") and a soft key control. The list controls and the soft key are all variants of
IMenuCtl. Thus, our applet data structure needs three
IMenuCtl pointers, an
ITextCtl pointer, and an
IStatic pointer in order to keep track of the main screen's controls.
Figure 3 displays some additional types that are needed in the applet data structure and, at the bottom, the actual applet data structure.
The first type is an enumeration named
PHONE. The applet data structure has a member of this type so that the code can include customizations specific to individual handsets.
currency has two elements. The first one,
pUSD, will point to a
double containing the US dollar equivalent for one unit of the foreign currency described in the string pointed to by
curDesc (currency description). Converter will maintain a list of these currency objects, thus we need to define the contents of a list
img type binds an image to its intended screen coordinates.
Finally, this brings us to the
typedef for the application data structure, named
converter. As you can see, its first element is of type
AEEApplet. The first member of the application data structure must be reserved for the
AEEApplet part that binds the applet to the AEE.
IStatic pointers are included, so that we can interact with user-interface (UI). Note the
IControl pointer. It can point to any of the UI controls so we'll use it to keep track of the control that currently has the input focus.
Figure 4. The modified
We'll consider a memory allocation failure a fatal error, and exit the application after displaying a message
(m_fatalMsg) to the user for a few seconds. While the error message is displayed, we need to communicate the application's busy state to the user. Hence,
m_pImg will hold the hourglass cursor that will be displayed while the user waits for the error message timer to expire. Since we might not be able to allocate enough memory when the failure occurs, we'll load the hourglass cursor and error string at startup and keep them around for the life of the applet.
Lastly, the purpose of each of the three flags (
m_newAmt, m_decIn, m_fatalFlag) will become apparent when we cover UI navigation in a future article.
Next, Figure 4 shows the modified
The first modification tells
AEEApplet_New() to allocate enough memory for an object of type
converter (which includes an
AEEApplet), instead of only requesting enough room for an
AEEApplet. Also notice that the final parameter to
AEEApplet_New() is no longer NULL. We've specified a
freeData() function for the applet.
Figure 6. The definition of the
If the call to
succeeds we pass the newly created
object to an
function that initializes all the members of the
r instance. Note that, if
fails, a call to
is necessary to free the memory allocated in the call to
Figure 5 shows the
init() function. The UI control instantiations are relegated to a
bldMainScrn() function that we'll explore in a later installment. The
getData() function retrieves the currency data from file storage and uses it to create the currency list that is stored in RAM for fast access.
Lastly, Figure 6 shows the definition of the
freeData() function, specified in the call to
AEEApplet_New(). Under normal circumstances, this function will be called by the AEE after
EVT_APP_STOP is processed.
Originally published in the BREW Wireless Resource Center