Inspirel banner

Programming Distributed Systems with YAMI4

10.4.2 C++

The C++ language mapping was designed in a way that should not be surprising to users of similar code-generation approaches like CORBA's IDL.

YDL packages are mapped to C++ namespaces and both header and implementation files are generated for each processed package. Package imports are mapped to the #include directive for respective header file and parent-child package relationships are implemented in terms of namespace nesting. Thanks to the fact that C++ namespace have similar name lookup rules as packages in YDL, the name qualification schemes are retained - in other words, those names that were directly visible in the YDL description are also directly visible between namespaces; other names need to be fully qualified.

Types from YDL are mapped to structure types in C++.

Type fields are mapped to member fields.

Field types are mapped to standard C++ types in the following way:

Optional fields are mapped to pairs of member fields, where the normally generated field is accompanied by the bool field named Field_valid that allows to express whether the given field is valid or not.

Each generated structure type has two member functions, write and read, which can be used to automate translation of the given structure type to and from yami::parameters.

For example, the following type description in YDL (taken from the calculator example):

   type Results is
      Sum : Integer;
      Difference : Integer;
      Product : Integer;
      Ratio : optional Integer;
   end Results;

is mapped to the following C++ definition:

struct results
{
    results();

    void write(yami::parameters & params) const;
    void read(const yami::parameters & params);

    int sum;
    int difference;
    int product;
    bool ratio_valid;
    int ratio;
};

and the following code in respective implementation file:

results::results()
{
    ratio_valid = false;
}

void results::write(yami::parameters & params) const
{
    params.set_integer("sum", sum);
    params.set_integer("difference", difference);
    params.set_integer("product", product);
    if (ratio_valid)
    {
        params.set_integer("ratio", ratio);
    }
}

void results::read(const yami::parameters & params)
{
    sum = params.get_integer("sum");
    difference = params.get_integer("difference");
    product = params.get_integer("product");
    yami::parameter_entry e_;
    ratio_valid = params.find("ratio", e_);
    if (ratio_valid)
    {
        ratio = params.get_integer("ratio");
    }
}

As can be seen, optional fields allow to work with parameters entries that might or might not exist at all. Structure types that are generated for user-defined types are used on both client- and server-side.

It should be noted that these functions do not clear the target object before performing the translation - this means that they are working in the incremental way, which can provide potential flexibility.

Interfaces, on the client-side, are mapped to class types with the same name that encapsulate the connection details of the remote object - the agent object in use, the server location, object name and requested operation timeout. The generated class has a constructor that allows easy initializaton and a set of functions, one for each message declared in the interface that encapsulate complete interactions with remote object and take care of parameter translation as well.

For the calculator example, the Operations interface is mapped to the class type:

class operations
{
public:

    operations(yami::agent & client_agent,
        const std::string & server_location, const std::string & object_name,
        int timeout = 0);

    void calculate(const operands & op, results & res);

private:
    // ...
};

As can be seen above, the user-defined types and their respective structure type in generated code are used in the calculate signature in a natural way.

The run-time implementation of this operation involves the following sequence of actions:

This sequence of actions allows to provide the remote procedure call functionality.

As a counterpart of these definitions, the generated code contains also an abstract class with pure virtual functions for use on the server-side. This type is intended to be a base type for user-defined implementation that provide the actual code for all message operations.

The Operations interface is mapped to the following server-side abstract class in C++:

class operations_server
{
public:

    virtual ~operations_server() {}

    virtual void calculate(const operands & op, results & res) = 0;

    void operator()(yami::incoming_message & im_);
};

The specializations of this type can be directly registered as YAMI4 objects thanks to the function call operator that redirects the incoming calls to virtual functions defined for each message in the given interface. In the calculator example the calculate virtual function above is supposed to be overriden by application code, where the actual implementation of this operation should be placed.