Converting active objects to Qt signals and slots

Qt and Symbian C++ have different asynchronous signalling mechanisms, so you need to convert between them if you are using a Symbian C++ component.

Background

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 an active object's 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 that 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.

  • Emit signals if an error occurs. Qt uses module-local errors, so you should translate global (or local) errors from the underlying interface into errors defined by the module public API.

Note: Sometimes it may be possible to do a zero-copy transfer from a Symbian object to the Qt Object. For example it is possible to initialize a QString to use a pointer to a buffer you have received from your asynchronous service. As always with C++, you need to ensure that the object you transfer has a lifetime that exceeds all of its users. Given the different approaches used by Qt and Symbian C++ for memory management, it is usually better to create a "fully new" Qt style object.

For a general introduction to active objects, see Introduction to active objects.

Dialpad example TelephonyPrivate active object

This topic uses the Dialpad TelephonyPrivate class (which is an active object) and builds on Mixing Qt and Symbian C++ in your application, which explains how the private object is constructed and started. This document explains how the private object signals events back to the Qt API.

Figure: TelephonyPrivate class is an active object with four states.

Signalling from the active object

You signal errors and events from the private object back to the Qt API by emitting an appropriate signal on the public class. This is possible because the private object is passed a pointer to the public object on construction. Any objects that are emitted must be Qt-style objects to hide the private class implementation. Errors that are emitted must be local to the current Qt component (typically defined as enums on the public API) and not 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 to the signal can throw, and this propagates back to the emit. For this reason we use QT_TRYCATCH_LEAVING barriers when we emit signals that can propagate into Symbian code.

RunL()

Below is a fragment of the TelephonyPrivate::RunL(), which is called when the asynchronous service completes. RunL() is called when call connection and disconnection requests complete. The code checks if the completing request was to connect or disconnect a phone call and it emits the appropriate signal. If there is an error, it is converted to a Telephony::TelephonyErrors enum value, which is emitted as a parameter with the error() signal.

// Called when an asynchronous service request completes.
// This is called when requests for connecting and disconnecting calls complete.
void TelephonyPrivate::RunL()
{

    // Check return code and emit the appropriate signal based on iCallState.
    if (iStatus == KErrNone)
    {
        // Start call request has completed successfully.
        if (iCallState == EDialling) {
            iCallState = EConnected;

            // Call connect was succesful - notify clients.
            // This Qt code could throw, need to convert to a Symbian Leave if it does.
            QT_TRYCATCH_LEAVING( emit iPublic->callConnected() );

            // Start a new async request to be notified if the called party hangs up.
            iTelephony->NotifyChange(iStatus, CTelephony::EVoiceLineStatusChange, iCallStatusPckg);
            SetActive();
        }

        // Hang up request has completed successfully.
        else if (iCallState == EDisconnecting) {
            iCallState = EIdle;

            // Notify clients of call disconnection.
            QT_TRYCATCH_LEAVING( emit iPublic->callDisconnected() );
        }

        // CTelephony::NotifyChange() has completed.
        // The call state has changed. Check if the called party has hung-up
        // by checking for disconnecting status.
        // Emit callDisconnected if necessary
        else if (iCallState == EConnected) {

            // Is the call disconnecting?
            if (iCallStatus.iStatus == CTelephony::EStatusDisconnecting) {

                iCallState = EIdle;

                // Notify clients of call disconnection.
                QT_TRYCATCH_LEAVING( emit iPublic->callDisconnected() );
            }
        }

    }
    // Return code was not KErrNone - so it could be an error or a timeout.
    // Convert the Symbian error code into an enum value defined on the Telephony interface.
    else {
        iCallState = EIdle;
        ConvertErrorL(iStatus.Int());
    }
}

SetActive()

The private functions startCall() and endCall() start the active object. They check the current object state, and if necessary, create an asynchronous request on a CTelephony object to start or end a phone call. Finally they call CActive::SetActive() to set the object active.

In the Dialpad example startCall() emits a signal on error (the object is already started) or to notify clients that it started successfully. Here we do not need a barrier around the emit call because although it can throw, startCall() can only be called by the Qt API. The behavior of endCall() is similar.

// Public interface slot implementation - start a call using number.
void TelephonyPrivate::startCall(QString number)
{
    // Only allow one call to be active at any time
    if (iCallState != EIdle) {
        return;
    }

    // Convert the QString to a descriptor
    // typedef TBuf<KMaxTelNumberSize> TTelNumber;
    CTelephony::TTelNumber telNumber(number.utf16());

    // Dial the call
    CTelephony::TCallParamsV1 callParams;
    callParams.iIdRestrict = CTelephony::EIdRestrictDefault;
    CTelephony::TCallParamsV1Pckg callParamsPckg(callParams);

    // iCallId is set to a unique ID which can be used to end the call later.
    iTelephony->DialNewCall(iStatus, callParamsPckg, telNumber, iCallId);

    // Emit the callDialling signal to clients.
    iCallState = EDialling;

    // This function is only called from Qt code so no need to wrap in QT_TRY_CATCH_LEAVING.
    emit iPublic->callDialling(number);

    SetActive();
}

// Public interface slot implementation - end the current call.
void TelephonyPrivate::endCall()
{
    // If the call is still dialling, cancel that request
    if (iCallState == EDialling) {
        Cancel(); // Calls DoCancel() to cancel any outstanding async request
        iCallState = EIdle;

        // Notify clients that the call disconnected.
        // This function is only called from Qt code so no need to wrap in QT_TRY_CATCH_LEAVING.
        emit iPublic->callDisconnected();
        return;
    }

    // The call has been answered - so hang up.
    else if (iCallState == EConnected) {
        iTelephony->Hangup(iStatus, iCallId);
        iCallState = EDisconnecting;

        // Only emit callDisconnected() when this async request has completed - see RunL().
        SetActive();
    }
}

DoCancel()

The function CActive::Cancel() can be called to cancel an asynchronous request, and in turn calls the derived class's CActive::DoCancel() implementation if it is called when the object is active. The Cancel() function MUST be called from the active object destructor to cancel any outstanding requests - the active scheduler panics because of a “stray signal” if a destroyed object is still waiting on a signal.

In the Dialpad example DoCancel() cancels an outstanding request to start or end a phone call.

// Cancel an outstanding asynchronous request.
// What to do here depends on what state the call is in.
void TelephonyPrivate::DoCancel()
{
    if (iCallState == EDialling) {
        iTelephony->CancelAsync(CTelephony::EDialNewCallCancel);
    }
    else if (iCallState == EDisconnecting)
    {
        iTelephony->CancelAsync(CTelephony::EHangupCancel);
    }
    else if (iCallState == EConnected)
    {
        iTelephony->CancelAsync(CTelephony::EVoiceLineStatusChangeCancel);
    }
    // If iCallState is EIdle there is no outstanding async request.
}

Callback APIs

Note: A callback interface is not implemented in the Dialpad example, where the TelephonyPrivate implementation is a CActive object instead.

Symbian C++ provides APIs that use callbacks to signal completion of an asynchronous service. (Internally 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. This private implementation owns an instance of the Symbian class and implements 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&amp;) {}
}

Note that the above code traps exceptions, and does not deal with them or re-throw them. Generally this is not good practice. However, here we are constrained by the API - we cannot leave because its prototype indicates that it may only be used in a leave-safe way. Similarly we cannot throw, because this leads back into Symbian code. Absorbing the errors is our only choice.

Caution: 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.

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).