Porting

From UIQ Books

Jump to: navigation, search

Contents

Porting Applications

In almost any software development project, re-use of existing source code is preferable to starting from scratch, particularly if the functionality is well tested. This is true irrespective of whether the source code was originally written for the Symbian platform or an alternative platform. This chapter focuses on porting or re-using C and/or C++ code originally written for other platforms for the Symbian platform, specifically on UIQ 3.

C++ is the primary development language for Symbian OS but many C++ programmers are initially confused by the differences encountered in attempting to read and understand Symbian OS code. A large part of this is down to style and, in particular, naming conventions. At a high level, Symbian OS supports its own C++ language dialect and a large set of libraries. It is the sheer volume of library calls available that makes the task of porting an application to the Symbian environment look daunting.

Most differences in Symbian OS compared to other environments, such as Windows, Mac OS and UNIX are as a result of the fundamentally different design goals for the operating system. Symbian OS is required to support graphically rich, highly robust interactive applications on small screen battery-powered mobile phones that have comparatively small amounts of memory and low processing power. Mobile phones are expected to be built to low cost, yet still perform tasks such as video telephony or act as a mobile TV. They must securely store significant quantities of personal data. There can be no expectation of a regular reboot, and incoming calls and messages must be presented reliably and without delay.

Since inception, Symbian OS has been focused on requirements to minimize compiled code size, runtime memory usage, power consumption and processor cycles. In addition, the core of the OS was designed before the C++ standard had been formalized, and some features were either not supported by the compiler used to build for target hardware, or were considered to potentially be inefficient on a mobile phone.

As a result, the C++ used to create Symbian OS (and applications to run on it) had some deliberate omissions, including use of standard C++ exceptions, templates and the standard template library. The new kernel architecture used in Symbian OS v9 (found in UIQ 3), and advances in silicon (Moore’s law) have led to the recent introduction of support for standard C++ exceptions and the potential for an out-of-the-box STL implementation on Symbian OS.

Symbian OS extends, or, perhaps more accurately, complements other standard development patterns. For example, as well as having multi-threaded multi-tasking capability, Symbian OS uses active objects as a lightweight alternative. Other examples include the use of descriptors instead of standard C strings, and leaves as lightweight exceptions.

So, while some compromises and simplifications have been made compared to an operating system designed for desktop usage, these are generally few and far between. On the other hand, compared to an embedded system, Symbian OS is very sophisticated. It contains file stores, database management, threads, processes, dynamic link libraries, client-server architectures, security models and a large set of other functionality required to make a mobile phone operate.

Where to Start

If you have little or no Symbian OS experience, you should first read Symbian OS Essentials, which provides an introduction to Symbian OS and specifically covers topics that you need to know if you are used to programming for another platform.

If you are completely new to Symbian OS, we recommend these books from Symbian Press:

  • Developing Software for Symbian OS: A Beginner’s Guide to Creating Symbian OS v9 Smartphone Applications in C++.
  • Symbian OS C++ for Mobile Phones Volume 3: Application Development for Symbian OS
  • The Accredited Symbian Developer Primer: Fundamentals of Symbian OS.

In order to follow this chapter, you should have a reasonable grasp of:

  • Symbian naming conventions
  • panics, TRAPs, leaves and the cleanup stack
  • descriptors
  • dynamic arrays
  • UIDs
  • active objects
  • C++.

C++ has been listed because Symbian OS tends to make extensive use of advanced C++ constructs, such as implicit type conversion, interface inheritance and thin templates. For example, when first presented with descriptors some programmers fail to recognize that a method that is declared to take one particular type of descriptor as a parameter can accept any other descriptor whose class hierarchy contains the first descriptor type (implicit type conversion).

If you are familiar with Symbian OS but new to UIQ 3, we suggest that you read the chapters in the User interface framework section of this book and work with some of the example applications to familiarize yourself with the UIQ 3 user interface.

The Aims of Porting

Prior to starting, you should consider the goals for your porting project. In particular, you need to identify which elements of your application can change and which parts must remain the same. For example, if your aim is to recreate an identical copy of your application on a UIQ 3 phone that does not have a touchscreen compared to a Palm or Windows Mobile application that expects a touchscreen, you are forced to make compromises and program for a non-touchscreen GUI.

Several key questions that you should consider during the porting exercise are:

  • should you create an identical Symbian version of the original application?
  • should you recreate the look and feel and functionality of the original application?
  • should you expect the application to support all UIQ 3 phones?

When you examine your project in detail, you may be surprised to find that mobile applications need more change than originally envisaged, in order to work best on a different platform. Unlike desktop applications, it is not quite as simple as recreating the same application on a different operating system.

Exactly recreating your application in UIQ 3 usually requires you to re-write large quantities of code, particularly for the UI. You should embrace UIQ 3 as best you can for your application, for example, working with building blocks and the Command Processing Framework. You will still need to rewrite a large amount of the UI, but by adapting to UIQ 3 techniques, you will not need to waste time trying to recreate unnecessary characteristics of your original application.

Re-writing is not really porting, even if it is implementing the same algorithm. Rewriting the UI is often quicker and cheaper than attempting to port UI code from another platform. The chapters in the User interface framework section of this book tell you all about creating applications in UIQ 3, so this chapter concentrates on porting in the strict sense of moving code from another platform to UIQ 3.

Recreating the functionality also has advantages and disadvantages. For example, you may be porting a desktop application that contains functionality appropriate to the desktop such as printing. Most mobile phones do not have much support for printing; indeed many users do not have an expectation of being able to print from their mobile. Therefore, it may be reasonable not to support the same functionality.

Different UIQ 3 phones have different characteristics and are targeted at different market segments. For example, you may have an application that requires extensive data entry. In this case it is probably best to concentrate on supporting UIQ 3 phones that have a hardware keyboard. While the application may run on other UIQ 3 phones, it may not be particularly usable. Often this type of issue is resolved at the UI design phase, particularly with bespoke systems where the customer has selected the style or model of mobile phone.

Porting Techniques

A core objective when porting software is to make the minimal number of changes possible to the source code. The process is less elegant when compared to writing code for a particular platform or when compared to designing the code to be highly portable between a known set of target environments. You have to make compromises, especially when limited time is available. All approaches described have advantages and disadvantages and need to be appropriately applied to the conditions and goals within which the porting takes place. There is no single right or wrong way; however, many companies have preferred techniques and procedures that promote portability of code.

Prior to considering some of the ways in which porting can be achieved, we look at the general environments within which we are working.

General Porting Considerations

The following section presents some of the most common issues you will meet, irrespective of the source platform from which you are attempting to port code. Later sections deal with more platform-specific issues and demonstrate some approaches you may choose to port your application.

Standard Data Types

In common with many other platforms, Symbian OS defines its own basic data types. The full set is defined in e32def.h. Ultimately, the compiler only knows about the language defined data types. An unsigned long int, a TUint32 or ULONG are almost identical to a compiler, irrespective of the name your platform uses.

When you port an application that contains platform-specific data types, you will need to select a suitable approach:

  • change data type declarations to the target platform data type
  • change data type declarations to a neutral data type
  • cause the source platform data types to be recognized in the new environment.

The following table presents some of the common data type declarations for each of the Symbian, Windows and Palm OS platforms. The table is not intended to be comprehensive, it merely demonstrates that there is more or less a one-to-one correlation been the different data type declarations on different platforms.

While we have not listed the full set of data type declarations, particularly from the Windows platform, there are not that many data types that need some kind of conversion.

C++ data types e32def.h Windef.h PalmTypes.h
void void VOID void
signed char TInt8 CHAR Int8
unsigned char TUint8 UCHAR UInt8
short int TInt16 SHORT Int16
unsigned short int TUint16 USHORT UInt16
long int TInt32 LONG Int32
unsigned long int TUint32 ULONG UInt32
signed int TInt INT Int32
unsigned int TUint UINT UInt32
float TReal32 FLOAT float
Pro tip:

This summary table can be used as a basis to change your source code to have platform neutral data types or change the data types to the current platform data type. Relatively few data types require conversion. Applications that incorporate the platform specific data types will, at some point in their #include file chain, reference the platform-specific header files containing the definitions. These header files will not exist on alternate platforms, however, that does not mean you cannot supply your own version to perform the data type declarations. By doing so you may be able to leave your source code untouched!

Platform Size

The Symbian OS platform is a 32-bit platform, that is, an integer is represented by 32-bits. Most desktop operating systems such as Mac OS, Linux and Windows, including Windows Mobile are also 32-bit platforms whereas Palm OS is only a 16-bit platform. (It is possible to build 32-bit applications on Palm OS, but that is not the default environment.) Migrating from 16- to 32-bit platforms is usually not too difficult. It is rare that any part of an application will rely on integer values being restricted to 16-bits or use any 16-bit integer wrap around or overflow effects.

Occasionally issues may arise when using constructs such sizeof(int) and sizeof(long) since the size will vary depending on the target platform.

While not widespread, 64-bit platforms are now available. Porting applications targeted at 64-bit platforms to 32-bit platforms requires you to understand the code more thoroughly. For example, you need to check the range of values a variable may contain and choose an appropriate data type. Symbian OS supports TInt64 and TUint64 should your application require them.

Structure and Data Type Alignment

Any processor has numerous characteristics. One that is important in the Symbian environment is the inability of the ARM processors to read integers from anything other than a four byte boundary, and in the case of some ARM processors, short integers from anything other than two byte boundaries. If your application attempts to exert specific control over the size of data structures, or makes specific assumptions about the memory layout of a structure, you are likely to have some porting problems.

For example, consider the following data structure:

typedef struct
  {
  char oneByte;
  int fourBytes;
  }

You should not rely on the size of the structure being a specific value, or rely on the position of elements within the structure being in specific places. For example, on a real Symbian phone this structure will be eight bytes in size and the integer will start at offset four.

Pro tip:

The Symbian OS emulator uses the Intel processor natively; your code is compiled for a different processor compared to a target build. Due to the different characteristics of the processor the above structure will be five bytes in size and the integer will be at offset one within the emulator environment on emulator builds.

