Web Analytics Made Easy - Statcounter
Skip to content

Introspection#

SAMSON Extensions expose their functionality to SAMSON and to other SAMSON Extensions thanks to an introspection mechanism. Exposition is achieved thanks to descriptor files that contain the list of exposed classes and functions, and that are compiled along with the SAMSON Extension. One of the major benefits of the introspection mechanism is the ability to share and combine functionality without the need for providing any type of source code or header code. It is thanks to this introspection mechanism that SAMSON Extensions form more than a collection of independent modules.

Exposing the functionality of a SAMSON Extension typically involves two main steps:

  • Writing one descriptor per class that should be exposed
  • Writing one descriptor for the SAMSON Extension

The SAMSON Extension Generator automatically generates descriptor files that expose the default functionality of the classes it generates. These descriptors may be modified as indicated below in order to expose custom functionality added to the generated classes.

Exposing classes#

Registering classes for exposition#

Exposing a class to SAMSON first involves three steps:

  • Adding the SB_CLASS macro to the declaration of the class
  • Registering the class with SAMSON's type system using the SB_REGISTER_TARGET_TYPE or SB_REGISTER_TYPE macros
  • Registering a potential derivation relationship with SAMSON's type system using the SB_DECLARE_BASE_TYPE macro

The following App header code, generated by the SAMSON Extension Generator (in a file called SEElementMyApp.hpp), demonstrates these three steps:

class SEElementMyApp : public SBDApp {

    SB_CLASS

public:

    /// \name Constructors and destructors
    //@{

    SEElementMyApp();                         ///< Constructs an app
    virtual ~SEElementMyApp();                ///< Destructs the app

    //@}

    /// \name GUI
    //@{

    SEElementMyAppGUI* getGUI() const;        ///< Returns a pointer to the app GUI

    //@}

};

SB_REGISTER_TARGET_TYPE(SEElementMyApp, "SEElementMyApp", 
    "D5A1BA95-9CD4-3424-9713-06B979F5C0D9");
SB_DECLARE_BASE_TYPE(SEElementMyApp, SBDApp);

The macros should be added in specific positions:

  • The SB_CLASS macro should be added inside the class declaration.
  • The SB_REGISTER_TARGET_TYPE macro should be added after the class declaration.
  • The SB_DECLARE_BASE_TYPE macro should be added after the class has been registered with SB_REGISTER_TARGET_TYPE.

The SB_REGISTER_TARGET_TYPE macro has the following syntax:

  • The first parameter (without quotes) is the type that should be registered
  • The second parameter (in quotes) is the short name of the class. This should be a single word with a syntax typical of class names. If a short name is not needed (e.g. because the original class name is short enough), then the original class name should be used.
  • The third parameter (in quotes) is the class's unique identifier, which could be generated thanks to the provided UUID Generator.

The SB_DECLARE_BASE_TYPE macro has the following syntax:

  • The first parameter is the derived type.
  • The second parameter is the base type. This class must be exposed as well (this is the case for base classes provided in the SAMSON SDK, such as the SBDApp class in the example above).

Note that, at the moment, SAMSON's introspection mechanism may not expose multiple inheritance relationships: while it is perfectly possible to expose a class that inherits multiple classes, the SB_DECLARE_BASE_TYPE macro may be used to expose only one derivation relationship, with only one of the base classes.

Writing class descriptors#

Once a class is registered and its potential derivation relationship is declared using the macros described in the previous section, a corresponding class descriptor may be written.

Class descriptor structure#

A class descriptor implements a proxy for the class, and contains five main descriptions:

  • A type description
  • A class description
  • A class version number
  • A factory description, i.e. a list of constructors descriptions
  • An interface description, i.e. a list of member functions descriptions

Descriptions are written using macros provided by the SAMSON SDK. In particular, a class descriptor is enclosed between two macros, SB_CLASS_BEGIN and SB_CLASS_END, that take as argument the class name (e.g. SB_CLASS_BEGIN(SEElementMyApp) and SB_CLASS_END(SEElementMyApp) for the example above).

The example below shows a descriptor generated by the SAMSON Extension Generator (in a file called SEElementMyAppDescriptor.hpp) for the App class it has generated:

