Possible Problems

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:

Use of operator new()

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:

  1. 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.

  2. 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.

Use of one definition rule

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.

Standard C++ calling Symbian C++ functions

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 (...)
        {
        }
    }

Related concepts