In most cases, the compiler will fill any data structures as required for the target environment. If you simply access data structure members by referencing the field names, you will not have a problem. It is only applications that attempt to map memory onto a data structure, often communications based applications, that will have difficulties.

Pro tip:

Early versions of Symbian OS allowed the ARM processor to throw an exception if your application attempted to access misaligned data. This feature is often disabled in modern Symbian devices; consequently, the problem has become much harder to track down. Code inspection or the use of ASSERTs will help.

Stack Size

In contrast to many platforms, the default stack size of an application on Symbian OS is 8k. Whilst this can be controlled to some extent, the stack size is static, that is, once defined within your build it will not dynamically shrink or grow. Some care is required when porting your application so as not to exceed the stack size that you have given your application. If you receive an unresolved external symbol __chkstk error message when building your application, this indicates that you are attempting to declare too many stack based variables.

Stack size is discussed in more detail in the Application MMP File section.

Unicode

Symbian OS is built as a Unicode environment only. This is primarily because Symbian OS supports many languages that cannot be handled by a single byte character set. For developers, a clear distinction needs to be made between textual content that is to be displayed to a user and any other textual content processed by your application.

For example, assume your application is required to read a text file and display the content to a user. In this scenario, you need to understand what your own application text file contains. In an English language application it contains ASCII characters with Carriage Return (CR) and/or Line Feed (LF) line delimiters. The content of the text file can be read, however it will be 8-bit data. Prior to being displayed to the user, it needs to be converted to Unicode. Fortunately, due to the design of Unicode, this is quite trivial.

If, however, your application enables the user to edit your text file you need to be aware that many languages contain characters outside the ASCII character set. You should consider whether this has any consequences for your application.

Standard C Library

In its native form, Symbian OS does not contain the set of functions that make up the standard C library. There are external libraries that you can link against that contain the majority of the standard POSIX C library functions as well as some POSIX style threading.

In the Symbian environment, there are a number of functions within the standard C library that need to be used with care. For example, the standard string functions assume ASCII or UTF-8 (with certain caveats). As previously described, text in Symbian OS is normally processed as Unicode. Attempting to use the standard C string functions on Unicode requires the usage of the wide character set functions. Few applications use the wide character string functions as they assume ASCII text. Therefore they will have to be changed to port successfully.

In general, the standard C library does not contain a large number of functions and most applications will only use a small number. For example, many of the standard character input/output functions have limited value in a GUI type application, printf and its variants are not useful in a graphically rich display environment. However, if your application contains a lot of ASCII text, string processing the standard C library has considerable value in any porting task.

C++ Standard Template Library

At the time of writing, the standard template library (STL) is not provided as part of Symbian OS or UIQ 3, although some unofficial versions are available. Prior to Symbian OS v9, it was difficult to implement the STL because standard C++ exceptions were not available, and dynamic cast was not supported. Symbian OS v9 now makes it possible, although it must still be carefully coded to avoid the bloat associated with template code. It is probable that an official STL implementation will be available on UIQ 3 at some point in the future.

If you are migrating code that uses the STL heavily, and the size of the binary is not a factor (for example, you are not providing your product as an over-the-air download) then you may wish to investigate the current STL implementations available for use on UIQ 3.

Symbian C++ and Naming Conventions

Symbian OS uses very distinct naming conventions. The prime objective is to provide as much information as possible to the reader without having to document the details. For example, if you consider the following code an experienced Symbian programmer can easily distinguish between the three different types of variable being used: local variables (k and i), a member variable (iVal) and method parameters (aCount and aList):

  k=aCount/2;
  for (i=0;i<k;i++)
    iVal+=aList[i];

If you compare this to the following fragment of code, it demonstrates that it is much harder to distinguish between local variables (although most programmers would be surprised if at least i and probably k are not local variables), method parameters and object property. A reader will almost certainly have to reference some other part of the code to determine which variables are method parameters or object property.

  k=count/2;
  for (i=0;i<k;i++)
      val+=list[i];

Naming conventions are simply that, conventions. Your code will not suddenly stop compiling and running because of what you choose to call them.

Pro tip:

In general, naming conventions are of benefit to anyone who reads the code. Over time, and perhaps tens of thousands of lines of code later, any hints within the code as to its operation becomes invaluable. We recommend that you adopt a writing standard that encourages self documentation and easy maintenance.

Symbian C++ and Exception Handling

Prior to Symbian OS v9 there was no support for C++ exception handling. Symbian OS had an equivalent, based on a cleanup stack, TRAP handler and leaves. Due to the evolution of the C++ language, C++ compilers and Symbian OS, C++ exceptions are now supported. There are still some restrictions on their usage and applications should not mix C++ exception handling with Symbian TRAPs and leaves. In particular, applications should not attempt to throw an error to a catch statement that jumps out of a TRAP(), or attempt to Leave() when inside a try...throw...catch block. Doing so is likely to break the error handling mechanisms since both techniques are manipulating the stack resulting in an application crash.

The Porting1 example shows a simple try...throw...catch C++ exception handling statement in action.

Pro tip:

Our experience is that it is unimportant as to whether you use Symbian OS keywords or C++ exception handling keywords to perform error handling. Neither solves the harder issues of rolling back to a self consistent state or cleaning up resources correctly in all conditions. As a programmer you still have to perform the hard work. Support for C++ exception handling simply provides a few more familiar tools to perform these tasks.

Symbian C++ and Writable Global Variables

Writable static data (WSD) is any per-process modifiable variable that exists for the lifetime of the process. In practice, this means any globally scoped data declared outside of a function or any function-scoped static variables.

Global data is only constant (i.e. non-writable) if it is one of the built-in types or an object of a class with no constructor. For example, this data is constant:

static const TUid KUidFooDll = { 0xF000C001 };
static const TInt KMinimumPasswordLength = 6;

The following definitions have non-trivial class constructors, which require the objects to be constructed at runtime, so while they may they appear to be constant, they are not. Although the memory for the object is pre-allocated in code, it isn’t initialized until the constructor has run:

// At build time, each of these is a non-constant global object.
static const TPoint KGlobalStartingPoint(50, 50);
static const TChar KExclamation(’!’);
static const TPtrC KDefaultInput =_L("");

Symbian OS has always supported the use of writable static data (WSD) in EXEs. WSD could not be used in DLLs built for target hardware on UIQ 2.0 or UIQ 2.1, (applications were previously DLLs) but this has now been made possible in UIQ 3 because of the new kernel architecture. In order to enable global writable static data on EKA2, the EPOCALLOWDLLDATA keyword must be added to the MMP file of a DLL.

Where possible, Symbian still recommend that you avoid the use of WSD in DLLs because it can lead to inefficient memory use and has limited support on the Symbian OS Windows emulator. It should only be used as a last resort, for example when porting code written for other platforms that uses writable static data heavily. You should consider implementing workarounds such as the following:

Thread-Local Storage (TLS)

Thread-local storage (TLS) is simply a 32-bit pointer, specific to each thread that can be used to refer to an object which simulates global writable static data. All the global data must be grouped within this single object, which is allocated on the heap on creation of the thread.

TLS can be accessed through class UserSvr – the pointer to the object is saved using UserSvr::DllSetTls(), and accessed using UserSvr::DllTls(). On destruction of the thread, the data is destroyed too.

Singleton using CCoeStatic

The CCoeStatic base class can be used to create a singleton class which is stored by the CONE environment (CCoeEnv) in TLS. You should derive from this class and implement the singleton. Once it has been created, it may be accessed through CCoeEnv::Static().

For more details, please see the Symbian Developer Library (Application Framework Guide) in the UIQ 3 SDK.

Client–Server Framework

Symbian OS supports writable global static data in EXEs. In UIQ 2, a common porting strategy was to wrap the code in a Symbian server (which is an EXE), and expose its API as a client interface. Since applications on Symbian OS 9 are now EXEs in their own right they can contain writable global static data. This eases the porting burden as the Symbian application model is now much closer to other platform application models.

Embed Global Variables into Classes

It is often possible to move global data into existing classes or create new containers, as long as these themselves are not global.

Symbian C++ and Multiple Inheritance

Symbian OS experts disregard full multiple inheritance, preferring only to use interface inheritance. The origins of this go back to the support for multiple inheritance offered by the compilers available at the time. There are ongoing discussions as to whether full multiple inheritance is necessary or desirable. For example, neither C# or Java programming languages support multiple inheritance.

Since Symbian OS continues to use industry standard compilers, it does not explicitly ban the use of full multiple inheritance. This is demonstrated by our Porting1 example application. There are, however, philosophical and comprehension issues associated with multiple inheritance and you may have specific views on the matter. On the whole, Symbian OS only uses interface inheritance, not least of which many objects derive from the same base class CBase.

Platform Security

Symbian OS implements a layer of Platform Security that imposes restrictions on the way some APIs can be used. For example, if your application comprises a series of plug-in modules which are dynamically searched for and presented to the user as application functionality, you may need to revise your product specification and/or implementation. In particular, if a plug-in comprises code, it has to be placed in the \sys\bin folder for Symbian OS to load it. However, Symbian OS will not easily allow applications to search the \sys\bin folder to locate the plug-ins! Potential solutions are to use the Ecom plug-in framework or to register your plug-ins via a public folder or use the \private\\import folder of the primary application.

Platform Security has other implications, particularly associated with application deployment.

General Porting Techniques

Your initial application design and your porting requirements dictate some of the techniques that can be employed to port your application. No one technique can solve all porting issues, some techniques may not be applicable at all whereas others may not adhere to your own or company standards. This section simply outlines some of the techniques you may consider using to port your application.

Component Isolation

When planning your porting task, one of your first considerations is to determine how easily you can port each component of your application. This, in turn, is often dictated by the application design and testing strategies that were employed during the original application development.

For example, if you have a data processing engine and you originally abstracted out the data supply and data consumption components, perhaps to ensure your data processing engine was automatically testable, it should be possible simply to replace the data supply and consumption components with platform-specific components. In that way, you may be able to port the data processing engine as a whole unit.

Porting and rewriting

Figure 1 Porting and rewriting