SB_CLASS_BEGIN(SEElementMyApp);

    SB_CLASS_TYPE(SBCClass::App);
    SB_CLASS_DESCRIPTION("SAMSON Extension generator pro tip: modify me");
    SB_CLASS_VERSION_NUMBER("1.0.0");

    SB_FACTORY_BEGIN;

        SB_CONSTRUCTOR_0(SEElementMyApp);

    SB_FACTORY_END;

    SB_INTERFACE_BEGIN;

    SB_INTERFACE_END;

SB_CLASS_END(SEElementMyApp);

Type description#

The class type is added to the class descriptor thanks to the SB_CLASS_TYPE macro. The argument of the macro should be one of the possible class types.

Class description#

The class description is added to the class descriptor thanks to the SB_CLASS_DESCRIPTION macro. The argument of the macro should be a short string.

Class version number#

The class version number is added to the class descriptor thanks to the SB_CLASS_VERSION_NUMBER macro. The argument of the macro should be a string corresponding to a three-part version number: "major.minor.patch", which should be increased in particular when serialization of the class changes.

Factory description#

The class factory, i.e. the collection of class constructors, is added to the class descriptor between the SB_FACTORY_BEGIN and SB_FACTORY_END macros:

SB_FACTORY_BEGIN;

    SB_CONSTRUCTOR_0(SEElementMyApp);

SB_FACTORY_END;

Each constructor should be added using one of the seventeen provided macros SB_CONSTRUCTOR_0, SB_CONSTRUCTOR_1, ... SB_CONSTRUCTOR_16, where the integer indicates the number of arguments taken by the constructor.

For example, the class constructor

SEElementMyApp(int i, double d, const SBAtom* atom);

may be exposed with the following macro:

SB_CONSTRUCTOR_3(SEElementMyApp, int, double, const SBAtom*);

If multiple constructors are exposed, they are all described in a unique pair of enclosing macros SB_FACTORY_BEGIN and SB_FACTORY_END, e.g.:

SB_FACTORY_BEGIN;

    SB_CONSTRUCTOR_0(SEElementMyApp);
    SB_CONSTRUCTOR_1(SEElementMyApp, const std::string&);
    SB_CONSTRUCTOR_1(SEElementMyApp, int);

SB_FACTORY_END;

Note that one constructor that takes no argument should always exist and be exposed.

Interface description#

The class interface, i.e. the collection of class functions, is added to the class descriptor between the SB_INTERFACE_BEGIN and SB_INTERFACE_END macros.

For example, the member function

void setName(const std::string&);

may be exposed with the following macro inserted between the SB_INTERFACE_BEGIN and SB_INTERFACE_END macros:

SB_FUNCTION_1(void, SEElementMyApp, setName, const std::string&);

If a function is const:

std::string getName() const;

then it may be exposed with a macro for const functions:

SB_CONST_FUNCTION_0(std::string, SEElementMyApp, getName);

Finally, if a function is static:

static unsigned int getNumberOfInstances();

then it may be exposed with a macro for static functions:

SB_STATIC_FUNCTION_0(unsigned int, SEElementMyApp, getNumberOfInstances);

In general, each function should be added using one of the fifty-one provided macros:

  • SB_FUNCTION_0, SB_FUNCTION_1, ... SB_FUNCTION_16 for regular (i.e. non-const, non-static) functions, where the integer indicates the number of arguments taken by the function.
  • SB_CONST_FUNCTION_0, SB_CONST_FUNCTION_1, ... SB_CONST_FUNCTION_16 for const functions, where the integer indicates the number of arguments taken by the const function.
  • SB_STATIC_FUNCTION_0, SB_STATIC_FUNCTION_1, ... SB_STATIC_FUNCTION_16 for static functions, where the integer indicates the number of arguments taken by the static function.

If multiple functions are exposed, they are all described in a unique pair of enclosing macros SB_INTERFACE_BEGIN and SB_INTERFACE_END, e.g.:

SB_INTERFACE_BEGIN;

    SB_CONST_FUNCTION_0(std::string, SEElementMyApp, getName);
    SB_FUNCTION_1(void, SEElementMyApp, setName, const std::string&);

    SB_CONST_FUNCTION_2(SBQuantity::length, SEElementMyApp, getDistance, 
        const SBAtom*, const SBAtom*);

    SB_STATIC_FUNCTION_0(int, SEElementMyApp, getNumberOfInstances);

