This tutorial is for ARCS1. A knowledge of Qt development is needed in order to follow this tutorial.
In order to make a component library, you will have to follow a few steps :
ARCS1 components are nothing less than objects instantiated from classes inheriting from QObject
of the Qt API. Therefore, their declaration is similar to classes inheriting from QObject
that would implement signals and slots. Those last ones would form the public interface of the component.
Here is how a component's header file should look like :
#include <qobject.h> class MyComponent : public QObject { Q_OBJECT public : MyComponent(QObject* parent=0, const char* name=0) : QObject(parent, name) { ... } public slots : void gogogo(); signals : void gone(); }
Here the QOBJECT
macro is necessary in order to enable signal and slot precompiling.
Slots (component inputs) are declared using the keywords public slots
. Signals (component outputs) are declared in a section called signals
.
Please also notice that the constructor should conform to the shown signature.
Once components have been developed, they must be stored in a shared library in order to instantiate them at runtime. The body of this library can be put in a separate C++ source file.
Its structure would be :
#include <metalibrary/metalibrarytoolkit.h> // .... other headers METALIB_BEGIN METALIB_OBJECT(ComponentName) // .... other components METALIB_END
The header metalibrary/metalibrarytoolkit.h
declares several required macros that are useful to create the library. Then the files that declares components will be listed.
The next code block is introducing the actual library body. It starts with the macro METALIB_BEGIN
. The next following lines should contain the macro METALIB_OBJECT
followed by the names of the components that would be made available through the library.
This list should end with the METALIB_END
macro signaling the end of the library's body.
When using Qt, a project file is a textual file with the extension .pro
.
Some important lines should be given in order to properly configure the project.
unix: TEMPLATE = lib win32: TEMPLATE = vclib TARGET = library_name CONFIG = qt thread debug win32: CONFIG+= dll extensions INCLUDEPATH += path_to_metalibrarytoolkit_header
The first two lines will enable the build of a library, with a Makefile
under linux and a project for Visual Studio under Microsoft Windows.
The next line gives the library name.
The next two line are adjusting configuration parameters, including specifics for Windows.
The last line is mandatory in order to compile the project: it helps to find the headers that are necessary to compile the project and make a component library.
When using Linux, you just have to execute the following lines in the library directory:
qmake make
When using Windows, you will have to run qmake in order to generate a Visual Studio project. Then you can open it and run its compilation.
This example is also distributed with the source code of ARCS1. It is located in the sample
directory. It follows the steps described above and is completed by an example of an application that should test and run these components.
We will start by a functional description of the components and then disclose their source code.
It is actually a component wrapping a loop. This component, once initialized with a given number of iterations, will iterate over a loop for the given number of iterations (slot setIterations()
). Each iteration in this loop will :
newIteration()
).
Once the loop has ended, another signal is emitted (signal sendToken()
).
This component contains a slot that takes an integer as an input (slot display()
). This number is then displayed on standard output.
The source code for the two components is found in two different files: one header and one body.
This one will contain the declaration for the two components as follows :
#include <qobject.h> #include <qstring.h> class Boucle: public QObject { Q_OBJECT public: Boucle(QObject* parent = 0, const char* name = 0); public slots: void setIterations(int n); signals: void newIteration(int i); void sendToken(QString s); }; class DisplayInt: public QObject { Q_OBJECT public: DisplayInt(QObject* parent = 0, const char* name = 0); public slots: void display(int i); };
It will contain the actual implementation of components. That is to say, constructors and slots instructions (signals do not have any actual implementation).
#include "boucle.h" #include <iostream> Boucle::Boucle(QObject* obj, const char* name) : QObject(obj, name) { /*Constructeur, ici vide*/ } void Boucle::setIterations(int n) { for (int i = 0; i < n ; i++) { std::cout << "Emitting iteration " << i << std::endl; emit newIteration(i); // Emission d'un signal } emit sendToken("end"); } DisplayInt::DisplayInt(QObject* obj, const char* name) : QObject(obj, name) {} void DisplayInt::display(int i) { std::cout << "Recieved integer " << i << std::endl; }
In order to make the library, we will need another source file to describe the library and a project file.
#include <metalibrary/metalibrarytoolkit.h> #include "boucle.h" METALIB_BEGIN METALIB_OBJECT(Boucle) METALIB_OBJECT(DisplayInt) METALIB_END
This one will be parsed by qmake
in order to generate a Makefile
.
unix: TEMPLATE = lib win32: TEMPLATE = vclib TARGET = boucle HEADERS = boucle.h SOURCES = boucle.cpp SOURCES+= lib.cpp CONFIG = qt thread release win32: CONFIG += dll exceptions
In order to test and run the components, we will have to create an application description file. It will be parsed bu the ARCS1 engine in order to load components from libraries, instantiate and configure them.
Here is the xml description of an application. Two components are declared: B
which is of the Boucle
type and D
which is of the DisplayInt
type.
Then signal newIteration()
from B
is connected to slot display()
of D
.
The whole application is started by setting in postconnection the slot setIterations()
to a given value.
<?xml version="1.0"?> <!DOCTYPE application SYSTEM "../xml/application.dtd"> <application> <libraries> <library name="./boucle"/> </libraries> <objects> <object classname="Boucle" id="B"/> <object classname="DisplayInt" id="D"/> </objects> <sheets> <sheet id="S"> <tokensender object="B"/> <connection> <wire objsource="B" signal="newIteration(int)" objdest="D" slot="display(int)"/> </connection> <postconnection> <init object="B" slot="setIterations(int)" type="int" value="10"/> </postconnection> </sheet> <sheet id="E"/> </sheets> <statemachine terminal="E"> <transition stepA="S" stepB="E" token="end"/> </statemachine> </application>
Once the xml file written, all that is needed is to pass its path as a parameter of the ARCS1 engine which is called metaruntime
.
bash-3.1$ metaruntime boucle.xml [META] document has not the right number of <defines> markup Waiting 1s before launching runtime ... Launching Runtime, go go go ! ========================================================== Emitting iteration 0 Recieved integer 0 Emitting iteration 1 Recieved integer 1 Emitting iteration 2 Recieved integer 2 Emitting iteration 3 Recieved integer 3 Emitting iteration 4 Recieved integer 4 Emitting iteration 5 Recieved integer 5 Emitting iteration 6 Recieved integer 6 Emitting iteration 7 Recieved integer 7 Emitting iteration 8 Recieved integer 8 Emitting iteration 9 Recieved integer 9 Emitting iteration 0 Recieved integer 0
Each line started with Emitting
is written by B
and each line starting with Recieved
is written by D
.