As a specific example, let us assume your application has an XML processing engine. At one level XML is simply a set of text strings that need to be processed. Further, let us assume the application normally receives the XML content from an HTTP component and delivers its results to a display component. If you originally wrote this application so that you can replace the HTTP and display components, enabling you to write automatic test code for the XML processing engine, then it should be a relatively simple task to replace the HTTP and display components with UIQ 3 specific components.

In such an example, the UIQ 3-specific components may have to massage the data between different data formats, introducing a layer of inefficiency; however, in most cases such inefficiencies are not significant. For example, Symbian OS frequently uses descriptors, while your data processing engine may be processing C like strings. Translating between the two entities is not difficult, for example to construct an 8-bit descriptor from a zero terminated C style text string requires the following code:

TPtrC8 descriptor(aZTString);

Depending on the parameters within which your porting project is operating, it may be possible for you to change a small amount of code in order to generate components that can be isolated.

Using a text file processing example, if your processing engine was written in C++ and included the file reading code, you may be able to extract that code into a separate object, and then define a virtual interface to the new object. Porting then involves small changes to the original source and the implementation of two versions of a file reading object, one of which contains the source code you already have.

If the code was originally written in C, you may be able to move all the file reading code into a few functions and define a ‘virtual interface’ to those functions. Porting then comprises of small changes to the original source and the implementation of two versions of the file reading functions, one for each platform.

In both cases you would use the project definition files, such as MMP or make files, to include only those source modules relevant to each platform.

Using C++ Object Hierarchies

If you have implemented some code in a C++ hierarchy and split functionality into numerous methods, you may be able to adjust the method declarations and split the source over several files to achieve portability.

In writing a text file processing application, it is likely that the file reading code will have been isolated using only a few methods, particularly if you have implemented some file buffering. In this case, you may be able to achieve portability by changing the methods to be pure virtual and require a platform-specific implementation of the object be instantiated.

Breaking up Source Modules

C++ does not require all the methods of a particular class to exist in the same source file. Similarly, the C programming language places little requirement on the programmer to put associated functions in the same source file. It is possible to break up a class or set of functions over multiple source modules and reference different source modules on different platforms. As long as an implementation of a method or function exists somewhere within the source code of your project, the linker will successfully work.

In our text file processing example, if you can easily isolate the file reading code, it is possible to create a new source module containing the platform-specific code. For each supported platform, you have a separate source file. You simply link with the appropriate source for the platform you are building the application for.

#define

The much maligned #define really is a very powerful tool when it comes to dealing with cross platform porting issues and can be put to several uses, for example:

Conditional Compilation

In our text file processing example, if you have a small number of functions containing the file reading code, you may be able to place the source code within #defines, causing platform-specific code to be referenced dependent on the compiler #define list.

Redefining Entities

In some porting scenarios, you may have a requirement to map one entity to another. For example, you may have some code on Palm OS that calls StrLen() to determine the length of a C style string. The equivalent in Symbian OS is User::StringLength(). It is possible to use a #define to map between these two functions. This technique means your source code can remain the same. The only testing required is to ensure that the functions you map between perform the same task.

The Porting1 example application shows how you might use #defines to cause Palm (or Windows) style code to build on Symbian OS. The following code fragments can remain identical on Symbian OS without requiring the implementation of any functions or using standard C libraries:

CHAR* WindowsLikeFunctions::AppendString(CHAR* str1,CHAR* str2)
 
  // Create a new string being the concatenation of the two original strings.
  {
  CHAR* result=(CHAR*)malloc(strlen(str1)+strlen(str2)+1);
  strcpy(result,str1);
  strcat(result,str2);
  return(result);
  }
Char* PalmLikeFunctions::AppendString(Char* str1,Char* str2)
 
  // Create a new string being the concatenation of the two original strings.
  {
  Char* result=(Char*)MemPtrNew(StrLen(str1)+StrLen(str2)+1);
  StrCopy(result,str1);
  StrCat(result,str2);
  return(result);
  }

Porting from a Standard C/POSIX Style Environment

When Symbian OS was originally conceived, POSIX support was not a priority because the hardware it was designed to run on had limited resources, and the emphasis was on support for lightweight mobile applications rather than the potential for porting mainstream desktop services. The evolution of Symbian OS, and the hardware it runs on, now makes it possible to provide a set of libraries to supplement the native Symbian OS C++ APIs with a POSIX compliant layer. These are intended to make programming for Symbian OS smartphones accessible to more developers, allowing them to bring existing desktop and server code to the platform more easily.

These extension libraries are generically called P.I.P.S. (P.I.P.S Is POSIX on Symbian OS). They provide a reasonably complete industry standard POSIX-compliant API. The P.I.P.S. libraries do not provide a UNIX application environment (source code will need to be rebuilt and may need some modification to run) but they do make software development for Symbian OS handsets more accessible to developers used to programming using the C language.

At the time of writing, there are no graphics libraries available, so you will need to consider how to present your application UI, should you need to do so. You can mix or incorporate Symbian native code into an application that links against the P.I.P.S. libraries, but it is a non trivial task to add the functionality supported by the Symbian native application framework code.

The P.I.P.S. libraries are appropriate if you want to port existing code, or write portable code from scratch. However, since the libraries are a layer over the native APIs, if you require tight, efficient code, a UI or direct integration with the hardware, you should use the native Symbian OS APIs in preference.

P.I.P.S. Libraries

The first version of P.I.P.S. comprises the following libraries:

  • libc, the standard C and POSIX API
    • stdio and fileio including printf(), scanf()
    • stdlib, including environment variable support, etc
    • string manipulation and character APIs
    • locale and system services
    • searching, sort and pattern matching
    • IPC mechanisms including shared memory, pipe,
    • FIFO and message queues
    • process creation including popen(), posix_spawn(), system()
    • networking and socket APIs.
  • libm
    • mathematical and arithmetic APIs.
  • libpthread
    • thread creation and synchronization mechanisms.
  • libdl
    • standard C dynamic loading and symbol lookup APIs.

The previous standard C library, stdlib, (estlib.dll) only provided a partially compliant and limited subset of the standard C/POSIX APIs sufficient for the implementation of a Java virtual machine on Symbian OS. In contrast, P.I.P.S. provides a much more complete and standards compliant solution. You should note that these two sets of libraries are not compatible, so you should only choose one for your application porting task. The existing stdlib library will be deprecated once P.I.P.S. has become more established, so it is recommended that any new porting tasks utilize the P.I.P.S. libraries in preference to the stdlib libraries.

P.I.P.S. POSIX Compliance

The P.I.P.S. libraries are compliant with ISO/IEC 9945 where this is possible within the limitations of a mobile operating system.

P.I.P.S. is not fully compliant because of some limitations in what Symbian OS can support (for example, versions of Symbian OS prior to v9.3 only support ordinal lookup in DLLs and cannot offer symbolic lookup, thus dlsym() in libdl cannot be used with a symbolic name). However, workarounds are suggested in the P.I.P.S. documentation.

Therefore, you should establish whether the current P.I.P.S. functionality is sufficient for your project. Further information can be found in the PIPS Booklet, available from the Symbian Developer Network, as described below.

Using P.I.P.S.

Assuming you have previously installed the UIQ 3 SDK then before you can use P.I.P.S. you need to download the P.I.P.S. SDK plug-in. The SDK and documentation are freely available from the Symbian Developer Network: P.I.P.S.. Few phones currently contain the libraries pre-installed, therefore you will need to add the P.I.P.S. environment to your application installation package. The website provides a number of examples of P.I.P.S. in action.

Porting from Palm OS and Windows Mobile

In this section, we look at some of the characteristics of Palm OS and Windows Mobile applications and show how these are mapped on to Symbian OS applications.

Programming Environment

The native Palm OS environment is the C Programming language. C is also the native language for Windows Mobile, though other mainstream development environments are supported.

In contrast, Symbian OS is built using C++. All communication with operating system functionality is through objects of some kind. Individual function calls don’t really exist and operations are usually closely associated with the data they manipulate.

Palm OS Environment

By definition, the C programming language is procedural and leads to a particular style of programming. While it is entirely possible to write C++ code and run it on Palm OS, any use of the operating system functionality will result in regular C function calls.

Symbian OS uses most C++ idioms quite heavily. For example, parameters are almost always passed by reference and not by pointer; templates are widely used, as is implicit type conversion. When moving from a Palm environment, you need to understand a different set of library functions, you have to change programming languages and you need to change your programming approach.

As an example, consider the point and rectangle support on both platforms.

On Palm OS you have:

typedef struct PointType {
  Coord x;
  Coord y;
} PointType;
 
typedef struct RectangleType {
  PointType  topLeft;
  PointType  extent;
  } RectangleType;

The equivalent in Symbian OS is:

class TPoint
  {
public:
  TInt iX;
  TInt iX;
  }
 
class TRect
  {
public:
  TPoint iTl;
  TPoint iBr;
  }

As you can, see these are practically identical. The major difference arises in how these are used in your application. In Palm OS you have a set of functions that take various PointType or RectangleType parameters and manipulate the passed data structures. In Symbian OS, the functions are methods that belong to the class definition and tend to manipulate the property belonging to the class itself. The methods have not been shown here for clarity.

For example, to determine the intersection of two rectangles, Palm OS has the following function:

  void RctGetIntersection (const RectangleType *r1P, const  RectangleType *r2P, RectangleType *r3P)

This is usually called in code with something like:

  RectangleType r1;
  RectangleType r2;
  RectangleType r3;
  RctGetIntersection(&r1,&r2, &r3);

In contrast, Symbian OS has the following method declared as part of the TRect class:

  void Intersection(const TRect& aRect);

This is usually called in code with something like:

  TRect r1;
  TRect r2;
  r1.Intersection(r2);

So, the method call is using the property belonging to the object as input data and storing the result in its own property. You may also notice that the compiler is passing r2 by reference and not by value, since the method declaration states that is what should occur.

None of this is particularly special to Symbian OS, it is simply the type of difference you should expect to encounter when moving from a C to a C++ or object oriented style environment.

Windows Mobile Environment

In addition to native C programming, Windows Mobile also supports mainstream development using managed code environments such as Visual Basic or C# within the NET framework and the MFC environment that uses C++. The MFC environment is really only a layer that interfaces directly with the native Win32 C like operating system functions. As such, MFC is a hybrid between C and C++.

