×
Namespaces

Variants
Actions

Using Qt and Symbian C++ Together

From Nokia Developer Wiki
Jump to: navigation, search

This article explains how to provide a clean separation between Qt and Symbian C++ code using the PIMPL pattern. In addition, it describes how to write code that safely mixes the two environment's exception handling mechanisms, coding styles and idioms, strings, geometry, containers, images, and data. Each section gives a high level overview of how a particular task is done in Symbian C++ and Qt, and how the idioms are mixed - along with links to key documents explaining the approach in more detail.

Article Metadata
Code ExampleTested with
Devices(s): Nokia 5800, Nokia N95, Nokia N8
Compatibility
Platform(s): S60 3rd Edition, FP1 and later, Qt 4.7 and later
Device(s): All (with bluettooth)
Platform Security
Signing Required: Self Signed
Capabilities: LocalServices
Article
Keywords: QBluetoothAddress
Created: hamishwillee (17 Dec 2010)
Last edited: hamishwillee (30 May 2013)

The accompanying example code (File:Qtbluetoothdiscoveryexample.zip) delivers a Qt-style API (and dialog) for discovering remote Bluetooth devices. This uses the PIMPL pattern to obtain the information from the underlying platform, and demonstrates how to safely mix the exception handling mechanisms, coding styles and strings.

Contents

Introduction

Symbian is an open-source software platform for mobile devices. Earlier versions of the Symbian platform (known as S60 and Symbian OS) can be found in hundreds of millions of devices, and are available globally from over 100 network operators.

Figure 1: Qt on the Symbian Platform
Qt is a cross-platform application and UI framework that allows developers to write applications that can be deployed to desktop, mobile, and embedded operating systems without need to rewrite the source code. From Qt 4.6, Qt includes the Symbian platform as a build target; Qt applications to run on Symbian platform devices, and on earlier S60 devices from S60 3rd Edition FP1. As shown in Figure 1, Qt on the Symbian platform is layered over both native Symbian C++ platform APIs and its standard C/C++ compatibility layer (Open C/C++).

While Qt has a rich set of APIs and development tools, inevitably some developers will need access to platform-level functionality that neither generic Qt or standard C++ APIs provide. This is particularly true when targeting mobile devices because the APIs to access important mobile functionality (like the camera, Bluetooth, contacts etc.) are not yet available. New Qt APIs are starting to address common mobile use cases, but some developers will always need or want to access native operating system functionality.

Where Qt does not provide a required API, the recommended way to access platform specific functionality is to write a generic cross-platform wrapper API and provide a private platform specific implementation. This approach, which makes it easier to port applications to other platforms, is discussed in the Platform Specific Implementations section.

Accessing device-specific functionality is not simply a matter of using familiar standard C++ APIs and idioms to call phone-specific APIs. Symbian's native programming language is Symbian C++, a variant of C++ that has evolved specifically to address the needs of resource constrained devices. Symbian C++ uses programming idioms and frameworks that promote robust and memory efficient code, sometimes at the expense of usability. It has its own exception handling mechanism, and omits standard C and C++ libraries that were not available (or considered too heavy-weight) when it was created.

Code from the different C++ variants can be mixed, but this requires care, and some understanding of Symbian C++. The article explains how to inter-work the different exception handling mechanisms, strings, geometry, containers, images, data, and approaches to multitasking etc. Each section provides a concise overview of what you need to do on each platform (with links to the key references), followed by examples/discussion on how they are mixed. Both Qt and Symbian C++ developers should get a good overview of what they can use of their existing knowledge, and where to go to get more.

Before we start discussing the mechanisms for cross platform compatibility, the following section provides a brief overview of the example code that is used to illustrate some of the key points.

Bluetooth Discovery Example

Overview

The example code delivers the BluetoothLibrary.dll, and a test application testbluetoothdiscovery.exe which uses it.

BluetoothLibrary.dll exports a Qt API (BluetoothDiscovery) for finding nearby bluetooth devices. BluetoothLibrary.dll has a private Symbian C++ implementation and a stub implementation for other platforms. BluetoothLibrary.dll also exports a convenient Qt dialog from which users can search for and select one or more devices.

The test application is GUI Main Window application which displays an empty screen on startup. It has softkey menu options for launching the single and multi selection dialogs.

The figures below show the dialogs for single and multiple device selection on the Nokia 5800, and a diagram showing the relationships between the main components.

API

Figure 5: Main Classes

The BluetoothDiscovery API provides slots to start and stop a search for nearby devices, and signals to notify connected clients when discovery is started or stopped, every time a new device is detected, and whenever and error occurs.

Once started the search will continue until the underlying platform determines that there are no more devices; at which point it will signal that discovery has stopped. Discovery will also cease in the event of an error, which will be signaled to clients through a locally defined error code enumeration.

The dialog QBluetoothRemoteDeviceDialog class exposes two static methods for launching the dialog and returning the user selection: the first method returns a single selection while the second allows users to make multiple device selections:

static QBluetoothAddress QBluetoothRemoteDeviceDialog::getRemoteDevice ( QWidget * parent = 0);
static QList<QBluetoothAddress> QBluetoothRemoteDeviceDialog::getRemoteDevices ( QWidget * parent = 0);

In addition to the dialog class a number of related Qt classes have been created: QBluetoothAddress for a device address, QBluetoothRemoteDevice to represent a remote device, and QBluetooth for miscellaneous enumerations describing possible device service classes and behaviour.

A simplified diagram of the public API is given in Figure 5. Note that this does not show the QBluetoothAddress or QBluetoothRemoteDevice classes.

The example dialog could be extended; it does not allow filtering of displayed devices (based on device type, service class or SDP profile) or specification of a local bluetooth device to use for searching, it ignores window flags passed to its constructor, and it doesn't display icons to indicate the type of device.

Building & Running the Example

The example can be built using the Qt SDK.

The easiest way to import the example into the Qt Creator IDE is through the PRO file: \QtBluetoothDiscoveryExample\qtbluetoothdiscoveryexample.pro which specifies the PRO files of the example and test code, and the required build order. The example builds for Symbian devices and on Windows; note however that it can't search for devices on Windows because no bluetooth implementation is provided.

The example DLL and test exe are deployed to the device using the test code installation file (testbluetoothdiscovery.sisx) in the usual manner. The executable is run on the Symbian by selecting the testbluetoothdiscovery icon from the Applications folder.

Tip.pngTip: On the Nokia N8, you need to turn on Bluetooth to get the app to work properly, on the Nokia 5800 the device turns Bluetooth on if it is needed

File Listing

The package has the following directory structure (under the \qtbluetoothdiscoveryexample\ folder):

Folder File Description
BluetoothLibrary\ bluetoothdiscovery (.cpp/.h) BluetoothDiscovery public API header & Source
bluetoothdiscovery_stub (.cpp/.h) Private implementation (stub) header & source files for non-Symbian platform targets (BluetoothDiscoveryPrivate)
bluetoothdiscovery_symbian (.cpp/.h) Private Symbian platform implementation header & source files (BluetoothDiscoveryPrivate)
BluetoothLibrary.pro Project file for the BluetoothLibrary.dll
globalbluetooth.h Global #defines used to define exports to be imported/exported from DLL
qbluetooth.h QBluetooth header file (contains public enumerations used by other classes)
qbluetoothaddress (.cpp/.h) QBluetoothAddress public header and source files
qbluetoothaddressdata.cpp QBluetoothAddress data implementation (for implicit sharing)
qbluetoothremotedevice (.cpp/.h) QBluetoothRemoteDevice public header and source files
QBluetoothRemoteDeviceDialog.cpp QBluetoothRemoteDeviceDialog public header and source files
QBluetoothRemoteDeviceDialog.ui QBluetoothRemoteDeviceDialog Qt designer file
eabi\ bluetoothlibraryu.def Frozen ordinals definition
testbluetoothdiscovery\ main.cpp main entry point for test application
testbluetoothdiscovery (.cpp/.h) Test code public source and header files.
testbluetoothdiscovery.pro Test code project file
testbluetoothdiscovery.ui Test code Qt designer file

Development Environment

The code in this example can be built using the Qt SDK, as described in Using Symbian C++ in the Qt SDK.

Platform Specific Implementations

While Qt provides a rich set of APIs, at the time of writing Symbian developers will not yet find Qt APIs to communicate using Bluetooth or IrDA, access camera, location, accelerometer, biometric or other sensors, send SMS or MMS messages, or read/write user data like the calendar or contacts. Qt Development Frameworks is working on providing cross platform APIs for many of these through the Qt Mobility project. If you want to use this functionality you you will need to use both Qt and Symbian C++. Doubtless there will be other functionality in future for which this is also true.

