Home > Developer > Prcatical Development with BREW using C++

SophiaFramework : BREW  C++ Class Library & GUI Framework & XML Middleware

Prcatical Development with BREW using C++

Preface

There are currently 3 different compilers available for BREW: ARM RealView Compilation Tools for BREW ( RVCTB ), ARM Developer Suite ( ADS ) and GNU Compiler Collection ( GCC ).

The following development procedure is suited to RVCTB. The same procedures may be applied to both ADS and GCC.

 ARM RealView Compilation Tools for BREW

Restrictions on RVCTB 1.2

RVCTB 1.2 does not support many C++ standards such as exception handling, RTTI ( RunTime Type Information ) and name space.

Alternative Solutions for Exception Handling

Since exception handling cannot be used with RVCTB, if an error occurs within a class-defined operator, and the information associated with that error is saved, it can be handled outside the class.

The "new" Operator

In standard C++, if the "new" operator fails to allocate memory, the exception handling function does not allow the code below the new operator to execute. (See list 1)

* List 1 : Safe coding using exception

try {
    _buffer = new char[128];    // If memory allocation fails
                                // code after this line 
                                // will not be executed.
    _buffer[0] = 0;

    // OK
}
catch (...) {
    // NG
}

If exception handling can not be used ( See list 2 ), the new operator can not throw exceptions.

When the operation fails, the new operator can only return NULL and the block of code below it will still be executed.

* List 2 : Hazard prone coding

_buffer = new char[128];        // Even if memory allocation fails
                                // execution will still continue
_buffer[0] = 0;

If exception handling can not be used, It is necessary to implement safety procedures according to the return value of the new operator. (See list 3)

* List 3 : Coding with error handling

_buffer = new char[128];
if (_buffer != NULL) {
    // Normal process
}
else {
    // Error handling
}

Generally, the new operator allocates memory and boots up the constructor. In standard C++, when new operator's allocation fails, the generated code does not boot up a constructor. The return value of new operator is assigned to NULL.

Overloaded Operators

The lack of exception handling will be very inconvenient when dealing with operators like the assignment operator in the string class, whose return value cannot be compared to NULL.

As shown in list 4, the return value of an assignment operator refers to itself, thus it cannot return error values.

* List 4 : General assignment operator

my_string& my_string::operator=(my_string const& param)
{
    // Return value is itself. 
    // Does not return error value.

    return *this;
}

Therefore, the error value must be saved within the class and should be handled outside.
( See list 5 )

* List 5 : Saving the error value

my_string& my_string::operator=(my_string const& param)
{
    if (...) {

        // When an error occurs, save it in _my_exception

        _my_exception = true;
    }
    return *this;
}

int main(void)
{
    my_string data;
    my_string temp;

    temp = data;

    // Check for error

    if (temp._my_exception) {
        // Handle error
    }
    return 0;
}

* Porting something like a huge collection class onto BREW, will be very labor intensive since exception handling must be manually injected throughout the class.

Alternative Solution for RTTI

In RVCTB, the "dynamic cast" operator can not be used to down cast a variable of an abstract parent class into one of a concrete child class. In addition, the "typeid" operator for accessing runtime type information is not available either. ( list 6 - 7 )

* List 6 : Coding with RTTI
void function(base* p)
{
    if (dynamic_cast<derived_a*>(p) != NULL) {
        // Variable p is a pointer for derived_a class
    }
    else if (dynamic_cast<derived_b*>(p) != NULL) {
        // Variable p is a pointer for derived_b class
    }
    return;
}
* List 7 : Coding without RTTI
void function(base* p)
{
    if (dynamic_cast<derived_a*>(p) != NULL) {
        // This condition is always true
    }
    else if (dynamic_cast<derived_b*>(p) != NULL) {
        // This condition is always false
    }
    return;
}

The dynamic_cast Operator

Code relying on type information using the dynamic_cast operator should be avoided as much as possible. ( See list 8 )

* List 8 : Inferior design
class base {
};

class derived_a : public base {
public:
    void do_a(void);
};

class derived_b : public base {
public:
    void do_b(void);
};

void function(base* p)
{
    if (dynamic_cast<derived_a*>(p) != NULL) {
        dynamic_cast<derived_a*>(p)->do_a();
    }
    else if (dynamic_cast<derived_b*>(p) != NULL) {
        dynamic_cast<derived_b*>(p)->do_b();
    }
    return;
}

It would be better to use virtual functions, rather than conditional branches relying on type information. ( See list 9 )

* List 9 : Designing with virtual function
class base {
    public:
        virtual void do(void) = NULL;
};

class derived_a : public base {
    public:
        virtual void do(void);
};

class derived_b : public base {
    public:
        virtual void do(void);
};

void function(base* p)
{
    p->do();
    return;
}

If dynamic casting at runtime is not required, use templates. (See list 10)

* List 10 : Designing with templates
class independent_a {
    public:
        void do(void);
};