If you are moving from a C style Windows environment, not only do you need to work with a different set of library functions, you also have to change programming languages, and hence approach, in quite a substantial way. In contrast, if you are moving from an MFC type C++ environment, then whilst the details will change, the general approach to programming is more or less the same.

However, if you are moving from a managed environment such as C#, the problems you will encounter converting your application to Symbian OS are similar to those you would encounter converting your application to Windows style MFC C++ code. Indeed, if you are attempting to port an application from C# you may be better off choosing Java as your target language rather than C++.

As an example of some of the similarities between Windows Mobile C/C++ and Symbian C++ consider the point and rectangle example below.

While not the exact class declaration, on Windows Mobile you have something like:

class CPoint : public tagPoint
{
public:
  LONG  x;
  LONG  y;
}
 
class CRect  : public tagRect
{
public:
  LONG    left;
  LONG    top;
  LONG    right;
  LONG    bottom;
} ;

In Symbian OS it looks like:

class TPoint
  {
public:
  TInt iX;
  TInt iX;
  }
 
class TRect
  {
public:
  TPoint iTl;
  TPoint iBr;
  }

These are very similar; a point comprises two integer values and a rectangle four. However, Symbian OS tends to support a more object oriented approach to manipulation of data structures. For example, to determine the intersection of two rectangles, Windows Mobile has the following method associated with the CRect class:

  void IntersectRect(LPCRECT lpRect1,LPCRECT lpRect2);

The method is often called in code something like:

  CRect r1;
  CRect r2;
  CRect r3;
  r1.IntersectRect(&r2, &r3);

Two rectangles are passed by pointer into the class. The result of the intersection of the rectangles is usually stored within a third rectangle object.

Symbian OS has the following method declared as part of the TRect class:

  void Intersection(const TRect& aRect);

It is usually called in code like:

  TRect r1;
  TRect r2;
  r1.Intersection(r2);

The method call is using the property belonging to the object as input data and stores the result in its own property. The compiler is passing r2 by reference and not by value since the method declaration states that is what should occur.

The task of porting from MFC and/or Windows style C++ to Symbian C++ is not very different from porting from MFC and/or Windows style C++ to any other C++ variant. If your code does not use many of the MFC classes, you will have a much easier task compared to code that makes extensive use of such classes. In general, you may be able to port some of your data processing code; however, it will be difficult to port much of the UI code.

From C# to Symbian C++

If you have been using C# for Windows Mobile development, you need to change to using the C++ or possibly Java programming languages. Since this book is concerned with programming in C++ on the Symbian platform we will not cover using Java as an alternative to C#.

While C# and C++ are similar in many respects, a number of important differences should be noted:

  • C# compiles to an intermediate language. This usually requires a virtual machine of some description. Apart from the size of such intermediate virtual machines, an additional layer can affect the performance of applications. It also requires that the virtual machine be present on all phones you are targeting. C++ compiles directly to native code.
  • C# performs automatic memory management, C++ does not. You will need to become familiar with the manual techniques employed within Symbian OS such as the use of the cleanup stack. In contrast, automatic garbage collection has inherent problems particularly if pointers are used within applications.
  • In C# there are no header files, all code is written inline. In that respect C# is very similar to Java. C++ has header files declaring classes; Symbian OS has literally thousands of header files within the epoc32\include folder. In practice, the task of determining how to use a specific class is not particularly different in either environment as both support a rich and comprehensive set of objects. Whether you look at header files, source modules or documentation there is a large volume of information to digest.
  • C# arrays are naturally bounds checked. Pure C++ arrays are not. While you can clearly implement regular C++ arrays in Symbian OS, this is not usually recommended. Symbian OS contains objects called descriptors that add properties such as bounds checking, for the exact reasons that Java and C# have added within the language itself.

C# and Symbian C++ data types are very similar:

C# Symbian OS
sbyte TInt8
short TInt16
int TInt32 (TInt)
long TInt64
byte TUint8
ushort TUint16
uint TUint32 (TUint)
ulong TUint64
float TReal32
double TReal64
char TChar

In general, the task of porting from C# to Symbian C++ is rather like porting from C# to any other C++ variant. Neither is particularly easy in the sense of being able to re-use any existing code. Most porting projects would really be re-writing the original in a new language.

Application Design

Although Palm OS, Windows Mobile and UIQ 3 are all designed for mobile devices, there are significant differences in the implementation of the UI. You are unlikely to be able to cause much of your Palm OS or Windows Mobile UI code to work on the UIQ 3 platform. However, some of your design work may be re-usable. For example, your application may be organized as a list or summary view with a details view to display data in full. Your application design work can be applied to UIQ 3 applications as they are commonly organized this way.

Application Architecture

Each platform has its own application architecture dictated by the platform vendor.

A typical Palm application contains start-up code like:

static void EventLoop (void)
{
  Word error;
  EventType event;
 
  do
    {
    EvtGetEvent (&event, evtWaitForever);
    if (!SysHandleEvent (&event))
      if (!MenuHandleEvent (NULL, &event, &error))
        if (!ApplicationHandleEvent (&event))
          FrmDispatchEvent (&event);
    }
  while (event.eType != appStopEvent);
}
 
DWord PilotMain(Word launchCode,Ptr cmdPBP,Word launchFlags)
{
  if (cmd == sysAppLaunchCmdNormalLaunch)
    {
    FrmGotoForm(MyFirstForm);
    EventLoop();
    StopApplication();
    }
}

A basic Windows application contains start-up code similar to:

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
  MSG msg;
  HACCEL hAccelTable;
 
  // Initialize global strings
  LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
  LoadString(hInstance, IDC_WIN322, szWindowClass, MAX_LOADSTRING);
  MyRegisterClass(hInstance);
 
  // Perform application initialization:
  if (!InitInstance (hInstance, nCmdShow))
  {
    return FALSE;
  }
  // Main message loop:
  while (GetMessage(&msg, NULL, 0, 0))
  {
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
 
  return msg.wParam;
}

Windows Mobile MFC and C# applications both embed the above type functionality within their application architecture and most applications do not need to be exposed to it. The above functionality is more visible in MFC type applications since it comprises a number of objects, each with message maps to translate between Windows type messages and methods.

In contrast, a typical UIQ 3 application has an entry point, E32Main(), and three application specific C++ objects. The vast majority of the functionality of these three objects is provided by the operating system framework code. Your application is really only filling in a few blanks with a bit of syntax. In particular, you should observe the objects tend to do little more than create application specific versions of the CQikApplication, CQikDocument and CQikAppUi objects:

void CAppSpecificUi::ConstructL(void)
 
  // Normal primary entry point to a Symbian App.
  {
  CQikAppUi::ConstructL();
 
  }
 
///////////////////////////////////////////////////////////
CAppSpecificDocument : public CQikDocument
  {
protected:
  CQikAppUi* CreateAppUiL();
public:
  CAppSpecificDocument(CQikApplication& aApp);
  static CAppSpecificDocument* NewL(CQikApplication& aApp);
  };
 
CAppSpecificDocument::CAppSpecificDocument(CQikApplication& aApp) :
  CQikDocument(aApp)
  {}
 
CAppSpecificDocument* CAppSpecificDocument::NewL(CQikApplication& aApp)
  {
  return(new(ELeave)CAppSpecificDocument(aApp));
  }
 
CQikAppUi* CAppSpecificDocument::CreateAppUiL()
  {
  return(new(ELeave)CAppSpecificUi);
  }
 
///////////////////////////////////////////////////////////
class CAppSpecificApplication : public CQikApplication
  {
protected:
  TUid AppDllUid() const;
  CApaDocument* CreateDocumentL();
  };
 
TUid CAppSpecificApplication::AppDllUid() const
    {
    return(KAppSpecificUid);
    }
 
CApaDocument* CAppSpecificApplication::CreateDocumentL()
  {
  return(CAppSpecificDocument::NewL(*this));
  }
 
///////////////////////////////////////////////////////////
LOCAL_C CApaApplication* NewApplication()
  {
  return(new CAppSpecificApplication);
  }
 
GLDEF_C TInt E32Main()
  {
  return(EikStart::RunApplication(NewApplication));
  }

All of the example applications provided with this book contain application start-up code similar to the above.

Those familiar with a Model, View, Controller type of application architecture should recognize this model within the UIQ 3 framework. Similarly those familiar with the MFC CApplication, CDocument and CView objects should be able to see how that maps onto the UIQ 3 framework.

Event Model

Palm OS, Windows Mobile and Symbian OS are event driven. When an event occurs somewhere in the system, it is delivered to the application, the application processes it and returns to wait for the next event.

In Palm OS, each application implements its own copy of the event loop, receives all events, and distributes those events to the different event handlers. Palm OS has a constant set of event handlers coded within your application EventLoop() function. The set comprises the system event handlers SysHandleEvent() and any application specific event handlers. Changing the set of event handlers depending on application state is a non trivial programming task.

As with Palm OS, each Windows Mobile application implements its own copy of the central event loop. Unlike Palm OS the distribution of events is performed by system code. Events are dispatched to one of many potential callback functions registered with the system.

Where has my central event loop gone ?

In Symbian OS, the application framework code implements the central event loop. It is called the Active Object Scheduler. All GUI based applications require an active scheduler to operate since various parts of the framework code assume such a component exists.

Events in Symbian OS can be considered as having several properties: the event type, an event owner and an associated counting semaphore. With this information, the active scheduler can rapidly determine who the event is for and inform the correct component, without having to obtain event specific data or pass events to other potential event recipients for inspection.

What is the Significance of the Term ‘Active’?

In Symbian OS, events are represented as objects. In particular, events are represented by objects that derive from the CActive class. The term active object is widely used in Symbian OS and simply refers to objects that derive from the CActive class.

The active scheduler maintains a priority ordered linked list of active objects. You may think of this as a priority ordered list of pointers to functions. When an event occurs, the active scheduler will search all the active objects to determine which is associated with that event. Once located it will call a method belonging to that active object, called RunL(), informing the active object that an event for which it was waiting has occurred.

In pseudo code, the active scheduler event loop looks something like this:

void CActiveScheduler::Start()
  {
  TDblQueIter<CActive> q(iActiveQueue);
  TInt level=iLevel++;
  while (iLevel>level)
    {
    // Your app should spend all its time in here.
    User::WaitForAnyRequest();
 
    q.SetToFirst();
    FOREVER
      {
      CActive *ao=q++;
      if (ao->IsActive() && ao->iStatus != KRequestPending)
        { // found object to accept the event
        ao->iActive=EFalse;
        TRAPD(errNo,ao->RunL());
        if (errNo!=KErrNone)
          {
          errNo = ao->RunError(errNo );
          if (errNo !=KErrNone)
            Error(errNo);
          }
        break;
        }
      }
    }
  }

The code User::WaitForAnyRequest() simply waits until the counting semaphore associated with the thread is signalled. Since this only occurs when an event is delivered to a thread the function call will only complete when an event is available for processing. If no events are outstanding, the operating system will suspend the calling thread. When all threads are suspended the OS can enter any power saving mode because no events are outstanding.

Palm OS Event Loop

The call to User::WaitForAnyRequest() is broadly equivalent to the Palm OS function EvtGetEvent(&event, evtWaitForever).

The first difference is that the WaitForAnyRequest() cannot timeout in the way the EvtGetEvent() function can. In Symbian OS, timer expiry is handled as an event in its own right. A timer active object would exist to represent the timer event, and would be added to the active scheduler queue. When a timer event occurs, for example, a specified elapsed time passes, the counting semaphore will be signalled, any event specific data generated and the active scheduler calls the RunL() method of the timer active object.

A second difference is that no event specific data is delivered to or processed by the central event loop. In Palm OS an EventType event structure is defined. This is necessarily a union of all the different event structures that exist within the system. In Symbian OS each of the active objects is free to design, in co-operation with its associated event source, any data structure they consider fit for their purpose. Typically, an active object making an event request will either pass a pointer to some memory it is prepared to accept the event specific data or fetch the event data when it has been told there is some available.

A third major difference between the Palm approach and Symbian approach is the flexibility in event handlers. In Symbian OS, event handlers are added and removed from the central event loop as and when required. This typically occurs when active objects are created and deleted. The change in event handlers does not require any change in the application code or central event loop. Indeed, many system components will add active objects automatically to perform their tasks. For example, the window server will generate keyboard, pointer and draw request events. These are delivered to an active object created by the application framework. This active object performs various tasks before calling much more familiar methods such as OfferKeyEvent(), HandlePointerEvent() or Draw().

Windows Mobile Event Loop

The Symbian OS call to User::WaitForAnyRequest() is broadly equivalent to the Windows function GetMessage(&msg, NULL, 0, 0).

The remaining code is broadly similar to the TranslateMessage() and DispatchMessage() functions where the framework locates some code that is prepared to handle the event. As can be seen from the CActiveScheduler::Start() pseudo code presented earlier, the Symbian code searches though a list of entities looking for a method to call. In particular, it is searching through the priority ordered linked list of active objects to find one that was active and whose event has been delivered. Once located the RunL() method of that object is called to process the event. Windows has to perform a very similar task to locate the call back function that has been registered.

You may notice that no event specific data is delivered to or processed by the central event loop. In Windows, a MSG structure is defined which contains a number of fields. These fields tend to have different meanings depending on the context of the message. This design requires events to format their information into the general purpose fields. Extracting the parameters often requires the use of macros such as LOWORD(lParam) or GEM_WM_COMMAND_ID(wParam,lParam).

In Symbian OS there are no general purpose fields, each active object is free to design, in co-operation with its associated event source, any data structure they deem fit for their purpose. Typically an active object making an event request either passes a pointer to some memory within which it is prepared to accept the event specific data or fetch the event data when it has been told there is some available. Few, if any macros, are required to extract the event data as an event specific object is delivered.

Application Lifecycle

In Palm OS, applications are typically launched when they become foreground and are expected to exit when they go background.

In both Windows Mobile and Symbian OS, an application is launched the first time it is selected by the user. In both cases the style guidelines recommend that applications do not contain any close or exit menu options. When a user tasks away to a second application, the first usually goes background but continues executing. Applications tend to only be closed when memory within the mobile phone becomes low.

On all the platforms, when applications exit they should preserve as much of their state as is required to give the user the impression that they are continuing from where they left off when they are next started.

Palm OS Launch Codes

Symbian applications do not support launch codes through the equivalent to the Palm OS PilotMain() application entry point. This is mainly due to the fact that Palm applications have usually exited when not foreground whereas Symbian applications rarely exit.

Launch codes are often used to enable interaction with other system components or to enable an application to provide a set of services to another part of the system. Symbian OS is a multi-tasking operating system. If a set of services is to be provided to multiple clients, this is normally implemented through the client-server architecture. Each client would register with the server and as events occur within the server or the server is informed of state changes that are of interest to a client, then an event would be sent to each registered client.

UIQ 3 applications can provide services to other applications through the view architecture. For example, the Contacts application provides the ability for applications to view and edit contact database entries. These services are supported through the Dynamic Navigation Link or DNL architecture.

Threads and Processes

At the application level, Palm OS is a single threaded OS. All application specific functionality is run within a single thread and there is no support for applications to create other threads or processes. Behind the scenes, Palm OS has other threads and processes running. Applications interact with these system components through standard Palm OS library calls. Whether these calls result in a client-server type interaction is largely hidden from the applications’ programmer.

Symbian OS is a multi-threaded, multi-process operating system. Applications can create threads and spawn other processes if required. In practice, very few Symbian applications actually need to create other threads or processes to perform their task. Any work that you have put into your Palm application design to partition compute intensive tasks to ensure your Palm application remains responsive to any user events is also applicable to Symbian OS.

Windows Mobile is also a multi-threaded, multi-process operating system. Unlike Windows Mobile, Symbian OS discourages the use of threads, preferring applications to use active objects instead. Both threads and active objects have advantages and disadvantages. If the application that you are porting contains threads, you are not forced to convert it to use active objects; you can continue to use threads. You will have to do some work to translate between the Windows Mobile thread management functions and the Symbian OS equivalents.

Similarly, if your application architecture requires the use of more than one process, this model can be accommodated by Symbian OS. Again, you must translate between Windows Mobile process management and Symbian OS process management functions

The client-server pattern is a familiar one in Symbian OS since it offers a good level of security and robustness. Most Symbian OS applications use some form of client-server interface to perform their tasks. For example, access to files is via the file server, access to the screen and keyboard is via the window server and associated processes and access to the communications subsystems is via the communications server.

Error Handling

Symbian OS distinguishes between two types of errors: ones that are most likely to be programming errors and ones that are caused by abnormal runtime conditions.

For example, attempting to put more data into a descriptor (a memory buffer) than there is memory declared to exist, writing to a NULL pointer or attempting to write to a file that has not been opened are considered programming errors. These types of errors results in your application being immediately terminated via a panic and reason code, it is said to have panic’ed. The Symbian OS philosophy is to terminate any application as soon as it detects what can only be a programming error, usually resulting in a more robust system and facilitating debugging.

The Symbian OS SDK defines hundreds of panic reason codes. These are listed within the system panic reference section of the Symbian Developer Library found in the UIQ SDK.

In contrast, errors caused by abnormal runtime conditions, for example, attempting to open a file that does not exist, running out of memory or errors validating user input, result in a recoverable error condition that applications should be able to handle.

Palm OS and native Windows Mobile usually handle this in the traditional way, returning error codes which applications need to test for and propagate back to the caller, performing error handling and rollback en route. Studies have shown that this technique tends to significantly add to the volume of code written, and can make the code much harder to follow. Symbian OS has a strong tendency to leave should an error occur.

If you are familiar with the setjmp/longjmp standard C mechanism, then Symbian OS effectively runs a stack of setjmp buffers. The TRAPD() macro is broadly equivalent to a setjmp, while a User::Leave() is broadly equivalent to a longjmp. For those familiar with MFC, TRAPD() is broadly equivalent to the TRY and CATCH macros, a leave is broadly equivalent to throwing a CException that contains a single integer value.

Typical Palm OS, native Windows Mobile or standard C code might look like:

  int ret=FunctionOne();
  if (ret==0)
    {
    ret=FunctionTwo();
    if (ret==0)
      {
      ret=FunctionThree();
      if (ret==0)
        {
        // All ok to continue.
        }
      else
        {
        TidyUpFunctionTwoResources();
        TidyUpFunctionOneResources();
        }
      }
    else
      {
      TidyUpFunctionOneResources();
      }
    }

In contrast, Symbian OS code would look something like:

  {
  TRAPD(ret,
    FunctionOneL();
    FunctionTwoL();
     FunctionThreeL();
 
    // All ok to continue.
 
    );
  }

Note that we have not deliberately missed out the TidyUp type functions to try to make it look a lot simpler. In general the methods being called would register any resources that need tidying up with a component called the CleanupStack. Should an error occur, the cleanup stack will automatically clean up the resources. Some of the real world complexity has not been shown in this short example, for example, registering resources with the cleanup stack, however, the general principle is to move most error handling out of the primary algorithm and let the framework code perform as much tidying up as you can.

Nothing in Symbian OS will stop you from writing C like error propagation code, particularly if you do not call any native API functions. However, rather than return error codes, a significant number of the native API functions will leave. Symbian OS naming conventions will usually inform you if this is the case, by suffixing the name of methods that leave with a trailing upper case L.

Text

Strings within the C programming language are arrays of bytes with a trailing null. Unfortunately, there are many spoken languages where the 256 unique value limit of a single byte is not sufficient to represent all the characters in the language. For example, latest estimates suggest that Chinese contains around 50,000 characters.

Unicode was developed as a standard representation of character sets. Unicode is designed to be compatible with ASCII in the sense that the characters in the ASCII table have the same numerical value as their equivalent in the Unicode table. The Unicode standard defines three encoding schemes, one of which is called UTF-8. In this scheme characters are represented as either: one, two, three or four bytes. As such, many platforms can continue to use standard string processing functions with some restrictions. For example, the number of characters in a UTF-8 string is not necessarily the same as the number of bytes occupied by the string. Similarly indexing characters in a UTF-8 encoded string is not as simple as using the array indexing operator [] you need to decode the string from the start.

