Mixing Qt and Symbian C++ in your application

Using Qt and Symbian C++ code together in an application requires the use of good object-oriented software design principles.

Background

This document assumes you have some basic knowledge of Symbian C++ programming. Essential information about Symbian C++ can be found in Introduction to Symbian C++ development.

Many of the device services provided on Symbian are available to your Qt applications through the Qt Mobility APIs and so some of your applications may not need to contain any Symbian C++ code. However, some Symbian device services and features are not accessible by using Qt and Qt Mobility APIs. To access these, you must use native Symbian C++ code. This document describes a design pattern for using Qt and Symbian C++ code together in a Qt application.

The essential rule is: do not mix Qt and Symbian C++ code in the same source file. The programming idioms for Qt and Symbian C++ are very different and you should not mix them. Ideally, write your application modules using Qt, and use native Symbian C++ code only where necessary. Provide clean interfaces between your Qt code and your Symbian C++ code.

Separating Qt and Symbian C++

If Qt does not provide an API for a feature or service that is available on a specific platform, the recommended way to access that service is to write a generic, cross-platform wrapper API and provide a private platform specific implementation of that API for each target platform. By isolating platform-specific code behind an interface, you can prevent widespread platform dependencies in your code. This facilitates porting applications that use the feature between different platforms. Using this approach, you can write most of your application code in the easy-to-use Qt programming idioms, and confine the platform specific code to individual code modules. This is the technique that Qt uses to abstract the underlying operating system so that developers do not need to be aware of each platform's native programming idioms, APIs, installation mechanisms, build systems and limitations.

There are a number of different design patterns you could use to structure your code. In this document we discuss the PIMPL (Pointer to Implementation) design pattern, in which platform-specific implementations are hidden behind a private pointer in a public API. PIMPL has a number of different names in design pattern literature, including "Handle-Body", "Compiler Firewall", "the Bridge", "Opaque Pointers", and "Cheshire Cat".

Note: You can use other design patterns and techniques in your code. For example, in Qt 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 provided the platform-specific implementation is not exposed by the public Qt API.

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

The Pointer to Implementation Pattern (PIMPL)