Where Qt does not provide a required API, the recommended way to provide access to platform specific functionality is to create public Qt-style APIs that have separate private platform specific implementation(s). This approach makes it easier to write the bulk of your code using the developer-friendly Qt programming idioms, while still making it easy to port applications to other platforms. In fact this is exactly the same approach that that Qt uses to abstract the underlying operating system so that developers don't need to be aware of each platform's underlying programming idioms, APIs, installation mechanisms, build systems and other limitations.

There are a number of design patterns that can be used to structure your code. In the following section we discuss the Pimpl (pointer to implementation) idiom, in which the (private) platform specific implementations are hidden behind a pointer in the public API. The technique has a number of variants/alternative names including: "Handle/Body", "Compiler Firewall", "the Bridge", "Opaque Pointers", and "Cheshire Cat".

Other patterns/techniques may be used. For example, most of the QPixmap API is implemented in a common source file, but QPixmap::grabWindow() is implemented in platform specific source files. This approach is acceptable as long as the platform-specific implementation does not "leak" into the Qt API.

In addition, there are cases where it is beneficial to expose some platform-specific detail through #Platform Specific Methods.

Pointer to Implementation Pattern

Overview

Figure 6: PIMPL Class Overview
PIMPL is a variant of the Handle-Body pattern, in which the public API contains a pointer to its private implementation class. The pointer to the private implementation is forward declared (instead of #included) in the header file, and is hence opaque to clients of the public API. Pimpl was popularised by Herb Sutter in Pimpls - Beauty Marks You Can Depend On and The Joy of Pimpls (or, More About the Compiler-Firewall Idiom).

If the private class needs to call methods in the public class we pass a pointer/reference to the public class into its constructor. If it needs to call private methods (e.g. in order to emit signals) we also make it a friend class. The class diagram in Figure 6 illustrates the relationships, showing a public class QMyClass which owns a private implementation QMyClassPrivate.

The implementation of the public class constructs (and destroys) the private implementation. In order to do so it needs to know the definition of the QMyClassPrivate, so it #includes the header for the current platform based on platform build defines - e.g. Q_OS_SYMBIAN (from qglobal.h):

//qmyclass.cpp
...
#ifdef Q_OS_SYMBIAN
#include "qmyclass_symbian.h" //Symbian definition of private class
#else
#include "qmyclass_stub.h" //Stub for all other platforms
#endif
...

Note that the private class header and source files must also be listed in appropriate platform specific sections of the project file (.PRO) - see #Project File below.

The definition and implementation of the private class are almost entirely up to the developer. The only firm constraint is that all platforms must use the same class name (otherwise we would need to include forward definitions/friend declarations in the public header file for each platform). Typically private classes also have the same functions - this allows us to implement the public class without platform specific behaviour:

// Implementation of a public class slot
void QMyClass::mySlots()
{
d_ptr->mySlots();
}

The private class can have any derivation.

Note.pngNote: While it is common for private classes to be QObject derived; this is in no way required (it could be useful if you want your implementation to have signals or slots). Note that the Qt codeline often uses QObjectPrivate for private implementations. As this is not part of the public API it should be avoided by 3rd parties.

For Symbian private implementations there are two basic class designs, shown in Figures 7 and 8 respectively. In the first approach the private implementation class is a Symbian class, derived from CBase. In the second approach, the private class uses one or more Symbian classes that have a callback into the private class.

The first mechanism requires less developer effort, because you do not need an additional level of indirection to create/call the Symbian class, or to get notification of completion (the callback class). However this approach results in more complicated object construction (for reasons given below) and may make it more difficult to conceptually separate Qt and Symbian C++ code. The example code, as described in the following sections, uses this approach.

Often it is better to use the second method. This results in a cleaner separation between the two programming idioms. Its also a better solution if the private class implementation needs to use a number of Symbian classes. The #Callback APIs section explains the second method in more detail.

Bluetooth Example Public API

Figure 9: BluetoothDiscovery Class Diagram
The public BluetoothDiscovery class is shown below. It has a pointer to the friend private implementation class BluetoothDiscoveryPrivate.

The class diagram (Figure 9) shows the private class for both a Symbian implementation and a stub implementation for other platforms. The private classes share the same name and have a superset of the public class slots and methods. Note that they don't re-implement the signals, as these are implemented by the toolchain; instead the private classes can call the public class emit through the pointer they get on construction.

//Forward declarations
class BluetoothDiscoveryPrivate;
 
class BluetoothDiscovery: public QObject
{
Q_OBJECT
public: //enums
enum BluetoothDiscoveryErrors
{ BluetoothNotSupported, BluetoothInUse, BluetoothAlreadyStopped,
BluetoothNotReady, DiscoveryCancelled, UnknownError };
public:
BluetoothDiscovery(QObject *parent = 0);
virtual ~BluetoothDiscovery();
 
public slots:
void startSearch();
void stopSearch();
 
signals:
void newDevice(const QBluetoothRemoteDevice remoteDevice);
void discoveryStopped();
void discoveryStarted();
void error(BluetoothDiscovery::BluetoothDiscoveryErrors error);
 
private: // Data
BluetoothDiscoveryPrivate *d_ptr; //private implementation
 
private: // Friend class definitions
friend class BluetoothDiscoveryPrivate;
};


Project File

Platform specific compilation of source files is controlled through the project file.

The public class header and source files are specified in the general section. The platform specific headers/sources are specified in platform specific blocks, as shown below.

...
HEADERS += qbluetoothaddressdata.h # public header
SOURCES += bluetoothdiscovery.cpp # public class implementation
...
 
symbian {
...
HEADERS += bluetoothdiscovery_symbian_p.h # Symbian private class header
SOURCES += bluetoothdiscovery_symbian_p.cpp # Symbian private class source code
LIBS += -lesock \
-lbluetooth
TARGET.CAPABILITY = LocalServices \
NetworkServices \
ReadUserData \
UserEnvironment \
WriteUserData
}
else {
HEADERS += bluetoothdiscovery_stub_p.h //private class declaration for other platforms
SOURCES += bluetoothdiscovery_stub_p.cpp //private class source for other platforms
}

The platform specific blocks list the platform libraries the code needs to link against, in this case esock.dll and bluetooth.dll.

Symbian implementations must specify the capabilities that the executable needs (you can find out more here:Fundamentals of Symbian C++/Platform Security). In this example we specify just those capabilities that can be granted to a self signed application.

Public Class Implementation

The public class implementation needs the private class header in order to construct the private class. We conditionally include the header based on the current platform:

//bluetoothdiscovery.cpp
...
#ifdef Q_OS_SYMBIAN
#include "bluetoothdiscovery_symbian_p.h" //Symbian definition of BluetoothDiscoveryPrivate
#else
#include "bluetoothdiscovery_stub_p.h" //Stub for all other platforms
#endif
Construction

The public class creates an instance of the private implementation class in its constructor as shown below:

BluetoothDiscovery::BluetoothDiscovery(QObject *parent)
: QObject(parent)
{
#ifdef Q_OS_SYMBIAN //Symbian specific compilation
QT_TRAP_THROWING(d_ptr = BluetoothDiscoveryPrivate::NewL(this));
#else
d_ptr = new BluetoothDiscoveryPrivate(this);
#endif
}

Note that because the private class is a Symbian CBase derived class we've chosen to use platform specific construction in the public class implementation. This is OK because the Symbian class implementation isn't visible in the public API. However as a rule we'd prefer to push as much platform specific implementation into the private class.

NewL() is a standard Symbian static factory class for creating objects of type BluetoothDiscoveryPrivate, which ensures that either the object is fully allocated, or it will be properly cleaned up in the event of an exception. As this can Leave, we wrap it in a QT_TRAP_THROWING to convert the leave into a throw. The section #Exceptions & Error Handling explains how you create exception handling barriers between Symbian and Qt code.

If you implement the private class using a CBase derived class then construction is simpler, and you can have a common public class implementation.

//Public class
BluetoothDiscovery::BluetoothDiscovery(QObject *parent)
: QObject(parent)
{
d_ptr = new BluetoothDiscoveryPrivate(this);
}
 
//Private class
BluetoothDiscoveryPrivate::BluetoothDiscoveryPrivate(QObject *parent)
: d_ptr(parent)
{
QT_TRAP_THROWING(symbianMember = CBluetoothDiscovery::NewL(this));
}

There are other ways of allocating the Symbian object. Whatever method you use, the important things to remember are:

  1. Symbian C classes have new overload from CBase that does not throw. If you construct with new then you need to use q_check_ptr to check the pointer (and throw on Null).
  2. Ensure that the object is cleaned up properly if construction fails.

The first point might apply if an CBase-derived object need not be initialised until it is first used. In this case you can construct using new, but you must check the pointer and throw if it is NULL.

The second point is important because its not always obvious, particularly when mixing Qt and Symbian C++ idioms, whether cleanup will occur properly. Consider the following code:

BluetoothDiscovery::BluetoothDiscovery(QObject *parent)
: QObject(parent)
{
d_ptr = q_check_ptr(new BluetoothDiscoveryPrivate(this)); //from Qt 4.6
#ifdef Q_OS_SYMBIAN
QT_TRAP_THROWING(d_ptr->ConstructL());
#endif
}

If ConstructL() leaves the macro will throw. Following normal C++ rules, the memory for BluetoothDiscovery is freed but the destructor is not called. Therefore any partially allocated objects in BluetoothDiscoveryPrivate will leak. For this to work d_ptr would need to be a smart pointer ( QScopedPointer).

The public class must delete the private implementation in its destructor.

Methods

We replicate the public API in the private class.

Public methods call the equivalent private class methods through the pointer to implementation e.g.:

// Called to start searching for new devices
void BluetoothDiscovery::startSearch()
{
d_ptr->startSearch();
}

Note that we haven't created any barrier between Qt and Symbian C++ exception handling mechanisms. This is because we know from the implementation that startSearch() cannot Leave. This method can throw, which is acceptable because it is never called from Symbian code.

Private Platform Implementation

Class Declaration

The Symbian platform BluetoothDiscoveryPrivate implementation is shown below. It is a Symbian active object. As it is a Symbian C++ class it is important to comply with Symbian coding standards, in particular:

  • Construct it using Symbian mechanisms
  • If using multiple inheritance, derive from CBase derived classes first, and only otherwise derive Mixin (interface) classes.

Warning.pngWarning: Never derive from both CBase and QObject as the new used for construction is undetermined.

class BluetoothDiscoveryPrivate : public CActive
{
public:
//static factory "constructor" function
static BluetoothDiscoveryPrivate* NewL(BluetoothDiscovery *aPublicAPI = 0);
 
~BluetoothDiscoveryPrivate(); //Destructor
 
public:
void startSearch(); //Called to start searching for new devices
void stopSearch(); //Called to stop searching
 
public: //From CActive
virtual void DoCancel(); //Implements cancellation of an outstanding request.
void RunL(); //Handles an active object's request completion event.
 
private:
BluetoothDiscoveryPrivate(BluetoothDiscovery *aPublicAPI = 0); //constructor
void ConstructL(); //Second phase constructor - connects to socket server and finds protocol
 
//Error translator - converts global errors into local format then emits to parent object
void ErrorConvertToLocalL(int err);
 
private: // Data
 
RSocketServ iSocketServ; //Socket server connection
RHostResolver iHostResolver; //Host resolver
TNameEntry iCurrentDeviceEntry; //The entry of the device just returned.
TInquirySockAddr iInqSockAddr;
TProtocolDesc iProtocolInfo;
 
BluetoothDiscovery *iPublicBluetoothDiscovery; //pointer to parent object (from constructor).
};

The class has a pointer to its parent BluetoothDiscovery that it uses to emit signals to the Qt clients when the active object completes/has an error.

The private class replicates the public classes API, reducing the conditional code in the public class implementation. Additionally it re-implements CActive's virtual methods RunL() and DoCancel() to handle completion and cancellation of the active object.

The class has private data members used to get the name and ids of the remote bluetooth devices from the Symbian's Bluetooth.dll. iHostResolver is the object that contains the asynchronous function with which the request is made.

Construction

As the private implementation is a Symbian class we construct it using the two-phase construction idiom, as discussed in the preceding sections.

The public static NewL() factory function uses a leaving constructor to create the object, pushes it on the CleanupStack, initialises it using the leaving second phase constructor, pops it off the stack and returns it to the user.

BluetoothDiscoveryPrivate* BluetoothDiscoveryPrivate::NewL(BluetoothDiscovery *wrapper)
{
BluetoothDiscoveryPrivate* self = new (ELeave) BluetoothDiscoveryPrivate(wrapper);
// push onto cleanup stack in case self->ConstructL leaves
CleanupStack::PushL(self);
// complete construction with second phase constructor
self->ConstructL();
CleanupStack::Pop(self);
return self;
}

As part of construction we also give the private object a handle to the public class - in this case a pointer, but should perhaps be a reference. The pointer can then be used to directly call methods, or to emit signals.

Methods

The implementation of the private class's methods is dependent on the functionality required. In general terms the most important thing to keep in mind is correct inter-working of the platform exception handling systems, as discussed in the Exception Handling section.

Note also the use of void ErrorConvertToLocalL(int err); to convert Symbian's global errors into local errors for this component.

The example discusses the RunL() and DoCancel() methods in #Converting Active Objects to Signals & Slots.

Platform Specific Methods

Where possible public APIs should be kept free of platform specific member and functions. Exceptions to this rule are very rare!

Occasionally platform specific helper functions are made public, where developers would otherwise need to role out their own methods. For example QPixmap, provides helper functions to convert to and from CFbsBitmap:

Similarly, it is sometimes necessary to reveal platform specific types and publically include platform headers in order to map them to generic typedefs. You can see this in QProcess where Q_PID is typedefed to a TProcessId on the Symbian platform.

In all cases, platform specific artifacts should be defined to be visible only in the specific platform. For example, from the QPixmap declaration:

#if defined(Q_OS_SYMBIAN)
CFbsBitmap *toSymbianCFbsBitmap() const;
static QPixmap fromSymbianCFbsBitmap(CFbsBitmap *bitmap);
#endif

File and Class Naming Conventions

In general, if the public class is called QMyClass then:

  • The private class will be QMyClassPrivate.
  • Public class source and header files share the name of the public class: qmyclass.h,qmyclass.cpp.
  • Private class headers and source file names are terminated with _p (e.g. qmyclass_p.h) unless the file is a platform specific implementation.
  • Platform-specific implementation headers and source files include the platform in the file name - e.g. qmyclass_symbian.cpp (the _p is not necessary as it is implied).

Multitasking

Multi-tasking is the ability to perform more than one task at time. It is important to GUI applications, because it allows them to perform long running or computationally expensive operations while still remaining responsive to user input.

There are two types of multitasking: preemptive and cooperative. In preemptive multitasking (or "multithreading"), the operating system gives each thread of execution some time in which to run - the thread has no control over when it will run or how much time it gets. In cooperative multitasking, a scheduler controls what task is run next, but the current task alone determines when it will complete.

Preemptive multitasking is heavier-weight in terms of RAM and execution speed, and is more difficult to program because of the need to mediate access to shared resources (and ensure threads don't dead-lock). Co-operative multitasking is easier - because access to resources is serialized. However individual tasks need to be short running so that the UI remains responsive.

In multitasking operating systems we use the term process to refer to a set of threads that share the same global memory space, and which can hence directly access each others variables; all the threads in an application will typically run in the same process! Note however that it is threads, not processes, which are scheduled for execution.

A multitasking operating system may also be multiprocessing; multiprocessing is where threads can run on more than one processor/CPU.

Multi-tasking on the Symbian platform

The Symbian platform is a modern preemptive multitasking operating system.

Applications are created in their own process, running in a single main thread. The kernel preemptively schedules all threads in the system, based on their priority. While it is possible to create secondary threads, Symbian strongly encourages applications to co-operatively multi-task using active objects.

Almost all Symbian services are provided by servers (or "daemons") running in other processes (including the file server, window server, font and bitmap server, location server etc). These usually export an asynchronous API that takes a reference to a TRequestStatus object that the server uses to signal completion of the request. Active objects provide a consistent and lightweight way to write code to submit the asynchronous requests and handle its completion.

Active objects are derived from CActive, which either owns or has a handle to an asynchronous server provider. The active object must add the object to the active scheduler in its constructor and provide a method to set itself as active - first calling the asynchronous method (passing in the TRequestStatus iStatus member) and then calling CActive::SetActive(). When the asynchronous service completes it signals the active object's thread semaphore and changes the status of the object's iStatus to show that it is no longer pending. The active scheduler will later call the object's RunL() method, which you must implement, to handle completion of the service. Note that this will not be immediate - active objects are co-operatively multi-tasked so the scheduler can only run one at a time, and only when the last one has completed. Lastly, developers must implement the virtual DoCancel() which cancels the asynchronous request, and ensure that they call Cancel() in the active object's destructor.

The above summary only touches on the nuances possible with active objects. Developers should read the article Fundamentals of Symbian C++/Active Objects. If you're interested in using or implementing services you may also be interested in reading Client Server.

Developers that prefer to use threads and processes can of course do so - in some cases this may be necessary. Symbian C++ processes and threads can be created and manipulated using the RProcess and RThread API, respectively. Symbian C++ has the usual synchronization primitives including mutexes (RMutex), semaphores (RSemaphore), Critical Sections (RCriticalSection) etc. All of these classes are discussed in Fundamentals of Symbian C++/Threads, Processes, and IPC. Developers who are interested in how these are implemented can read chapter 3 of the Symbian OS Internals book (replicated on the Symbian Foundation wiki: Symbian OS Internals/03. Threads, Processes and Libraries). If you're programming with standard C++ (through Open C/C++) then you can create pthreads as normal.

Threads in the same process can easily share data directly (taking care to serialize access to shared data). Threads in other processes need to communicate using Symbian's inter-process communications mechanisms. These include Client-Server, Publish & Subscribe, Message Queues, and RPipe. The following documents provide useful discussions of these mechanisms:


Multi-tasking with Qt

Qt applications use both co-operative and preemptive multi-tasking.

Qt's main application thread runs its own event loop which processes events generated in response to user interaction (key presses, mouse events etc) and from timers and the window system. This event loop is an example of co-operative multitasking - events are queued and handled synchronously. If too much time is spent on one event then the UI can become unresponsive.

If the computationally intensive operation can be broken into a number of steps - for example writing to a file, then you can call QApplication::ProcessEvents() at regular intervals during the operation to give the event loop time to handle other events from the UI. This approach is discussed in chapter 7 of C++ GUI Programming with Qt 4, Second Edition, Jasmin Blanchette and Mark Summerfield, Prentice Hall (2006) (the first edition is available free online here).

Multithreading (preemptive multitasking) is a more common approach. Developers subclass QThread and re-implement its run() function to execute code in the new thread. Synchronisation classes include QMutex to provide mutually exclusive access to a resource, QReadWriteLock which provides unrestricted access for reading but blocks on writing, QSemaphore which generalises QMutex to allow access to a specified number of resources, and QWaitCondition which blocks until some condition comes true. There are also a few helper classes like QMutexLocker, which simplify mixing mutexes programming and standard C++ exception handling.

Thread Support in Qt provides a good overview to the thread classes, and links on to topics on Synchronizing Threads, Reentrancy and Thread-Safety, Threads and QObjects,Concurrent Programming,and Thread-Support in Qt Modules. There are a few good examples here: Mandelbrot Example, Semaphores Example, and Wait Conditions Example. C++ GUI Programming with Qt 4, Second Edition, Jasmin Blanchette and Mark Summerfield, Prentice Hall (2006) contains an excellent discussion of the multithreading (with some duplication of the other links) in chapter 14.

Threads communicate with each other using shared memory and the above synchronization classes. Threads communicate with the main thread using signals and slots. Note however that by default the signals are not synchronous as they are within a single thread.

Applications can also multitask using other processes. For example it is possible to create a QProcess to launch another process, set its command line arguments and to detect its startup, error and completion status.

You can also use standard C++ threads, processes and inter-process communication mechanisms.

Converting Active Objects to Signals & Slots

When using Qt and Symbian C++ together, you may need to make use of a Symbian service with an asynchronous API. The best way to do this is to wrap the request in an active object and then notify completion of the request to Qt code using a Qt signal.

Converting active objects completion events into signals and slots is straightforward:

  • Use the PIMPL idiom to create a public Qt-style API
  • Create the active object as the Symbian private implementation class (or as an object owned by the private class).
  • Map functions/slots in the public class to functions which start the active object.
  • Emit signals when the active object completes successfully using its pointer to the public implementation. Only Qt-style objects should be emitted with the signal(obviously).
  • Emit signals on error. Note that Qt uses module-local errors, so you should translate global (or local) errors from the underlying interface into local errors defined in the module public interface.

Note.pngNote: In some cases it may be possible to do a zero-copy transfer from the Symbian object to the Qt Object: for example its possible to initialise a QString to use a pointer to a buffer you've received from your asynchronous service. As always with C++, care should be taken to ensure that the object you're transferring will have a lifetime that exceeds all its users. Given the different approaches used by Qt and Symbian C++ for memory management you will usually be best off creating a "fully new" Qt style object.

The BluetoothDiscovery example above is an active object, so you've already seen how to construct the object, pass in a pointer to the public class, and start the object (by calling the private startSearch() through the public startSearch()).

As the active object is otherwise exactly the same as described in Fundamentals of Symbian C++/Active Objects, all that remains is to talk a little about how you signal completion back to the Qt API.

Signalling from the Active Object

You signal either errors or events by calling the public class emit function, using the private pointer to the parent passed in on construction. The objects that we emit should be Qt-style objects. Errors that are emitted should be local to the current Qt component (ie defined as enums in its public header rather than global Symbian errors).

The signal that is emitted can be connected to any number of slots. This has two implications:

  • Slots that run inside the context of our non-preemptive active scheduler (RunL) must be kept short. If the total time spent in all slots is too great the UI may become unresponsive (or worse block).
  • Any of the slots connected the the signal can throw, and this will propagate back to the emit call. For this reason we use the QT_TRYCATCH_LEAVING barriers around emit calls that might propagate into Symbian code!

Below is a fragment of the RunL(), which is called when the asynchronous service completes. On successful completion it creates a new QBluetoothRemoteDevice, populates it with information from the equivalent Symbian C++ classes, and emits this in our signal. If there is an error, we call ErrorConvertToLocalL() to convert it to a module specific error.

RunL()

/**
Handles an active object's request completion event.
*/

void BluetoothDiscoveryPrivate::RunL()
{
if (iStatus == KErrNone) {
...
//Create a QBluetoothRemoteDevice and populate it with
//information from the asynchronous service
QBluetoothRemoteDevice remoteDevice(qtBtDeviceAddress);
...
//emit the device as a signal from the public class
QT_TRYCATCH_LEAVING (emit iPublicBluetoothDiscovery->newDevice(remoteDevice) );
//Search for the next device
iHostResolver.Next(iCurrentDeviceEntry,iStatus);
SetActive();
}
else if (iStatus == KErrHostResNoMoreResults) {
//No more devices to detect
QT_TRYCATCH_LEAVING (emit iPublicBluetoothDiscovery->discoveryStopped() );
}
else {
//Error. Emit "discovery stopped" signal and then translate
//to local error (which is also emitted within the function)
QT_TRYCATCH_LEAVING (emit d_ptr->discoveryStopped() );
ErrorConvertToLocalL(iStatus.Int());
}
}

SetActive()

The private startSearch() is used to start the active object. It checks if the object is already active, creates a request on an asynchronous service provider, and then calls CActive::SetActive() to set the object active.

In addition, the method emits a signal on error (the object is already started) and to notify clients that its started. In this case we do not need a barrier around the emit calls because although emit can throw, this function can only be called by the Qt API. The behaviour of stopSearch() is similar.

void BluetoothDiscoveryPrivate::startSearch()
{
...
emit iPublicBluetoothDiscovery->discoveryStarted();
...
}

DoCancel()

CActive::Cancel() may be called to cancel an asynchronous request, and will in turn call CActive::DoCancel() (which we need to implement) if it is called when the object is active. Cancel() must be called from the active object destructor to cancel any outstanding requests - the active scheduler will panic if a removed object is still waiting on a signal.

In the example below DoCancel() emits a signal that device discovery has stopped, which is a potentially throwing operation (the signal could be connected to any slot, and any of that connected code might throw). As discussed in the section Mixing Symbian C++ and Qt Exception Handling, since this method is called in a destructor we should avoid using throwing or leaving code if at all possible. For the sake of argument we're assuming this emit is unavoidable and therefore catching all standard exceptions (only).

void BluetoothDiscoveryPrivate::DoCancel()
{
//Note that must trap any errors here as
// Cancel() is is called in destructor and destructor must not throw.
try {
emit iPublicBluetoothDiscovery->discoveryStopped();
}
catch (std::exception&) {}
 
iHostResolver.Cancel();
}

Callback APIs

Symbian C++ provides APIs that use callbacks to signal completion of an asynchronous service (under the hood these are implemented as active objects; using a callback simplifies the use of the API for client code). A good example of this is CMdaAudioPlayerUtility, which takes an instance of MMdaAudioPlayerCallback in its static factory constructor, and calls methods on this to indicate when initialisation and playing are complete.

Wrapping a Symbian C++ callback is even easier than using an active object directly. Again you can use PIMPL to create a Qt class, with a private implementation. In this case the private implementation will own an instance of the Symbian class and implement its callback interface.

class MyAudioPlayerPrivate : public QObject, public MMdaAudioPlayerCallback
{
public:
QMyAudioPlayer (MyAudioPlayer *wrapper = 0);
~QMyAudioPlayer ();
 
public:
//methods, slots intiate play (duplicate API of the public class)
 
public: //From MMdaAudioPlayerCallback
virtual void MapcInitComplete(TInt aError, const TTimeIntervalMicroSeconds &aDuration);
virtual void MapcPlayComplete(TInt aError);
 
private:
void ErrorConvertToLocal(int err);
 
private: // Data
CMdaAudioPlayerUtility *iAudioPlayer; //The audio player object
MyAudioPlayer *d_ptr; // pointer to Qt public API
};
 
 
MyAudioPlayerPrivate::MyAudioPlayerPrivate (MyAudioPlayer *wrapper)
: d_ptr(wrapper)
{
QT_TRAP_THROWING(iAudioPlayer=CMdaAudioPlayerUtility::NewL(this));
//note, throws if can't construct object
}
 
MyAudioPlayerPrivate::~MyAudioPlayerPrivate()
{
delete iAudioPlayer;
}

We can then emit any needed signals from the callback:

MyAudioPlayerPrivate::initialisationComplete()
{
QT_TRY {
emit d_ptr->initialisationComplete();
}
QT_CATCH (std::exception&) {}
}

Note that the above code traps exceptions, and does not deal with them or re-throw them: generally this is considered a bad thing! However we are constrained by the API - we can't leave because it's prototype indicates that it may only be used in a leave-safe way, and we can't throw, because this will lead back into Symbian code. Absorbing the errors is our only choice.

Warning.pngWarning: It is important to remember that any callback may be run from within the context of a RunL(). If you have any Qt code that might throw in a leaving callback, this should be wrapped in a QT_TRYCATCH_LEAVING method.

Coding Standards & Conventions

Symbian Coding Standards

Symbian's very extensive set of coding standards and conventions are captured the document: Coding Standards and Conventions. If you're developing on top of the platform using Symbian C++ (and not planning to contribute directly to the platform source code) then you will find the Symbian C++ Coding Standards Quick Start is a more useful starting point.

The coding standards cover rules related to code formatting, safety, ease of maintenance and efficiency. Developers can reasonably ignore the standards related to formatting personal code, however the other conventions are essential for writing robust and efficient code with Symbian C++.

Qt Coding Standards

Qt provides a compact set of coding guidelines that are separated into Coding Style and Coding Conventions documents. The former defines best-practice for code format/layout, while the conventions discuss C++ features you shouldn't use, approaches to including headers, casting, maintaining binary compatibility and compiler specific issues. As you would expect, there is a clear focus on coding to ensure cross platform compatibility.

Tip.pngTip: If you are using Carbide.c++, you can use the Qt_Code_Style.xml template to make your projects follow the Qt coding style by default. To add it, download and import the file through Carbide.c++ menu: Window > Preferences... > C/C++ > Code Style.

Coding Standards For Mixed Symbian C++ and Qt Code

Qt applications should typically follow the Qt Coding Style rules with respect to code layout for both Symbian C++ and Qt code. Otherwise, Qt code should follow Qt conventions and Symbian C++ code should follow Symbian C++ Coding Standards Quick Start.

This approach is followed in the File:Qtbluetoothdiscoveryexample.zip accompanying this article. The exception to this rule is that the BluetoothDiscoveryPrivate class is a Symbian C class (derived from CActive), and should ideally be named using the C prefix. In this case the class name is necessary to maintain the "opacity" of the private implementation and the risk of not using the class prefix is low because BluetoothDiscovery is its only user.

Exceptions & Error Handling

Symbian Exceptions & Errors

Symbian C++ uses its own exception mechanism consisting of TRAPs (somewhat equivalent to try), Leaves (a bit like throw), and the Cleanup Stack (which allows locally scoped heap-allocated objects to be safely deleted in the event of an exception).

Potentially leaving code is run inside a TRAP macro. In the event of a Leave the call stack is unwound up to the TRAP, automatic variables are deallocated and the Cleanup Stack deletes or otherwise cleans up any objects on the current TRAP level. The TRAP macro returns the leave error code (a single integer) and execution continues immediately afterwards (there is no separate "catch"). Much like catch blocks, the code following a TRAP is expected to handle leave codes that it understands, and Leave again with any that it doesn't.

TInt result;
TRAP(result, MayLeaveL());
 
if (KErrNone!=result) {
// Deal with errors that can be handled at this level
// Leave again with any errors you choose not to handle
}

Tip.pngTip: TRAPs are relatively heavy weight in terms of executable size and RAM consumption. While these can be nested and used at any level, its usually better to trap at a high level, grouping a number of Leaving functions.

Symbian uses a naming convention for functions and class types to help users understand how to safely allocate objects to prevent memory leaks:

  • Functions that might Leave are by convention named with a trailing L (or LC to indicate that the object remains on the Cleanup Stack when the method returns).
  • Classes that should be allocated on the heap usually first derive from CBase and are named with the C prefix.
    • CBase provides zero member initialization on construction, an overload of new that Leaves (new (ELeave)) in the event of allocation failure, and a virtual destructor that the Cleanup Stack can use to delete the object in the event of a Leave (The cleanup stack also provides methods to delete other heap based objects that are not derived from CBase - e.g. arrays and interface classes).
    • C classes are constructed using the leave-safe two-phase construction idiom.
  • T prefix is used for stack allocated classes that don't own pointers to heap resources and don't have a destructor.
  • R prefix is used for stack classes that own resources elsewhere - and hence require explicit support for cleanup.

Note.pngNote: In Symbian's original implementation, the destructors of automatic variables were not called in the event of a Leave. As a result smart pointers could not be used for local object cleanup. The naming conventions allow developers to work out whether objects require explicit cleanup support via the cleanup stack (R, C classes) or not (T classes).

The document Fundamentals of Symbian C++/Leaves & The Cleanup Stack provides a more detailed discussion of how the exception mechanism works, how to write leave-safe code, and the naming convention. Fundamentals of Symbian C++/Class Types & Declarations explains the different class types, and Fundamentals of Symbian C++/Object Construction explains leave-safe object construction.

Note.pngNote: At the time Symbian C++ was created, standard C++ try-catch-throw exception handling was considered too memory-expensive for an embedded operating system. In addition, there was limited support in the compilers Psion needed to use.

Standard C++ exception support was added in Symbian OS v9, the precursor operating system to the Symbian platform, used in Nokia S60 3rd Edition

Symbian's exception handling mechanism is now implemented in terms of try, catch and throw. Developers can use try, catch and throw in standard C++ code, and even mix them with Symbian C++ (with care!). The new exception handling mechanism, and how to interwork with standard C++ exceptions is discussed in A Comparison of Leaves and Exceptions and the Hybrid Coding Guide.

Symbian functions use either Leaves or error codes to signal an exception condition. Returning an error is usually preferred to Leaving when the error condition is "expected" - e.g. attempting to read off the end of a file. However here are no firm rules (other than class construction will always leave with KErrNoMemory if there is no memory to allocate the object), and you'll see errors and Leaves used in an ad-hoc manner within the Symbian C++ API. Note that it is considered bad programming practice to have a function that both leaves and returns an error, or to convert a leave to an error (using a TRAP without good reason). As with normal exceptions.

There is a set of global error codes (negative integer values), defined in e32err.h. Other error codes are defined within individual components - there are a number of lists, including this one Symbian OS Error Codes.

Developers should also be aware that Symbian defines a second class of exception called a Panic. A Panic is used to signal programmatic errors, and result in immediate termination of the application - this is an appropriate response for misuse of an API.

Qt Exceptions & Error Codes

Qt supports the standard C++ exception handling mechanism where it is available on the underlying platform and compiler.

While Qt has supported the mechanism for 3rd party code since its inception, historically Qt framework code has itself not used the mechanism (for reasons of cross-platform compatibility). Qt code instead uses locally defined error codes to notify client code of error conditions. In the case of failure to allocate memory, framework code simply returned a null pointer and failed on first pointer dereference (except for some larger objects, where special measures have been taken.

Qt 4.6 promises basic exception safety (component invariants are preserved and no resources are leaked) for Qt's container classes as well as for the QFile class:

  • The QScopedPointer smart-pointer has been added to allow locally scoped objects to be cleaned up in the event of an exception (during use or on construction).
  • Objects now throw std::bad_alloc if memory cannot be allocated (using q_check_ptr() to check the returned pointer)
  • try/catch blocks have been added where appropriate

Qt does not (at the time of writing) define its own exception class hierarchy, and the framework code only throws std::bad_alloc. Qt itself still uses error codes rather than exceptions to propagate errors other than allocation failure. Developers should assume that almost any Qt class can throw, and that where it interacts with 3rd party code, it can throw virtually anything (an exception is QScopedPointer's constructor, which means that it can safely be used for cleaning up locally scoped objects). When Qt catches exceptions, it always rethrows if these are not handled.

Developers should use the macros QT_TRY, QT_CATCH, QT_THROW, and QT_RETHROW (defined in qglobal.h) in preference to calling try, catch and throw directly. This ensures that exception handling code is only compiled on platforms that support exceptions. Note that if you write your application to be exception safe, it will work whether exceptions are enabled or not.

Mixing Symbian C++ and Qt Exception Handling

Qt uses standard C++ exceptions while Symbian C++ uses Leaves. While Leaves are implemented in terms of exceptions, care needs to be taken to interleave the two idioms.

Symbian exception safety succinctly explains the issues, and describes the barrier functions that you can use to convert between the idioms; A Comparison of Leaves and Exceptions and the Hybrid Coding Guide are useful additional references.

The barrier functions allow you to catch standard exceptions and convert them to Symbian errors or leaves for propagation into Symbian code, and similarly to convert Symbian C++ leaves or errors into standard exceptions. I've listed the methods here for convenience: qt_symbian_throwIfError(), q_check_ptr(), QT_TRAP_THROWING(), qt_symbian_exception2Error(), qt_symbian_exception2LeaveL(), QT_TRYCATCH_ERROR(), QT_TRYCATCH_LEAVING().

The barrier methods can only catch and convert standard exceptions - application specific exceptions will propagate through, and cause the application to terminate if they reach a TRAP. This is expected behaviour - by convention code should catch and handle only those exceptions that it understands - all other exceptions must be re-thrown.

Note.pngNote: It's tempting to catch all exceptions with catch (...) or QT_CATCH (...) to prevent them propagating to a TRAP and terminating the application. However if you do this the error condition will still exist - all you've done is remove any opportunity for the application to handle the error appropriately - the code may still leak memory or fail in unpredictable and hard-to-debug manner.

There are a few cases that are worth more detailed consideration: where a function must not fail, and where it can fail but must not throw or leave. In both cases "must not fail" should be treated as "must not fail with a leave or standard exception" - other exceptions must be allowed to propagate!

Destructors are a case where a function must not fail (either throw exceptions or Leave). When implementing a destructor:

  • Try to only use functions that will not fail
  • If you must call a function that can Leave, stop it from propagating using a TRAP macro.
  • If you must call a function that can throw, stop standard exceptions with a QT_CATCH (const std::exception&). Other exceptions must be allowed to propagate - do not QT_CATCH (...) without rethrowing the exception.
  • A logical consequence of this is that a Qt slot connected to the QObject::destroyed() signal must be implemented as if it were destructor code, following all the rules above (it is emitted in the QObject destructor).

Some functions can fail, but must not throw or leave. For example Symbian C++ callback methods that do not have the L suffix cannot leave because the calling code may not have been written to be leave-safe. Nor can it throw, because there may well be a TRAP at a higher level:

  • If the function can return an error code then you can use the Qt barrier functions to convert leaves or standard exceptions to errors
  • If the function prototype does not allow errors to be returned, then you can absorb errors you understand but must throw others.
  • In both cases, non standard exceptions must be allowed to propagate.

Converting Between Common Qt & Symbian Types

This section explains how you convert between some of the more common types.

Strings

Strings are used in almost every application, and are probably the most common conversion between Symbian C++ and Qt code.

Symbian Strings

Figure 10: Descriptor class inheritances
Symbian C++ uses descriptors to handle both text and data. Descriptors are self-describing: they use the minimum amount of memory to store the string data and information about its length and memory layout. Descriptors do not resize automatically, and instead will panic if an operation goes over the buffer length - this promotes robust code on devices that are designed to be rarely or never rebooted. As mentioned above, they can be used for both text and data because the length is not determined from the presence of NULL ('\0') terminators.

Symbian's descriptor class hierarchy is complicated; providing concrete modifiable and non-modifiable descriptors that store their data on the stack or heap, and a number of base classes that are used for function return types and parameters, but which are not intended for instantiation (TDes, TDesC etc). There are even some pointer descriptors (TPtr, TPtrC) which simply point to data stored in other locations.

Every descriptor class comes in a narrow (8 bit) and wide (16 bit) variant (e.g. RBuf8, RBuf16 respectively). The 8 bit variant is primarily used for data, while the 16 bit variant is used for Unicode text. For string data most developers will actually use the bare version of the descriptors (e.g. RBuf) shown in the figure: this is a typedef over the 16 bit variant on all current versions of the Symbian platform.

This wiki has some excellent documentation on descriptors: Fundamentals of Symbian C++/Descriptors and Descriptors Cookbook, as does the Developer Library: Using descriptors. Developers should also be aware of TLex, used for lexical parsing and converting numeric strings into number types, the various typdefs classes used for characters: TText, TChar, and Charconv for conversion between character sets (converts more sets than Qt and is extensible).

Qt Strings

Qt uses a single class, QString for almost all Unicode string handling. The class provides almost all functionality developers might need, including string comparison, conversion to and from numeric types, and conversion between character sets. The class integrates with Qt's regular expression classes to provide powerful parsing and string manipulation. Its also works seamlessly with Qt's internationalization APIs.

QString will automatically resize if necessary to accommodate a larger string (if the string can not be reallocated the operation will throw). QString uses implicit sharing (copy-on-write) to reduce memory usage and to avoid the needless copying of data. This means that while the variable is stored on the stack, the associated data is stored on the heap.

Note.pngNote: In terms of memory usage, Symbian C++ developers may find it helpful to think of QStrings (and any implicitly shared objects) as reference-counted R-Classes. The object data is allocated on the heap; copy operations that won't change the data simply increase the count without need to create a new object. The data is only freed when all objects that use it go out of scope.)

The QString API is much easier to understand than the descriptor hierarchy, and is more powerful in many respects. QString is less memory efficient than the descriptor classes, but still relatively efficient and robust.

Qt also provides QByteArray (an array of bytes) which can be used for string operations. QByteArray is often used for data, and is discussed in the section Qt Binary Data.

Converting a Descriptor to a QString

Use QString::fromUtf16 to create a new QString with a deep copy of the data at a specified address and length. The address and length are obtained with TDesC16::Ptr() and TDesC::Length() respectively:

QString myString = QString::fromUtf16(theDescriptor.Ptr(), theDescriptor.Length());
This approach is used in the example code to copy the device name and address from descriptors into to QStrings, prior to assigning to the QBluetoothRemoteDevice (see implementation of BluetoothDiscoveryPrivate::RunL().

Note.pngNote: qcore_symbian_p.h defines QString qt_TDesC2QStringL(const TDesC& aDescriptor) which does this conversion. Since this file is not part of the public API, you may choose to copy the source code from qcore_symbian_p.cpp into your own project.)

In most cases you will use the above method to create a copy of the data. If you need to do a zero copy transfer of data, and you can guarantee that theDescriptor lifetime exceeds that of possible Qt variables, then you can use QString::fromRawData() to get a QString pointing at theDescriptor's data:

QString myString = 
QString::fromRawData(reinterpret_cast<const QChar*>(theDescriptor.Ptr()),theDescriptor.Length());

8 bit descriptors containing text may be converted to Unicode within Symbian C++ and then transferred as above. This has advantages because Symbian's character conversion classes allow auto-detection of the character set, and out-of-the-box support conversion between more character sets.

However you may find it easier to convert them within Qt code. You can use const TUint8* TDesC8::Ptr() const; to get a pointer to the data in the descriptor. If you know the character set you can use QString's fromAscii(), fromLatin1(), fromUtf8() to do the conversion; if you just know the data is in the current locale's default set you can use: QString::fromLocal8Bit().

Converting a QString to a Descriptor

Use QString::utf16() or QString::constData() to get a pointer to the data in the QString myString and cast this to a TPtrC16 (TPtrC) as shown:

TPtrC myDescriptor (static_cast<const TUint16*>(myString.utf16()), myString.length());

or

TPtrC myDescriptor (reinterpret_cast<const TText*>(myString.constData()),myString.length());

The pointer descriptor is valid while the original QString (or any shallow copies) are still in scope. Unless you can guarantee this, you should copy the string into a heap or buffer descriptor. Using an (RBuf) heap descriptor do:

    RBuf buffer;
qt_symbian_throwIfError(buffer.Create(myDescriptor));

For a buffer descriptor you can do either of:

TBuf buffer(myDescriptor);

or

TBuf<KBufLength> buffer(text.utf16());

Note.pngNote: qcore_symbian_p.h defines HBufC* qt_QString2HBufC(const QString& aString) and TPtrC qt_QString2TPtrC( const QString& string ) which do these conversions. Since this file is not part of the public API, you may choose to copy the source code from qcore_symbian_p.cpp into your own project.)

Input/Output & Binary Data

In this section we briefly discuss mechanisms for serializing object data, saving it to files, and transferring it between devices and threads.

Symbian Binary Data

The Symbian C++ stream classes are used to serialize object's internal data into a series of bytes and to initialize them from a series of bytes.

Objects that need an external representation define ExternaliseL() and InternaliseL() methods as shown below (note that classes which define these methods can use the global >> and << streaming operators to externalise/internalize data to/from a stream).

void ExternalizeL(RWriteStream& aStream) const;
void InternalizeL(RReadStream& aStream);

The methods write/read the object's members in terms of their own ExternalizeL()/InternalizeL() methods, and ultimately platform-independent representation of the basic types (including descriptors) defined in the RWriteStream/RReadStream classes.

RWriteStream/RReadStream are abstract classes. When we externalize objects we use a concrete stream that sends the data to a file, file store, raw memory, or a fixed or dynamic buffer. Some streams are initialized with other stream objects - for example to compress or encrypt the data. Native Symbian applications usually store their data as file based "stores" of steams, that are associated with the application using its unique identifier.

There is good overview of stores and streams here:Fundamentals of Symbian C++/Streams And Stores, and in the application reference here: Streaming and here: Stores. There is a worked example showing you how to save your application's data using the streaming mechanisms here: GUI/Engine Split

Symbian uses the concrete 8 bit variant descriptors as buffers for non-string data: TBufC8, TBuf8, TPtrC8, TPtr8, RBuf8, HBufC8. For example, they used as the send and receive buffers to the RSocket APIs. We can also use a stream interfaced sockets to serialize our objects directly to/from a socket.

Package buffers (aligned 8 bit descriptors) are used for the purpose of transferring objects between threads and processes. These buffers allow developers to package any value type (a T class) as a descriptor. Note that this approach is acceptable because we don't need a platform independent representation to communicate with another thread.

There are three package buffer variants: TPckgBuf takes a copy of the object data, while TPckgC and TPckg simply point to existing const and non-const objects (respectively).

Qt Binary Data

Qt's QIODevice is an abstraction for a "device" capable of reading or writing blocks of data. This has a number of subclasses (including QTcpSocket, QUdpSocket, QBuffer, QFile, QLocalSocket, QNetworkReply, and QProcess) that are used for writing to files, processes, sockets, buffers etc.

Qt also provides higher level stream classes QDataStream and QTextStream, that can be used to stream binary and text data (respectively) to any QIODevice. The stream classes serialize data in a platform independent (but Qt-version specific) manner. Classes that can serialize data overload the >> and << operators with variants that take a QDataStream argument (or have an associated method).

In-memory 8-bit text and binary data are usually stored in a QByteArray. This is an array of bytes which has a very similar API to the QString class. Note that QBuffer class provides a QIODevice interface for a QByteArray.

The Input/Output and binary classes are well documented in the class documentation. There is also a very good overview of QByteArray in chapter 11 and Input/Output in chapter 12 of C++ GUI Programming with Qt 4, Second Edition, Jasmin Blanchette and Mark Summerfield, Prentice Hall (2006) (the first edition is available free online here).

Converting Between Qt and Symbian Binary Data

Symbian and Qt's classes and approaches for serializing data are fundamentally the same; data is externalized to a stream in a platform independent form. In Symbian this stream might be a file or memory buffer, while in Qt the stream is associated with a QIODevice that is a file, buffer etc. The main difference between the implementations is that Symbian C++ has a very small set of platform-independent types (that it has maintained consistently across versions), while Qt has a richer set of types for which the implementation has varied across versions.

The good news is that there is unlikely to be a reason to convert between the Qt and Symbian serialization mechanisms. If you do need to transfer data that is serialized in one or the other development environments then first import and then convert appropriately (using casts, or some more complicated method).

If you're working with raw data then converting between a QByteArray and a descriptor is much the same as converting between a QString and a descriptor - e.g.:

TPtrC8 myDataDescriptor( reinterpret_cast<const TText8*> (myQtData.constData()),myQtData.size());
 
//Take a copy of the data
HBufC8* buffer = HBufC8::New(myDescriptor.Length());
 
Q_CHECK_PTR(buffer);
buffer->Des().Copy(myDataDescriptor );

Remember that the data returned by QByteArray::constData() and data() belongs to the QByteArray, so you may need to take a copy as shown above.

To convert the other way you can use the QByteArray to create a deep copy of the data in the descriptor, or QByteArray::fromRawData() if you know the lifetime of your QByteArray will exceed that of its users in Symbian C++ code:

QByteArray myQtArray(reinterpret_cast<const char*>(theDescriptor.Ptr()),theDescriptor.Length());

Geometry: Points, Sizes, Rectangles

Qt and Symbian C++ define similar geometry types

TPoint and QPoint are effectively the same. Both store a two-dimensional point in Cartesian co-ordinates using x and y co-ordinate values (of type TInt (typedefd to signed int) and int respectively).

TSize and QSize are also the effectively the same. Both store the width and height value, again using a TInt and int respectively. Converting is straightforward:

QSize myQSize = QSize(myTSize.iWidth, myTSize.iHeight);  //To QSize
TSize myTSize = TSize(myQSize .width(), myQSize .height()); //to TSize

TRect and QRect both define a rectangular area with a particular position within a co-ordinate system. Both store the top left co-ordinate of the rectangle. TRect stores a TSize for the rectangle, while QRect stores separate values for the width and height. Converting is straightforward:

QRect myQRect = QRect(myTRect.iTl.iX, myTRect.iTl.iY, myTRect.Width(), myTRect.Height());  //to QRect
TRect myTRect = TRect(TPoint(myQRect.left(), myQRect.top()),
TSize(myQRect.width(), myQRect.height())); //to TRect

Note.pngNote: qcore_symbian_p.h defines the following inline functions which do these conversions.

  • static inline QSize qt_TSize2QSize(const TSize& ts)
  • static inline TSize qt_QSize2TSize(const QSize& qs)
  • static inline QRect qt_TRect2QRect(const TRect& tr)
  • static inline TRect qt_QRect2TRect(const QRect& qr)
Since this file is not part of the public API, you may choose to copy the code into your own project.)

Note that QRect also provides mechanisms for getting the bottom and right co-ordinates of the rectangle. These should not be used for translating to TRects, as for historical reasons they deviate from the "true" bottom right of the rectangle. See the QRect documentation for more information.

Images

Symbian Images

Symbian's main class for representing a bitmap to be displayed is CFbsBitmap, a bitmap managed by the font and bitmap server. Its API provides methods to load (and compress) bitmaps from Symbian's native image file format ("the multibitmap (MBM) file"), create and access hardware owned bitmaps, access bitmap data, resize bitmaps, etc. It also provides a mechanism to duplicate the current bitmap handle so that the bitmap can be shared with other threads. The derived CWsBitmap represents a bitmap to which the window server already has a handle, and provides slightly faster operations than those relying on a CFbsBitmap.

Most of the image handling functionality is defined in the Image Converter Library (ICL). The ICL provides classes to load image files into a CFbsBitmap. The library can decode a number of common desktop and mobile formats, and can also encode a subset of these. A comparison of formats provided by Qt and the Symbian platform is given below.

The ICL also provides classes to perform common image manipulation, including rotation (in 900 steps), flipping, scaling (keeping aspect ratio and stretching). Different classes allow scaling of CFbsBitmap objects, or on still images held in files or descriptors.

The ICL APIs are asynchronous; clients are signaled on completion of a requested operation. This allows the user interface to remain responsive while performing computationally expensive operations.

Qt Images

Qt provides four main classes for handling image data: QImage, QPixmap, QBitmap and QPicture.

QImage is a hardware-independent image representation that allows direct pixel access and manipulation - this is the closest equivalent to a Symbian CFbsBitmap. In addition to accessing pixel information, the class can directly load a number of common image formats from file or a buffer, representing them using one of a number of formats - ARGB, RGB32, Mono etc). QImage provides powerful image manipulation functionality, including rotation, scaling, mirroring, mask creation, and complex transformations within the 2D co-ordinate system).

