A Symbian Wizard

Creating a Symbian C++ project, with all its framework, is a fair amount of work. To facilitate easy creation of new applications, I created (and am still working on) a tool I call symwiz (Symbian Wizard). It is a Python program that creates the expected directory structure of the project, and creates all the various project and resource files that are required, as well as the source code for the basic classes that are required to get one off the ground.

The latest version is from Aug 12, 2005.

Prerequisites

There are two distributions. The recommended way is to get both Python and symwiz sources. If you use Python, you should get Python from Python site (and not use Python that you got, e.g., from cygwin). Just make sure it's on your path. (In cygwin, typing "type -p python" should return the path of the python executable, if it doesn't, edit your .bashrc to add it to the path).

You also need the files for symwiz, and example *.init files. Those you can find in this tarball, which you should be able to open either with Winzip or something similar, or from command line by calling gunzip -c symwiz.tar.gz | tar xvf - from the cygwin command line.

If you don't want to download and install Python, you can take this tarball that has symwiz.exe which works without Python. Instead of calling python symwiz.py, you just call it symwiz.exe.

First run

Symbian tools can't handle directory names with spaces. These examples have been run in the Series60Ex directory, but both symwiz and Symbian projects should work from anywhere (as long as the path doesn't have spaces).

Copy the helloworldbasic.init from the symwiz distribution to your working directory, and execute

