MobileGetting Started with the BREW SDK, Part II

Getting Started with the BREW SDK, Part II

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Part II – Hello BREW on the Emulator

If you wish to perform the steps outlined in this article, you require the following items:

  1. Microsoft Visual C++ 6.0® or better, and
  2. Version 1.1 of the BREW SDK.

To view the minimum system requirements and obtain specific, SDK installation instructions, view the SDK 1.1 readme file.

Note that I assume you have read What is BREW? and Part I – Preliminaries, the preceding articles in this series. I further assume that you have created a module information file (helloBREW.mif) and a BREW Applet Resource file (helloBREW.bar) and have copied them to ...yourBREWdirExamplesmif256Color and ...yourBREWdirExamplesen256Color respectively. Also, the helloBREW_res.h file produced by the BREW Resource Editor must reside in the ...yourBREWdirExampleshelloBREW directory, along with the application source file created by the BREW Application Wizard (“helloBREW.c“). If you want more detail, please read Part I as noted above or consult the documentation supplied with the SDK.

Throughout this example, you need to know that file naming is important. Specifically, the application’s directory and module information file (.mif) must have the same name (i.e. prefix) as the target .dll. Note that you can specify the .dll’s name on the Link tab of the Visual C++® Project Settings dialog (choose Project>Settings). If you worked through Part I, you already have the appropriately named files in the proper directories.

Lastly, note that the terms “applet” and “application” are used interchangably throughout this article.

Setting Up Visual C++

Figure 1




Click here for larger image

In Part I, the BREW Application Wizard™ took care of most of the necessary project settings. We still need to supply an executable file to run the .dll during debugging sessions and ensure that BREW will be able to find the application .dll. To satisfy the first requirement, we need to supply the path to BREW_Emulator.exe, as shown in Figure 1.

We satisfy the second requirement by making sure that the linker writes helloBREW.dll to the project’s root directory ...helloBREW), instead of the default location ...helloBREWdebug. This is necessary since the emulator will automatically look for helloBREW.dll in a directory named, you guessed it, helloBREW. Figure two shows how the Output file name should appear on the Link tab of the Visual C++ Project Settings dialog.

Figure 2




Click here for larger image

Understanding the Source Code

First, let’s take a brief look at the source files the Application Wizard included in the helloBREW project. If you haven’t already done so, open the helloBREW project with Visual C++, then select FileView and expand the Source Files folder. You should see 3 source files: AEEAppGen.c, AEEModGen.c, and helloBREW.c. AEEModGen.c and AEEAppGen.c provide the glue that binds our application to the BREW Application Execution Environment (AEE). A module, governed by AEEModGen.c, is essentially a container for 1 or more, possibly interdependent, applets. When an end user invokes an application, the AEE creates the module which, in turn, instantiates the application by calling the AEEClsCreateInstance() function that each application must provide.

This leads us to the reason that the terms “applet” and “application” can be (loosely) used interchangably. To see why this is so, think of an “applet” as the container for everything that merely allows developer-created functionality to interact with the AEE and run on a device. For example, such a container might supply an interface to shell services and an interface to the device’s display. Thus, we can think of an “application” as an “applet” plus the functionality created by the developer. In fact, if we were using C++ for this example, helloBREW (the developer-created functionality) would publicly inherit from BREW’s AEEApplet struct. Viewed in this light, an application “is a” applet. AEEAppGen.c provides the AEEApplet_New() function that helloBREW calls to set up the AEEApplet part of the helloBREW application. More on AEEClsCreateInstance() and AEEApplet_New() shortly.

Figure 3




Click here for larger image

Now that we have a general idea of how the pieces fit together, let’s take a look at the skeleton source code created by the BREW Application Wizard. Figure 3 shows this source code, stripped down to the bare essentials. With respect to the included headers, you might have guessed that AEEModGen.h contains the declarations for AEEModGen.c. AEEAppGen.h is similarly paired with AEEAppGen.c. AEEShell.h provides declarations for the shell interface API. If you want to learn more about this important interface, look up IShell in the BREW API Reference included with the SDK documentation. We’ll skim the surface of this API when we add our own code to helloBREW.c.