QPixmap is designed and optimized for showing images on screen. It does not provide direct access to the image data, but like QImage, can load common file formats, and do much the same image manipulation. QPixmap::toImage() and QImage::fromImage() can be used to convert between the two image classes.

QBitmap is a convenience class that inherits QPixmap, ensuring a depth of 1. Finally, QPicture is a paint device that records and replays QPainter commands.

Qt supplies a number of additional image classes, including: QImageReader (provides finer grained control when loading images from files than QImage or QPixmap), and QIcon that provides scaleable icons you can use in Qt widgets to represent a particular action.

Inter-working Symbian & Qt Image Formats

If you're working with images stored in the file system using common file formats, you should not need to use Symbian's image handling code directly. QImage and QPixmap work across platforms, and have a more convenient API for loading and manipulating file-based image data.

You may choose to use Symbian's APIs if you're working in file formats that Qt does not support. A list of the default supported formats on each environment is given below - note that both architectures allow the supported image formats to be extended with plugins, so this may not be canonical on a particular platform.

Table: Default Qt and Symbian Image file formats
Format Description Qt's support Symbian's support
BMP Windows Bitmap Read/write Read/write
EXIF Exchangable Image File format - Read/write
GIF Graphic Interchange Format (optional) Read Read (Single and multi frame, bitmap mask support)/write (Single frame, no transparency)
ICO Icon - Read (Single and multi frame)
JPG/JPEG Joint Photographic Experts Group Read/write Read/write
MBM Symbian Multi Bitmap - Read (Single and multi frame)/write (Single frame)
MNG Multiple Image Network Graphic Read Read/write
PNG Portable Network Graphics Read/write Read (Bitmap mask support)/write (No transparency)
PBM Portable Bitmap Read -
PGM Portable Graymap Read -
PPM Portable Pixmap Read/write -
SMS OTA SMS Over The Air - Read
TIFF Tagged Image File Format Read/write Read (LittleEndian and BigEndian sub-type support)
WBMP Wireless Bitmap - Read
WMF Windows Meta File - Read (Std, apm and clp sub-type support)
XBM X11 Bitmap Read/write -
XPM X11 Pixmap Read/write -

