Introduction

Reflection is a mechanism making it possible to investigate yourself. If we are speaking about programming languages, reflection is used to investigate format of objects at runtime, invoke methods and access fields of these objects. Reflection is required if you want to implement some generic code which can work with objects of unknown (at the time of creation of this code) types. There are several tasks in which reflection can be used: remote method invocation, serialization, object dumps, database interfaces. How it works? Given some object we first should ask for its type. As a result we are given class descriptor, which provides information about class methods and fields. We can use these field descriptors to fetch/store object fields and can use method descriptors to lookup and invoke methods.

Many programming languages provide built-in reflection mechanism. For example, in Java there is special package java.lang.reflect . But unfortunately C++ doesn't support reflection. Not so long ago first step was made in this direction - RTTI support was added to the language. But RTTI provides only very restricted subset of reflection: it allows to get object compile-time and runtime type (it is possible to get object runtime type only if object class contains virtual functions). You can compare types and you can get type name - and that is all you can do with RTTI.

There are several ways of extracting type information for C++:

ApproachAdvantagesDisadvantages
Parse debugging information
  • Program should not be changed
  • It is possible to extract complete information about types used in the programs
  • Program was build with enabled debugging support and distributed with debugging information
  • There are a lot of different formats of storing debug information, while there are some standards for formats of executable files (COFF, ELF,...) format of debugging information usually is more or less specific to the particular compiler
Special preprocessor which will parse C++ sources and build class descriptors
  • Program should not be changed
  • Alignment and offset of fields in classes, as well as virtual function table format and naming conventions are specific to each compiler, so it is not possible to create generic preprocessor which will produce type information directly. Instead of it, it should generate some C++ code, which should be later processed by normal C++ compiler. So build procedure becomes too complex.
  • Parsing of C++ code is not trivial task. Also to be able to modify preprocessed code, this tool should fully handle standard C++ preprocessor directives and has to be called with the same options as normal compiler.
Create specialized compiler which will support reflection
  • This is most convenient case for the user - no extra steps during build are required to produce runtime type information.
  • Compiler can generate efficient code for reflection methods
  • Creating own C++ compiler is very challenged task.
  • Users will still prefer to use their own favorite tools (Visual C++, Borland,...). It is mostly because most of the modern compiler are not only compilers but IDE with builtin editor, debugger, profiler, wizards, class libraries....
Let programmer to provide this information himself
  • This is the most portable approach which can be used with any C++ compiler
  • This approach is the simplest to implement
  • With this approach it is possible to include additional type information (user defined attributes, for example transient)
  • Program has to be changed
  • Programmer has to do more work
  • There is a chance for programmer to make mistake describing class format

Architecture

As far as creating C++ preprocessor or specialized compiler requires huge amount of work and still can not solve all the problems, I decide to implement first and last approaches (getting type information from debug information and let programmer to provide this information). So CppReflection product consists of the following parts:
  1. Common core: descriptors for classes, fields, methods which provide all reflection functionality.
  2. Helpers for describing type information by programmer: set of macros, overloaded methods, and supplementary classes which makes it possible for programmer to specify only names of fields and methods and all other information (type, offset, size, profile) is calculated automatically. Using this helpers make task of describing types more convenient and less error prone.
  3. Library which is used to extract information from ELF, COFF and some other executable files formats.
Reflection API is described in this document generated by Doxygen. Code for extracting type information from debug data was taken from GNU objdump utility. The rest of this chapter provide more information about describing types by programmer.

It seems to be better to start with an example:

#include "reflect.h"
#include "typedecl.h"

class A { 
  public:
    int    i;
    char*  pc;
    double d;
  protected:
    long   larr[10];
    A**    ppa;    
    A*     pa;
  public:
    RTTI_DESCRIBE_STRUCT((RTTI_FIELD(i, RTTI_FLD_PUBLIC),
                          RTTI_PTR(pc, RTTI_FLD_PUBLIC),
                          RTTI_FIELD(d, RTTI_FLD_PUBLIC),
                          RTTI_ARRAY(larr, RTTI_FLD_PROTECTED),
                          RTTI_PTR_TO_PTR(ppa, RTTI_FLD_PROTECTED),
                          RTTI_PTR(pa, RTTI_FLD_PROTECTED)));
};

RTTI_REGISTER_STRUCT(A, 0);
So, you see that there two special macros RTTI_DESCRIBE_STRUCT and RTTI_REGISTER_STRUCT. First one describes class components and is used inside declaration of the class. And RTTI_REGISTER_STRUCT macro should be used in implementation module (.cpp file) to register class descriptor in repository. Class fields should be described using the following macros:

MacroField type
RTTI_FIELDScalar, structure or class
RTTI_PTRPointer to scalar, structure or class
RTTI_PTR_TO_PTRPointer to pointer to scalar, structure or class
RTTI_ARRAYOne dimension array of scalars, structures or classes

First parameter of these macros specifies field and second - bit mask of arbitrary flags (qualifiers) for this field. There are some predefined flags:

    enum RTTIFieldFlags { 
        RTTI_FLD_INSTANCE  = 0x0001, 
        RTTI_FLD_STATIC    = 0x0002, 
        RTTI_FLD_CONST     = 0x0004, 
        RTTI_FLD_PUBLIC    = 0x0010, 
        RTTI_FLD_PROTECTED = 0x0020, 
        RTTI_FLD_PRIVATE   = 0x0040, 
        RTTI_FLD_VIRTUAL   = 0x0100, // used for virtual base classes
        RTTI_FLD_VOLATILE  = 0x0200, 
        RTTI_FLD_TRANSIENT = 0x0400
    };
So that is all with describing class field. Now lets see how methods can be described:
class B : public A { 
    int x;
  public:
    virtual void foo(); 
    char* echo(char* p); 

    RTTI_DESCRIBE_CLASS(B, (RTTI_BASE_CLASS(A, RTTI_FLD_PUBLIC), 
                            RTTI_FIELD(x)),
			   (RTTI_PROC(foo, RTTI_FLD_PUBLIC|RTTI_MTH_VIRTUAL), 
			    RTTI_FUNC(echo, RTTI_FLD_PUBLIC)));
};
	  

RTTI_REGISTER_CLASS(B, 0);
Here class B is declared, which is derived from class A and has methods foo and echo. As you can see base classes are described within list of fields. And array of field descriptors returned by RTTIClassDescriptor::getFields method also returns field descriptors for base classes (them has RTTIDerivedType type with tag RTTI_DERIVED). It was done to simplify iteration through the object fields. Usually, to store/retrieve values of all object fields, recursive method is implemented which iterate through all fields in the class and is recursively invoked for each compound field. There is no difference for such method between inherited in included components. If base classes are not included in list of fields, then such method has to implement additional loop through all base classes. And in our case everything can be done within one loop.

When you are describing class with methods, you should use RTTI_DESCRIBE_CLASS macro instead of RTTI_DESCRIBE_STRUCT. In this macro you should specify name of the class, list of the field descriptors and list of method descriptors. For describing method two macros are used: RTTI_PROC - for describing void methods and RTTI_FUNC for method returning some value.

Limitations

In both models of using reflection (explicitly described by programmer and extracted from debugging information) there are some limitations. Below we enumerate them grouping in three part - common for proposed reflection API and specific for each approach.

Common limitation

Limitation of extracting information from descriptors written by programmer

Limitations of extracting types from debug information

Usage

To build the package under Window with Microsoft Visual C++, just execute nmake.exe. It will build reflect.lib library and testrtti.exe example. To be able to use RTTI in you application, you need to include reflect.h and typedecl.h header files. Then add type descriptors using macros defined in typedecl.h header file and decide how you will determine type of the object. There are several ways of determining object type:

  1. Create base class with virtual method char* getType and derive all classes for which you want to get runtime type information from this class.
  2. Use templates to parameterize object type. If you have template parameter T, then T::RTTIDesciptor is descriptor of this class.
  3. Use C++ RTTI mechanism. Once again you need to have some common base class with virtual function. But it is not necessary to redefine this function in all classes. Instead of it you can use RTTI typeof operator to get runtime type of the object, using pointer to this object.

If you know name of the class, you can locate it in repository using RTTIRepository::findClass method. You also can request and iterate through complete list of application classes (see testrtti.cpp example).

Having class descriptor, it is possible to find field or method with specified name or iterate through all components.

At Unix with GCC and GNU make package can be build with make -f makefile.gcc command. It builds libreflect.a library and testrtti example. To be able to use debugging information you should change directory to ./bfd and do make here. As a result library libbfdreflect.a and code>testrtti example will be build. To extract type descriptors from the image with debugging information you need both of these libraries (libreflect.a and libbfdreflect.a) and should use class RTTIBfdRepository instead of RTTIRepository. The method RTTIBfdRepository::load(char const* filePath) loads debug information from specified file. Certainly it will be able to it only if image was compiled with enabled debug information (-g option for GCC).

Distribution terms

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

I will provide e-mail support and help you with development of CppReflection package.


Look for new version at my homepage | E-Mail me about bugs and problems