Most Palm OS applications are written using single byte character sets to contain any text presented to the user.

Symbian OS chooses to use Unicode to represent text throughout. However, rather than using the UTF-8 encoding scheme, it uses the UTF-16 encoding scheme, as does Windows Mobile. Here, each character is represented by two bytes. This has a number of advantages over UTF-8, for example, the character at position X within a string can be indexed directly using the operator []. A disadvantage is that strings now occupy two bytes for every character, however this is a relatively minor problem given the large amount of RAM available in today’s smartphones.

From a programming perspective, most of the standard C string functions as defined in , or their Palm OS equivalents, cannot be used to process text displayed to and collected from a user. The wide character equivalents would be required. Similarly string constants cannot simply be quoted strings such as “A standard C string”, they need to be their equivalent wide character versions, LA wide char string” or perhaps more frequently written as TEXT(“A wide char string”) in Windows Mobile. .

Symbian OS uses a set of classes collectively called descriptors to process textual data. Buffer overrun errors are a common cause of problems in systems. Standard C strings and buffers are prone to this type of problem as there is no natural mechanism of limiting the amount of content a programmer can attempt to place into such a container. Being objects, Symbian descriptors also contain information about the size of buffer they represent. They can therefore determine if the amount of data you expect the object to contain is greater than the size of the buffer you have declared.

Rather than using the wide character string definition directly you will commonly see _LIT() within Symbian code. Not only are these declaring the strings to be wide character ones, they are also constructing descriptor objects to wrap the strings.

Descriptors

Descriptors come in two types; one for 8-bit data and one for 16-bit data. The 16-bit variants are normally used to process UTF-16 encoded text, as collected from and displayed to a user. The 8-bit variants are normally used to process binary data and perhaps ASCII text.

Some typical binary data processing to count the number of occurrences of a specific value on the Palm OS or Windows Mobile platform might look like:

  {
  unsigned char buffer[100];
  ….
 
  int bufSize=50; // Amount of live data within buffer[].
  int i;
  int count=0;
  for (i=0;i<bufSize;i++)
    {
    if (buffer[i]==val)
      count++;
    }
  }

The equivalent using descriptors in Symbian OS may look like:

  {
  TBuf<100> buffer;
  …..
 
  TInt bufSize=buffer.Length();
  TInt i;
  TInt count=0;
  for (i=0;i<bufSize;i++)
    {
    if (buffer[i]==val)
     count++;
    }
  }

Notice that the data structures being manipulated are quite different but the code is remarkably similar. Some further examples of the similarities you can expect to observe are presented in the Porting1 example application.

Pro tip:

Descriptors are perhaps the most difficult objects for newcomers to Symbian OS to understand. A day or two spent studying the object hierarchy, understanding standard C++ implicit type conversion and writing some example code of your own will save you an enormous amount of time later on. Besides the examples given in the Symbian OS Essential chapter, the sub-directory of the UIQ 3 SDK installation named, SymbianCompatible\Base\BufsAndStrings folder, contains some additional sample applications you can review.

Do I Have To Use Descriptors?

Absolutely not. You can continue to use standard C style buffers, however, at some point you will probably want to call a native API function that only accepts a descriptor as a parameter. If you are writing new code for the Symbian platform, we recommend that you embrace descriptors. If you have existing code fragments or want to write code compatible with regular C style buffers, you can create temporary pointer type descriptors (ones that simply reference content as opposed to containing the content itself), when it comes to interfacing to such a function.

For example, supposing you have a string in a buffer that you wish to write to a file. The RFile class requires a descriptor. We can create a temporary pointer type descriptor as follows:

  {
  unsigned char ztsBuffer[100]
  …..
 
  // Some pseudo code generates a zero terminated string.
  ztsBuffer[]="String 1";
  ….
 
  // Create a temporary descriptor from the zts and write to file.
  file.Write(TPtrC8(&ztsBuffer[0]));
  }

Templates and Templated Functions

Symbian OS makes extensive use of thin templates. Writing thin template objects can be quite complex but using them is usually straightforward. Few applications in Symbian OS can be written without using descriptors or variable array type classes, both of which are based on thin templates.

For example, you might declare a descriptor to be capable of containing up to fifty bytes of data as TBuf8 buffer, as opposed to a regular array as unsigned char buffer[50].

While there is some difference in syntax, the meaning of both is reasonably clear.

Pro tip:

Fully comprehending how the code that you are writing is actually working is highly desirable but the use of templated objects is one area where you need to place a significant amount of trust in the system. With familiarity in using templated classes comes a gradual understanding of exactly what is going on. The syntax required to define templated classes is a bit obscure but the above example should demonstrate that using such classes is relatively easy to understand.

Files and Databases

Palm OS uses database structures extensively; even application code is stored in database records! Palm OS databases are flat record based structures. Symbian OS does not have the direct equivalent to Palm OS databases but perhaps the nearest equivalent is a dictionary stream store.

Symbian OS supports files at numerous levels. At the basic level you are provided with raw file access to open, read, write, seek and close files as required. These functions impose no structure on the file. It is entirely up to your application how it chooses to read and interpret content.

A second layer of file access is provided through the stream store classes. These classes provide functionality to present your file as a series of one or more logical sections or streams which you can read and write. Stream stores are typically used by Symbian applications to serialize their object structure. If you are familiar with using the serialization features of CArchive and CObject in Windows Mobile, then, along with the >> and << operators, you should be able to translate any serialization code into their equivalent Symbian OS classes. Unfortunately, it is unlikely you will be able to use the original code due to the substantial differences in available functionality on the two platforms.

At a higher level, Symbian OS supports a general purpose relational database model through the DBMS sub-system. Such a database will have multiple tables, indices and views. Views can be created via an SQL query on the underlying database tables. Since Symbian OS is a multi-threaded OS, the database management system also provides transactional based services to facilitate multiple clients simultaneously accessing the same database. The functionality available is broadly similar to that provided by the MFC CDaoDatabase class, however, you are unlikely to be able to use the original code.

Windows Mobile: Registry

Symbian OS does not contain the direct equivalent to a registry. The registry is often used to store application-specific information, such as registration keys or trial mode status information, most recently used file lists and perhaps application settings. In Symbian OS, applications typically store this type of information in an application-specific file, usually one based on a stream store. The Symbian OS framework contains direct support for saving configuration information within INI files. It should be noted that these are NOT the same as a typical Windows INI file.

A further use of the registry is to publish an association between data files and applications. Windows knows which application to launch when a file is selected by looking at these registry entries.

Symbian OS strongly prefers to associate data files with applications based on the content of a file. Symbian OS files normally contain a header comprising a number of UIDs describing the content of the file, including which application should be associated with the content. If this is not possible Symbian OS uses a system of recognizers to associate file types with applications.

A further use of the registry is to share information between co-operating applications. Symbian OS has an equivalent to this, called Publish and Subscribe. In the Symbian OS model, one application publishes the value of a variable. A second application can obtain the current value and/or register an interest in being informed when a particular variable changes value.

Summary

This section has looked at some of the common characteristics of Palm OS and Windows Mobile and their equivalents within Symbian OS. While there are substantial differences, particularly in the detail, there are enough similarities to facilitate the direct porting of some software components between these platforms.

Porting from S60 3rd Edition

Since both S60 3rd edition and UIQ 3 are based on Symbian OS, the majority of issues encountered when attempting to port applications from non Symbian OS platforms to Symbian OS should not apply. For example, programmers should already be familiar with descriptors, TRAP() harnesses, leaves, cleanup error handling, active objects and other idioms that are specific to Symbian OS.

This section covers the porting of applications from S60 3rd Edition to UIQ 3. Both platforms are based on Symbian OS v9, so this section will not consider the issues related to porting applications from earlier platforms such as S60 2nd Edition. Further information on this task can be found in the UIQ Developer Community FAQ and the document Porting to UIQ 3 from S60 - A practical approach.

Target Phone Differences

Prior to starting your porting activity, you should carefully consider the differences in the target phones that are currently supported by the two platforms, and how this might affect a user who needs to use your application. At the time of writing, there are no S60 devices with touch sensitive screens and, while they are expected soon, the number of non-touchscreen phones will outnumber them for some time to come. For phones that do not have a touch sensitive screen, user interaction is very similar, if not identical. However, touchscreen mobile phones are typically used in different ways, not least of which the user expects to be able to tap on displayed components to cause actions.

For example, a chess game must provide a way for the user to move the pieces around the board. In a touchscreen implementation, the pen can be used to drag them. Otherwise, the user needs a way of selecting a piece and then moving it around using say arrow keys on the keypad and finally confirming the new location.

Apart from touchscreen, the two platforms have many things in common:

  • Screen size. Both S60 and UIQ support a range of screen resolutions. In particular, both platforms support a 240x320 (QVGA) screen resolution.
  • Processing capability. Both S60 and UIQ phones are capable of significant data processing. Our experience shows they are broadly equivalent, if you have a processing bottleneck on one phone, the same bottleneck will occur on the other.
  • Available memory. Current S60 and UIQ phones contain comparable amounts of RAM, typically many 10s of MB. Very few applications should need to worry about the differences here.
  • Keyboard availability. S60 supports a range of phones, some of which contain physical keyboards. Likewise, some UIQ 3 mobile phones present much more of a keyboard than others. This may or may not be a consideration for your specific application.

.

Application Metafiles

Symbian applications include a number of metafiles which describe various aspects of the application. When porting from S60 to UIQ 3 there are a number of differences in the content of these metafiles as described below.

Application Icons

A S60 application icon comprises a single SVG image. A UIQ 3 application icon comprises a set of three bitmap based images with associated mask. UIQ version 3.1 has introduced the ability to use SVG format icons.

In S60 your application icon is compiled to a MIF file via the mifconv utility program outside of the primary MMP file build. Therefore S60 MMP files do not make any reference to an application icon. In contrast UIQ 3 icons are compiled into a MBM file via the bmconv utility. The makefile statement to cause this is contained within the MMP file:

// Create application icon.
START BITMAP    Porting1_icons.mbm
SOURCEPATH      Graphics
TARGETPATH      \Resource\Apps
SOURCE          c16 AppIcon18x18.bmp
SOURCE          1 AppIcon18x18mask.bmp
SOURCE          c16 AppIcon40x40.bmp
SOURCE          1 AppIcon40x40mask.bmp
SOURCE          c16 AppIcon64x64.bmp
SOURCE          1 AppIcon64x64mask.bmp
END

In both platforms the icon is referenced from the application registration file, or more precisely from the localisation file included by the application registration file.

A typical S60 application CAPTION_AND_ICON_INFO statement would be:

  CAPTION_AND_ICON_INFO
    {
    caption = "Porting1";
    number_of_icons = 1;
    icon_file = "\\Resource\\Apps\\ Porting1_icons.mif";
    }

The UIQ 3 equivalent would be:

  CAPTION_AND_ICON_INFO
    {
    caption = "Porting1";
    number_of_icons = 3;
    icon_file = "\\Resource\\Apps\\Porting1_icons.mbm";
    }

Applications Appearing in Softkey Style Small Modes

UIQ 3 applications need explicitly to inform the application launcher that they can be listed within the Softkey Style Small view of any phone that supports such a mode. While there is no direct equivalent S60 phone at this time, numerous phones support multiple display sizes, for example, the internal and external screens as found on a Nokia E90. S60 applications are expected to support all display sizes of a particular phone.

In UIQ 3 the LOCALISABLE_APP_INFO resource would typically contain the following field to indicate support for both a default sized UI configuration and Softkey Style Small:

  view_list =
    {
    VIEW_DATA
      {
      uid=EViewIdPrimaryView;
      screen_mode=0;
      caption_and_icon =
        {
        CAPTION_AND_ICON_INFO
          {
          }
        }; 
      },
    VIEW_DATA
      {
      uid=EViewIdPrimaryView;
      screen_mode=EQikScreenModeSmallPortrait;
      caption_and_icon =
        {
        CAPTION_AND_ICON_INFO
          {
          }
        }; 
      }
    };

On S60 this field would not be defined within the resource and therefore takes the system default of zero entries in the view_list array.

Application UID

The same application UID can be used for S60 and UIQ 3 versions of an application. This includes applications that will be submitted to Symbian Signed for testing. If you include the application UID in any source filename for the purposes of Symbian Signed you clearly need to be able to distinguish between files targeted at S60 and those targeted at UIQ 3. Your version control software should be capable of handling this.

Backup Registration Files

Assuming you are adopting the same backup policy on both platforms, the same backup_registration.xml file can be used to declare the backup policy of your application.

Package File

Apart from any changes in the list of files to install (for example, on S60 3rd edition you have a MIF file containing your icon, on UIQ 3 you have an MBM file), the major change to apply to your PKG file is the declaration of platform or phone compatibility.

An S60 PKG file would typically contain the following statement:

; ProductID for S60 3rd Edition
 [0x101F7961], 0, 0, 0, {"S60ProductID"}

The equivalent for UIQ 3 is as follows:

; ProductID for UIQ 3.0
 [0x101F6300], 3, 0, 0, {"UIQ30ProductID"}

The most important part is the UID declaration. This determines what platforms an application can be installed on without users being presented warnings about possible incompatibilities.

MMP File

Most of your application attributes will remain the same, for example your application will need the same stack size and capabilities; however there are some differences so you will need to make a number of changes to your MMP file.

In general, you will need to reference a different set of source modules and library files. One goal of porting is to try and re-use as many source modules as possible. However, the content of numerous files, for example, your application start up module, application UI class and your application resource file are likely to change considerably. A S60 application will reference some S60 specific libraries such as avkon.lib. The equivalent UIQ 3 libraries need to be referenced instead.

Using Folders and Version Control

A traditional Symbian application places the metafiles within a group folder but remember that this is only a naming convention. The build tools still work if you use a different folder name. If you create a GroupS60 and a GroupUIQ folder, you can partition your application instead of having to use #define statements to manage, say, a single MMP file.

Folder structure

Figure 2 Folder structure

Don’t forget to consider all the tools available to you when it comes to managing the porting process. Since S60 and UIQ 3 projects exist at different locations on a hard disk, your version control software may be able to help with sharing the source code that remains identical across platforms, while allowing you to use the same folder and file names for different versions of the logically equivalent files.

Application Source Files

A prime objective of porting is to re-use as many of the application source files as possible. If you have followed previous Symbian recommendations regarding application architecture, in particular ensuring that engine and UI code are separated into different source modules, you stand a good chance of being able to re-use a significant proportion of your application. If your engine and UI code is intermingled, you have a much harder task. In this case it is often preferable to spend some time splitting the original source code into re-usable and non re-usable components. Be sure to test that the newly split code compiles and works on S60 before you proceed and start porting it to UIQ 3.

Assuming you have split up your application into engine and UI classes, porting between S60 and UIQ 3 frequently becomes a task of replacing S60 UI components (Avkon) with their equivalent UIQ 3 UI components. There are however, a number of architectural changes between the platforms that also need to be considered. The GUI in UIQ 3 has specific features that enable applications to run on different types of hardware, for example, with or without a touchscreen. We recommend that you spend some time going through the chapters and example applications presented earlier in this book so that you are familiar with the following UIQ 3 features:

  • menus and command handling
  • view configurations
  • layout managers
  • building blocks.

Learning how to use these components effectively enables you to port your application so that it works well on the range of different UIQ 3 mobile phones.

Application Framework and Startup

The fundamental application-framework is the same on both S60 and UIQ 3. On both platforms you have an application class, a document class, an application user interface class and a set of views. Events are represented by active objects and the framework code runs an active scheduler on behalf of your application.

The application startup code on both platforms is very similar, the code fragments below show the typical S60 and UIQ 3 class hierarchy and application startup code.

class CAppSpecificUi : public CAknViewAppUi
  {
protected:
 
  // From CEikAppUi.
  void ConstructL();
 
  .. remaining methods + property
  };
void CAppSpecificUi::ConstructL()
 
  // Normal primary entry point to a Symbian App on S60.
  {
  BaseConstructL(CAknAppUi::EAknEnableSkin);
 
  .. add views here.
  }
 
///////////////////////////////////////////////////////////
class CAppSpecificDocument : public CAknDocument
  {
protected:
  CEikAppUi *CreateAppUiL();
public:
  CAppSpecificDocument(CEikApplication& aApp);
  static CAppSpecificDocument* NewL(CEikApplication& aApp);
  };
 
CAppSpecificDocument::CAppSpecificDocument(CEikApplication& aApp) :
  CEikDocument (aApp)
  {
  }
 
CAppSpecificDocument* CAppSpecificDocument::NewL(CEikApplication& aApp)
  {
  return(new(ELeave)CAppSpecificDocument(aApp));
  }
 
CEikAppUi* CAppSpecificDocument::CreateAppUiL()
  {
  return(new(ELeave)CAppSpecificUi);
  }
 
///////////////////////////////////////////////////////////
class CAppSpecificApplication : public CAknApplication
  {
protected:
  TUid AppDllUid() const;
  CApaDocument* CreateDocumentL();
  };
 
TUid CAppSpecificApplication::AppDllUid() const
  {
  return(KAppSpecificUid);
  }
 
CApaDocument* CAppSpecificApplication::CreateDocumentL()
  {
  return(CAppSpecificDocument::NewL(*this));
  }
 
///////////////////////////////////////////////////////////
LOCAL_C CApaApplication* NewApplication()
  {
  return(new CAppSpecificApplication);
  }
 
GLDEF_C TInt E32Main()
  {
  return(EikStart::RunApplication(NewApplication));
  }

In contrast, the typical UIQ 3 class hierarchy and application startup code is as follows:

class CAppSpecificUi : public CQikAppUi
  {
protected:
  // From CEikAppUi.
  void ConstructL();
  };
 
void CAppSpecificUi::ConstructL()
 
  // Normal primary entry point to a Symbian App.
  {
  CQikAppUi::ConstructL();
 
  .. add views here.
  }
 
///////////////////////////////////////////////////////////
class CAppSpecificDocument : public CQikDocument
  {
protected:
  CQikAppUi* CreateAppUiL();
 
public:
  CAppSpecificDocument(CQikApplication& aApp);
  static CAppSpecificDocument* NewL(CQikApplication& aApp);
  };
 
CAppSpecificDocument::CAppSpecificDocument(CQikApplication& aApp) : CQikDocument(aApp)
  {
  }
 
CAppSpecificDocument* CAppSpecificDocument::NewL(CQikApplication& aApp)
  {
  return(new(ELeave)CAppSpecificDocument(aApp));
  }
 
CQikAppUi* CAppSpecificDocument::CreateAppUiL()
  {
  return(new(ELeave)CAppSpecificUi);
  }
 
///////////////////////////////////////////////////////////
class CAppSpecificApplication : public CQikApplication
  {
protected:
  TUid AppDllUid() const;
  CApaDocument* CreateDocumentL();
  };
 
TUid CAppSpecificApplication::AppDllUid() const
  {
  return(KAppSpecificUid);
  }
 
CApaDocument* CAppSpecificApplication::CreateDocumentL()
  {
  return(CAppSpecificDocument::NewL(*this));
  }
 
///////////////////////////////////////////////////////////
LOCAL_C CApaApplication* NewApplication()
  {
  return(new CAppSpecificApplication);
  }
 
GLDEF_C TInt E32Main()
  {
  return(EikStart::RunApplication(NewApplication));
  }

As you may see, apart from sub-classing slightly different objects, the structure is identical and the details are almost the same. This suggests that the code may be easily re-used with appropriate #defines to cover the differences. However, we recommend that you maintain two separate versions, one for each platform, so that each is clear and easy to read.

Resource File Content

Both S60 and UIQ 3 applications use resource files. Apart from language localization, the two platforms use resource files in completely different ways and have little in common. If you have adopted the practice of splitting your language text strings into a separate file, often called a LOC file in S60 and a RLS file on UIQ 3 these files can remain the same, although you are likely to have a slightly different set of strings between the platforms. For example, a S60 LOC file will typically contain the Options, Exit and Back strings. UIQ 3 applications tend not to display such text. The equivalent to the Options text, More, is automatically displayed by the UIQ 3 framework, Exit is not a typical application option and Back is usually handled by a dedicated phone key.