For the moment, let’s skip past the declaration for helloBREW_HandleEvent() and examine AEEClassCreateInstance(). This function is where helloBREW meets the AEE. AEEModCreateInstance(), defined in AEEModGen.c, calls the applet-specific AEEClassCreateInstance() function whenever the AEE passes on a request for an application to start. Since BREW is single-threaded, only one applet can be active at any given time, regardless of the number of applets contained in a module. An applet can be started by another applet, via a call to ISHELL_StartApplet(). In this case, the current applet is suspended and AEEClassCreateInstance() is called using the class ID supplied in the call to ISHELL_StartApplet(). Obviously, AEEClassCreateInstance() needs a logical branch for each class ID contained in the module. The helloBREW applet is the only one in our module, so our AEEClassCreateInstance() only needs to handle one class ID.

From the previous article, recall that we generated helloBREW’s class ID in helloBREW.mif. The MIF Editor created helloBREW.bid which #define‘s this class ID as AEECLSID_HELLOBREW. Clearly, the module obtains the class ID from the module information file (“helloBREW.mif”) and passes it as the first parameter to AEEClsCreateInstance().

The second parameter, pIShell, is an IShell pointer that the AEE supplied to the module when it was first loaded. Our AEEApplet structure’s m_pIShell member will be set to pIShell in the call to AEEApplet_New(). This IShell pointer provides helloBREW’s access to all the API’s in the IShell interface.

The third parameter, po, is a pointer to the module that contains helloBREW. This parameter will be used to initialize the AEEApplet struct’s m_pIModule parameter in the call to AEEApplet_New(). None of our code will refer to this pointer. The AEEAppletRelease() function, defined in AEEAppGen.c, needs this pointer in order to free the dynamically allocated module upon termination of helloBREW.

Lastly, ppObj is a pointer to a generic pointer. A void ** is used here because *ppObj may point to either of two things. Fortunately, we need only concern ourselves with objects of type IApplet. In case you’re a little rusty with pointer usage, double indirection must be used here so that *ppObj points to a valid IApplet object when AEEApplet_New() returns. If ppObj was simply declared as void *ppObj (i.e. single indirection), ppObj would not be changed when AEEApplet_New() returns. The C language’s parameter passing mechanism would provide AEEApplet_New() with a mere copy of the pointer ppObj, leaving the original unchanged back in AEEClsCreateInstance(). Since we want AEEApplet_New() to modify the same pointer referred to in AEEClsCreateInstance(), we have to pass the pointer by reference instead of by copy.

The second from last parameter passed to AEEApplet_New() is a pointer to our application’s event handler. The AEE will call this function whenever it receives an event destined for helloBREW. If the last parameter were not NULL, we would need to pass a pointer to a function that frees our application’s dynamically allocated data upon termination. This PFNFREEAPPDATA is a pointer to a function receiving a parameter of type IApplet * and returning void. For demonstration purposes, we’ll include such a function in helloBREW.

This brings us to the skeletal definition of helloBREW_HandleEvent(). The first parameter is a pointer to to our IApplet instance. Within this function, pi can be used to access the applet’s m_pIShell and m_pIDisplay data members. We will discuss this in greater detail when we add our own code to helloBREW.

The second parameter, eCode, is, as implied by its context, an event code used to discover what action led to the call to helloBREW_HandleEvent(). The last two parameters contain event-specific data. An exhaustive list of events, together with a description of the data supplied via the event-specific parameters wParam and dwParam, can be found in the header file AEE.h. A more user-friendly enumeration of these events, together with the key codes associated with the various key events and a description of the event-specific contents of wParam and dwParam, can be found in Appendix A of the BREW SDK User’s Guide. It would be a good idea to familiarize yourself with the events that an application can handle.

Adding Source Code