Finally, you may need to use Symbian image classes if that is the format in which the image becomes available to the platform. For example, the image from a camera becomes available to the platform in a CFbsBitmap.

Qt provides #Platform Specific Methods for converting CFbsBitmap to QPixmap (from which you can convert to a QImage if necessary)

CFbsBitmap *QPixmap::toSymbianCFbsBitmap() const;
static QPixmap QPixmap::fromSymbianCFbsBitmap(CFbsBitmap *bitmap);

Note.pngNote: If you need to get the image format (QImage::Format) based on the current Symbian display mode (TDisplayMode) there is a private function qt_TDisplayMode2Format defined in the Qt 4.6.0 version of q_s60_p.h that you can copy into your own project. Note that as this method is private it is subject to change.

Containers/Lists

Symbian Containers

Symbian C++ provides a large number of array classes, which should be used in preference to standard C/C++ arrays because of the protection they provide against memory overruns etc. The arrays behave much like standard C++ arrays (stl::vector<>); they are indexed by an integer, elements can be of any type (or pointer to a type), the array does its internal reallocation when adding or deleting.

The topic Fundamentals of Symbian C++/Arrays gives a good overview of the different array classes. There are four main categories

  • CArrayX (CArrayFixFlat, CArrayVarFlat, CArrayPakFlat, CArrayPtrFlat, CArrayFixSeg, CArrayVarSeg, CArrayPtrSeg)
  • RArrays (RArray, RPointerArray)
  • Fixed Array (TFixedArray)
  • Descriptor Arrays (CDesC16ArrayFlat, CDesC8ArraySeg, CDesC8ArrayFlat, CDesC8ArraySeg, CPtrC8Array, CPtrC16Array)

