C++ Issues
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: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:
- Link all the application's modules together using the -r linker option to create application.o
- Generate the c'tors/d'tors information file for this module, application_ctdt.c
- Compile application_ctdt.c to produce application_ctdt.o
- 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).
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.
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++; }
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.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.#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(); }
Calling from the Shell
Adding to the Initialisation Sequence
-- JohnGordon - 13 Jul 2003VxWorks API Wrapper Classes
STL