SB_INTERFACE_END;

Note that even when no functions are exposed, the class descriptor should always contain a class interface:

SB_INTERFACE_BEGIN;

SB_INTERFACE_END;

Finally, note that virtual functions that are part of the API of SAMSON (i.e. are member functions of classes defined in the SAMSON SDK), and that are reimplemented in derived classes in SAMSON Extensions, do not have to be exposed: since the function is defined in the SAMSON SDK, developers may write code that calls the function without relying on the introspection mechanism, and the appropriate implementation will be called at runtime.

Attributes description for the Inspector#

If your class can be accessed through the Inspector, you can expose the class functionality using the following macroses from the class interface:

  • SB_ATTRIBUTE_READ_ONLY - expose a read-only attribute.
  • SB_ATTRIBUTE_READ_WRITE - expose a changeable attribute.
  • SB_ATTRIBUTE_READ_WRITE_CLEAR - expose a changeable attribute that can be cleared.
  • SB_ATTRIBUTE_READ_WRITE_CLEAR_ARRAY - expose a changeable array that can be cleared.
  • SB_ATTRIBUTE_READ_WRITE_RANGE - expose a changeable attribute that has minimum and maximum values defined.
  • SB_ATTRIBUTE_READ_WRITE_RANGE_SLIDER - expose a changeable attribute that has minimum and maximum values defined.
  • SB_ATTRIBUTE_READ_WRITE_LIST - expose a changeable attribute that can be selected from a list.

These macroses will also expose the associated to an attribute getter, setter, and other functions depending on a macros.

Exposing a SAMSON Extension#

Once descriptors have been written for all classes that should be exposed, a descriptor may be written for the SAMSON Extension itself.

Extension descriptor structure#

A SAMSON Extension descriptor contains five parts:

  • A description
  • A documentation address
  • A version number
  • A list of exposed classes
  • A list of categories

Again, descriptions are written using macros provided by the SAMSON SDK. Typically, this descriptor is generator by the SAMSON Extension Generator, and needs to be completed when more classes are added to the SAMSON Extension:

SB_ELEMENT_DESCRIPTION("This SAMSON Extension contains an App that shakes atoms.");
SB_ELEMENT_DOCUMENTATION("Resource/Documentation/doc.html");
SB_ELEMENT_VERSION_NUMBER("1.0.0");

SB_ELEMENT_CLASSES_BEGIN;

    SB_ELEMENT_CLASS(SEElementMyApp);

SB_ELEMENT_CLASSES_END;

SB_ELEMENT_CATEGORIES_BEGIN;

    SB_ELEMENT_CATEGORY(SBClass::Category::General);

SB_ELEMENT_CATEGORIES_END;

Description#

The description is added to the SAMSON Extension descriptor thanks to the SB_ELEMENT_DESCRIPTION macro. The argument of the macro should be a short string.

Documentation address#

The address of the SAMSON Extension documentation is added to the SAMSON Extension descriptor thanks to the SB_ELEMENT_DOCUMENTATION macro. The argument of the macro should be a short string containing the address relative to the installation folder of the SAMSON Extension (typically in the generated Resource/Documentation/ folder).

Version number#

The version number of the SAMSON Extension is added to the SAMSON Extension descriptor thanks to the SB_ELEMENT_VERSION_NUMBER macro. The argument of the macro should be a short string containing a well-formated version number. Note that the version number of the SAMSON Extension is completely independent of the version number of the SAMSON SDK used to build the SAMSON Extension. Please refer to the chapter about versioning for more information.

List of exposed classes#

The list of exposed classes is added to the SAMSON Extension descriptor between the SB_ELEMENT_CLASSES_BEGIN and SB_ELEMENT_CLASSES_END macros:

SB_ELEMENT_CLASSES_BEGIN;

    SB_ELEMENT_CLASS(SEElementMyApp);

SB_ELEMENT_CLASSES_END;

Each exposed class (i.e. each class which has a descriptor) should be added using the SB_ELEMENT_CLASS macro.

If multiple classes are exposed, they are all described in a unique pair of enclosing macros SB_ELEMENT_CLASSES_BEGIN and SB_ELEMENT_CLASSES_END, e.g.:

SB_ELEMENT_CLASSES_BEGIN;

    SB_ELEMENT_CLASS(SEElementMyApp);
    SB_ELEMENT_CLASS(SEElementMyVisualModel);
    SB_ELEMENT_CLASS(SEElementMyFirstInteractionModel);
    SB_ELEMENT_CLASS(SEElementMySecondInteractionModel);

SB_ELEMENT_CLASSES_END;

List of categories#

The list of SAMSON Extension categories is added to the SAMSON Extension descriptor between the SB_ELEMENT_CATEGORIES_BEGIN and SB_ELEMENT_CATEGORIES_END macros:

SB_ELEMENT_CATEGORIES_BEGIN;

    SB_ELEMENT_CATEGORY(SBClass::Category::General);

SB_ELEMENT_CATEGORIES_END;

Each category should be added using the SB_ELEMENT_CATEGORY macro, which takes as argument a category.

If the SAMSON Extension should belong to multiple categories, they are all listed in a unique pair of enclosing macros SB_ELEMENT_CATEGORIES_BEGIN and SB_ELEMENT_CATEGORIES_END, e.g.:

SB_ELEMENT_CATEGORIES_BEGIN;

    SB_ELEMENT_CATEGORY(SBClass::Category::General);
    SB_ELEMENT_CATEGORY(SBClass::Category::Medicine);
    SB_ELEMENT_CATEGORY(SBClass::Category::Biology);

SB_ELEMENT_CATEGORIES_END;

Using exposed classes#

Once a class is exposed, SAMSON and other SAMSON Extensions may create instances of it and call its member functions. This section explains how.

Class proxies#

The functionality of an exposed class is accessible through its class proxy, which gathers the class factory (its collection of constructors) and the class interface (its collection of member functions).

The class proxy of an exposed class is retrieved using SAMSON:

SBProxy* classProxy = SAMSON::getProxy("SEElementMyApp");

In case there is a risk that several SAMSON Extensions might provide a class called SEElementMyApp, it is possible to specify the name and the UUID of the SAMSON Extension:

SBProxy* classProxy = SAMSON::getProxy("SEElementMyApp",
    SBUUID("C8EC88EA-38CE-70F4-2A74-C71F2C86A692"));

The SAMSON Extension UUID is guaranteed to be unique if the SAMSON Extension has been installed from SAMSON Connect.

Constructing class instances#

The creation of a class instance depends on the type of the exposed class.

Special classes#

For exposed classes that derive from some classes from the SDK, SAMSON provides convenience factory functions to create instances.

Assume for example a SAMSON Extension provides a particle interaction model:

class InteractionModel : public class SBMInteractionModelParticleSystem {

    InteractionModel(SBMDyamicalModelParticleSystem* dynamicalModel);    
    ~InteractionModel();

    ...

    virtual void updateInteractions();

    ...

};

Then, an instance of this class can be created as follows:

SBMInteractionModelParticleSystem* interactionModel = 
    SAMSON::makeInteractionModel(dynamicalModel, "InteractionModel");

where dynamicalModel is the argument that would be passed to the constructor of the interaction model.

If only the reimplemented virtual functions of the interaction model should be used (e.g. updateInteractions), then the interactionModel pointer is sufficient, even though interactionModel is a pointer to a SBMInteractionModelParticleSystem and not a pointer to an InteractionModel.

General case#

In case the exposed class does not derive from a type for which SAMSON offers convenience functions, such as the makeInteractionModel function mentioned in the previous section, a class instance may be directly created from a class proxy.

Assume for example a SAMSON Extension contains the following class:

class A {

SB_CLASS

public:

    A() {}
    virtual ~A() {}

};

SB_REGISTER_TYPE(A, "A", "BF99103E-06FE-C4C1-D929-4C6E833B101C");

as well as the following class descriptor:

SB_CLASS_BEGIN(A);

       SB_CLASS_TYPE(SBCClass::App);
       SB_CLASS_DESCRIPTION("Empty class");

       SB_FACTORY_BEGIN;

           SB_CONSTRUCTOR_0(A);

       SB_FACTORY_END;

       SB_INTERFACE_BEGIN;
       SB_INTERFACE_END;

SB_CLASS_END(A);