The array naming convention uses Fix where the elements of the array all have the same length and are copied directly into the array buffer (for example, TPoint, TRect) and Var where the elements of the array are pointers to objects of variable lengths contained elsewhere on the heap (such as HBufC* or TAny*). Pak (for ‘packed’ array) is used where the elements of the array are of variable length but are copied into the array buffer with each preceded by its length (for example, T class objects of variable length). Ptr is used where the elements of the array are pointers to CBase-derived objects

In general use the Descriptor arrays for storing lists of strings as they have convenient methods for sorting and finding matching strings. Otherwise use the RArray classes in preference to the CArrayX. The exception to this rule is when the array is likely resized often - in which case you might use one of the segmented variants.

Symbian provides a number of templated associative arrays:

  • RHashMap - associative array with key type K and value type V, using a probe-sequence hash table. Both the key and value objects are copied into the table when they are added. A bitwise binary copy is used here, so neither of the types K and V may implement a nontrivial copy constructor.
  • RPtrHashSet- unordered extensional set of objects of type T using a probe-sequence hash table. The objects are not copied into the set when they are added; rather the set stores pointers to the contained objects.
  • RPtrHashMap. an associative array with key type K and value type V, using a probe-sequence hash table. Neither the key nor value objects are copied into the table when they are added - only pointers are stored.

