Converting input/output, binary and geometry data

Streams are used for serializing object data, saving it to files, and transferring it between devices and threads. In most cases there is no need to convert stream data between Symbian and Qt. Geometry conversion involves converting points, sizes, rectangles and x and y coordinates and in most cases is straight-forward.

Symbian binary data

In this section we briefly discuss mechanisms for

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 streams, that are associated with the application using its unique identifier.

Symbian uses the concrete 8 bit variant descriptors as buffers for non-string data: TBufC8, TBuf8, TPtrC8, TPtr8, RBuf8, HBufC8. For example, they are used as the send and receive buffers to the RSocket APIs. We can also use a stream interfaced socket 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

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.

Copyright note

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