class independent_b {
    public:
        void do(void);
};

template <typename T>
void function(T* p)
{
    p->do();
    return;
}

The typeid Operator

The "typeid" operator in RCVTB is only supported for use with variables of primitive types and non-abstract ( concrete ) classes. (See list 11)

* List 11 : Code not supported by RVCTB
class base {
    public:
        virtual ~base(void) {}
};

class derived : public base {
};

int main(void)
{
    base* p = new derived;

    printf("%s\n", typeid(*p).name());

    delete p;
    return 0;
}

To get the type information of an abstract class, use either a variable in the base class that retains type information, or a virtual function that returns it. ( List 12, 13 )

* List 12 :Code with variable in base class
class base {
    private:
        int _typeid;

    protected:
        base(int id);
    public:
        int get_typeid(void) const;
};

class derived_a : public base {
    public:
        derived_a(void);
};

class derived_b : public base {
    public:
        derived_b(void);
};

base::base(int id) : _typeid(id)
{
    return;
}

int base::get_typeid(void) const
{
    return _typeid;
}

derived_a::derived_a(void) : base(0x0000000A)
{
    return;
}

derived_b::derived_b(void) : base(0x0000000B)
{
    return;
}
* List 13 : Code with virtual function
class base {
public:
    virtual int get_typeid(void) const = NULL;
};

class derived_a : public base {
    public:
        virtual int get_typeid(void) const;
};

class derived_b : public base {
    public:
        virtual int get_typeid(void) const;
};

int derived_a::get_typeid(void) const
{
    return 0x0000000A;
}

int derived_b::get_typeid(void) const
{
    return 0x0000000B;
}

Other

RVCTB does not support complex templates and name spaces.

Restrictions on BREW

The biggest restrictions on BREW are that global and static variables cannot be used.

Reasons

The preconditions of a BREW application are the following,

A static variable is allocated to an area of read/write memory. This area may not overlap with the code area, thus a relative address from the code area cannot be used to access the static variable.

The following diagram represents BREW memory allocation:

memory

Notes:

A BREW application is dynamically loaded onto memory, absolute addresses cannot be used since this address varies each time.

Moreover, a BREW application cannot use the SB register, because it is already occupied by the BREW environment.

The RVCTB compiler has an option to change the SB register. ( See list 14 )

* List 14 : How to use __global_reg

struct my_global {
    int i;
};

__global_reg(8) my_global* sb;

int get(void)
{
    return sb->i;
}

int main(void)
{
    sb->i = 0;
    return get();
}

This method cannot be used to access a static variable via a register chosen by the programmer, since some callback function may require the register in question.

* Sophia Cradle offers a special tool for using static variable in BREW. See svHacker.

Available Static Variables

The following are static variables:

Generally, a BREW application cannot use these static variables.

* List 15: Static variables that cannot be used

class my_counter {
    public:
        static int _counter;
};
int my_counter::_counter = 0;               // NO

int global_variable = 0;                    // NO

int main(void)
{
    static int local_variable = 0;          // NO

    return 0;
}

However, global or static variables can be used if they are constants of primitive types. Since they will not be updated at run time, they may be allocated in the code area.

* List 16: Static variables that can be used

class my_counter {
    public:
        static int const _counter;
};

int const my_counter::_counter = 0;         // OK

int const global_variable = 0;              // OK

int main(void)
{
    static int const local_variable = 0;    // OK

    return 0;
}

Static Variable Repercussions

Static Variables are required for certain functions in the ARM compiler, as well as the ANSI C standard library. Without static variables, these functions and libraries are not open to BREW applications. (See list 17 )

* List 17: Functions that can not be used

int main(void)
{
    void* buffer;

    buffer = malloc(128);       // NG
    free(buffer);               // NG
    return 0;
}

The function in list 18 adds two floating-point numbers. This function will be transformed into the _fadd function when compiled in FPU mode.

* List 18: Functions included in compiler
int main(void)
{
    double a;
    double b;
    double c;

    a = 1.0;
    b = 2.0;
    c = a + b;                  // Transformed into _fadd
    return 0;
}

A BREW application cannot use floating point operations because they will be transformed into compiler functions.

Instead, BREW has helper functions emulating ANSI C standard library and functions for floating point operations.
Here are some examples.

Function Name Definition
MALLOC Allocates memory
REALLOC Re-allocates memory
FREE Frees memory
STRSTR Searches for string
F_ADD Adds floating points
F_SUB Subtracts floating points

These helper functions are generally used when developing BREW applications. ( See list 19 - 20 )

* List 19: Floating point operation available
int main(void)
{
    double d;
    float f;
    int i;

    d = 1.0;
    f = 1.5;
    d += f;
    i = 2;
    d += i;
    return 0;
}
* List 20 : Floating point operation not available

