12.2. MetaObject class

MetaObject provides the following information about classes and objects:

12.2.1. Obtaining a MetaObject

Obtaining a pointer to a metaobject describing the most derived class of a local object is simple - just call the get_metaobject() method of the object.

Otherwise you have to use the ClassManager singleton and use one of its get_metaobject() methods as illustrated in this example:

Example 12.1. Obtaining a MetaObject
using namespace Massiv::Core;

/* Get reference to the class manager. */
const ClassManager & class_manager = Global::class_manager();

/* Get metaobject by fully-qualified class name. */
const MetaObject * const m1 = class_manager.get_metaobject( "Foo" );

/* Get metaobject by C++ class type info. */
const MetaObject * const m2 = class_manager.get_metaobject( typeid( Foo ) );

/* Get metaobject by class type id. */
const WeakPointer< Object > object = ...;
const ObjectId & object_id = object.get_object_id();
hope( object_id.is_object_info_available() );
const ClassTypeId class_type_id = object_id.get_class_type_id();
const MetaObject * const m3 = class_manager.get_metaobject( class_type_id );

The example should be pretty self-explanatory, except for the “get metaobject by class type id” part, which illustrates how a metaobject pointer can be obtained from an object pointer to a possibly remote object. In that case we must extract a class type id from its object id. Unfortunately some object ids may be missing the class type information, if the referenced object is remote. This may happen when an object id referencing an object of an alien kind is archived and then loaded back, for example. You can assume that the class type id is present when you know that the pointer you have extracted the id from never migrated, or migrated only between nodes of the same type (nodes supporting the same set of class kinds). To make it short and simple: you can use this approach on server nodes if the object pointer was retrieved from a server-only object.

None of the get_metaobject() methods that were mentioned so far returns NULL. They all throw the Lib::UndefinedClassTypeException exception if no metaobject is available for the given class. This may happen even in the third version (getting the metaobject by the class type id) if the object id references an object of an alien class kind. For each get_metaobject() method variant a corresponding method called get_metaobject_nothrow() exists, which never throws an exception and returns NULL on error instead.

12.2.2. Passing Pointers to MetaObject Methods

In the MetaObject API, pointer-to-object arguments are often used. However, they are usually not object pointers as described in Chapter 5, Pointers. Instead, these two pointer types are used:

  • PtrToObject is a lightweight pointer to Object. Unlike simple Object *, it ensures that the referenced object will not be destroyed by the garbage collector, i.e. the pointer pins the object until it stops referencing it. To convert an object pointer to the PtrToObject, use the following code:

    Pointer< Object > object = ...;
    const PtrToObject ptr_to_object( object.dereference() );

  • VariantPointer is similar to simple void *. If debugging is enabled, it remembers the type of the pointer it has been initialized with, and allows you to cast it only to the pointer of the exactly same type. The following example constructs a VariantPointer and then casts it back to the correct type:

    Foo * const native_foo = ...;
    
    VariantPointer variant_foo( native_foo );
    /* variant_foo now knows it points to Foo *, regardless */
    /* of the most derived class type of *native_foo. */
    
    /* This is the only correct variant_cast that may be performed on variant_foo. */
    Foo * const back_to_foo = variant_cast< Foo * >( variant_foo );
    
    /* This fails in debug builds, unless Bar is typedef to Foo. */
    Bar * const error = variant_cast< Bar * >( variant_foo );

    The variant pointers are used to pass pointers to object components (see Section 12.2.3.3, “Class Inheritance”). Most methods of metaobject for class Foo require that all variant pointers passed to them are of type Foo *. You can use methods downcast() and upcast() of the MetaObject cast pointers (and create corresponding variant pointers) at run-time, without any compile-time class type knowledge. The easiest way to get a variant pointer to the most derived class of given object, use the following code:

    Pointer< Object > object = ...;
    
    VariantPointer complete_object( object.get_complete_object() );
    /* If the most derived class of the object is Foo, complete_object is */
    /* now of type Foo *, even if the object pointer previously */
    /* refered to some of its ascendants. This is the variant pointer most */
    /* methods of the metaobjects expect. */

12.2.3. Class Information

This section describes how to obtain information about a class, such as class name, class type info, class attributes and inheritance hierarchy.

[Note]Note

From now on any method names refer to methods of the MetaObject class, unless specified otherwise. “The class” refers to the class described by the metaobject we're talking about.

12.2.3.1. Class Name and Type Info

To get fully-qualified name of a class, use the get_class_name() method. The methods get_class_type_info(), get_class_pointer_type_info() and get_class_type_id() return information about the class type.

12.2.3.2. Class Attributes

The get_attributes() method returns a reference to a structure containing values of all class attributes. You can also query value of an attribute foo using a get_attribute_foo() method. For complete list of class attributes defined in object.idl, refer to Section 10.9.3, “Class Attributes”.