or etc., depending on where you opened the tarball. Actually, since that is fairly cumbersome to type, I create myself an alias into my $HOME/.aliases and then I only need to type Take a look at a log. A lot of directories and files should have now been created for you. Now you can go down to swHelloBasic/group, run make or just type 'bldmake bldfiles' and 'abld build wins udeb' directly by yourself, see how the project is compiled and linked, just ignore the warnings, and you can try out the application in an emulator by typing 'epoc' and trying it out (or typing 'epoc &' so the emulator runs in the background and doesn't block your command line prompt).

What's in helloworldbasic.init?

The SDK comes with a wealth of information. Open the SDK help now (in my system I get it from Start Menu -> Programs -> Series 60 Development Tools -> ... -> SDK Help). On the left, at the contents bar, you see for example "Getting Started Guide". This guide gives a good overview, walks you through compiling and running the helloworldbasic example that came with the SDK, modifying it, trying it out on the emulator, and even installing it to your handset. You should (quickly) read this document.

Open the helloworldbasic.init file in an editor (e.g., emacs, or notepad, etc.). It's not very long, and there are some comments that explain what it does. You can see that the project name is given (swHelloBasic), based on that name the project directory is named, and the main files and classes have that as part of their name. On the next line you see the UID definition. All projects must have a unique UID. Read a short description in the SDK Help (Contents -> Symbian Developer Library -> Tools and utilities -> UIDs). Other than that, there's a simple menu structure with a couple of simple callbacks. Once you've tried the application once, you can experiment changing some of the menu texts, or the infonote string. When you recreate the project, an editor will pop up and show you the differences in the project that were caused by the edits. By default that's emacs, to change it to notepad, edit file preferences.py (you can also change there that you are using vc6 rather than vc7, that is, .NET). Here is a documentation sheet that collects *.init file structure in one place.

Take a quick look at the basic structure of symwiz. Open symwiz.py, scroll down towards the end (to THE MAIN SECTION). You'll see that first command line arguments are handled, then the key classes are initialized, the *.init file is read and its Python code is executed, the directory structure is created, and finally all the files are created.

Understanding the Symbian project

Go to swHelloBasic/src and take a look at the files. In the inc/ directory there's a *.h file for each of the four classes (application, document, appui, appview). There's also In aif/ there is: In data/ there is: In the group/ directory you can find Finally, there is one file in the sis/ directory,

In order to learn a bit more, and see how the help works, let's study the drawing part, which is the Draw function in AppView. Peeking into inc/swHelloBasicAppView.h we can find out the class CswHelloBasicAppView derives from. In SDK Help, in the Index tab, type in the class name (CCoeControl). We'll learn in which header file it is declared (coecntrl.h), and which library is needed (cone.lib). There's also a very succinct class description. We're interested in the Draw method, find that in the list of members and click the link. In the description it mentions that a graphics context is needed for drawing, suggests using SystemGC(), which indeed is what the Draw() implementation in swHelloBasicAppView.cpp does as the first thing. Find also the documentation for SystemGC() and Rect() (both are members of CCoeControl). One more hint about documentation: sometimes the header files (can be found in /epoc32/include, starting from the SDK root) contain some information or additional documentation that didn't make its way to the SDK Help.

These examples are modeled after the examples that come with the SDK. You can find them in the /Series60Ex/ directory. If you installed the 7610 Camera plugin, you'll find only two camera-specific examples, the rest are actually in the /Series60Ex/ directory of the basic 2.1 SDK which you had to install first. The SDK Help contains documentation for these examples.

Going forward: helloworldplus.init

Study the helloworldplus.init file. A couple of things should be familiar. We have given the project a name, and use a different UID. The menubar is slightly larger, it has now five entries. The first new thing here is infonote_RES() and resource.strings. resource.strings is a list of pairs (initially empty), where the first part of the pair is the name of the resource string, the second one is its contents. With '+=' we concatenate to it a new such list, which contains one pair. That pair is used by the infonote_RES(). Let's see where this resource is used. The easiest way is perhaps to first run 'symwiz helloworldplus.init' once, then comment out the line with resource.strings (just begin the line with '#', and run symwiz again. An editor pops up with the results of diff, you'll see that the resource string used to go to aif/swHelloPlus.rss. If you'll also comment out the line in user_events that uses the string, you'll learn where it is used. The function infonote_RES() simply returns a string of C++ code that implements an information note that reads the string to show from text resources.

Another new thing is that you can give the application a display name that is different from the project name. Then notice that two of the user events call a method in the AppView object. One of them, DrawNow, is already defined in CCoeControl (find it in SDK Help from Index -> DrawNow), but UserDraw is not defined. For that, we need to add a public method called UserDraw into the AppView class. In symwiz.py, an object called appview, of type CClass, is defined before the *.init file is read. By calling method add_public() in it we can add a public member to the AppView C++ class. In this case it is a constant method, that returns void, with name UserDraw, no arguments, and three lines of code. Experiment by removing that call and see the effects.

The appview class has a method for key events. That works in symwiz a bit like a simple menu, you call appview.keyevents() with a list of event - callback pairs. Since the infonotes used there require the inclusion of a header file <aknnotewrappers.h>, we add the include into the *.cpp file by adding it to the appview.inc_src list.

Finally, we want to enhance the Draw() method so it displays the current time. The default behavior, which gets the graphics context and clears the screen is fine, we don't want to replace it but add more calls after it. Every CClass, including appview, has a dictionary called members that is indexed by the member name. So we can access the method Draw by calling appview.methods['Draw']. The method again has an attribute called code, which is a string containing the code. We simply append to that string another string with the additional commands. In Python, multiline strings are delimited with triple quotes """.

Commenting and documentation support in Symwiz

All entitities such as classes, methods, function arguments, variables, etc., take an optional comment argument. Most of them are defined in method.py. Here are some examples. This creates a private variable into the appview class, and the class destructor will delete this it. In
appview.add_private( Var( 'CFbsBitmap*', 'iBitmap', 'The current image' ), destroy_var = True )
the third argument of Var is optional, and yields the following in the header file:
    /*! @var iBitmap The current image */
    CFbsBitmap* iBitmap;
where the comment is in the correct format for using Doxygen to generate documentation for your project.

Here we add a public method. The first argument is either a string (return type) or a tuple of two strings (return type, and a comment), the second argument is also optionally either the function name (string) or also a description of the function (tuple of two strings, like here). The third argument is a list of function arguments (none here). Fourth argument is a string encoding the C++ code for this method.

appview.add_public( Method( ('CFbsBitmap*', 'A pointer to the current bitmap'), 
                            ('GetBitmap', 'Return the current bitmap'), [], 
                            'return iBitmap;' ) )
produces the following code in the header file
    /*!
      @function   GetBitmap
      @discussion Return the current bitmap
      @result     A pointer to the current bitmap
    */
    CFbsBitmap* GetBitmap( void );
and
appview.add_public( Method( 'void', ('SetBitmap', 'Set new value for iBitmap'),
                            [Arg('CFbsBitmap*', 'aBitmap', 'The new bitmap')], """\
    if (iBitmap)
        {
        delete (iBitmap);
        iBitmap = NULL;
        }
    if (aBitmap)
        {
        iBitmap = aBitmap;
        }""" ) )
yields
    /*!
      @function   SetBitmap
      @discussion Set new value for iBitmap
      @param      aBitmap          The new bitmap
    */
    void SetBitmap( CFbsBitmap* aBitmap );
If you are feeling lazy, instead of giving an name-document tuple, just give the name
appview.add_public( Method( 'CFbsBitmap*', 'GetBitmap', [], 'return iBitmap;' ) )
yields
    /*!
      @function   GetBitmap
      @discussion <NO DESCRIPTION>
      @result     <NO DESCRIPTION>
    */
    CFbsBitmap* GetBitmap( void );
Long comments are broken into separate indented lines so they remain readable, e.g.
    /*!
      @function   SetBitmap
      @discussion Set new value for iBitmap.  Yadda Yadda Yadda Yadda Yadda Yadda Yadda
                  Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda
      @param      aBitmap          The new bitmap. Yadda Yadda Yadda Yadda Yadda
                                   Yadda Yadda Yadda Yadda Yadda Yadda
    */

Using the camera: capturing video

The next example, cameraoptions.init, shows how to operate the camera. The first thing you notice is that the menu structure is much more complicated than before. The menubar is still a list of pairs, where the first element is a string and the second something else. That something else can be the C++ code that should be executed when this item was chosen, or another list of pairs, in which case a cascading menu is created.

Two new libraries are needed, fbscli.lib for bitmaps, and ws32.lib for flushing the window server session.

Many of the menu actions call ErrorNotifyL() which shows an error notification in case there was an error. Since especially during development it is very useful to show such notifications, the symwiz class CClass contains a convenience method called add_notify(). Take a look how that is done in the file symwiz.py. First, it adds a method that returns a string describing a given error. Then it creates another C++ member function, ErrorNotifyL(), that uses the previous method to print an error message, and finally a third function for infonotifications. The menu handling happens in AppUi, so we add these functions there (appui.add_notify()).

The rest three entries that affect appui all deal with including the videorecorder header file to the appui header file, declaring a private variable that points to a CVideoRecorder class instance (and destroying that pointer in the destructor), and constructing that instance in the second phase constructor (ConstructL).

AppView gets only one modification. It creates a new DrawImage() method, using which the VideoRecorder can draw a captured picture on the display. The method obtains a graphics context, the window of the AppView is associated with the graphics context and they are activated, the bitmap is blitted in the center of the screen, graphics context is deactivated, and finally flushes the window server session so the image is actually displayed.

The rest of the init file deals with the VideoRecorder class. First, we create an instance of a symwiz CClass object, and the argument gives it a name and a documentation string (you'll find the doc string in the VideoRecorder.h). The engine derives from CBase (the base of all Symbian classes, hence quite important; read the class description in the SDK Help).

videoeng.use_camera() is implemented in CClass. It includes the ecam.h header file and ecam.lib library, and makes the class to inherit from MCameraObserver. Symbian only allows a class to have a single concrete class as a parent, in this case that class is CBase. MCameraObserver is a mixin class, a pure virtual class that only defines an interface that its descendants have to implement. Take a look in the SDK Help class description, you have to implement this class (i.e., its methods) in order to use the camera API. However, use_camera() adds those methods for you as well, with some error checking. Info and error notes are added, a member variable CCamera* iCamera is created and destroyed in the destructor, and if the class doesn't already have an iAppView pointer, one is created. Other methods that are added include StartViewFinder() and StopViewFinder(), and AppView gets a method DrawImage().

The real work of constructing a class usually happens in the so-called second phase constructor, ConstructL. Symbian programming guidelines are really picky about not creating memory leaks, and being able to recover gracefully even when you run out of memory. You could think that if you run out of memory, that's it, there's nothing you can do (except fix your code). And while you're developing your system you in most cases that is precisely so. However, once your application is ready and others use it, and they run out of resources, one should be able to recover, then release some resources by, e.g., closing other applications, and try again, without having caused any memory leaks that would soon force a reboot. There are a couple of conventions that are used throughout in the Symbian examples and libraries. Even if you want to live dangerously, and just use the basic C++ memory handling, you should get familiar with the basic conventions so you can follow what's going on. Further, Symbian doesn't support the C++ catch, throw, and try mechanisms but implements its own mechanism (trapping and leaving). So read now SDK Help Contents -> Series 60 Developer Guides -> Symbian OS: Coding Conventions in C++, sections 3.2 - 3.3. It's only eight pages, so won't take that long. I really recommend reading the whole document, but those two sections will do for now. use_engine() calls another CClass convenience function, two_phase, which creates the two-phase construction functions, including creation of iCamera in the ConstructL.

The camera processing starts in ConstructL, where the camera is reserved (iCamera->Reserve()). Later, as the asynchronous reserve completes, CVideoRecorder::ReserveComplete() is called. If there was no error, iCamera->PowerOn() follows, which again calls CVideoRecorder::PowerOnComplete(). Again if there's no error, that function calls iCamera->StartViewFinderBitmapsL(), which calls CVideoRecorder::ViewFinderFrameReady() when those frames are ready. That function simply passes the bitmap to the DrawImage function in AppView for display.

Actual video recording begins when the user calls CVideoRecorder::StartVideoCaptureL() from the menu. There we first call iCamera->PrepareVideoCaptureL() with arguments that give image size, etc., and there the camera reserves resources. Then iCamera->StartVideoCapture() starts the process. When each buffer has been filled with the required number of frames (as set in the PrepareVideoCaptureL), FrameBufferReady() is called, and it just checks that the frame is grabbed OK, but in this simple example doesn't do anything with the frame. StopVideoCapture() merely calls a similarly named method of the camera.

The rest of the class mostly concerns changing the camera settings, such as brightness, digital zoom factor, etc., from the menu callbacks. videoeng.intro_src contains several variables that are global to this class, these are simply copied verbatim after the inclusion of header files.

Using the camera: capturing and saving still images

The second camera demo, camerasnapshot.init, repeats many of the concepts presented above, but breaks them into smaller steps which you can execute interactively from the menu so you can experiment on the errors that arise if things are done out of order. Going into the init file, you'll see how a submenu is created using a double loop of python code. It generates text-command pairs for three different color depths and four different image sizes that you can use for still imaging. The submenu is then used in the Still Image -> Prepare Capture part of the menu.

The rest of the file deals with appui and appview. Appview is the simpler one, it just adds functionality to have a private variable for the bitmap, and drawing routines that display it.

In this example almost all of the real work is done in appui. It is appui that now calls use_camera and hence inherits from MCameraObserver, etc. The difference to the previous example is that the chain of setting things up is not automatic, but is done interactively from the menus. When ViewFinderFrameReady() is called, the image is just sent to appview for display, when ImageReady() is called the bitmap is also stored into appview. A major new functionality is the ability to save a captured image, which starts at SaveCapturedImageL(). It fetches the bitmap from appview, uses a helper function GetNextFileName() (also defined here, scans the file names in the image directory, makes an assumption of a naming convention basename + number + file ending, and finds the next unused name) and saves the bitmap using the file name. Another useful piece of functionality can be found in LaunchMediaGallery(). This function hard codes the UID used be the MediaGallery (image viewing and browsing application), and activates the application on the same directory that it uses to store the images.

Separating the engine from the GUI

It is a sound design principle to separate the engine performing the actual application from the GUI framework. In a sense the cameraoptions example already showed a way to do that, by separating the real work into a class (VideoRecorder), and in that case a pointer to that class was held in a private variable inside the appui class. In the guiengine.init example the pointer to the engine is stored in the document class. Note that symwiz creates a pointer (iDocument) inside both appui and appview classes that allow easy access of the document class. This example takes the separation further by splitting the engine into its own subproject that is separately compiled into a DLL, which is then linked together with the main application.

The guiengine.init file uses a new function, use_engine(), on its third line. In this case it's given two arguments, the name of the main class of the engine, and a UID for the engine DLL. If you scroll down the file you see that there is a variable 'engine' that isn't initialized; this variable was created by the use_engine() function. Another thing that happens is that the document class creates an instance of the engine, stores it to a private variable, and adds a method called Model() that returns that pointer. iDocument->Model() is then used for example in the keyevent for pressing the 5-way navikey (EKeyDevice3). The engine has a two-phase constructor and an empty method Clear() that can be called to (re)initialize the engine.

We've seen already before how the list of project libraries can be extended (resource.libraries += [ 'lib1', 'lib2', ... ]); the engine can have its own set of libraries as well (resource.eng_libs += [ 'lib1', 'lib2', ... ]).

The actual application draws circles and rectangles on the screen. appview has a variable to hold the state which should be drawn (circle or rectangle), allows a cursor to be moved around with the navikey, and calls the model to add the shape that was last selected when the navikey is pressed (see appview.keyevents()). In the appview's Draw() method the code is replaced. A graphicscontext is obtained, a pen color and size are set, a simple crosshair is drawn for the cursor, and the shapes are drawn one at a time by fetching them using iDocument->Model()->GetNextShape(), which the engine implements, and calling the shape's own Draw() method.

The rest of the *.init file consists of adding methods such as AddShapeL(), GetNextShape(), and Clear() to the ShapeListMrg engine. Another class in the engine is TShape. Calling 'shape.dll = True' tells symwiz that the TShape class should go together with the engine, rather than the GUI framework. It is also a pure virtual class that defines interfaces for drawing a shape and getting its bounding box. Two classes, TCircle and TRectangle, derive from TShape and implement the missing functions. One more thing to notice is that all these shape classes are under a common namespace called NShapes, and the Shape file defines the Enum TShapeType inside the namespace but outside the class.

Active objects

Event-driven programs are by nature asynchronous. The program shouldn't wait for events in a busy loop wasting resources, but if there is nothing to do, it can go to sleep until a new event such as a request to refresh the window or a new key press arrives. Similarly, if an application connects to a server that provides some service, often in another process, it is nicer if the client that is asking for the service doesn't have to actively wait for the service, but instead can sleep or service some user interface events.

Active objects are the preferred mechanism to do this is in Symbian OS. An event-handling thread has a single active scheduler which is responsible for deciding the order in which events are handled. The tasks that the scheduler deals with are wrapped in an active object, derived from the CActive class. The active object must add itself into the active scheduler, this is typically done in the (second stage) constructor. It also needs to implement one or more methods that request a service. That function should first check that there are no pending active requests for that object (call IsActive() derived from CActive), then request the service, typically passing a reference to derived member iStatus, and finally marking itself active (call SetActive()).

There are two additional functions that an active object needs to implement: RunL() and DoCancel(). When the server has served the request, it sends a message that wakes up the thread if it is sleeping, and notifies the active scheduler that one of its active objects is ready to run. The scheduler then calls RunL, and the program can study the contents of iStatus to see whether the service was completed successfully. DoCancel() implements the user code that may be necessary if the active object is cancelled.

Symwiz CClass provides a method make_active() that creates a two-phase constructor to the class, makes the class to derive from CActive, adds itself into the active scheduler inside ConstructL, and adds the RunL() and DoCancel() methods. For example bmpmanip.init provides an example on the use of make_active().

Reading, writing, manipulating bitmaps

bmpmanip.init is rewritten so that it uses the new ICL library, instead of the old, deprecated image manipulation library that you can find in the SDK examples.

3D Graphics: OpenGL ES

There are two examples on how to use OpenGL ES (from SDK 2.2 onwards).