int main(void)
{
    double d;
    float f;                    // Error
    int i;

    d = 1.0;
    f = 1.5;                    // Error
    d += f;                     // Error
    i = 2;
    d = F_ADD(d, FASSIGN_INT(i)); //Helper function used
    return 0;
}

BREW cannot use variables of type "float".

Using Floating Point Operations in BREW Application

ANSI C standard libraries and ARM compiler functions can be customized to the developer's environment using a method called "re-target".

If the RVCTB library is re-targeted for BREW environment, then ANSI standard functions and floating point operations can be used.

Re-target floating point operation library

If compiled with RVCTB in software FPU mode, all the floating-point operations of a BREW application will be transformed into compiler functions included in rt_fp.h. ( See list 21 )

* List 21 : A part of floating point operation function

<rt_fp.h>
/*
 * Single-precision arithmetic routines.
 */
extern __softfp float _fadd(float, float);
extern __softfp float _fsub(float, float);
extern __softfp float _fmul(float, float);
extern __softfp float _fdiv(float, float);

* Some ARM compiler functions contain static variables.

ARM's Floating Point Operations ( FPO ) library expects an initializing function, generated automatically by the compiler, to be executed on startup prior to main function. ( See list 22 )

* List 22 : Initializing function

<rt_fp.h>
/*
 * Call this before using any fplib routines, if you're trying to
 * use fplib on the bare metal.
 */
extern void _fp_init(void);

Re-target for BREW environment

To re-target the floating point operations library, relocate debug codes or functions with static variables, then move the initializing functions to a proper place. ( See list 23 )

The floating-point operations library can be re-targeted for the BREW environment using C for the start-up code. In case of C++, the extern "C" keyword must be used.

* List 23 : Functions to be re-targeted

<rt_misc.h>
/*
 * Redefine this to replace the library's entire signal handling
 * mechanism in the most efficient possible way. The default
 * implementation of this is what calls __raise (above).
 */
void __rt_raise(int /*sig*/, int /*type*/);

<errno.h>
extern __pure volatile int *__rt_errno_addr(void);

<rt_fp.h>
/*
 * This returns a pointer to the FP status word, when it's stored
 * in memory.
 */
extern unsigned *__rt_fp_status_addr(void);

Due to the lack of main function in a BREW application, the initializing function for floating point operation library, _fp_init, should be called manually from AEEClsCreateInstance function.

Three functions to be re-targeted must be replaced by the modified implementation.

Two of the functions must return the address of the variable pool they use. These variable pools can be customized freely by the developer. In BREW applications, it is suggested to reserve variable pools somewhere in the AEEApplet structure.

The __rt_raise function, invoked when exceptions occur within the library, may be replaced by a dummy function when applied to BREW applications.

List 24 shows the start up code for a BREW application with re-targeted part for floating point operations library included.

* List 24 : Start up code for using floating point operation

#if defined __arm
#include <rt_misc.h>
#include <errno.h>
#include <rt_fp.h>
#endif

// Insure that SWI instruction will not be linked

#if defined __arm
#pragma import(__use_no_semihosting_swi)
#endif

typedef struct SoftFPApplet {
    AEEApplet aee;

// Variable pool for retargetting

#if defined __arm
    int volatile _errno;
    unsigned int _fpstatus;
#endif
} SoftFPApplet;

#if defined __arm
void __rt_raise(int signal, int type)
{
    return;
}

int volatile* __rt_errno_addr(void)
{
    SoftFPApplet* ap = (SoftFPApplet*)GETAPPINSTANCE();

    return &ap->_errno;
}

unsigned int* __rt_fp_status_addr(void)
{
    SoftFPApplet* ap = (SoftFPApplet*)GETAPPINSTANCE();

    return &ap->_fpstatus;
}
#endif

int AEEClsCreateInstance(AEECLSID ClsId, 
                IShell* pIShell, IModule* po, void** ppObj)
{
    *ppObj = NULL;

    if (ClsId == AEECLSID_SOFTFP) {
        if (AEEApplet_New(sizeof(SoftFPApplet), ClsId, pIShell, po,
            (IApplet**)ppObj, 
            (AEEHANDLER)SoftFP_HandleEvent, NULL) == TRUE) {

// Initialize floating point operation library

#if defined __arm
            _fp_init();
#endif

// Add code for application

            return AEE_SUCCESS;
        }
    }
    return EFAILED;
}

BREW applications using the start-up code in list 24, may use floating-point operations anywhere without errors. List 25 presents the same floating point add operation, after re-targeting.

* List 25: After re-targeting

void function(void)
{
    double d;
    float f;                    // OK!
    int i;

    d = 1.0;
    f = 1.5;                    // OK!
    d += f;                     // OK!
    i = 2;
    d += i;                     // OK!
    return;
}

Although most ANSI C standard library functions can be used in BREW applications, they exist inside the application, and will increase application size. BREW helper functions exist apart of the application and are called dynamically; they will not increase the application size.