12.2.3.3. Class Inheritance

In the metaobject terminology, a component refers to any class inherited, directly or indirectly, by a given class, including the class itself. For example, if Foo inherits classes Bar and Baz, Bar inherits Xyzzy and Fyzzy and Baz inherits Xyzzy and Buzzy, these are all components of Foo:

  • Foo itself.

  • Bar, inherited directly.

  • Baz, inherited directly.

  • Xyzzy, inherited via Bar.

  • Fyzzy, inherited via Bar.

  • Xyzzy, inherited via Baz.

  • Bazzy, inherited via Baz.

The method get_component_infos() returns a reference to a constant vector (in the STL sense) of ComponentInfo structures, describing all components of the class. For each component, information about its inheritance and pointer to the relevant metaobject describing the component is provided.

Several variants of the upcast() method are offered, each of them converts a variant pointer to refer to a specified component. The method inherits() checks if a class inherits a class specified by given C++ type info.

12.2.3.4. Example

The following example prints class-related information about a given object.

Example 12.2. Class introspection
using Massiv::Core;
Pointer< Object > object = ...;
const MetaObject * const metaobject = object->get_metaobject();
hope( metaobject );

/* Get fully-qualified name of the most derived class of the object. */
std::cout << "class of the object is "
          << metaobject->get_class_name()
          << std::endl;

/* Get value of a class attribute. */
std::cout << "the class is "
          << metaobject->get_attribute_abstract() ? "" : "not"
          << " abstract"
          << std::endl;

/* Print information about direct and virtual base classes. */
std::cout << "inherits: " << std::endl;
const MetaObject::ComponentInfos & components( metaobject->get_component_infos() );
MetaObject::ComponentInfoIterator it;
for( it = components.begin(); it != components.end(); ++it )
    {
    const MetaObject::ComponentInfo & component = *it;
    if( component.is_parent() )
        {
        /* The class directly inherits the component. */
        std::cout << "    " << component.name << std::endl;
        }
    else if( component.virtual_component )
        {
        /* The class inherits the component virtually. */
        std::cout << "    virtual " << component.name << std::endl;
        }
    /* Other components are ignored here, they are inherited indirectly */
    /* via other components this code prints out. Their component.name */
    /* is not a simple class name, it contains complete "path" */
    /* to the component from the most derived class. */
    }

12.2.4. Properties

Each MetaObject keeps information about all properties of the class it describes, both defined in the class and inherited from base classes.

12.2.4.1. Property Information

The method get_property_infos() returns a reference to a constant vector of PropertyInfo structures. Each structure describes a single property - its name, type (as a string in the IDL syntax), reference to a component the property is defined in and a structure with values of all property attributes.

The method find_property_infos() can be used to retrieve information about properties matching given wildcard pattern. To obtain information about a property of an object when given a pointer to the property (see below) and the object, use one of the find_property_info() method variants.

12.2.4.2. Getting Property Pointers

Given a pointer to a local object and an identification of a property of the object, the pointer to the property can be obtained using the get_property() method. To retrieve pointers to multiple properties of a given object matching a given wildcard pattern, use the find_properties() method.

12.2.4.3. Performing an Operation on a Set of Properties

To perform an operation specified by the Property::PropertyOperation functor on all properties of a given object, call the for_each_property_do() method.

12.2.4.4. Example

The following example prints types, names and values of all properties of a given object.

Example 12.3. Property introspection
using Massiv::Core;
Pointer< Object > object = ...;
const MetaObject * const metaobject = object->get_metaobject();
hope( metaobject );

/* Cast to pointer referencing the most derived class of object. */
/* Many MetaObject methods that access the object require this. */
/* This is a shortcut for metaobject->downcast(); */
VariantPointer complete_object( object->get_complete_object() );

/* Print info about all object's properties. */
const MetaObject::PropertyInfos & properties( metaobject->get_property_infos() );
MetaObject::PropertyInfoIterator it;
for( it = properties.begin(); it != properties.end(); ++it )
    {
    const MetaObject::PropertyInfo & info = *it;
    /* We could simply print complete description of the property in */
    /* the IDL syntax using the operator<< of the info object here. */

    /* Print property type in IDL syntax and property name. */
    std::cout << info.type << " " << info.name << " = ";

    /* Get pointer to the property. */
    /* Because complete_object points to the most derived class of */
    /* the object, the get_property_from_complete_object() method can be used. */
    /* Otherwise a bit slower but more generic get_property() would */
    /* have to be used. */
    const Property * const property = metaobject->get_property_from_complete_object
        ( complete_object, it );
    hope( property );

    /* Print value of the property. */
    /* Each property defines operator<< which prints its value */
    /* in a format easy-to-read for a human being. */
    /* If we wanted to print the value in textual serialization format, */
    /* we would have to call property's get() method or use a TextWriter */
    /* object bound to std::cout and call the text_write() serialization */
    /* method of the property. */
    std::cout << *property << std::endl;
    }