Now we’ll flesh out helloBREW by adding our own code. To see a simpler version of “hello”, look at helloworld.c in ...yourBREWdirExamplesHelloWorld. To avoid duplication, and make our example a little more interesting, we’ll create our own applet data structure, supply InitAppData() and FreeAppData() functions, load strings from the resource file (helloBREW.bar, see Part I – Preliminaries), and display them using a BREW IStatic control.

Figure 4




Click here for larger image

As shown in Figure 4 above, we need to add AEEStdLib.h to the list of included headers. This header contains the definitions for MALLOC() and FREE(), in addition to the other helper functions described in the BREW API Reference.

Additionally, our application requires access to its class ID in helloBREW.bid and the string ID’s in helloBREW_res.h.

Lastly, Figure 4 shows a couple of constant definitions; one for buffer size and one for the number of pixels between the screen edges and the static control’s boundaries.

Figure 5




Click here for larger image

Figure 5 starts off with the definition for our applet data structure. First off, its very important to note that an application cannot contain any static (global) data. Thus our application data structure is defined globally as a struct (for C programs, at least), and we let AEEClsCreateInstance() look after its instantiation on the heap. This brings up another important point. Stack space is extremely limited on the device. The Kyocera QCP3035e, for example, has only about 500 bytes of available stack space. Clearly, you need to stay off the stack as much as possible by keeping the depth of function calls to a minimum, allocating non-atomic local variables on the heap, and passing pointers for everything bigger than 4 bytes.

The application data structure provides a container for everything the programmer needs to maintain throughout the life of the application. This includes the AEEApplet part of an application, where, for example, access to shell, display, and module pointers is maintained. It is imperative that the AEEApplet part be included in this data structure as the first member. The only other thing needed in this data structure is a pointer to the IStatic control that will display our strings.

Figure 6




Click here for larger image

Figure 6 shows our modified AEEClassCreateInstance() function. The first, and arguably most important, modification involves the first parameter passed to AEEAppletNew(). In addition to the space required for the AEEApplet part of helloBREW, we need to have AEEApplet_New() allocate space for the applet data structure’s IStatic *. This is accomplished by passing sizeof(hb_app) as the first parameter to AEEApplet_New(), instead of just sizeof(AEEApplet).

Next, in order to add a FreeAppData() function to helloBREW, we need to pass a pointer to it (hb_FreeAppData()) as the last parameter to AEEApplet_New(). This function will be registered with the AEE and automatically called when the application is terminated. Figure 9 shows the definition of hb_FreeAppData(). As you can see, its sole task is to release the IStatic control by calling ISTATIC_Release().

The last change to AEEClsCreateInstance() is the call to hb_InitAppData() where, as shown in Figure 9, the IStatic * member of our applet data structure is initialized to NULL.

Figure 7




Click here for larger image

As Figure 7 shows, helloBREWHandleEvent() contains the bulk of the modifications to the initial source generated by the BREW Application Wizard. At the top, we declare a pointer to our applet data structure and assign it the appropriately cast IApplet * passed in as the function’s first parameter. Throughout the event handler, this pointer is used to access the m_pIShell member of the AEEApplet portion of helloBREW, in addition to providing access to the m_pIStat pointer we added to account for our IStatic control.

The first local variable declared at the top of EVT_APP_START is of type AEEDeviceInfo. Among other things, this structure includes members that will hold the device’s screen dimensions and color depth. An exhaustive description of data members can be found in the Data Structures section at the back of the BREW API Reference. In view of my earlier warning about limited stack space, we should really allocate memory for this struct on the heap and have a locally declared pointer keep tabs on it. Since this small program is in no danger of running out of stack space, for simplicity’s sake I declared the struct as a local variable.

Next, we have a simple rectangle, that we’ll use to size and position the static control, and a couple of buffers that will hold m_pIStat‘s title and text, respectively. AECHAR is a typedef for uint16 (see AEE.h), which is, in turn, a typedef for unsigned short (see AEEComdef.h). Thus, an AECHAR is used to hold Unicode, or wide, characters. Hence wbufTtl and wbufTxt are pointers to null terminated strings of wide characters. Refer to the Helper Functions section of the BREW API Reference to learn about facilities for the manipulation of wide character strings.