In addition to the Symbian C++ array classes, you can also use STL containers provided by Open C/C++.

Qt Containers

Qt allows you to use STL containers, or to use its own general purpose template-based container classes. The Qt classes are designed to be lighter, safer and easier to use than STL classes. They are implicitly shared, reentrant, and thread-safe in situations where they are used as read-only containers by all threads used to access them. Lastly, they are available on all Qt platforms (STL is not available on Qt Embedded).

Qt provides both sequential containers (QList,QLinkedList, QVector, QStack, and QQueue), and associative containers (QMap, QMultiMap, QHash, QMultiHash, and QSet). There are also specialist classes like QCache and QContiguousCache that provide efficient hash-lookup of objects in a limited cache storage, and non-template specialisations like QStringList (inherits from QList<QString>) that make it easier to work with lists of strings.

The containers can be traversed using either java-style or STL-style iterators, or using the convenient foreach keyword. Qt provides a set of Generic Algorithms that you can use to sort, find, fill, count, delete items in the container (on container types that have an STL iterator).

The template classes store items of a specified type T. The value type T can be a basic type, a pointer type, a class that has a default constructor, a copy constructor and an assignment operator, or a container that meets the same criteria as a class.

The Qt container classes are well documented in the Qt reference: Qt container classes. There is also an excellent discussion in C++ GUI Programming with Qt 4, Second Edition, Jasmin Blanchette and Mark Summerfield, Prentice Hall (2006) (the first edition is available free online here).