12.2.5. Methods

The MetaObject can be also used to obtain information about methods defined in the class. Note that no information about inherited methods (methods defined in base classes) is kept in the MetaObject. However, several methods automatically search metaobjects of base classes to provide information about all class methods (including the inherited ones).

12.2.5.1. Method Information

The method get_method_infos() returns a reference to a constant vector of MethodInfo structures. Each structure describes a single method of the relevant class - its name, description of arguments and a return type, and a structure with values of all method attributes. For each method argument, information about its name, type (as a string in the IDL syntax) and the calling semantics (called argument kind in the structure) is provided.

The method find_method_infos() can be used to retrieve information about methods matching a given wildcard pattern, including methods defined in ascendant classes. Given a wildcard pattern and a pointer to a local object, find_methods() finds all methods matching the pattern (including inherited ones), and returns information about the methods, including pointers to metaobjects of components the methods are defined in and correctly casted variant pointers to the components.

12.2.5.2. Dynamic Local Calls

The MetaObject allows you to call any IDL-described method of a local object dynamically (i.e. construct the call at run-time). Use one of the overloaded call_method() methods to perform such a call. The method to be called can be identified in many ways, arguments are specified in their textual serialization form, separated by whitespace. There is no way to obtain results of a dynamic local call.

The MetaObject of the class the method is defined in must be used when calling call_method(). MetaObject describing a derived class can't be used to perform the call. The call expects a variant pointer referencing the component of the type the metaobject describes. The find_methods() may provide useful to obtain pointer to the correct metaobject and correctly casted variant pointer. To simplify things a bit, several call_methods() methods are provided. These methods allow you to easily call all methods matching a given wildcard pattern on a local object, including methods defined in base classes.

[Note]Note

The dynamic local call API is not very nice and it does not allow you to obtain call results. You may want to use dynamic RPC even if the callee object is local.

12.2.5.3. Dynamic RPC

Probably the most useful part of the method-related API of the MetaObject class is the dynamic RPC. It allows you to construct and perform a remote call at run-time. In many ways it is similar to the dynamic local call API described above, but it's easier to use and allows you to examine call results.

Use one of the overloaded remote_call_method() methods to call a method of an object (remote as well as local) dynamically. Check Section 8.5.5, “Dynamic RPC” for detailed description of the dynamic RPC.

12.2.5.4. Example

The following example prints types, names and values of all properties of a given object.

Example 12.4. Method introspection
using Massiv::Core;
Pointer< Object > object = ...;
const MetaObject * const metaobject = object->get_metaobject();
hope( metaobject );
VariantPointer complete_object( object->get_complete_object() );

/* Unlike property infos, method infos do not contain descriptions */
/* of methods defined in base classes. We must examine all components */
/* manually. Because components contain information about the most */
/* derived class too, we don't have to treat it as a special case. */
const MetaObject::ComponentInfos & components( metaobject->get_component_infos() );
MetaObject::ComponentInfoIterator c_it;
for( c_it = components.begin(); c_it != components.end(); ++c_it )
    {
    /* Iterate over all methods of the component. */
    const MetaObject * const component_metaobject = c_it->metaobject;
    hope( component_metaobject );
    const MetaObject::MethodInfos & methods
        ( component_metaobject->get_method_infos() );
    MetaObject::MethodInfoIterator m_it;
    for( m_it = methods.begin(); m_it != methods.end(); ++m_it )
        {
        /* Print complete description of the method in IDL syntax. */
        /* We could also reimplement this manually by printing all */
        /* attribute values, argument declarations and the return type. */
        const MetaObject::MethodInfo & info = *m_it;
        std::cout << "method "
                  << info.attributes
                  << " "
                  << info
                  << ";"
                  << std::endl;
        }
    }

12.2.6. Massiv Core and Demo Examples

Power of the object introspection is demonstrated in the implementation of the console. The console allows you to read and write to all properties of local and remote objects, even to properties of member objects, elements of arrays and dictionaries, and properties of objects pointed to by migration-group pointers (i.e. objects owned by the same node as the object owning the pointer). You can also call methods of both local and remote objects, create and destroy objects, force migration of objects, etc. The console provides the bash-like command completion in almost every context regarding an object local to the node the console is connected to. The implementation of the command parsing and the completion is in the src/demo/lib/shared/console.cpp file, the commands themselves are implemented in many files, probably the most interesting is src/demo/lib/shared/node_console_commands.cpp.

Another nice example of the introspecition usage is the implementation of the operator<< of the MetaObject, which generates description of the class in IDL syntax. It is more simple and easier to understand than the console implementation. You can check it out in src/core/object/metaobject.cpp.