PIMPL is a variant of the Handle-Body design pattern, in which a public API class contains a pointer to a private implementation class. The private implementation class is forward declared (instead of using #include) in the header file, and is therefore hidden to clients of the public API.

If the implementation class must call methods in the public class we can pass a pointer or reference to the public class in the implementation class's constructor.

If the implementation class needs to call private methods on the public class (for example, in order to emit signals to public API clients) you can make the implementation class a friend of the public class. The class diagram shown below illustrates the relationships between classes, showing a public API class QMyClass which owns a private implementation class QMyClassPrivate.

Figure: Public API and private implementation classes.

The implementation of QMyClass creates and destroys the private implementation QMyClassPrivate. In order to do this, QMyClass must have the definition of QMyClassPrivate and so the QMyClass source file must #include the QMyClassPrivate header files for the different target platforms. This can be achieved by using platform specific #define statements to support conditional compilation for different platforms. Qt defines Q_OS_SYMBIAN (in qglobal.h) which can be used in your application to conditionally compile Symbian-specific code.

// In fictional myclass.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
...

The definition and implementation of your private class implementations are almost entirely up to you. The only real constraint is that you must use the same private implementation class name for all of your target platforms (otherwise you would need to include separate forward definitions and friend declarations in your public API header file for each platform). Also, typically all of your private classes for the different target platforms should expose the same function names to the public API class – this simplifies the implementation of the public class.

// Implementation of a public class slot mySlot() is defined on all
// private implementation classes for different target platforms and
// so can be called in the same way from the QMyClass public API class.
void QMyClass::mySlot()
{
    d_ptr->mySlot();
}

A private implementation class can derive from any base class. Although it is common for implementation classes to be derived from QObject, this is not a requirement (although it is necessary if you want your implementation to have signals or slots). Qt code often uses QObjectPrivate for private implementations. This is not part of the public Qt APIs and you should not use it in your own application code.

For private implementations written in Symbian C++ there are two basic class designs:

  • Private implementation class is-a CBase .

  • Private implementation class uses-a CBase and implements a callback interface.

The following diagram shows a private implementation class that is-a CActive (CActive derives from CBase).

Figure: Private implementation class is-a CActive

The following diagram shows a private implementation class that uses-a CBase and implements a callback interface.

Figure: Private implementation class uses-a CBase

The “is a CBase” design requires less programming effort, because you do not need an additional level of indirection to create and use the Symbian class, or to get notification of asynchronous function call completions through a callback interface. This is a good design choice if you only need to use asynchronous function calls onto a single Symbian C++ class, in which case you can often make the private class a CActive (which derives from CBase). One disadvantage of the “is a CBase” design is that code to construct the private implementation class is a little more complex and it may be more difficult to conceptually separate Qt and Symbian C++ code. The Dialpad example code, which is described in the following sections uses a private implementation class derived from CActive.

Using the “has a CBase” design results in a cleaner separation between the Qt and Symbian programming idioms. It is also a better solution if your implementation needs to use several Symbian classes. The Callback APIs section in Converting Active Objects to Signals & Slots explains this design pattern in more detail.

Dialpad example public API Telephony class definition

The public Telephony class from file telephony.h is shown below. It has a pointer to the friend private implementation class TelephonyPrivate defined in files telephony_symbian.h (for Symbian) and telephony_stub.h (a dummy implementation).

The two private classes share the same name and have a superset of the public class slots and methods. Note that they do not re-implement the public API class's signals: they can cause the public API class to emit signals by calling emit on the public class pointer they get on construction.

/*
 * Copyright (c) 2010 Nokia Corporation.
 */

#ifndef TELEPHONY_H
#define TELEPHONY_H

#include <QObject>

// Forward declaration of the implementation class.
class TelephonyPrivate;

// An interface to start and stop a phone call
// and notify clients of the call status.
class Telephony : public QObject
{
    Q_OBJECT

public:
    explicit Telephony(QObject *parent = 0);
    ~Telephony();

    // An enum for returning error codes to clients of this interface.
    // This enum is not fully defined in this demonstration code.
    // Your own interface should define some meaningful errors.
    enum TelephonyErrors {TelephonyNotSupported, TelephonyError, TelephonyError2, TelephonyError3};

signals:

    // Emitted when the request for the call is first made.
    void callDialling(QString number);

    // Emitted when telephone call is connected.
    void callConnected();

    // Emitted when telephone call is disconnected.
    void callDisconnected();

    // Emitted if some kind of error occurs.
    void error(Telephony::TelephonyErrors error);

public slots:

    // Starts a call using number.
    void startCall(QString number);

    // Ends the current call.
    void endCall();

private:

    // The class that does the work.
    TelephonyPrivate* d_ptr;

    // Make TelephonyPrivate a friend so it can emit
    // signals using its pointer to its parent Telephony object.
    friend class TelephonyPrivate;

};

#endif // TELEPHONY_H

The Qt project file

The platform-specific inclusion and 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.
# From Dialpad.pro example

# Needs to be defined for Symbian
DEFINES += NETWORKACCESS

# Common header and sources for all build targets
HEADERS += telephony.h
SOURCES += main.cpp \
    telephony.cpp \

# Symbian specific values
symbian {
TARGET.UID3 = 0xE53F898F
TARGET.CAPABILITY += NetworkServices
HEADERS += telephony_symbian.h
SOURCES += telephony_symbian.cpp
LIBS += -letel3rdparty
}
else {
# Stub sources and headers for platforms other than Symbian
HEADERS += telephony_stub.h
SOURCES += telephony_stub.cpp
}
...
The platform-specific sections also list the platform libraries the code must link with, in this case etel3rdparty.dll .

A Symbian implementation must specify the capabilities needed by the library or executable. The Dialpad project file specifies NetworkServices in order to allow the Dialpad application to make a phone call.

Public class implementation

The public class implementation needs the private class header in order to construct the private class. The private class header is conditionally included based on the current platform.

// In telephony.cpp
// Conditional include for the target platform.
#ifdef Q_OS_SYMBIAN
#include "telephony_symbian.h"   // Symbian definition of private implementation class.
#else
#include "telephony_stub.h"      // Stub class for all other platforms.
#endif

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

// Constructor.
Telephony::Telephony(QObject *parent) : QObject(parent)
{
    #ifdef Q_OS_SYMBIAN
        // Symbian private class implementation.
        // This code can generate a Symbian Leave.
        // If it does, convert it into a throw.
        QT_TRAP_THROWING(d_ptr = TelephonyPrivate::NewL(this));
    #else
        // Stub class implementation
        d_ptr = new TelephonyPrivate(this);
    #endif
}

Because the private class is a Symbian CBase-derived class, we use platform-specific construction in the public class implementation. This is acceptable because the Symbian class implementation is not visible in the public API. As a general rule, however, it is better to put as much of the platform-specific implementation as possible into the private class.

Function NewL() is a standard Symbian static factory class for creating new objects. It is implemented by TelephonyPrivate to ensure that the object is either fully allocated, or is properly cleaned up in the event of a problem. Because NewL() can leave, it must be wrapped in a QT_TRAP_THROWING macro to convert the leave into a standard C++ throw. The document Exceptions & Error Handling explains how to create exception handling barriers between Symbian and Qt code.

If you implement the private class using the “has-a CBase” design instead, constructing the implementation class from the public class is simpler, and there is no need for conditional macros.

// Public class
Telephony::Telephony(QObject *parent)
: QObject(parent)
{  
d_ptr = new TelephonyPrivate(this);
}
 
//Private class
TelephonyPrivate::TelephonyPrivate(QObject *parent)
: d_ptr(parent)
{  
// symbianMember is a CBase derived member variable in TelephonyPrivate.
QT_TRAP_THROWING(symbianMember = CTelephonyManager::NewL(this));
}

There are other strategies for allocating the Symbian object in your code. Whatever method you use, the important things to remember are:

  • Symbian C classes have their new function overloaded from CBase and it does not throw exceptions. If you construct your private implementation class using new, you must use the q_check_ptr() function to check the returned pointer and throw if it is Null.

    This is important if a CBase-derived object is not initialized until it is first used. You can construct the object using new, but you must check the pointer before you use it for object initialization and throw if it is NULL.

  • It is your responsibility to ensure that the object is properly cleaned up if its construction fails.

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

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

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

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

The public API methods are replicated in the private class. Public methods call the equivalent private class methods through the pointer to implementation, for example:
// Public slot - starts a call using number.
void Telephony::startCall(QString number)
{
    d_ptr->startCall(number);
}
Note that we have not created any barrier between Qt and Symbian C++ Exception handling mechanisms in the above code. This is because we know from the implementation that startCall() cannot Leave. This method can throw, which is acceptable because it is never called from Symbian code.

Private platform implementation

The Symbian platform TelephonyPrivate class implementation is shown below. It is a Symbian active object. As it is a Symbian C++ class it is important to comply with the Symbian Coding Standards, in particular:

  • Construct the class using leave-safe Symbian mechanisms.

  • If using multiple inheritance, derive from the CBase derived classes first, and only otherwise derive mixin (interface) M classes.

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

/*
 * Copyright (c) 2010 Nokia Corporation.
 */

#ifndef TELEPHONY_SYMBIAN_H
#define TELEPHONY_SYMBIAN_H

#include "telephony.h"
#include <e32base.h>
#include <etel3rdparty.h>

// The Symbian implementation of Telephony interface.
// Starts and stops a phone call on Symbian
// and notify clients of the call status.
class TelephonyPrivate : public CActive
{

public:

    // Symbian style static constructor
    static TelephonyPrivate* NewL(Telephony *aPublicAPI);
    ~TelephonyPrivate();

    // Start a call using number
    void startCall(QString number);

    // End the current call
    void endCall();

public: // From CActive

    // Cancel an outstanding request
    void DoCancel();

    // Called when an asynchronous service request completes
    void RunL();


private:

    // Constructor is private - NewL must be used to create objects
    TelephonyPrivate(Telephony* publicInterface);

    // Symbian second stage constructor
    void ConstructL();

    // Convert from Symbian error code to interface Telephony::TelephonyErrors enum
    void ConvertErrorL(int err);

    // The Symbian telephony API class for making phone calls
    CTelephony* iTelephony;

    // Pointer to the public interface object that owns this private implementation
    Telephony* iPublic;

    // ID of the current call - used to end the call
    CTelephony::TCallId iCallId;

    // Status of the call
    CTelephony::TCallStatusV1 iCallStatus;
    CTelephony::TCallStatusV1Pckg iCallStatusPckg;

    // An enum for tracking the connection state of this object
    enum TCallStates {EIdle, EDialling, EConnected, EDisconnecting};
    TCallStates iCallState;

};

#endif // TELEPHONY_SYMBIAN_H

A TelephonyPrivate object has a pointer to its parent Telephony that it uses to emit signals to the Qt clients when the active object completes or has an error.

The private class replicates the public class's API, reducing the conditional code in the public class implementation. As it derives from CActive it must implement its base class virtual methods RunL() and DoCancel() to handle completion and cancellation of the active object.

The class has a private data member, iCallId, to store the ID of the current call made using the Symbian CTelephony class . A pointer to the CTelephony class is held in the iTelephony member variable.

As the private implementation is a Symbian class it is constructed using the Symbian Two-phase construction idiom.

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

TelephonyPrivate* TelephonyPrivate::NewL(Telephony *aPublicAPI)
{
    TelephonyPrivate* self = new (ELeave) TelephonyPrivate(aPublicAPI);
    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop(self);
    return self;
}

As part of its construction, the private object receives a pointer to the public class object. This pointer is used to emit signals from the public class.

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

Note also the use of the method void ConvertErrorL(int err) to convert Symbian global errors into errors defined by the public class API.

Handling of the RunL() and DoCancel() methods for this example is discussed in Converting Active Objects to Signals and Slots.

Platform-specific methods

Wherever possible, keep public APIs free of platform-specific members and functions. Exceptions to this rule are very rare.

Occasionally platform-specific helper functions are made public, where developers would otherwise need to implement their own methods. For example, QPixmap provides helper functions to convert to and from a CFbsBitmap:
  • CFbsBitmap * QPixmap::toSymbianCFbsBitmap () const
  • static QPixmap QPixmap::fromSymbianCFbsBitmap ( CFbsBitmap * bitmap )

Similarly, it is sometimes necessary to reveal platform-specific types and publicly include platform headers in order to map them to generic typedefs. This can be seen in QProcess where Q_PID is a typedef to a Symbian TProcessId.

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

Copyright note

Some of the material in this topic is based with permission on a Symbian Foundation wiki article Apps:Using Qt and Symbian C++ Together . The version used was that available at Symbian Foundation on 3 November, 2010. The content in this page is licensed under the Creative Commons Attribution-Share Alike 2.0 UK: England & Wales License (http://creativecommons.org/licenses/by-sa/2.0/uk).