Converting Between Qt and Symbian Containers

There is no need to convert container/lists if you're using the standard STL containers, as these are supported by both Qt and the Symbian platform.

If you do need to convert between Qt and Symbian arrays, this usually a matter of iterating through one container, converting each contained object into the correct type for the environment (e.g. QString vs descriptor), and then adding it to the new container.

When converting to Qt, QList is the best "general use" re-sizable container template if you have less than 1000 items (its implemented as an array-list but provides very fast prepends and appends). If you're working with lists of strings, use QStringList instead. When converting to Symbian C++, use a descriptor array for strings, or RArray for other objects that are |greater than 4 bytes in size (unless you're expecting a lot of array re-sizing).

The following fragment shows conversion of a set of integers from an RArray to a QList:

RArray<TInt> intArrayToList;
QList<int> integerList;
...
for (int i = 0; i < count; i++) {
integerList.append(intArrayToList[i]);
}

This fragment shows a QList of integers being imported into an RArray, using foreach to iterate the QList. Note that a TInt is typdef of signed int:

QList<int> integerList;
RArray<TInt> listToIntArray;
...
foreach (int integerItem, integerList) {
listToIntArray.Append(integerItem);
}

Converting lists of strings uses exactly the same approach, except that you use a QStringList rather than a QList, a descriptor array instead of an RArray, and you need to convert the contained string to the correct format for the environment, as discussed in the #Strings section above.

