This section describes the possible problems that you may encounter when you develop applications or libraries on the Symbian platform based on Standard C++. These problems can occur in the following scenarios:
The function signatures of the global operator new
on
Symbian C++ and Standard C++ differ only in the exception specification. As
per Standard C++, global operator new
has the following signature:
void* operator new(std::size_t size) throw(std::bad_alloc);
While in Symbian C++, it is:
void* operator new(unsigned int aSize) throw();
Since the compiler name-mangles references to both these symbols as the same, it is possible (for the linker) to resolve a reference to one with the definition to the other. If such a combination is not identified at build time, the memory allocation may fail in the following cases:
If a reference to Symbian
C++ operator new
is resolved against the definition of Standard
C++ operator new
, then it throws an exception which may leave
the CleanupStack
in an unstable state.
If a reference to Standard
C++ operator new
is resolved against the definition of Symbian
C++ operator new
, then the operator new
returns NULL (while
an exception was expected). Since the return is not checked by the compiler,
it may cause a bus error.
So, the declarations must be kept separate in the translation unit. In this case only one of them can appear in a translation unit. Also, since they have the same mangled names, they cannot appear in the same link unit.
As per Standard C++, one definition rule (ODR) says that there must be only one definition of a symbol in a program. But to be able to maintain a single definition in a program, symbol pre-emption must be possible. This functionality is not supported on the DLL model of the Symbian platform.
Example
The following example illustrates the problem with the use of one definition rule:
//MySingleton.h template <class T> class MySingleton { public: static T& GetInstance() { static T * aT = NULL; if (!aT) aT = new T; return *aT; } }; class X: public MySingleton<X> { public: X(): iX(0) {}; int Prod() { return ++iX; } private: int iX; };
Foo.cpp
#include <MySingleton.h> EXPORT_C int Foo() { return X::GetInstance().Prod(); }
Bar.cpp
#include <MySingleton.h> EXPORT_C int Bar() { return X::GetInstance().Prod(); }
In a scenario where Foo.cpp
is in Foo.dll
and Bar.cpp
is
in Bar.dll.
To call Foo
and Bar
from
an application:
int Baz() { return Foo() + Bar(); } int E32Main() { printf("%d\n", Baz()); }
This example must have displayed the output as 1, 2. But on the Symbian platform, it displays 1,1.
The problem
here is, the Symbian platform's DLL model. In particular, the Symbian platform
does not support symbol pre-emption. static T * aT
gets allocated
in each DLL in which X::GetInstance
is invoked. Here the
problem arises because the Symbian platform cannot redirect the references
in distinct DLLs to a single unique location as required by the ODR.
The following example illustrates the nature of one of the problems associated with achieving seamless integration of Standard C++ with Symbian C++. Here seamless means: without the manual introduction of additional harness or barrier code to effect the integration.
SymbianCallerLC
calls
two different functions:
one which expects standard
C++ semantics (CppCallee
), and
another which expects
the Symbian platform semantics (GetAK1LC
).
SymbianCallerLC
itself belongs to the Symbian C++
world since it participates in the LC convention (it imposes a contract on
its caller with respect to the object that it returns).
K1 * SymbianCallerLC() { K1 * aK1 = GetAK1LC(); CppCallee(aK1); return aK1; }
The sole point of this function is to demonstrate that stack
depth cannot be used as an implicit means to synchronize the cleanup stack
with the control stack. It might be thought that recording stack depth at
the point at which an object is pushed onto the cleanup stack can be used
by the runtime to determine if the object should be popped and destroyed during
a standard C++ throw. Such a belief would be erroneous. For example,
a stack allocated object of sufficient size (aArray
in this
case). The stack depth when the object allocated by K1::NewLC
is
pushed on the cleanup stack is bound to be greater than the stack depth when
the try
statement is executed in CppCallee
.
From this example, therefore, that the stack depth at which an object
was allocated (or rather pushed onto the cleanup stack) gives no indication
of the ordering of various operations and can therefore not be used to determine
the inclusion of one extent within another.
K1 * GetAK1LC() { int aArray[100]; // stack frame for GetAK1LC has space for 100 ints - i.e. sp in this call to K1::NewLC // will be less than at the 'try' in CppCallee return K1::NewLC(aArray); }
In the current context the object returned by GetAnotherK1LC
will
be pushed on the cleanup stack when stack depth is less than it was in the
earlier call to GetAK1LC
, more evidence that stack depth
cannot be used to determine ordering or extent inclusion.
K1 * GetAnotherK1LC() { return K1::NewLC(); }
CppCallee(K1 * aK1)
is a catcher which
calls CppCallee(K1 * aK1, K1 aK2)
which can throw. It also
calls GetAnotherK1LC()
in the extent of the try
statement.
If CppCallee(K1 * aK1, K1 aK2)
throws it might seem desirable
that anotherK1
must be cleaned up by the runtime as part
of the throw.
void CppCallee(K1 * aK1) { try { K1 * anotherK1 = NULL; anotherK1 = GetAnotherK1LC(); CppCallee(aK1, anotherK1); } catch (...) { } }