The following sections describe how Qt code can interoperate with Symbian's exception safety system.
Qt and Symbian have different exception systems. Qt works with standard C++ exceptions, whereas Symbian has its TRAP/Leave/CleanupStack system. So, what would happen if you mix the two systems? It could go wrong in a number of ways.
Clean-up ordering would be different between the two. When Symbian code leaves, the clean-up stack is cleaned up before anything else happens. After that, the objects on the call stack would be cleaned up as with a normal exception. So if there are any dependencies between stack-based and objects owned by the clean-up stack, there could be problems due to this ordering.
Symbian's
XLeaveException
, which is used when Symbian implements leaves as exceptions, is not derived from
std::exception
, so would not be caught in Qt catch statements designed to catch
std::exception
.
Qt's and standard C++'s
std::exception
derived exceptions result in program termination if they fall back to a Symbian TRAP.
These problems can be solved with barrier macros and helper functions that will translate between the two exception systems. Use them, in Qt code, whenever calling into or being called from Symbian code.
When calling Symbian leaving functions from Qt code, we want to translate Symbian leaves to standard C++ exceptions. The following help is provided:
User::LeaveIfError
.
malloc()
. The function is equivalent to Symbian's
User::LeaveIfNull
.
TRAP
and
TRAPD
from the Symbian libraries can be used to convert leaves to error codes.
HBufC* buf=0; // this will throw a std::bad_alloc because we've asked for too much memory QT_TRAP_THROWING(buf = HBufC::NewL(100000000)); _LIT(KStr,"abc"); TInt pos = KStr().Locate('c'); // pos is a good value, >= 0, so no exception is thrown qt_symbian_throwIfError(pos); pos = KStr().Locate('d'); // pos == KErrNotFound, so this throws an exception qt_symbian_throwIfError(pos); // we are asking for a lot of memory, HBufC::New may return NULL, so check it HBufC *buffer = q_check_ptr(HBufC::New(1000000));
When writing Qt code,
new
will normally throw a
std::bad_alloc
if the allocation fails. However this may not happen if the object being created has its own
operator new
. For example, CBase and derived classes have their own
operator new
which returns 0 and the
new(ELeave)
overload for a leaving
operator new
, neither of which does what we want. When using 2-phase construction of CBase derived objects, use
new
and
q_check_ptr
().
例如,若有代码像
CFbsBitmap* fbsBitmap = new(ELeave) CFbsBitmap;
可以把它重写成
CFbsBitmap* fbsBitmap = q_check_ptr(new CFbsBitmap);
When Qt code is called from Symbian, we want to translate standard C++ exceptions to Symbian leaves or error codes. The following help is provided:
KErrGeneral
被返回。
f
, catches any std::exceptions thrown from it, and sets err to the corresponding Symbian error code. err is set to
KErrNone
否则。
f
, catches any std::exceptions thrown from it, and throws a corresponding Symbian leave.
TInt DoTickL() // called from an active object RunL, ie Symbian leaves expected { // without the translation to Symbian Leave, we get a USER:0 panic QT_TRYCATCH_LEAVING({ int* x = new int[100000000]; // compiled as Qt code, will throw std::bad_alloc delete [] x; }); return 0; }
Try to minimise the interleaving of Symbian and Qt code, every switch requires a barrier. Grouping the code styles in different blocks will minimise the problems. For instance, examine the following code.
1. TRAPD(err, m_playUtility = CMdaAudioPlayerUtility::NewL(*this); 2. QString filepath = QFileInfo( m_sound->fileName() ).absoluteFilePath(); 3. filepath = QDir::toNativeSeparators(filepath); 4. m_playUtility->OpenFileL(qt_QString2TPtrC(filepath)));
Line 1 starts a Symbian leave handling block, which is good because it also uses a Symbian leave generating function.
Line 2 creates a
QString
, uses
QFileInfo
and various member functions. These could all throw exceptions, which is not good inside a
TRAP
块。
Line 3 is unclear as to whether it might throw an exception, but since it's dealing with strings it probably does, again bad.
Line 4 is tricky, it calls a leaving function which is ok within a
TRAP
, but it also uses a helper function to convert string types. In this case the helper function may cause an unwelcome exception.
We could rewrite this with nested exception translations, but it's much easier to refactor it.
QString filepath = QFileInfo( m_sound->fileName() ).absoluteFilePath(); filepath = QDir::toNativeSeparators(filepath); TPtrC filepathPtr(qt_QString2TPtrC(filepath)); TRAPD(err, m_playUtility = CMdaAudioPlayerUtility::NewL(*this); m_playUtility->OpenFileL(filepathPtr));
Now the exception generating functions are separated from the leaving functions.
When using Symbian APIs in Qt code, you may find that Symbian leaving code and Qt exception throwing code are just too mixed up to have them interoperate through barriers. In some circumstances you can allow code to both leave and throw exceptions. But you must be aware of the following issues:
CleanupClosePushL()
to push stack based R-classes onto the cleanup stack. However if the stack has unwound due to an exception before the cleanup stack cleanup happens, stack based objects will now be invalid. Instead of using the cleanup stack, consider Symbian's new
LManagedHandle<>
(or a custom cleanup object) to tie R-class cleanup to the stack.
TRAPD(err, QT_TRYCATCH_LEAVING( f ));
or
QT_TRAP_THROWING( QT_TRYCATCH_LEAVING( f ));
, depending if you want an error code or exception as a result.