The following code shows how CDesCArrayFlat is converted to QStringList:

CDesCArrayFlat* arrayToStringList;
...
QStringList qlistOfStrings;
for (int i = 0; i < count; i++) {
qlistOfStrings.append(QString::fromUtf16(
arrayToStringList->MdcaPoint(i).Ptr(),
arrayToStringList->MdcaPoint(i).Length()));
}

Summary

This article describes techniques and best practices for using Symbian C++ code in Qt applications, using the PIMPL pattern.

In addition, it describes how to write code that safely mixes the two environment's exception handling mechanisms, coding styles and idioms, strings, geometry, containers, images, and data. Each section gives a high level overview of how a particular task is done in Symbian C++ and Qt, and how the idioms are mixed - along with links to key documents explaining the approach in more detail.

The accompanying example code (File:Qtbluetoothdiscoveryexample.zip) delivers a Qt-style API (and dialog) for discovering remote Bluetooth devices. This uses the PIMPL pattern to obtain the information from the underlying platform, and demonstrates how to safely mix the exception handling mechanisms, coding styles and strings.

CreativeCommons attribution sharealike 2.5 by-sa2.5 88x31.png© 2009 Nokia Corporation and/or its subsidiary(-ies). This document is licensed under the Creative Commons Attribution-Share Alike 2.5 license. See http://creativecommons.org/licenses/by-sa/2.5/legalcode for the full terms of the license.

This page was last modified on 30 May 2013, at 09:34.
232 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×