blueDonkey.org

Books.VxWorksCookbookCPP

C++ Issues


Compiling C++ Modules

Compiler Flags

Munching

Once all your modules have been compiled, you need to arrange to get them correctly initialised on the target system. For the most part, this involves calling the constructors (c'tors or sometimes just ctors) for all the static objects. At the other end of the timeline, when the application is terminating, the destructors (d'tors, or sometimes just dtors) must be called.

For process model operating systems, the arrangements necessary to make this happen are made by the linker. In VxWorks, where the relocatable modules are either linked directly with the kernel or dynamically downloaded, the linker is not present (although it is used to create the VxWorks kernel bootable image, that image is not a standard executable in the sense of having support for application-level initialisation and termination - it is the kernel after all).

So, for VxWorks something else must be done. The munch tool is used to parse the modules (or, more precisely, the output of the nm tool applied to the modules), and extract the names of any static object c'tors and d'tors that need to be called. The result of this process is a single C source file that must then be compiled (usually with special flags) and linked into the application. For practical purposes, I would recommend a process like this:

  1. Link all the application's modules together using the -r linker option to create application.o
  2. Generate the c'tors/d'tors information file for this module, application_ctdt.c
  3. Compile application_ctdt.c to produce application_ctdt.o
  4. Link application.o and application_ctdt.o, again using the -r flag, to produce application.out (or perhaps application or even application.exe - pick a naming convention for your munched files so that everybody knows which ones to download onto the target system).

On the target system, the C++ runtime system detects the information generated by munch, and arranges for the c'tors to be called (or the d'tors if you are unloading the module). Note that the order in which they are called is unspecified. Your static object contructors and destructors should not rely on other static objects being initialised before them. For any C++ modules with static objects that were linked with the kernel, this process happens during the kernel's initialisation. For dynamically loaded modules, the process is invoked by the loader, as long as the C++ constructor mode is set to automatic (see the table below).

There are a number of routines for controlling this behaviour, so that you can debug problems in your c'tors and/or d'tors. The table below shows some of the more important of these; for the rest see the section in your VxWorks Reference Manual for cplusLib:

Routine Description
cplusXtorSet() Controls whether the loader will invoke the c'tors automatically or not. Set the strategy to 0 for manual and 1 for automatic.
cplusCtors() Invoke the constructors of one or more dynamically loaded modules. Pass 0 to call c'tors for all loaded modules, or the module's name to call them for just one module.
cplusDtors() Same as cplusCtors(), but calls the static object destructors instead of the constructors.
cplusDemanglerSet() Controls the display mode for C++ symbols in the shell's routines. When working with C++ modules, I recommend having this set to complete mode, where the full form of class member symbols is shown.

Dealing with Name Mangling

Member Function Pointers

Connecting to ISRs or Hooks

Since VxWorks is primarily a C API operating system, connecting C++ member functions as interrupt service routines, or as hooks to the various parts of the kernel that support them can be difficult. The main problem is the implied this pointer that is passed to all these functions when called from C++ code. The kernel code that calls these connected functions does not expect C++ semantics, and will not pass a this parameter. There are three solutions to this problem:

  • Use a pointer to an object of the correct type as the parameter to the ISR or hook routine. This relies on the fact that the compiler simply passes the this pointer in the first argument. That behaviour is not part of a specification though, and could change in future releases of the compiler (or between different compilers), rendering the code useless.
class Device
{
        public:
        Device ();
        ~Device ();
        
        private:
        void isr ();
        volatile int interruptCount;
};

Device::Device ()
{
        interruptCount = 0;
        intConnect (DEVICE_VECTOR, (FUNCPTR) isr, this);
        intEnable (DEVICE_VECTOR);
}

void Device::isr ()
{
        interruptCount++;
}    

  • Use a static member function, and, as in the previous example, pass an object pointer as the parameter to the ISR or hook routine. This is functionally the same as before, but now uses a documented feature of the language, so it will be much less likely to fail as a result of a compiler change. Notice in this case that the ISR code must also make explicit use of the pointer it is passed; being a static member function it no longer has a this pointer, so cannot make implicit references to member data.
class Device
{
        public:
        Device ();
        ~Device ();
        
        private:
        static void isr (Device * dev);
        volatile int interruptCount;
};

Device::Device ()
{
        interruptCount = 0;
        intConnect (DEVICE_VECTOR, (FUNCPTR) isr, this);
        intEnable (DEVICE_VECTOR);
}

void Device::isr (Device * dev)
{
        dev->interruptCount++;
}    

  • Finally, a friend function could be used. This is almost identical to the technique using a static member function, except that the ISR function is now declared as a friend of the class, and will be a non-member function.

Maintaining Virtual Function Behaviour

There are numerous places where the inheritance behaviour of C++ can make coding much simpler. There are cases where it might be desirable for the example above to allow the ISR function to be over-ridden in a derived class. In that case, the preferred strategy of a static member function will not work - they cannot be over-ridden. Instead, something like this can be added to the scheme to maintain the virtual function behaviour:

class Device
{
        public:
        Device ();
        virtual ~Device ();
        
        protected:
        virtual void isr ();
        
        private:
        static void isrWrapper (Device * dev); 
        volatile int interruptCount;      
};

Device::Device ()
{
        interruptCount = 0;
        intConnect (DEVICE_VECTOR, (FUNCPTR) isrWrapper, this);
        intEnable (DEVICE_VECTOR);
}

void Device::isrWrapper (Device * dev)
{
        // Call the actual ISR function (might be in a derived class)
        dev->isr();
}    

void Device::isr ()
{
        // Base class ISR implementation
        interruptCount++;
}

Spawning Tasks

As with the section describing hooks and interrupt handlers, starting tasks that use member functions as their entry points requires a little care. This is especially true on systems using the newer compiler which is a little stricter about its type checking. Here is an example of code that uses a static member function to wrap a true member function.

#include "vxWorks.h"
#include "taskLib.h"

class Application
{
        public:
        Application ();
        
        private:
        int        taskEntry ();
        static int taskWrapper (const Application * myself);
        int        myTID;
};

Application::Application ()
{
        myTID = taskSpawn ("App", 100, VX_FP_TASK, 20000,
                (FUNCPTR) taskWrapper,
                (int) this, 0,0,0,0,0,0,0,0,0);
        
        //
        // Should check for an error here and do something
        // if the task failed to start...
        //
}

int Application::taskEntry ()
{
        FOREVER
        {
                // Do whatever the task does
        }
}

int Application::taskWrapper (const Application* myself)
{
        return myself->taskEntry();
}

In the example, the taskWrapper() function is used to provide a bridge between the C taskSpawn() API, and the world of C++. You could equally well place all the task code in this static member function, but then every reference to member functions or member data would need to be prefixed with myself-> which makes for less readable code in my opinion.

Starting C++ Applications

Calling from the Shell

Adding to the Initialisation Sequence

Class Libraries

VxWorks API Wrapper Classes

STL

-- JohnGordon - 13 Jul 2003

 
 
© 2003-5 blueDonkey.org, except where otherwise noted. All rights reserved.