Then SAMSON may be used to retrieve a proxy to the class A:

SBProxy* classProxy = SAMSON::getProxy("A");

which may then be used to create an instance of class A:

SBValue* objectHolder = classProxy->createInstance();

It must be noted that, in this case, the pointer to the object of class A return by the proxy is held in a value holder.

Assume a SAMSON Extension called SEElement defines a class A, and another SAMSON Extension called SEOtherElement wants to use it. If the SAMSON Extension called SEOtherElement does not have access to the declaration of class A (typically provided in a header file included in SEElement), it cannot use the symbol A in its source code. For example, if the source code of SEOtherElement contained the line:

A* a = new A();

the compiler would complain that the symbol A is not defined.

In order to provide access to exposed classes, SAMSON uses a mechanism to hold values (for example, pointers to objects) in a uniform way. In the example above, the constructor exposed in the SAMSON Extension SEElement returns a value holder that stores a pointer to the new instance of class A:

return new SBValueHolder<A*>(new A());

and the SAMSON Extension SEOtherElement sees this value holder as a pointer to a SBValue (the base class of SBValueHolder<A*>):

SBValue* objectHolder = classProxy->createInstance();

Calling functions#

Assume a SAMSON Extension contains the following class:

class A {

SB_CLASS

public:

    A() {}
    virtual ~A() {}

    int multiplyByTwo(int i) {

        return 2 * i;

    }

};

SB_REGISTER_TYPE(A, "A", "BF99103E-06FE-C4C1-D929-4C6E833B101C");

as well as the following class descriptor:

SB_CLASS_BEGIN(A);

       SB_CLASS_TYPE(SBCClass::App);
       SB_CLASS_DESCRIPTION("Integer multiplier");

       SB_FACTORY_BEGIN;

           SB_CONSTRUCTOR_0(A);

       SB_FACTORY_END;

       SB_INTERFACE_BEGIN;

           SB_FUNCTION_1(int, A, multiplyByTwo, int);

       SB_INTERFACE_END;

SB_CLASS_END(A);

In this example, the function multiplyByTwo may be called by other SAMSON Extensions. Since the function is not static, we must first construct an instance of class A:

SBProxy* classProxy = SAMSON::getProxy("A");
SBValue* objectHolder = classProxy->createInstance();

We may then call the function:

SBValue* argumentHolder = new SBValueHolder<int>(17);
SBValue* resultHolder = classProxy->call(objectHolder, "multiplyByTwo", argument);

int result = static_cast<SBValueHolder<int>*>(resultHolder)->getValue();

delete argumentHolder;
delete resultHolder;

Here, also, we use value holders in order to wrap arguments provided to the called function. In this example, we are able to unwrap the int held in the value holder with:

int result = static_cast<SBValueHolder<int>*>(resultHolder)->getValue();

because int is a standard C++ type that all SAMSON Extensions may use. In case the return value of the exposed function was not of a type known by the SAMSON Extension that calls the function, unwrapping would not be possible, but the functionality of the return type may still be accessible through its proxy, and the returned value holder may still be passed as argument to other exposed functions.

Remark: if an exposed function is static, no class instance is required, and the pointer passed to the call function can be set to 0.

Optimizations#

When multiple instances of the same class are created, it is more efficient to store a pointer to a class constructor:

SBProxy* classProxy = SAMSON::getProxy("A");
SBFactory* classFactory = classProxy->getFactory();
SBConstructor* classConstructor = classFactory->getConstructor();

which we may then use to construct an instance of class A:

SBValue* objectHolder = classConstructor->createInstance();

Similarly, when multiple calls to the same function are performed, it is more efficient to store a pointer to a class function and reuse it:

SBProxy* classProxy = SAMSON::getProxy("A");
SBInterface* classInterface = classProxy->getInterface();
SBFunction* classFunction = classInterface->getFunction("multiplyByTwo", "int");

We may then use the exposed function by wrapping arguments and unwrapping results:

SBValue* objectHolder = classProxy->createInstance();
SBValue* argumentHolder = new SBValueHolder<int>(17);
SBValue* resultHolder = classFunction->call(objectHolder, argumentHolder);

int result = static_cast<SBValueHolder<int>*>(resultHolder)->getValue();

delete argumentHolder;
delete resultHolder;