Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Building a Commercial App
Part III: Getting Started with Source Code

The adventure—building, testing, and selling a small commercial BREW-based app—continues. Last time, we created our MIF file. Now let's see the code generated by the BREW AppWizard and how it hooks into the Application Execution Environment. Then we'll walk through the initial modifications required by practically every BREW application.


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 TBT—it 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.

The Framework
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 m_pIShell, an IShell pointer that provides the applet with access to shell services; m_pIModule, an IModule pointer that keeps track of the module that "owns" the applet; m_pIDisplay, an 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 freeData() function, m_pIShell and 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 AEEApplet.

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.

After the #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 void pointer.

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 instance.

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 AEEApplet_New() returns, *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_START and EVT_APP_STOP. Note that, if EVT_APP_START returns 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 freeData() function.

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 initAppData() and freeData() functions.

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.

Next, type 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 node. The 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.

The IMenuCtl, ITextCtl, and 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 AEEClsCreateInstance() function.

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 AEEClsCreateInstance() function.

Figure 5.
The init() function.

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 freeData() function.
If the call to AEEApplet_New() succeeds we pass the newly created converterobject to an init()function that initializes all the members of the converter instance. Note that, if init() fails, a call to IApplet_Release() is necessary to free the memory allocated in the call to AEEApplet_New().

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

Murray Bonner is President of Golden Creek Software Inc., a BREW software developer and a provider of custom training and software development services. Murray has been working with the BREW SDK for nearly 2 years and writes to share his BREW development experience, gained mostly at the expense of his hairline.
Comment and Contribute






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



Thanks for your registration, follow us on our social networks to keep up-to-date