Qt and Symbian C++ have different asynchronous signalling mechanisms, so you need to convert between them if you are using a Symbian C++ component.
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.
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.
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&) {} }
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.
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).