Porting a mobile application from Java to BREW is not a trivial task. Developing an application from scratch is also quite much harder in BREW than in Java. Generally, Java Microedition is a much higher-level programming platform than the combination of C/C++ and the BREW API. There are quite a number of reasons for this:
There are substantial differences between the programming languages. It is possible to program for BREW in C++, though most BREW examples you will see on the net are coded in plain C. Yet even C++ is not so similar to Java as you might initially think.
The BREW phone platform imposes some pretty heavy restrictions on what you can do and what not. We will point out some of the significant restrictions later on.
The ARM compilers (the compilers for ARM processors which are used in BREW phones) do not support some important features of the C++ language like exceptions or static variables.
The basic concepts of BREW and Java Microedition are completely different. BREW is an exclusively event-driven platform, while J2ME programs usually rely on using long loops and blocking operations.
The BREW API is written for programming in C, which means it does not use OOP, while Java is strictly object-oriented. As a result the programming APIs of BREW and J2ME are completely different.
These are just a number of the problems one encounters when developing or porting an application for BREW. Porting normally becomes as heavy as rewriting almost the whole application. This process not only takes a long time but it usually introduces lots of new bugs that have to be fixed and leads to frustrated developers. We do not want this.
The BREW Framework targets to eliminate the problems mentioned above. The result is that porting or developing with the framework is several times faster, much less error-prone and much less frustrating. When porting, the resulting source code is very, very similar to the original code, which makes it much easier to maintain later on. Code produced with the framework is also way more readable than code written in the traditional BREW programming style.
Let's be more specific and take a look at the key problems in BREW programming one by one and see how the framework helps:
The BREW Framework solves these two problems by wrapping the BREW APIs into classes that provide the same interface as the equivalent classes in J2ME. For example, there is a String class in Java - the BREW Framework also provides a String class, with the same interface. The BREW Framework covers the most important J2ME classes. For some parts of J2ME, for example the Stream hierarchy we do not provide equivalent interfaces, but provide classes that implement the same functionality, usually at a much higher level. For example fetching something from the Internet is done with a single call, while J2ME code for the same thing is 10-20 lines long. This is all better covered in the overview, so we will not go into further detail here.
This is a very important difference. The BREW platform is essentially single-threaded. Everything the phone does happens in a single infinite loop. When your application receives time to do something, it has to do it quickly and return execution, otherwise the phone will hang. You cannot wait for a resource to be fetched from the Internet for example. Instead you instruct the phone to fetch the result, you specify a callback function and return from your code. Then, when the result is ready it will a call your callback function and you can continue. However this means you have lost your implicit program stack context.
This is what we mean by saying that BREW is purely event-driven. This also means you cannot have a single infinite loop in your application and probably 95% of all J2ME games are coded this way.
The BREW Framework implements a Thread class for cooperative multithreading which almost completely hides the above restriction from you. You will see this best in action in the sample porting project. See also the Thread section in the overview.
Note: the Thread class uses the IThread BREW interface, which is not supported on some very old BREW phones. However, there is an older version of the framework which uses coroutines to provide similar functionality, albeit not so transparently.
One of the major pains in BREW programming is memory management. First, C++ does not provide a garbage collector. Second, with the C-style BREW APIs you do not get any destructors and you have to free the memory all by yourself. Third, the BREW application certification procedure is very strict - not only must your program release every bit of memory and every limited resource it has used, but also every time it allocates new memory, it has to check whether the memory was really allocated correctly - if it uses the NULL pointer returned by MALLOC() because there is no more free memory, the phone would restart. This, combined with the fact that you cannot use Exceptions is a real pain compared to Java programming.
The BREW Framework does not provide a garbage collector, however it implies such a programming style that actually the developer rarely has to use the new keyword - everything is hidden in the constructors and destructors of the classes in the framework. Besides, most classes are written in such a way that they can be used in the code by value instead of by reference. This means there is rarely need for using pointers. To provide for easier handling of out-of-memory conditions, the framework implements smart global new and delete which are guaranteed to not return NULL pointers even when the memory is very low. This is better explained in the memory section of the overview.
We plan to implement a real Garbage Collector in one of the future versions of the framework to make things even easier.
The ARM compilers do not support Exceptions. But the BREW application certification procedure is strict when proper error handling is concerned - accessing a NULL pointer just because something could not be fetched from the Internet would result in a phone restart. The BREW Framework cannot provide Exception support because there is no way to jump up the function stack, but it provides a number of macros and functions instead to help in this aspect. Besides, the error-prone functionality is hidden in the framework classes (which properly take care of errors) so the developer is free from all the tedious checking of return codes. You will see error handling demonstrated in the sample porting project. It is also described in the overview.
The BREW Framework provides a very rich String class with all the necessary constructors, cast operators and overloaded operators to provide for a transparent experience when dealing with Strings. Besides the String class hides from the developer the difficulties of dealing with both single-byte (char) strings and wide character (AECHAR) strings. See the overview.
For dealing with arrays, the BREW Framework implements a template Array class, which supports boundary checking and has a length() method. There are also template Vector and Hashtable classes. See the overview.
For example classes are defined in a different way - a class in C++ is split between a header and cpp file and there are many other small differences. Static methods are accessed with Class.method() in Java, but with Class::method() in C++. There are many things like this.
As part of the BREW Framework we have a utility called the BREW Translator that reads J2ME code, applies some transformations and outputs BREW code (which uses the BREW Framework). The result is not ready to compile and run, but it takes care of the most tedious differences automatically.
This is again a restriction of the compilers. The BREW Framework helps alleviate this problem by providing static getInstance() methods in the important Singleton classes in the framework. Often this removes the need for some of the static variables. Actually, this restriction is usually not so important, since most J2ME applications use a single (or several) Canvas classes and the static variables in them can just as well be non-static without sacrificing anything. Notice that in BREW you can have static constants.
The requirements cover many aspects of the application, such as memory management, resource handling, error handling, suspend/resume behaviour, etc. The BREW Framework is implemented in accordance with these requirements and usually the developer does not have to do almost anything in order to conform to these requirements - the ported, running application already meets them. For example when receiving a suspend event, the BREW Framework would release all limited resources (like Media objects) and would pause the execution of threads. On resume, it would properly redraw the screen, thus bringing the application back to its pre-suspend state. You can find more about suspend/resume in the overview.
Porting a Java application to BREW, or developing a new application is several times faster when using the framework than when starting the BREW application from scratch. There are multiple reasons for this significantly increased productivity:
Porting with the framework results in source code that is almost identical to the original sources. This is great when costs for maintenance are considered. For example, applying a bug-fix to the ported code is pretty much the same as applying the same bug-fix to the original code. You can usually use file-comparison tools like WinMerge to apply patches to both versions.
Source code produced with the BREW Framework is very readable - just as much as Java code. Much more readable than source code written in the traditional BREW programming style. This makes it easier to introduce new developers to the project at a later time.
Porting with the BREW Framework is quite easy. Normally you just follow a step-by-step guide. Even unexperienced programmers can do it under the supervision of someone more experienced. Of course, good understanding of C++ always helps.
Porting with the framework removes the large part of the unpleasant emotions the developers hit while they struggle to bring an application working on the much higher-level Java to the much lower-level BREW. This is especially valid when it comes to porting someone else's source code - it is usually quite difficult and frustrating to try to understand a badly written or poorly documented code. With the framework you do not need to understand most of it. Besides, it is rewarding to see the application running in the emulator a couple of days after the port has started.
The framework has been used for quite a lot of projects that have passed the BREW certification procedure. It is already well tested and handles some requirements automatically. Usually the developer does not have to do almost anything in order to conform to the requirements - the ported, running application already meets them.
We already covered some aspects of the framework, now let's make a more comprehensive list of the supported functionality.
Nota Bene: The framework has been used mostly for porting from the DoJa profile, so the interfaces are closer to those in DoJa (wherever there are differences between DoJa and MIDP), however it also supports some MIDP-specific methods and classes. We are working on a version, better suited for porting from MIDP. Still, since the classes in DoJa and MIDP are almost equivalent in the large part, the current version of the framework is perfectly suitable for porting from MIDP as well, as you can see from the sample porting project.
The BREW Framework implements equivalents for the most important J2ME classes. The framework classes provide the same interface and sometimes add some utility methods that are lacking in Java but are helpful while developing applications from scratch (for example Vector::sort()). Here we will cover the main classes in the framework, giving small code samples for most of them.
The String class is almost fully identical to the Java String class in terms of interface. It is immutable and uses memory sharing between copies of the same String. It provides all the necessary constructors and cast operators to work both with char* and AECHAR* strings.
There is also a StringBuffer class implemented.
Both these classes implement the Nullable interface, whose purpose is to enable the developer to use null object values without having to use pointers everywhere. You can read more about it here.
Sample Java code:
String s = null;
...
if (s == null) {
s = "Hallo!";
s = s.replaceAll("all", "ell");
String s1 = s.substring(1, 3);
int width = graphics.getFont().stringWidth(s);
graphics.drawString(s, 0, 0);
graphics.drawString("aaa" + 5 + "bbb", 0, 20);
StringBuffer sb = new StringBuffer();
for (int i=0; i<10; i++) {
sb.append(i);
}
graphics.drawString(sb.toString(), 0, 0);
String s2 = s + s1;
int len = s2.length();
int value = Integer.parseInt("123");
}
String s = NULL_OBJ; // or simply String s;
...
if (s == NULL_OBJ) { // or s.isNull(). you can read more about this in the Nullable section.
s = "Hallo!";
s = s.replaceAll("all", "ell"); //replaceAll() does not support regular expressions
String s1 = s.substring(1, 3);
int width = graphics.getFont().stringWidth(s);
graphics.drawString(s, 0, 0);
graphics.drawString((String)"aaa" + 5 + "bbb", 0, 20);
// in the above line "aaa" + 5 is actually char pointer arithmetic in C, not String concatenation.
// if the code was "aaa" + "bbb" the compiler would complain that we are trying to add two char pointers.
// in such cases we must explicitly cast the first char pointer to a String
StringBuffer sb;
for (int i=0; i<10; i++) {
sb.append(i);
}
graphics.drawString(sb.toString(), 0, 0);
String s2 = s + s1;
int len = s2.length();
int value = String::parseInt("123");
//we don't have an Integer class, instead we provide a method for parsing integer values in the String class
}
The framework provides both an Array class to replace the standard Java arrays, and Vector and Hashtable classes for the equivalent collection classes in J2ME. All these are template classes, so they can be used with different classes and primitive types (int, char, etc.). This is the reason the framework does not provide classes for Integer, Long and the other Java primitive-type wrapper classes - they are mainly used in Java for storing in collections and with the template classes in the framework there is no need for them.
There are some requirements to the classes that can be used with these template collections - the class should provide a default constructor and also, depending on the collection methods that will be used, it may have to provide a copy constructor, operator=, operator==.
Multi-dimensional arrays can also be used with Array<Array<X>>. Java has a special syntax for initializing arrays - the Array class has some constructors to help converting such code.
These classes implement the Nullable interface. They also provide copy constructors and assignment operators. Array uses memory sharing between the copies - this means copies of the same array will point to the same memory. Unlike Array, Vector and Hashtable do not implement memory sharing between different copies - the copy constructor and assignment operator make a deep copy of the collection (act as clone() in Java). This should be taken into account when porting code like:
Vector v1;
Vector v2 = v1;
v2.add("Text");
if (!v1.isEmpty()) {
...
}.
In such cases the user has to use references or pointers in the C++ code.
Sample Java code:
int[] a = null;
Vector v = new Vector();
Hashtable h = new Hashtable(); // Integer -> String
if (a == null) {
a = new int[5];
for (int i=0; i < a.length; i++) {
a[i] = i;
v.addElement(new Integer(a[i]));
h.put(new Integer(a[i]), new String(a[i]));
String value = (String)(h.get(new Integer(a[i])));
}
}
int[] b = new int[] { 1, 2, 3 };
int[][] c = new int[][] { {1, 2, 3}, {4} }; //two dimensional array with different length of the rows
Array<int> a = NULL_OBJ; //or simply Array<int> a;
Vector<int> v;
Hashtable<int, String> h; //the default constructors of Vector and Hashtable, unlike that of Array, produce a real non-null object
if (a == NULL_OBJ) {
a = Array<int>(5); //there is a constructor for a specific length
for (int i=0; i < a.length(); i++) { // length() is a method, not a member field
a[i] = i;
v.addElement(a[i]);
h.put(a[i], String(a[i]));
String value = (String)(h.get(a[i])); //the cast is redundant here, but we can leave it
}
}
Array<int> b = Array<int>(3, 1, 2, 3); // variable number of arguments. You should specify the length as a first argument
Array< Array<int> > c = Array< Array<int> > ("{ {1, 2, 3}, {4} }"); //two dimensional array with different length of the rows
// you can use the above two constructors only with native types (int, char, etc.)
Because the BREW platform is essentially single-threaded there is no way to provide real threading support, but instead we have implemented a Thread class which provides cooperative multithreading. Cooperative multithreading means that only one Thread executes at a given time and it has to explicitly yield from time to time in order to give the other threads time to execute. The Thread class is based on the IThread BREW interface, which is unofficially supported on most phones with BREW 2.1 and is officially supported since BREW version 3.1.
The Thread class is also the way to achieve blocking functionality in BREW. For example if we want our code to look like this:
if (showDialog("Quit?", "Yes", "No")) { quit(); }
then we must call this code from inside a Thread. Code like this is used very often in Java and it is impossible to have similar code in the traditional style of BREW programming - we would have to split it into two parts: 1. show the dialog and register a callback; 2. process the result in the callback function.
Using Thread resembles in many aspects the usage of Threads in Java, but there are also differences. One difference is that if the Thread is going to execute for a long time, the developer is obliged to explicitly add yielding - for example once per every iteration in an infinite loop. Another difference is that wait() and notify() are methods of the Object class in Java, while in the BREW Framework they are methods of Thread.
Since the BREW platform actually never executes more than one piece of code at a time, there is no need for providing a replacement for the synchronized keyword. Because we can have multiple inheritance in C++ there is no need for a Runnable class in the framework, eighter.
Thread does not implement Nullable and its copy constructor and assignment operator are forbidden. This means that usually references or pointers have to be used when dealing with Threads. This is usually not a problem because of the way Thread is used in Java - more to define an interface than to be used directly in the code.
Sample Java code:
class MainCanvas {
private MainThread mainThread;
public void init() {
mainThread = new MainThread();
mainThread.start();
}
}
class MainThread extends Thread {
public void run() {
String result;
...code to fetch something from the Internet in order to authenticate the user...
if (!result.equals("OK")) {
midlet.notifyDestroyed();
return;
}
while (true) {
System.out.println("Doing something in an infinite loop....");
Thread.sleep(1000);
}
}
}
class MainCanvas {
private:
MainThread* mainThread;
public:
void init() {
mainThread = new MainThread();
mainThread->start();
}
~MainCanvas() {
delete mainThread; // We need to release the memory - there is no GC. We could have used a SmartPointer instead.
}
}
class MainThread : public Thread {
public:
void run() {
String result;
WebRequest webRequest(this, Applet::getInstance().getCurrentCanvas());
webRequest.sendRequest("http://some.url.for.authentication");
if (!webRequest.getResultAsString().equals("OK")) {
Applet::getInstance().close();
return;
}
while (true) {
DBGPRINTF("Doing something in an infinite loop....");
WAIT(1000); //this is called once per iteration and is enough for yielding
}
}
}
The sample porting project is a more comprehensive example of using Threads.
This class serves as an equivalent to the J2ME Canvas class. As in Java, the Canvas class sets an interface which subclasses are supposed to implement. The methods in the class deal with painting the screen and handling application events, such as keypress events, suspend and resume. As in Java the application always has an active Canvas. There are some differences from Java: the paint() method in Java accepts a Graphics object into which to draw, while we think that it is easier to have access to the Graphics object not only in the paint() method so Canvas has a method getGraphics(). Softkey support is closer to that in DoJa - there is no Command concept as in MIDP, instead softkey events are dealt like ordinary keypress events; there is a method setSoftLabel() to set the left or the right softlabel.
Canvas does not implement Nullable and its copy constructor and assignment operator are forbidden. This means that usually references or pointers have to be used when dealing with Canvases. This is usually not a problem because of the way Canvas is used in J2ME - more to define an interface than to be used directly in the code.
Sample BREW code:
class SomeDialog : public Canvas {
String message;
Canvas& parent;
public:
SomeDialog(Canvas& parent, String message) :
parent(parent),
message(message)
{
setSoftLabel(SOFTLABEL_LEFT, "");
setSoftLabel(SOFTLABEL_RIGHT, "Ok");
}
void paint() {
Graphics& g = getGraphics();
g.drawParagraph(message, 0, 0, getWidth(), getHeight()); //takes care of wrapping the string properly
}
void processKey(int keyCode) {
if (keyCode == KEY_SOFTKEY_RIGHT) {
applet.setCurrentCanvas(parent);
}
}
}
These classes provide the means for drawing on the screen. Their interface is very close to the interface of the Java equivalents, and some additional features have been added. With these classes it is possible to draw images, flip, scale or rotate images (using an arbitrary angle), draw lines, rectangles, circles, set clipping rectangles or translate the origin of the drawing system, draw strings, draw wrapped text, change fonts, use bold or underlined fonts, compute string width, etc.
Since Images and Fonts are often initialized on the fly and stored in collections, these two classes implement the Nullable interface and provide copy constructors and assignment operators.
Graphics also implements the Nullable interface and provides a copy constructor and an assignment operator, however the copies don't share the same memory for storing things like clipping rectangle, flipping mode, color, translation, etc. - all the mutable properties. This means that the original object and the copy might end up having different properties and this may lead to undesirable results. In such cases it is advisable to use references or pointers to Graphics instead of copying by-value.
Sample Java (DoJa) code:
Image img = Image.createImage("/image.png");
graphics.drawScaledImage(img, 0, 0, getWidth(), getHeight(), 0, 0, img.getWidth(), img.getHeight()); //scale the image into the whole screen
Font font = font.getFont(Font.FACE_SYSTEM | Font.SIZE_MEDIUM | Font.STYLE_PLAIN);
graphics.setFont(font);
String s = "Hello!";
int width = font.stringWidth(s);
int height = font.getHeight();
graphics.drawString(s, 0, 0);
Image img = ResourceManager::getInstance().loadImage(IDI_IMAGE); //we assume the image is in the BAR file (the BREW application resource file) graphics.drawScaledImage(img, 0, 0, getWidth(), getHeight(), 0, 0, img.getWidth(), img.getHeight()); //scale the image into the whole screen Font font = font.getBrewFont(AEE_FONT_NORMAL, -1); //creating fonts is different in BREW than in Java // in the above line we could have used font.getBrewFont(20) to get the system font which is most close to 20 pixels in height. // this is very convenient for preparing an application for many different handsets graphics.setFont(font); String s = "Hello!"; int width = font.stringWidth(s); int height = font.getHeight(); graphics.drawString(s, 0, 0);
These two classes provide persistency in BREW. The BREW Framework does not implement the whole Stream hierarchy from Java, so Scratchpad is a replacement for DoJa's Connector.openInputStream("scratchpad://....") and provides the same functionality using a slightly different interface, again adding some additional helper methods for convenience. Scratchpad has methods for reading and writing byte arrays, bytes, integers and longs at a specific position in the scratchpad.
The RecordStore class is still work in progress and is not part of the current version of the framework. When it is ready, it will have an equivalent interface to MIDP's RecordStore class.
Sample Java (DoJa) code:
InputStream in = null;
try {
in = Connector.openInputStream("scratchpad:///0;pos=100");
DataInputStream dis = new DataInputStream(in);
int value = dis.readInt();
...use value...
} catch (IOException e) {
//handle exception
} finally {
//properly close the input stream object
if (in != null) {
try {
in.close();
} catch (Exception ex) {
}
}
}
Scratchpad& scratchpad = Scratchpad::getInstance(); TRY( int value = scratchpad.readInt(100) ); ...use value... CATCH( //handle exception );
InputStream provides reading from a file or from the BAR. Apart from the standard methods known from Java, it also provides some helper methods to read the whole stream with a single call. OutputStream provides outputting into a file. GzipInputStream implements gzip decompression. It can be used to replace the DoJa JarInflater class where compression is needed.
The BREW APIs for gzip decompression require using callbacks (because decompression could be slow), so the GzipInputStream class should be used from inside a thread.
Sample BREW code:
class SomeThread : public Thread {
public:
void run() {
InputStream inputFile("zipped.gz");
Array<signed char> arrData = inputFile.readAll();
GzipInputStream zipMemory((char*)arrData.getPtr(), arrData.length());
// we could have created the GzipInputStream object around inputFile instead
zipMemory.decompress();
zipMemory.join((Thread*)this); // wait for the decompressing thread to finish its job
Array<char> data = zipMemory.getData();
...use data...
}
}
The MediaPlayerBox provides means for playing sounds in BREW by wrapping the IMedia functionality. This class is distantly following the interfaces of DoJa's AudioPresenter and KDDI's MediaPlayerBox.
Sample BREW code:
MediaPlayerBox mpb;
MediaResource mr("resources.bar", 6100, AEECLSID_MEDIAMMF); //a sound resource from the BAR, resource ID 6100, the type is MMF
mpb.setResource(mr);
mpb.setVolume(50); //set lower volume
mpb.play();
The Applet class is one of the central classes in the framework. It serves as a central dispatcher for events, provides error handling, memory handling. From developer's standpoint, it is most important with the helper methods it provides. There are methods for checking free memory, free file system space, reading arguments from a configuration file (an equivalent to IApplication.getArgs() from DoJa), dealing with the backlight, vibration, logging, etc. Many of the methods in this class correspond to methods of Java's System class.
The Math class provides some of the methods in the Java Math class, as well as support for generating random numbers (there is no dedicated Random class).
Apart from the adapter classes listed above, there are also several classes which implement functionality without trying to conform to the interface of the corresponding Java classes (actually for some of them there are no corresponding Java classes at all). This allows us to implement them in a way that they are much more convenient to use in most of the use cases. These are also very useful when the framework is used to develop applications from scratch.
The WebRequest implements fetching resources over HTTP. Both GET and POST requests are supported. The class supports using custom HTTP headers. WebRequest implements blocking functionality so it is a Thread.
Sample BREW code:WebRequest webRequest((Thread*)this, (Canvas*)this); SEND_REQUEST(webRequest, "http://some.url"); int resCode = webRequest.getResultCode(); String webResult = webRequest.getResultAsString();
ResourceManager is used to load images, resources or byte arrays from the BAR file or from files. It also supports loading gzip-compressed resources.
Sample BREW code:
ResourceManager& res = ResourceManager::getInstance();
TRY( Image image1 = res.loadImage(IDI_IMAGE) ); //load an image from the BAR
TRY( Image image2 = res.loadImage("image.png") ); //load an image from a file
TRY( String text = res.loadString(IDS_TEXT, "alternate.bar") ); //load a string from a different BAR file
TRY( Array<char> data = res.loadBlob(someThread, IDI_GZIPPED_DATA)); //load an (optionally) gzipped data from the BAR
...use the loaded objects...
CATCH(
//handle exceptions
);
The SoundManager implements sound playback. It is very well suited for the Yamaha MA3 and MA5 sound systems used in Japanese BREW handsets and provides transparent solutions for some specific restrictions of these sound systems - for example it is impossible to play an SPF file and a MMF file or more than four SPF files simultaneously; SPF files can only have up to four tracks while the file formats used in J2ME allow more tracks, etc.
Porting sound-related functionality from Java proved to be difficult without such a centralized solution, so we built the SoundManager on top of MediaPlayerBox. This class also brings the interface for audio playback closer to DoJa's AudioPresenter.
SoundManager supports playing different file formats, registering for sound-related events, setting volume, provides transparent suspend/resume functionality and generally makes dealing with sounds easy.
The SoundManager does not currently support jumping at arbitrary positions in the sound or playing streaming audio. This class is still work in progress.
Sample Java (DoJa) code:
AudioPresenter[] players = new AudioPresenter[2] {AudioPresenter.getAudioPresenter(), AudioPresenter.getAudioPresenter()};
for (int i = 0; i < players.length; i++) {
players[i].setMediaListener(this);
}
MediaSound[] sounds = new MediaSound[5];
for (int i = 0; i < sounds.length; i++) {
sounds[i] = MediaManager.getSound("scratchpad://...."); //loading sound resources
}
......
players[0].setSound(sounds[0]);
players[0].play(); //play some sound
SoundManager players(2);
for (int i = 0; i < 2; i++) {
players[i].setMediaListener(this);
}
Array<MediaResource> sounds(5);
for (int i = 0; i < sounds.length(); i++) {
sounds[i] = MediaResource(BAR_FILE, 6001 + i, AEECLSID_MEDIAPHR); //AEECLSID_MEDIAPHR is for SMAF Phrase files
}
players.start(); //the SoundManager runs in a separate Thread, so we need to start it
......
players[0].setMediaResource(sounds[0]);
players[0].play(); //play some sound
Making a game run on many different handsets is difficult. One of the hard things is making strings look well-aligned on all handsets. In order to simplify this, the BREW Framework comes with a Java utility called StringsToSprites, and a class SpritesFont. With the Java utility, the developer can preliminary render all the strings that will be used in the game into pictures and then can use a special subclass of Font - SpritesFont in order to draw strings using these pictures instead of using the ordinary string-drawing routines in BREW. This ensures that the drawn strings will look the same on all handsets. This combo can also work for drawing strings in a more fancy manner, using custom symbol images - for example for game scores, for the titles in the main menu, etc.
The utility is run on the BRX file where all the strings are defined and it builds a number of images, containing all the characters that are used in the strings of the game. It also creates a mapping to describe where each character can be located in these images. The utility automatically updates the BRX file to include the images and the mapping string. Later on, when using the SpritesFont, the application will use the mapping string to find each one of the characters in the string that it has to draw. The whole process is very, very transparent from the developer's standpoint.
The BREW Framework still supports changing the color of strings when strings are drawn using the SpritesFont.
Usage: StringsToSprites -brx <input brx> -res <resources directory> [-out <output brx>]
[-fontface <"Font Face">] [-bgcolor <RRGGBB>] [-fgcolor <RRGGBB>]
[-width <charmap image width>] [-height <charmap image height>]
The default values are:
-out <the input brx file> - by default the input brx will be overwritten
-fontface "MS Gothic" -bgcolor 000000 -fgcolor FFFFFF - white on black
-width 120 -height 120 - compatibility with small screens
String title = ResourceManager::getInstance().loadString(IDS_TITLE_TEXT); graphics.setFont(Font::getSpritesFont()); graphics.drawString(title, graphics.getWidth()/2, graphics.getHeight()/2, Graphics::VCENTER | Graphics::HCENTER);
The YesNoDialog class implements a dialog with a customizable title, text and softlabels. TextBoxDialog implements a dialog for text input from the user (this class is work in progress - we will provide a more powerful and customizable control for user input).
Sample BREW code:
// from inside a Thread:
YesNoDialog dialog;
dialog.showDialog(this, "Question", "Really quit?", "No", "Yes");
if (dialog.getResult()) {
Applet::getInstance().close();
}
This class can be used to change some of the colors in a PNG image to other colors. This is useful for large applications, when space is restricted and some of the images are the same except for the colors. It can also be used to achieve some cool visual effects.
Sample BREW code:Array<signed char> imageData = ResourceManager::getInstance().loadBytes(IDI_IMAGE); PNGPalette pal; unsigned int oldColor = 0xFF0000; //red unsigned int newColor = 0x0000FF; //blue pal.changePalette((unsigned char*)imageData.getPtr(), 0, imageData.length(), oldColor, newColor); Image image = Image::createImage((char*)imageData.getPtr(), 0, imageData.length());
Apart from providing the classes listed above, the BREW Framework also provides solutions to some of the most severe BREW-specific problems and restrictions.
The lack of Exception support in the ARM compilers is one of the worst restrictions in BREW programming. The framework provides a set of macros - TRY and CATCH, and an error-setting mechanism to help in this aspect. The methods in the framework that can potentially fail use this mechanism to raise errors in case of failure. Such methods usually have the phrase "Requires TRY" in the documentation. The calling code is supposed to wrap calls to such methods in TRY(), like this:
TRY( Image image = ResourceManager::getInstance().loadImage(IDI_IMAGE) );
If the code fails, TRY would notice the raised error and would jump directly to the CATCH() section.
However, this approach is quite different from Java's try/catch combo and there are a number of things one should follow:1. TRY should surrounds just a single statement - if we have several statements in a row and each of them could fail, we have to put each of them in a separate TRY. This is because everything TRY surrounds is executed at once, but if the first statement fails we don't want to execute the rest of the statements.
2. In each method that uses TRYs, there should be a single CATCH somewhere below the TRYs. This is because the CATCH actually defines a label and TRY uses goto to jump to this label. This means we cannot have different catch sections in the same method. If we need such fine control, we might use the methods Applet::setError() and Applet::getError() manually.
3. TRY and CATCH cannot jump through the function call stack like Exceptions do. The sample code below demonstrates what should be done in such cases.
Sample Java code:
void f1() {
try {
f2();
} catch (Exception e) {
//handle exceptions
}
}
void f2() throws Exception {
f3();
}
void f3() throws Exception {
loadImage1();
loadImage2(); //this will not be executed if the line above throws an exception
}
void f1() {
TRY( f2(); );
CATCH(
//handle exceptions
);
}
//Requires TRY
void f2() {
TRY( f3(); );
CATCH(); // do nothing here - just declare it so that TRY can jump here. The error will be handled in an upper function.
}
//Requires TRY
void f3() {
TRY( loadImage1(); );
TRY( loadImage2(); ); //this will not be executed if the line above fails
CATCH(); // do nothing here - just declare it so that TRY can jump here. The error will be handled in an upper function.
}
4. In Java you could have different types of Exceptions. With TRY and CATCH you cannot have such fine control.
Sometimes the compiler will complain with the following error: initialization of 'someVar' is skipped by 'goto CATCH_LABEL'. Most probably your code looks like this:
TRY(something); // or TRY_MEM; int someVar = 5; // or whatever the type of someVar is ...use someVar... CATCH();
You can fix the error by wrapping the usage of someVar in an inner block:
TRY(something);
//use a block to wrap the usage of someVar:
{
int someVar = 5;
...use someVar...
}
CATCH();
We already mentioned what are the main differences of BREW and J2ME in terms of memory handling in the introduction. Let's see how the framework addresses these issues.
First - the lack of Garbage Collection. Most classes in the framework are implemented in such a way that you can you use them by value, not by reference. This means you can use code like this:
String s1; s1 = "aaaa"; print(s1); String s2 = s1;
String* s1;
s1 = new String("aaaa");
print(s1);
String* s2 = s1;
delete s1;
This means that most objects stay in the stack, not in the heap. The stack memory is automatically managed in C and C++, so you don't have to take care of it. This does not mean that very large objects are stored in the stack - the real data stays in the heap, stored in the member fields of the framework classes and their constructors and destructors take care of it properly. Besides, the most important framework classes like String, Array and others are implemented in such a way that they can be used almost like pointers - variables can be assigned to each other, without the real data being copied redundantly. For example look at this code in Java and in BREW:
int[] a = new int[5]; int[] b = a; b[3] = 2; //this also affects a, because a and b are references to the same thing - they are at the same address
Array<int> a(5); Array<int> b = a; b[3] = 2; //this also affects a, even though a and b are not at the same address
This approach almost entirely eliminates the need for a garbage collector. Nevertheless, for the cases where using pointers and memory allocation cannot be avoided, the framework provides a SmartPointer class (similar to STL's auto_ptr). With SmartPointers we could have code like this:
// instead of returning a char* which is not clear who is supposed to delete, we'll return a SmartPointer:
SmartPointer<char> getMessage() {
char* p = new char[100];
//fill in the char buffer with whatever we want
return p;
}
......
void someFunction() {
......
SmartPointer<char> p = getMessage();
//now use p as a normal char pointer:
DBGPRINTF(p);
// no need to call delete here - the SmartPointer's destructor will take care
}
Actually in this example we could have used String, or Array<char> instead of working with C-style char arrays. It is even hard to think of an example where memory allocation should be performed "by hand"...
Second - there is a strict requirement in BREW to check the result of each and every memory allocation. The BREW certification procedure sometimes even includes a test in which the calls to MALLOC() would fail in a random manner and your application is supposed to behave properly. In order to release the developer from having to check the result of every memory allocation, the framework uses the following approach:
When the application starts it allocates the so called critical memory buffer. The new and delete operators are overloaded and if a memory allocation fails, new raises a panic flag and starts returning memory from the critical buffer. Thus, the user can check the memory just once per iteration in the long loops of the application (and not after each and every memory-consuming statement), this is sufficient to notice the out-of-memory condition before the critical buffer memory is consumed. Then the application still has memory to show a nice dialog "Out of memory. Exiting" and exit after the user presses a key.
The checking of memory is done with the macro TRY_MEM. Here is some sample code:
class MainCanvas : public Canvas, public Thread {
private:
YesNoDialog errorDialog;
//we create this dialog at construction-time to avoid the need to create it when memory is very low.
//this is an extra precaution, just in case. YesNoDialog is quite a lightweight class anyway.
....
public:
void run() {
Vector< SmartPointer<char> > v;
while (true) {
TRY_MEM; // check for out-of-memory at least once in every longer loop during which memory is allocated
v.addElement(new char[10000]); //an implicit SmartPointer construction kicks in here
// do other things without caring too much about memory
.....
WAIT(0); // always yield during infinite loops
}
CATCH(
errorDialog.showDialog(this, "Error", "Out of memory. Application will exit.", "", "Ok");
applet.close();
);
}
}
Using real objects instead of pointers in the BREW Framework comes at some price - with pointers you could have a NULL value (as you can with references in Java), while real objects cannot be NULL. To avoid this trouble most adapter classes in the framework, such as String, Image, Array, etc. implement the Nullable interface. This means that they have a so called NULL-state, which can be checked using the isNull() method. They also have a constructor, which puts them in the NULL-state initially - usually it can be invoked with code like Array<int> a = NULL_OBJ. For some of the Nullable classes, the default constructor also constructs a NULL-state object. Such classes are String, Array, Image. However, for some classes like Vector, Hashtable and StringBuffer the default constructor makes more sense to create a real object. The NULL_OBJ constant we used several times is a constant from a special type and Nullable classes always provide a constructor for it, which puts the object in the NULL-state.
Since null and NULL in C++ are defined as 0, and some of the classes in the framework have meaningful constructors from integer values, you should be careful with the null-s used in the Java code. Usually you can search for "null" and replace it with "NULL_OBJ". Checks like a == null or a != null can eighter be translated as a == NULL_OBJ (respectively a != NULL_OBJ), or as a.isNull() (respectively !a.isNull()).
The BREW certification procedure mandates that the suspend and resume events are handled in a proper manner by the application. This means that on suspend the application should release some specific limited resources like IMedia and IWeb objects and should cancel all set timers. On resume it should continue from the same state as the one it was in when the suspend event was received.
The BREW Framework classes are all implemented to release limited resources, cancel timers and pause threads on suspend and continue properly on resume. The BREW Framework also takes care to redraw the screen properly when resuming - it keeps a copy of the drawn screen and uses it to redraw the screen on resume. Using this intermediate drawing buffer causes a very small speed decrease - the developer can turn it off by passing false for useIntermediateBuffer to the Canvas constructor. In this case the application should ensure the screen is properly redrawn on resume - the paint() method of the active Canvas will be called on resume and it should repaint the whole screen.
This usually means the developer does not have to do anything for suspend and resume to work properly. Still, in the rare cases when some application class has to perform specific suspend/resume actions, it can register a listener for system events with Applet::registerEventListener().
Since BREW is essentially single-threaded, some operations cannot be performed while user code is being executed. For example painting the screen cannot take place while your code is executing. If you want the phone to perform such operations you need to "pass the control" to the system. Without using Threads this means you have to exit from your entire function call stack and schedule a timer to be reexecuted on the next iteration of the global system loop. This means you have to lose the implicit state of the application within the code and this forces you to use member variables for things for which you would normally just use local variables. Besides it leads to very unreadable and spaghetti-like style of coding.
With Thread you just have to callWAIT(0) in order to give BREW some time to perform the system operations. Thus you can yield control to BREW without losing your current implicit program state.
Still, the need to yield control to the system is a characteristic of BREW that has to be taken into account in some cases. Again, the most prominent example is painting the screen. When you call some drawing BREW routine, it is actually performed on a memory bitmap. The screen is updated with this bitmap only after you call a special BREW API function and pass control to the system. In order to do this with the BREW Framework you have to use Canvas::updateScreen() and you still have to pass control to BREW afterwards by using WAIT. You will see this demonstrated in the sample porting project. There is a good side to this - this means you do not need double buffering in BREW to avoid flickering. It is quite similar to DoJa's concept, where Graphics::lock() and Graphics::unlock() are used to avoid flickering - actually the Graphics class in the BREW Framework also provides lock() and unlock() methods and unlock() calls updateScreen(), so if you are porting from DoJa you may not have to do anything special in regard to painting.
The need to yield control to BREW also affects event processing and most notably key events. Such events can be passed to the application only while BREW has the control. You cannot receive a keypress event while in the middle of some application code. This has a positive side, too - synchronization is much easier this way.
You also have to yield control during loops that could potentially last long. For example if you are loading hundreds of images from the resources one by one, you'd better put WAIT(0) in the loop. If the application does not yield control to the system for a longer period of time (say 1 minute or so), the phone will restart. Besides, even if it the loop does not last that long for the phone to restart, the application will be unresponsive during the loop - the user will not be able to exit for example.
Some of the classes in the framework, e.g. WebRequest and GzipInputStream have to be able to yield control to BREW, that is why you have to use them from inside a Thread.
In order to demonstrate better the framework and to help developers who are using it become faster acquainted with it we prepared a simple MIDP application and ported it to BREW, describing the porting process step-by-step. The demo application consists of three classes: Demo, DemoCanvas and PaintThread. Demo is a MIDlet, it activates the DemoCanvas. The DemoCanvas loads some resources and starts several PaintThreads. The PaintThreads draw various things on the screen in infinite loops. Though the source is really short, this application demonstrates many aspects of J2ME programming - there is resource loading, working with strings and arrays, multithreading, various drawing operations, processing keys. The keys are: * to clear the screen, 0 to exit, any other key to pause or resume drawing.
You will see that the code of the BREW equivalent is really close to the original code and it performs just as well as the original.
The sample project is included in the BREW Framework Demo Kit, which can be found in the download section. The archive includes Java and BREW sources and binaries. You can run it in the BREW SDK emulator by pointing it to the bin directory of the BREW project.
Let's see how we developed the BREW version step by step. You can follow the same step-by-step guide for your BREW Framework based projects.
Your first task is to get acquainted with the original project. You should run the application and see what it is doing. You should briefly explore the sources and see what classes are used, what the general architecture is, what threads are used, is there a main infinite loop or the application is completely event driven, does it use functionality which is not present in the BREW Framework.
We will assume Microsoft Visual Studio 2005 will be used for development.
The next step is to set up the application project. We recommend following the same folder structure as in the sample application. You could then reuse the Visual Studio project file and just use search and replace to change the application name as appropriate. That is how we started the Demo project. Alternatively, you can create a project from scratch - you need to set up an empty MSVS C++ project, then add the BREW SDK headers and the framework inc folder to the Additional Include Directories list (in Project Settings -> C/C++ -> General) and set up the linker to use the BREW Framework library (this is done in Project Settings -> Linker -> General -> Additional Library Directories and Linker -> Input -> Additional Dependencies). Please use the Demo project as a reference.
If you just want to play with the demo, you could simply open the VS project file. The project assumes you have the BREWDIR31 environment variable pointing to your BREW 3.1 SDK folder and the BREW_FRAMEWORK_DIR variable pointing to the BREW Framework directory (where inc and dist can be found).
The BREW Framework provides a very simple template project, which consists of only two classes and which can be used as a base for every project. One of the classes is the MainApplication class - add this class (header and cpp) to your project. The other class is ApplicationCanvas - the canvas that is started from MainApplication. You can add this class to the project or use it as a skeleton for your own first Canvas class.
MainApplication defines the entry point of the application. You should edit MainApplication.cpp and change a couple of things there: the included files, the class ID, the name of the BAR (BREW Application Resource) file, the name of the Canvas class. Everything is pretty obvious when you look in MainApplication.cpp.
Apart from adding these classes to the project, you also need to change or create the BID (class ID definition), MIF (application descriptor) and BRX (BREW resource descriptor) files appropriately. We recommend putting these in the project root folder. The sample and the template project assume this structure.
It is a good idea to check that everything compiles and builds now. The sample project has some post-build steps which create a bin directory and put everything needed to run the application on the emulator in it.
It is a good idea to afterwards convert all used resources (images, sounds, etc.) into the proper format for BREW, prepare the BRX file and build the BAR and BRH files (the BRH contains resource ID definitions). Thus your entire application skeleton will be ready. You can leave this step for later as well.
Your next big step would be to convert the sources from Java into C++ and make them compile
The next step is to make the initial convertion of Java classes to C++ classes. This means creating a header and a cpp for every Java class and changing the class definition into the C++ syntax. Think only about the class structure - members, method signatures, access levels, member initialization, constants, etc. - and not about the method implementations for now. This is one of the steps where the BREW Translator helps a lot.
Let's consider our sample. First the Demo class - it is our MIDlet class and it does not do pretty much anything apart from defining the entry point and starting up DemoCanvas. Our MainApplication in the BREW project already does the same, so we may simply forget the Demo class.
Let's move to DemoCanvas. We have to create a header and a cpp. We will use the ApplicationCanvas from the framework template for this and will just replace "ApplicationCanvas" with "DemoCanvas". We need to put all the class members and methods in the header and their initialization and implementation in the cpp.
Member initialization. In C++ we cannot initialize member variables with the declaration - we have to put the initilization in the constructors. In Java all member fields are always initialized, even if you do not provide an explicit initialization. This is not so in C++ so you have to initialize even those member fields which are not initialized in Java. Something important: all the changes you make during the porting process should turn Java code into semantically equivalent C++ code. For example if a String is initialized to "X" in Java you should initialize it to "X" in the C++ constructor (or constructors). Most often the default constructors in the BREW Framework are a perfect replacement for the default initialization of members in Java. However this does not help for members of native types like int, boolean, etc. - you have to initialize them in the constructor.
The BREW Translator does a big part of this work automatically.
Access levels. Something we should think about is access levels. Usually package access means we should make the fields public in C++, but that is up to the specific class. The BREW Translator takes care of access modifiers in a way which works well in most cases.
Thrown exceptions. If it is not obvious how to port some piece of code you can comment it out - you'll think about it later. It is good to use some special sort of comments for commented code that has to be ported later on. For example we use "//PORT". Thus you can easily find such code later on and it can be distinguished from ordinary commented code. For example it is not obvious what to do with the "throws IOException" clause in init() in DemoCanvas. We'll leave this clause commented in the C++ header and cpp. We will get back to this throws declaration later on when we implement error handling.
The BREW Translator comments out exception-related clauses, but you have to add error handling yourself afterwards.
Constants. Constants in C++ are defined in the header, but should be initialized in the cpp.
While porting we can make some changes which are not simply syntax adaptation, but are nevertheless obvious. For example for the DemoCanvas class we removed the MIDlet parent member field - it will not be needed.
Arrays. Java array variables should most often be converted into template Array variables. Sometimes for multidimensional arrays it is best to use the Array(String) constructor by passing the original Java initialization string (such as "{{1,2},{1}}") as an argument.
The BREW Translator deals automatically with most arrays, both member fields and local variables.
Static variables. Moving to the PaintThread. It is even easier - the only obstacle is that we have a static variable random. We can't have static variables in BREW. Usually we have to eighter make such variables non-static or move them to a different class, where they can be accessed easily. In this case the first solution would be okay, but we can also move the variable to DemoCanvas - PaintThread keeps a reference to it, so it would be easy to access it. However, in the BREW Framework we don't have a Random class and random() is a static method of Math so we actually don't need this variable at all and remove it. We can change the places where it is used immediately, or we can leave it for later - the compiler would anyway complain about the usage of an uknown variable.
#includes and forward declarations. In Java all the classes in a package see each other without having to import anything. Besides you can use wildcards when importing. In C++ you'll have to think somewhat more about the included header files. Sometimes you'll have to make forward declarations to avoid cyclic #include references - Java handles cyclic dependencies automatically, C++ does not. This may also force you to use references or pointers in some places instead of value-type objects.
Inner classes. Inner classes are different in Java and C++. Inner classes in Java have an implicit reference to the outer class and both classes can access each other's member fields. different approaches can be used for converting inner and anonymous classes.
Sometimes some methods have to be renamed or implemented in order to conform to the interface of the parent class. For example Canvases should implement a paint() method, and optionally a key handler - processKey(), or keyPressed(). Threads should have a run() method. Most times the interface is the same as that of the Java interface and you hardly have to do anything. The compiler would anyway complain later on if something is not implemented.
In our case DemoCanvas is a Canvas AND a Thread in C++ (because we used the ApplicationCanvas class as a base to create DemoCanvas), and it only implements the interface of Canvas. We see that MainApplication calls DemoCanvas::start() instead of DemoCanvas::init(). We could add a run() method which simply calls init(). Or we can make the class not inherit Thread and call init() directly from MainApplication. We prefer to leave it inherit Thread - this will be useful later on when we implement error handling.
We should also remove the Graphics parameter that is passed to paint() - we can use getGraphics() instead.
Next you start fixing the code in method bodies until you are able to compile. There are a number of conversions you can do before you start looking at the methods one by one and even before you try to compile.
SomeClass.staticMemberOrMethod -> SomeClass::staticMemberOrMethod This is a trivial difference between Java and C++. You can easily use search and replace to find such code. The compiler would also tell you that such code is invalid in C++. The BREW Translator changes most such calls automatically. Sometimes SomeClass is missing in the BREW Framework and the method is moved in another class, or there is a different way to do the same thing. An example is Integer.parseInt() -> String::parseInt()
null You should find all usages of null and take proper measures. Usually you can simply replace all usages of null with NULL_OBJ. For comparisons like a==null or a!=null you can use the isNull() method. See the section about Nullable.
The BREW Translator does most of this work automatically.
new You should find all usages of new and take proper measures. Most times you can remove the usage of new and use Array or create a single object in the stack (without new), not in the heap. For example String[] a = new String[10] would become Array<String> a = Array<String>(10) and String a = new String("aaa") would become String a = String("aaa") or simply String a("aaa"). In some cases you have no other option but to use pointers - new will remain and you eighter have to use SmartPointer or add a call to delete or delete[] at the proper place. See the section about memory handling.
Arrays, array.length Most time you replace java arrays with Array<X>. You also have to replace the usage of array.length with array.length(). The BREW Translator does most of this work automatically. See the section about arrays in the overview.
In our sample we have an array of PaintThread. In the overview we mentioned that in order to use Array and the other collections with a non-native class (such as PaintThread), the class should have a default constructor and, depending on the Array methods you will use - some other constructors and overloaded operators. In our case we would need operator=, however it is not possible to have one with Threads. Instead we will use smart pointers for our PaintThreads and store them in the array.
try/catch/finally, throws Usually during this phase you simply have to comment out with "//PORT" all try clauses, catch and finally blocks, and throw declarations you have missed during the classes conversion. You'll add error-handling at a later step.
Threads, Thread::sleep(), synchronized You have to adapt the usage of Java's Threads to what the BREW Framework's Thread class provides. Usually you can remove the synchronized keyword and replace Thread::sleep(X) with WAIT(X). In Java there is always an implicit Thread, while it is not so in BREW and WAIT(X) has to be used from inside a Thread - you may need to make your class a Thread. wait() and notify() are methods of Object in Java, but members of Thread in the BREW Framework. See also the section about threads in the overview.
String literals: "aaa" + 5, "aaa" + "bbb" The compiler would complain if it sees code like "aaa" + "bbb" - this means adding two char pointers in C++ and adding pointers is forbidden. You have to explicitly cast the first literal to a String: (String)"aaa" + "bbb". Cases like "aaa" + 5 or "aaa" + x, where x is an integer, are much worse - this is valid C++ code, so the compiler will not issue an error. However it does a completely different thing from what we want - this is not String concatenation, this is pointer arithmetic. It can lead to bugs. You have to carefully check for such code and cast the first literal explicitly to a String. See also the String-related section in the overview.
Here the BREW Translator also helps a lot.
After all these conversions you can try to compile. You'll almost certainly get quite a lot of errors. Don't panic - it's normal. From now on the errors the compiler issues should lead you. Sometimes it will complain that some class or method does not exist - if it is present in the BREW Framework, you should #include the proper header. Otherwise you should think about alternative code to replace the original. We should stress again: while doing such changes you should strive to always keep the code semantically equivalent to the original. Otherwise you introduce subtle bugs that will be hard to find later on.
It is often valuable to use "//PORT" comments when you are not sure how to port something during this phase until you reach a state in which the code compiles.
Note: We have noticed that sometimes the Visual Studio compiler would issue very strange and out-of-place looking errors. Very often the reason for such errors is a cyclic reference in the #include-s or generally incorrect #include directives.
If we get back to our sample project, there were a couple of things that we had to change during this phase:
isSuspended member field in DemoCanvas - there is a method with the same name and this is not allowed in C++.Image::createImage("/smiley.png") with ResourceManager::getInstance().loadImage(IDI_SMILEY)random.nextInt() with Math::random().Your code should compile and build okay now. However it is yet early to run it - you should replace the PORT-commented code with semantically equivalent code. Most often you would use the functionality in the framework. In some cases though you might have to write you own code on top of the BREW API.
Sometimes you may leave some PORT-commented code for later. For example we usually prefer to get the application running without sounds and port the sound-related code after we have the rest of the application running properly.
In "Yielding control to BREW" we described the difference between calling drawing routines in Java and in BREW. If at a certain place in the code you want the last drawing operations to actually be shown on the screen, you have to call updateScreen() and then yield control to BREW. For example, in our sample application, the DemoCanvas::paint() method is the perfect place to put the call to updateScreen(). Control will be passed to BREW once the PaintThread that is calling repaint() reaches the WAIT() in its loop.
You also have to yield control during loops that could potentially last long. This is also described in the overview. We already have a call to WAIT in the infinite loop in PaintThread.
You are ready to start the application for the first time. Probably in the beginning it will not work as expected - again, don't panic! There is no way to carry all the steps above on a large application without introducing an error here and there. That is why we stressed several times that it is important to preserve the semantical meaning of code while doing changes. It is important to be as accurate as possible - little typos will make us spend time searching for the cause of a bug. Fortunately the BREW SDK provides quite a decent emulator and it is usually easy to trace down and fix bugs in Visual Studio.
Usually at this part of the project we have to start understanding bigger parts of the code. This is inevitable if we have to add new functionality.
This is one of the hardest parts. We usually leave it for last - after we have implemented everything else.
The section for exceptions in the overview describes how error handling is implemented in the BREW Framework.
Usually you should search for error-prone code in the BREW source at the same places where exception-throwing code is used in the Java source. If you have used PORT-comments to comment out trys and catches, this will help you find such code. Error-prone methods of the framework have a note "Requires TRY" in the javadocs.
In Java it is acceptable (though not a good practice) not to catch some exceptions in the application and let the operating system show the exception to the user. In BREW, however, you are supposed to handle such cases in a better way. That is why in the CATCH in DemoCanvas we added a dialog to show a nice message if the resource image cannot be loaded.
Another good idea is to call TRY_MEM in memory consuming loops or generally in all places where large objects are created. We don't have such places in the sample application, but we have nevertheless added TRY_MEM in the PaintThread to show you how error handling can be organized when several threads are used.
After you add error handling it is good to really test that it works as expected - by removing the BAR file, running with low memory, etc.
As you already read in the overview, the framework handles most of the suspend/resume-related tasks automatically. If your application has to do something special on suspend or resume, don't forget to register a listener for these events. In any case, it is mandatory to test that suspend and resume work properly. For the sample application everything in this aspect is already okay.
Includes the BREW Framework headers, documentation, template project and a static library for building and running BREW applications in Microsoft Visual Studio. Also includes the sample porting project - Java and BREW sources and binaries, project and build files, ready to be tested and modified inside Microsoft Visual Studio.
Required environment settings
In order to use the project and build files in the Demo Kit you need to have the BREWDIR31 environment variable pointing to your BREW 3.1 SDK folder (such as "C:\Program Files\BREW SDK v3.1.2\sdk") and the BREW_FRAMEWORK_DIR variable pointing to the BREW Framework directory (in which inc and dist can be found).