|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
About the Visual Component FrameworkThe Visual Component Framework was inspired by the ease of use of environments like NeXTStep's Interface Builder, Java IDEs like JBuilder, Visual J++, and Borland's Delphi and C++ Builder. I wanted a generic C++ class framework I could use to build apps quickly and visually (when designing GUIs), as well as have the core of the framework be as cross platform as possible. The Visual Component Framework is an Open Source project, so feel free to grab and use it if you think it might be useful. If you're really adventuresome, you can volunteer to help develop it, making it even better, especially in tying it into the VC++ environment as an add-in. For more information on either the project, helping out, or just browsing the doxygen generated documentation, please go to the VCF project on Source Forge here, or the project website. The code is available from CVS (follow the how-to here for setting up CVS on Windows), or as a tar.gz file in files section of the project. IntroductionThis tutorial introduces working with the RTTI/Reflection APIs of the Visual Component Framework. We'll see how to add basic support for RTTI to a class, and then progress through the more advanced options. This tutorial will run on all Win32 platforms, Linux 2.4 or better, and Solaris 2.8 or better. RTTI, or Run Time Type Information, is fundamental to a component based system like the VCF. The VCF has many parts, which may not be fully know at runtime, and therefore needs some sort of RTTI or Reflection like API to describe a component. Other languages like Java, C#, ObjectPascal, Smalltalk, Objective C (to name a few), as well as other C++ frameworks, such as Qt, wxWindows, MFC, COM (well this isn't really a C++ only framework, but we'll let that go), VRS3D, and many others, also implement some sort of RTTI system to varying degrees of complexity. Most of the languages offer full support for things like dynamic object creation from a class name, dynamic properties, and dynamic method invocation. Some also have support for discovering the fields of a class (like Java) and for discovering the events the class supports. C++ itself offers the ability to determine the super class of a given class, i.e., using the
This article will take you through the steps of creating a series of C++ classes that use the VCF to expose greater levels of RTTI, and then a little console program to demonstrate how to use RTTI in the framework. RTTI BasicsRTTI in the VCF is described through a series of abstract classes, and implemented through heavy use of templates. This makes it type safe, and does not require any weird void pointer type casting. To simplify matters, a number of macros exist that enable you to specify the RTTI elements you would like to expose in your class declaration, so there are no separate build steps like IDL compiling or other pre-processor tools. These macros basically "collect" all the stuff that the C++ compiler knows about during compilation, but throws away. Each RTTI element is registered in a central repository called the The basic RTTI element is the
As you can see, these methods allow us to access most of the important C++ class information. Adding support for Class names and dynamic creationOK, so let's create a C++ class with just the minimum support for RTTI. #define SIMPLECLASS_CLASSID "FB685669-6D44-4ea7-8011-B513E3808002" class SimpleClass : public VCF::Object { public: BEGIN_CLASSINFO( SimpleClass, "SimpleClass", "VCF::Object", SIMPLECLASS_CLASSID ) END_CLASSINFO( SimpleClass ) SimpleClass(){} virtual ~SimpleClass(){ } }; This declares a class called
The The The only requirement to this is that you must have a default constructor with no parameters, or a constructor that has default values for all the parameters. Adding PropertiesTo add properties to a class, you use the property macros, and place them, one for each property, between the For basic types, like Type <methodName>(); For basic types, like void <methodName>( const Type& ); For ObjectType* <getMethodName>(); void <setMethodName>( ObjectType* ); When adding properties, there are several different macros that you can use, depending on the property type, and whether you want the property read only. When considering the type, note that there are special property macros for dealing with values that are enumerations, to allow the system to display the symbolic names of the
So, to put this in practice, let's look at our next sample class: #define LITTLELESSSIMPLECLASS_CLASSID "F027BCD9-B8BF-4e52-A6E0-0EA3CC080B90" class LittleLessSimpleClass : public SimpleClass { public: BEGIN_CLASSINFO( LittleLessSimpleClass, "LittleLessSimpleClass", "SimpleClass", LITTLELESSSIMPLECLASS_CLASSID ) PROPERTY( long, "size", LittleLessSimpleClass::getSize, LittleLessSimpleClass::setSize, pdLong ) PROPERTY( bool, "cool", LittleLessSimpleClass::getCool, LittleLessSimpleClass::setCool, pdBool ) PROPERTY( char, "char", LittleLessSimpleClass::getChar, LittleLessSimpleClass::setChar, pdChar ) PROPERTY( String, "name", LittleLessSimpleClass::getName, LittleLessSimpleClass::setName, pdString ) READONLY_OBJECT_PROPERTY( Object, "owner", LittleLessSimpleClass::owner ) END_CLASSINFO( LittleLessSimpleClass ) LittleLessSimpleClass():m_size(0),m_cool(true), m_char('f'),m_name("Howdy"),m_owner(NULL){}; virtual ~LittleLessSimpleClass(){} long getSize() { return m_size; } void setSize( const long& size ) { m_size = size; } bool getCool() { return m_cool; } void setCool( const bool& cool ) { m_cool = cool; } char getChar() { return m_char; } void setChar( const char& ch ) { m_char = ch; } String getName() { return m_name; } void setName( const String& s ) { m_name = s; } Object* owner() { return m_owner; } protected: long m_size; bool m_cool; char m_char; String m_name; Object* m_owner; }; OK, so this is a bit more complex in the sense that we have exposed a series of properties for the Classes that inherit from the #define FooBarBazifier_CLASSID "1658339E-5132-4007-A9B4-0DE58F89AEBE" class FooBarBazifier : public Object { public: BEGIN_CLASSINFO( FooBarBazifier, "FooBarBazifier", "VCF::Object", FooBarBazifier_CLASSID ) METHOD_VOID( "printMe", FooBarBazifier, printMe, &FooBarBazifier::printMe ) METHOD_2VOID( "add", FooBarBazifier, add, const double&, const double&, &FooBarBazifier::add, "dd" ) METHOD_RETURN( "whereAmI", FooBarBazifier, whereAmI, String, &FooBarBazifier::whereAmI ) METHOD_2RETURN( "addAndReturn", FooBarBazifier, addAndReturn, int, const double&, const double&, &FooBarBazifier::addAndReturn, "dd" ) END_CLASSINFO( FooBarBazifier ) FooBarBazifier() { } virtual ~FooBarBazifier() { } void printMe() { System::print( "print me!\n" ); } String whereAmI() { return "Where am i ?"; } void add( const double& d1, const double& d2 ) { System::print( "%.4f + %.4f = %.4f\n", d1, d2, d1+d2 ); } int addAndReturn( const double& d1, const double& d2 ) { return (int)(d1 + d2); } }; As you can see from the above example, the method macros have the following form: METHOD_<argument number><return type> where the argument number is 1 through 6 (or nothing if the method takes no arguments) and the return type is Adding MethodsOur next class declaration will demonstrate how to expose the various methods of a class using the VCF RTTI macros. Like properties, you place these macros in between the A method is wrapped using the Any method can be exposed through RTTI, but there is (currently) a limit to the number of arguments that the method can have: currently the maximum number of arguments your method can have is 6. This could be altered by writing more macros if you wish - please see the vcf/include/ClassInfo.h file to get an idea how this works. Method argument types are described in a special string format where each character specifies a specific argument type:
Thus, a string of "h+lod" would indicate a set of parameters that consists of a The arguments for the method macros are as follows:
Registering a classSo far we have seen how to add the various pieces of RTTI to your class declaration, but this does nothing until you register the class information with the REGISTER_CLASSINFO( LittleLessSimpleClass ) By using the REGISTER_CLASSINFO( LittleLessSimpleClass ) REGISTER_CLASSINFO( LittleLessSimpleClass ) REGISTER_CLASSINFO( LittleLessSimpleClass ) will only register the class type Creating a class dynamicallyOnce classes have been registered, creating an instance from a class name is trivial, as the following code demonstrates: Object* simpleClassInstance = NULL; ClassRegistry* classRegistry = ClassRegistry::getClassRegistry(); classRegistry->createNewInstance( "SimpleClass", &simpleClassInstance ); We get the the class registry by calling the static method We create the instance by calling the Alternately, if you have access to a Object* anObject = ....//we have an object from somewhere... Object* newInstance = NULL; Class* clazz = anObject->getClass(); clazz->createInstance( &newInstance ); Presto! We have created a new instance of whatever class is represented by the Querying for RTTI informationRetrieving RTTI information is quite easy. void reportRTTIData( Object* object ) { Class* clazz = object->getClass(); if ( NULL != clazz ) { System::print( "\n\nClass retreived, information: \n" ); System::print( "Class name:\t%s\nSuper Class name:\t%s\n", clazz->getClassName().c_str(), clazz->getSuperClass()->getClassName().c_str() ); System::print( "Number of properties known to RTTI: %d\n", clazz->getPropertyCount() ); Enumerator<Property*>* properties = clazz->getProperties(); while ( properties->hasMoreElements() ) { Property* property = properties->nextElement(); VariantData data = *property->get(); System::print( "\tProperty \"%s\", is read only: %s, value: %s\n", property->getDisplayName().c_str(), property->isReadOnly() ? "true" : "false", data.toString().c_str() ); } System::print( "Number of interfaces known to RTTI: %d\n", clazz->getInterfaceCount() ); System::print( "Number of methods known to RTTI: %d\n", clazz->getMethodCount() ); Enumerator<Method*>* methods = clazz->getMethods(); while ( methods->hasMoreElements() ) { Method* method = methods->nextElement(); System::print( "\tMethod name: \"%s\", number of arguments: %d\n", method->getName().c_str(), method->getArgCount() ); } } } The Enumerator class used in the above example is not a home brewed collection class, instead it is a very thin wrapper to hide the implementation specific collection class (like list, vector, map, etc.) when all you need is the ability to iterate through a series of items. Invoking methods dynamicallyInvoking a method dynamically is likewise quite easy. A method can be retrieved from a Object* object = NULL; ClassRegistry* classRegistry = ClassRegistry::getClassRegistry(); classRegistry->createNewInstance( "FooBarBazifier", &object ); Class* clazz = object->getClass(); Method* addMethod = clazz->getMethod("add"); if ( NULL != addMethod ) { VariantData* args[] = {new VariantData(), new VariantData()}; *(args[0]) = 23.909; *(args[1]) = 1220.3490; addMethod->invoke( args ); delete args[0]; delete args[1]; } This demonstrates creating a class of type Pros and ConsI would like to think that this has demonstrated a reasonably powerful RTTI system, which this article only scratches the surface of. It offers a comparable feature set to languages like Java, or frameworks like .NET. The main drag (IMO) is that it requires manually adding macros to class definitions, and this could get tedious. I have experimented with a separate tool that could be used to create a separate .h/.cpp that was generated from parsing the classes declaration and outputting the necessary information automatically.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||