You will notice that class names in Symbian begins with different letters. The letter with which they begin depends on the type of the class.
If the name begins with T then it is a so called T class. Such classes cannot own external data - i.e. they cannot have pointers to data that is not owned by a different object. Such classes usually don't need a destructor. They can be created on the stack (i.e. as automatic variables) or inlined in a class.
If a class owns some external data, then it must be a subclass (direct or
indirect) of CBase and it's name will begin with C - this will be a C class.
The CBase provides a virtual destructor, so even after casting to CBase * the
right destructor will be called. Another nice feature of C classes is that it
fills the data area with 0s, so you don't need to initialize variables to 0
or NULL (what is the most common case). Because the C classes will be pushed
on the cleanup stack (which will be described later), C classes cannot be
created on the stack - they must be created with the new
operator
(usually new(ELeave)
as will be explained later).
Somehow analogical to T and C classes are the struct
s and
class
es in C#. Symbian uses also classes which are analogical to
Java or C# interface
s. They are prefixed with M and can contain
only pure virtual functions. Symbian discourages multiple inheritance unless
all the superclasses except at most one are M classes.
The last commonly used prefix is R. These classes access to some external
resources (e.g. files, sockets) and needs to be closed before exit. These
classes don't have a common ancestor and there is no single closing function.
The closing function is most often called Close()
, sometimes
Dispose()
or Delete()
.
There is also a convention for variables names - the class fields have names starting with 'i' while functions arguments have names beginning with 'a'.
Note that although the T classes can be created on the stack it is not
allways the best idea. The stack on the device is very small - by default 8kB.
Some T classes are relativly big - e.g. a TFileName
uses more
then 512 bytes. The emulator uses the Windows NT stack (which can grow up
to 8MB by default), so you will notice the overflow only when testing on the
device.
Another Symbian feature is that DLLs cannot contain static data. The problem
is that applications (*.app
files) are DLLs, so when writing an
application you should not use static data. Static data can be used in servers
(*.exe
).
Symbian doesn't use C++ exceptions because it was written before the exceptions where standardized and they require lots of resources. Instead Symbian uses a mechanism of leaves very similar to exceptions.
A leave is generated with User::Leave(errorCode)
. The error
code is an int
which carrys the information about the reason of
the leave (there are many predefined error codes, like
KErrNotFound
). It is like the throw
instruction in C++
with the differance that the information about the failure must be contained
in the int
error code, not in an arbitrary object. The
User::LeaveIfError(errorCode)
is also very usefull.
When a leave occurs then (similarly to exceptions) the control will
return
from all the functions (the stack will be unwinded) until
it reaches a trap harness (which is like a try
/catch
block in standard C++). A trap harness is defined by TRAP
:
TInt error; TRAP(error, FunctionThatLeavesL()); // try if (error==KErrNotFound) // catch { //... } if (error!=KErrNone) // catch { //... }
There is also a TRAPD
macro that automatically defines the
variable in the first parameter.
When a leaves occures all the data which is not owned by an object (i.e. data which is only pointed by an automatic variable in one of the functions we return from because of the leave) should be destroyed to prevent a memory leak. To achieve that Symbian uses a concept of Cleanup Stack.
If you have an object that should be destroyed in case of a leave push it
on the cleanup stack with CleanupStack::PushL(object)
. When
the leave occures all the objects on the cleanup stack pushed after entering
the trap harness will be destroyed. When an object should be removed from the
cleanup stack, e.g. because it will be destroyed or the ownership of it will
be taken by an object use CleanupStack::Pop()
to remove the
top-most object from the stack (In the first case
CleanupStack::PopAndDestroy()
it very practical). There is also
a function CleanupStack::Pop(object)
with checks if the poped
object is the same as the pushed one what is usefull for debugging.
The CleanupStack::PushL()
function has an overloaded version
that accepts a CBase *
class which has a virtual destructor.
When a leaves occures the destructor will be called and the owned data will
be deleted. That's why all objects that owns some external data should be
decendant of CBase
. The R classes doesn't have a common ancestor
so the closing function won't be called automatically. However there are
functions CleanupClosePushL()
, CleanupReleasePushL()
and CleanupDeletePushL()
that, thanks to the magic of templates,
will guarantee a call of Close
, Release
or
Delete
before destroying the object by the Cleanup Stack.
Note that every object pointed by an automatic variable should be put ont the cleanup stack - this objects whould be orphaned if a leaves occures. However if an object if owned by another object it should not be put on the cleanup stack or else it will be destored twice - once during the leave and once in the owner's destructor.
It is imporatant to know if a function can leave, so all the function that
may leave have a L suffix (e.g. PushL
). Symbian provides an
overloaded new
operator - new(ELeave)
- that in
case of a lack of memory leaves instead of returning NULL
.
Example: iApp = new (ELeave) CMyApp();
Due to reasons that are simple but outside of the scope of this brief
tutorial a constructor must not leave. What's why Symbian uses a so called
two phase construction. Often after calling the constructor you should call
a function ConstructL
that executes all the functions that may
leave (e.g. memory allocations). Some classes provides static functions
NewL
and NewLC
that does all the construction
(the C suffix is another common Symbian suffix - it means that the returned
object was pushed on the cleanup stack).
Symbian doesn't use the C char *
strings but provides classes
that does range checking called descriptors. (on overflow in a
char str[n]
string is probably the most common mistake that allows
an attacker to take control over a system. This is achieved by overwriting the
stack frame. So it's nice that Symbian libraries does all the range checking).
There are several classes of the desctiptors. The TDesC
is an
abstract class which is an ancestor of constant strings. The TDes
is a subclass TDesC
and adds some functions to modify strings - it
is an ancestor of all the modifiable strings. A TBuf<n>
is a class
that keeps n characters as a part of itself (thrus the T prefix). It is
usually used to keep some temporary strings as automatic variables. However
because of the small stack in Symbian devices you should not keep too many
string on the stack.
TPtr
objects stores a pointer to a buffer (with information about
the string length and maximum length) which it doesn't own. E.g. a
TPtr
can point to characters 5 to 10 of a
TBuf<64>
. Destroying the TPtr
will not hurt
the data in the TBuf
. However if you destory the TBuf
,
the TPtr
will become invalid. A TPtrC
is a
non-modifiable version of TPtr
.
If you want to keep the string on the heap use a HBufC
. The
HBufC
objects cannot be kept on the stack but must be created on
the heap like C classes but it also doesn't need a virtual destructor as all
the data is inlined like in T classes. That's why is has a special prefix -
H. As the C suffix suggests that class is non-modifiable directly but there is
a Des()
function which returns a TPtr
to the data,
which will allow you to modify the content.