Statements such as the following are effectively the same:

  rls_string STR_R_CMD_NEW  "New"
 
  #define STR_R_CMD_NEW  "New"

When a resource file is compiled, a textual substitution between the token and value pairs occurs. Therefore, it is not particularly relevant if you have more statements than are actually used within a particular application version. The token is never located in the resource file so no substitution is required. Combining both S60 and UIQ 3 token value pairs in a single file, with no pre-processor, directives have little effect on the output for either platform.

Menus and Commands

The S60 platform supports a very traditional approach to the definition of menus within resource files. On S60 a menu is usually defined though a resource structure like the following:

RESOURCE MENU_BAR r_application_menubar
  {
  titles =
    {
    MENU_TITLE
      {
      menu_pane=r_ application _menu;
      }
    };
  }
 
RESOURCE MENU_PANE r_ application _menu
  {
  items=
    {
    MENU_ITEM
      {
      command=EAppCmdHelp;
      txt=STR_R_CMD_HELP;
      },
    MENU_ITEM
      {
      command=EAppCmdAbout;
      txt=STR_R_CMD_ABOUT;
      }
    };
  }

The content, placement and ordering of commands is firmly defined and fixed by the resource definition. In contrast, UIQ 3 does not have strict menu definitions; you simply define a set of commands which the application framework distributes around the user input controls available on a specific phone:

RESOURCE QIK_COMMAND_LIST r_application_commands
  {
  items=
    {
    QIK_COMMAND
      {
      id = EAppCmdHelp;
      type = EQikCommandTypeHelp;
      groupId = EAppCmdMiscGroup;
      priority = EAppCmdHelp Priority;
      text = STR_R_CMD_HELP;
      },
    QIK_COMMAND
      {
      id = EAppCmdAbout;
      type = EQikCommandTypeScreen;
      groupId = EAppCmdMiscGroup;
      priority = EAppCmdAboutPriority;
      text = STR_R_CMD_ABOUT;
     }
    };
  } 

Your application can have a reasonable level of influence upon the placement of controls by using attributes such as groupID and priority. We recommend that you allow the UIQ 3 framework to take care of the commands, rather than trying to dictate too much in your application.

A primary task of a menu is to allow the user to choose which command they wish the application to perform. On both platforms a command ID is defined in the resource and is delivered to the HandleCommandL() method of the current view. However these methods are different on each platform.

The S60 version is delivered a single integer value:

void CAppSpecificAknView::HandleCommandL(TInt aCommand)
 
  // Menu commands come here on S60.
  {
  switch (aCommand)
    {
  case EAppCmdHelp:
  case EAppCmdAbout:
    break;
    }
  }

In contrast a CQikCommand object is delivered on UIQ 3:

void CAppSpecificQikView::HandleCommandL(CQikCommand& aCommand)
 
  // Handle the commands coming in from the controls
  // that can deliver cmds.. menus etc.
  {
  switch (aCommand.Id())
    {
  case EAppCmdHelp:
  case EAppCmdAbout:
    break;
 
  default: // Exit button...
    CQikViewBase::HandleCommandL(aCommand);
    break;
   
    }
  }

Since each platform has a different view class hierarchy it is not particularly easy to re-use the code from one platform on the other. In general, the more functionality you can push into an engine-like component, even if it is processing UI centric data elements, the more portability you can achieve. There is however, a limit to this process and at some point it becomes quicker, easier and more maintainable simply to write new code.

Views

Both S60 and UIQ 3 contain a view architecture. They are similar in many respects: views have ids, they can be activated and deactivated, are informed of dynamic screen changes and are the primary entry point for processing user commands.

They do however differ quite significantly, particularly when you look at the detailed implementation. For example, views in S60 subclass CAknView while views in UIQ 3 subclass CQikViewBase or CQikMultiPageViewBase. Different data is delivered to the HandleCommandL() method.

At a more fundamental level, views on S60 have minimal resource definition via the AVKON_VIEW resource. This only describes the menu and CBA. In contrast, a UIQ 3 view is defined via a QIK_VIEW_CONFIGURATIONS resource. Not only does this resource define the menu, via the command list, it also defines how to construct the view dependent on the current phone type and details which controls should be placed within the view.

The following section gives an example of the differences between views on the two platforms in the context of an application that displays a simple list box.

List Boxes and Views

List boxes are commonly used in S60 and UIQ 3 applications. Both platforms provide a substantial list box framework within which applications can work; however, the details differ significantly.

The following code fragments demonstrate a list box view being created for the S60 platform. The code is not intended to be complete and we have extracted out the relevant sections to demonstrate the differences between the platforms.

Firstly we have the S60 view resource definition:

RESOURCE AVKON_VIEW r_application_list_view
  {
  cba=r_ application _list_cba;
  menubar=r_ application _list_menubar;
  }
 
RESOURCE CBA r_ application _list_cba
  {
  buttons =
    {
    CBA_BUTTON
      {
      id=EAknSoftkeyOptions;
      txt="Options";
      },
    CBA_BUTTON
      {
      id=EAknSoftkeyBack;
      txt="Exit";
      }
    };
  }
 
RESOURCE MENU_BAR r_ application _list_menubar
  {
  titles =
    {
    MENU_TITLE
      {
      menu_pane=r_application_list_menu;
      }
    };
  }
 
RESOURCE MENU_PANE r_application_list_menu
  {
  items=
    {
    MENU_ITEM
     {
     command=EAppCmdAbout;
     txt="About";
      }
   };
  }

In S60, views typically comprise two objects, the view and a container. This code fragment demonstrates a typical class definition and implementation of the container:

class CListContainer : public CCoeControl,
                   public MEikListBoxObserver,
                   public MDesCArray
  {
protected:
 
  // From CCoeControl.
  void SizeChanged();
  TInt CountComponentControls() const;
  CCoeControl* ComponentControl(TInt aIndex) const;
 
  // From MEikListBoxObserver.
  void HandleListBoxEventL(CEikListBox* aListBox, MEikListBoxObserver::TListBoxEvent aEventType);
 
  // From MDesCArray.
  TInt MdcaCount() const;
  TPtrC MdcaPoint(TInt aIndex) const;
 
public:
  ~CListContainer();
  CListContainer(CAppEngine* aEngine);
  void ConstructL(const TRect& aRect);
 
protected:
  CAppEngine* iEngine;
  CAknSingleGraphicStyleListBox* iListBox;
  };
 
CListContainer::CListContainer(CAppEngine* aEngine) : iEngine(aEngine)
  {
  }
CListContainer::~CListContainer()
  {
  delete(iListBox);
  }
 
TInt CListContainer::MdcaCount() const
 
  // Report number of items in our list – our engine knows this.
  {
  return(iEngine->ListCount());
  }
 
TPtrC CListContainer::MdcaPoint(TInt aIndex) const
 
  // Report text to display in the list – our engine generates correct format str.
  {
  return(iEngine->ListDataAt(aIndex));
  }
 
void CListContainer::ConstructL(const TRect& aRect)
 
  // We have our own window in which to display everything.
  {
  CreateWindowL();
  SetRect(aRect);
 
  iListBox=new(ELeave)CAknSingleGraphicStyleListBox();
  iListBox->ConstructL(this,0);
  iListBox->SetListBoxObserver(this);
 
  iListBox->Model()->SetItemTextArray(this);
  iListBox->Model()->SetOwnershipType(ELbmDoesNotOwnItemArray);
 
  iListBox->CreateScrollBarFrameL(ETrue);
  iListBox->ScrollBarFrame()->SetScrollBarVisibilityL(
  CEikScrollBarFrame::EOff,CEikScrollBarFrame::EAuto);
 
  iListBox->SetRect(Rect());
  iListBox->ActivateL();
 
  ActivateL();
  }
 
TInt CListContainer::CountComponentControls() const
 
  // Report the number of sub-component controls we have.
  {
  return(1);
  }
 
CCoeControl* CListContainer::ComponentControl(
  TInt aIndex) const
 
  // Report handle of 'aIndex' control.
  {
  return(iListBox);
  }

Finally the S60 view itself:

class CListAknView : public CAknView
  {
protected:
 
  // From CAknView.
  void HandleCommandL(TInt aCommand);
  void DoActivateL(const TVwsViewId& aPrevViewId, TUid aCustomMessageId, const TDesC8& aCustomMessage);
  void DoDeactivate();
 
public:
 
  // From CAknView.
  TUid Id() const;
 
  // New methods.
  CListAknView(CAppEngine* aEngine);
  ~CListAknView();
 
protected:
  CAppEngine* iEngine;
  CSessionListContainer* iContainer;
  };
 
CListAknView::CListAknView(CAppEngine* aEngine) :
  iEngine(aEngine)
  {
  }
 
CListAknView::~CListAknView()
  {
  delete(iContainer);
  }
 
void CListAknView::HandleCommandL(TInt aCommand)
  {
  switch (aCommand)
    {
    ... process commands here
    }
  }
 
TUid CListAknView::Id() const
  {
  return(KUidListView);
  }
 
void CListAknView::DoActivateL(const TVwsViewId& aPrevViewId, TUid aCustomMessageId, const TDesC8& aCustomMessage)
  {
  CListContainer* q=new(ELeave)CListContainer(iEngine);
  CleanupStack::PushL(q);
  q->SetMopParent(this);
  q->ConstructL(ClientRect());
  AppUi()->AddToStackL(*this,q);
  CleanupStack::Pop();  // q
  iContainer=q;
  }
 
void CListAknView::DoDeactivate()
 
  // The view is being deactivated, lose any components.
  {
  if (iContainer)
    {
    AppUi()->RemoveFromStack(iContainer);
   delete(iContainer);
    iContainer=NULL;
    }
  }

In contrast, an equivalent UI on UIQ 3 might be described by the following resource and classes. The resource and code has been extracted from the ListView1 example application supplied as part of this book:

RESOURCE QIK_VIEW_CONFIGURATIONS r_list_view_configurations
  {
  configurations =
    {
    QIK_VIEW_CONFIGURATION
      {
      ui_config_mode = KQikPenStyleTouchPortrait;
      command_list =