After testing the validity of m_pIShell, we call ISHELL_GetDeviceInfo() to populate di. The screen dimension members di.cxScreen and di.cyScreen are then used to set the dimensions of the rectangle that will hold the location and size of the static control. In the screen coordinate system, the origin is the top left corner of the display and x increases as we move to the right while y increases as we move down.

The call to ISHELL_CreateInstance() specifies AEECLSID_STATIC as the class ID of the requested interface. Assuming success, m_pIStat will point to a valid IStatic when this call returns. Class ID’s for other interfaces can be found in aeeclassids.h. After creating the IStatic, we set its rectangle by calling ISTATIC_SetRect(), passing it a pointer to the rectangle structure we just initialized.

After allocating memory for the previously declared buffers, we call ISHELL_LoadResString() twice: once for the IStatic‘s title and once for its text. The buffers then supply the title and text to ISTATIC_SetText(). ISTATIC_SetProperties() is used to position the title and text and a call to ISTATIC_Redraw() displays the static control on the screen. More information about these function calls can be found in the IStatic section of the BREW API Reference.

Note that BREW does not tolerate memory leaks. Thus you need to ensure that all calls to MALLOC() are paired with a corresponding call to FREE() and all calls to ISHELL_CreateInstance() have a corresponding call to I*_Release(). The two buffers are FREE()‘d at the bottom of EVT_APP_START and helloBREW frees the IStatic instance by calling IStatic_Release() in hb_FreeAppData().

Figure 8

Figure 8 shows the remainder of helloBREWHandleEvent(). An EVT_KEY event has been added in order to implement standard handling of the phone’s “CLR” key. By returning FALSE when the “CLR” key is pressed, we signal that the AEE should close the application. On the phone, the intended meaning of the “CLR” key is “back up one level” and your applications should implement this behavior.

Since hb_FreeAppData() looks after freeing m_pIStat, our EVT_APP_STOP event handler doesn’t have to do anything.

Figure 9

Running helloBREW

Figure 10




Click here for larger image

Start the BREW Emulator, either by clicking the shortcut included in the BREW SDK program group or clicking the red exclamation point button in Visual C++. Use the left and right direction keys to locate helloBREW in the BREW Application Manager and, with the large 85×40 image displayed on the emulator screen, press “Enter” on your PC’s keyboard. The emulator’s display should appear similar to the second frame of Figure 10, above.

Your emulator display will not be identical to first frame of Figure 10 as I’ve specified a separate MIF directory for helloBREW.mif. I’ve done this in order to avoid having to hunt for helloBREW’s icon among all the SDK’s sample program icons. That is, the emulator only displays those applications that have a valid .mif file in the emulator’s currently specified MIF directory. You can move helloBREW.mif to, for example ...yourBREWdirExamplesyourmif256Color and set this as the emulator’s MIF directory. Please refer to the BREW MIF Editor Guide for instructions on setting the MIF directory.

Debugging With the Emulator

If you followed the instructions in the Setting Up Visual C++ section at the beginning of this article, you can use the Visual C++ debugger as you normally would. For example, to ensure that AEEClsCreateInstance() is working as expected, you could place the cursor inside the function and click “Run to Cursor” on the debug toolbar. From there, you can step through the function and watch certain variables, step into other functions, or position the cursor in another function or event handler and click “Run to Cursor” again. If you are unfamiliar with the Visual C++ debugger, you should read the associated sections in the documentation that came with the compiler.

If the emulator does not bring up the Sharp Z800 phone skin upon startup, you can change it by selecting File>Load Device…. Select the Z800 phone skin from the *.qsc files available in ...yourBREWdirdevices and click open to have the emulator load the device.

As a final note, the emulator included with the version 2.0 SDK incorporates stringent heap checking that will help you find memory leaks, array overruns, and other errors early in the development process. The version 2.0 emulator can be used in conjunction with the 1.x SDK’s. Check the SDK 2.0 installation instructions for detailed information including issues associated with having more than one SDK version installed on a single machine.

© Copyright 2002, Golden Creek Software Inc.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories