How to start using Wallaroo in your projects.
The easiest way to get a copy of Wallaroo is to download an archive from the page Downloads of the sourceforge page. Wallaroo is delivered in a ZIP file for Windows users and in a compressed TAR file (.tgz) for Unix/Linux users. Both archives contain the same files, the only difference is that all text files in the ZIP files have line endings suitable for Windows (CR-LF), while the text files in the TAR file have line endings suitable for Unix/Linux (LF only).
Otherways, you can clone the git repository from the page Source.
You can also browse the source tree online.
Wallaroo library is header-only: it consists entirely of header files containing templates and inline functions, and require no separately-compiled library binaries or special treatment when linking.
Wallaroo relies on some of the famous boost libraries. You can donwload the latest version from here, extract it wherever you want in your hard disk and include its path when compiling your application that use Wallaroo.
Since Wallaroo uses header only boost libraries, you don't need to compile boost.
Starting from v. 0.2, if your compiler supports C++0x features, you can get rid of boost libraries and relies only on the new C++ standard features.
You can find some examples in the directory samples. Each example comes with:
All methods require you to specify the boost library path. You can do it in the following ways.
make UCFLAGS=-I<boost_path>
example:
make UCFLAGS=-I/opt/boost_1_50_0/
You must set the environment variable BOOST
.
To build your own project, the compiler needs to know the include path of the wallaroo libraries.
If your compiler is not C++0x compliant (or if you plan to use XmlConfiguration or JsonConfiguration classes), you should also provide the boost path.
Fore example, if you use gcc, you can use the following syntax (with boost):
g++ -Wall -I<boost_path> -I<wallaroo_path> *.cpp -o <app_exe>
or (without boost):
g++ -Wall -std=c++0x -I<wallaroo_path> *.cpp -o <app_exe>
Using Wallaroo you will be able to separate the setup phase (object building and wiring) from the logic of your classes. In addition, you will be able to change the class of your objects without rebuilding anything: in this way you can change the behaviour of your application, or perform mock testing using the same executable.
With Wallaroo you will be able to focus on the logic of your classes, without worrying about the wiring phase.
This mechanism is called Dependency Injection. Someone calls it Inversion of Control, but actually that has a broader meaning (see here).
The syntax of Wallaroo is based on the catalog metaphor. The objects you build and link together are "parts", that use "collaborators". You can add a dependency from a part to another by linking a collaborator of the first to the second.
To be instantiable by wallaroo, a class must derive from wallaroo::Part
. So, to make a class Car
instantiable by wallaroo, you must declare it in an header file in this way:
#include "wallaroo/registered.h" using namespace wallaroo; class Car : public Part { public: Car( const string& color ); // other methods declaration here ... };
and you must define it in the implementation file in this way:
WALLAROO_REGISTER( Car, string ) Car::Car( const string& color ) { ... } // other methods definition here ...
The parameter string
you can see inside the clause WALLAROO_REGISTER
is the parameter type of the Car
constructor. Wallaroo can manage constructors having up to 2 parameters (see wallaroo::Attribute for a way to overcome this limit).
The previous declaration and definition, allow you to create instances of Car
in this way:
#include "wallaroo/catalog.h" using namespace wallaroo; ... Catalog catalog; // this is the container of your objects // populate the catalog with some objects: catalog.Create( "ferrari_f430", "Car", string( "red" ) ); catalog.Create( "maserati_granturismo", "Car", string( "black" ) ); ...
The methods used have the following signature:
template< class P1 , class P2 > void Catalog::Create( const std::string &id, const std::string &className, const P1 &p1, const P2 &p2 ) template< class P > void Catalog::Create( const std::string &id, const std::string &className, const P &p ) void Catalog::Create( const std::string &id, const std::string &className )
The first parameter is the name of the instance you're going to create, the second is the name of its class and the (optional) others parameters are the parameters you want to pass to the constructor of the object.
After filling the catalog, you can get a pointer to an object in this way:
shared_ptr< Car > maserati = catalog[ "maserati_granturismo" ];
As you can see, the catalog returns a shared pointer.
So far so good. Now, to do useful work, your objects need to talk to each other. For example, class Car
may need to invoke Engine
methods.
With this code you say that class Car
has a "pointer" to reach an object of class Engine
:
class Car : public Part { ... private: Collaborator< Engine > engine; ... };
Please note that by deriving Car
from Part
you make it possible associate a specific instance of class Engine
to the member engine
at run-time.
In the Car
constructor, you must specify that Car
has an engine
Collaborator
named mainEngine
:
Car::Car( const std::string& color ) : engine( "mainEngine", RegistrationToken() ) { ... }
Inside the Car
implementation you can use it as a standard pointer:
void Car::Start() { engine -> SwitchOn(); }
Just remember that Collaborator< Engine >
is really a weak_ptr
: if in the meantime someone deleted the pointed object, when you try to use it you will get a Wallaroo::DeletedPartError
exception.
Please note that you can have a collaborator container in your classes, by using the syntax:
Collaborator< Book, collection > library;
by default, in this case wallaroo will use std::vector. However, you can specify another std container in this way:
Collaborator< Book, collection, std::list > library;
You can also provide constraints about a collaborator, i.e. you can specify if a collaborator is optional, mandatory (default) or its range (for a collection collaborator):
class Car : public Part { ... private: Collaborator< Engine > engine; // one instance of Engine Collaborator< AirConditioning, optional > airConditioning; // zero or one instance of airConditioning Collaborator< Airbag, collection > airbags; // an indeterminate number of airbags (std::vector by default) Collaborator< Speaker, collection, std::list > speakers; // an indeterminate number of speaker in a std::list Collaborator< Seat, bounded_collection< 2, 6 > > seats; // from 2 to 6 instances of seats ... };
So far so good. But how can you link engine
to a previously created object? With this code:
use( catalog[ "f136e" ] ).as( "mainEngine" ).of( catalog[ "ferrari_f430" ] ); // this means ferrari_f430.engine=f136e
In this way you assigned the object f136e
to the role engine
of the object ferrari_f430
. This wiring operation can be done in the main, where you set up all your application, or wherever you like in your code, providing you have access to catalog
.
If your application uses a unique catalog, wallaroo provides the following syntax to avoid you some typing:
Catalog myCatalog; ... wallaroo_within( myCatalog ) { use( "f136e" ).as( "engine" ).of( "ferrari_f430" ); use( "m139p" ).as( "engine" ).of( "maserati_granturismo" ); }
this is equivalent to:
Catalog myCatalog; ... use( myCatalog[ "f136e" ] ).as( "engine" ).of( myCatalog[ "ferrari_f430" ] ); use( myCatalog[ "m139p" ] ).as( "engine" ).of( myCatalog[ "maserati_granturismo" ] );
the latter code is useful when your objects are stored in multiple catalogs.
After performing the wiring, you can check the constraints by using Catalog::IsWiringOk()
(that returns a bool) or Catalog::CheckWiring()
(that throws an exception).
By using strings, both creation and wiring information can be loaded from a configuration file or external DB.
You can build your own mechanism to load a spec from a file and fill a wallaroo::Catalog
with the objects built and configured or you can use the wallaroo::XmlConfiguration
or wallaroo::JsonConfiguration
classes, that let you fill a catalog loading respectively an xml or json file (the syntax is explained here).
This is the content of the xml file:
<wallaroo> <parts> <part> <name>ferrari_f430</name> <class>Car</class> <parameter1> <type>string</type> <value>red</value> </parameter1> </part> <part> <name>maserati_granturismo</name> <class>Car</class> <parameter1> <type>string</type> <value>black</value> </parameter1> </part> <part> <name>m139p</name> <class>Engine</class> </part> <part> <name>f136e</name> <class>Engine</class> </part> </parts> <wiring> <wire> <source>ferrari_f430</source> <dest>f136e</dest> <collaborator>mainEngine</collaborator> </wire> <wire> <source>maserati_granturismo</source> <dest>m139p</dest> <collaborator>mainEngine</collaborator> </wire> </wiring> </wallaroo>
This is the content of the json file:
{ "wallaroo": { "parts": [ { "name": "ferrari_f430", "class": "Car", "parameter1": { "type": "string", "value": "red" } }, { "name": "maserati_granturismo", "class": "Car", "parameter1": { "type": "string", "value": "black" } }, { "name": "m139p", "class": "Engine" }, { "name": "f136e", "class": "Engine" } ], "wiring": [ { "source": "ferrari_f430", "dest": "f136e", "collaborator": "mainEngine" }, { "source": "maserati_granturismo", "dest": "m139p", "collaborator": "mainEngine" } ] } }
So, instead of populating manually the catalog, you can do it by loading a xml file:
Catalog catalog; XmlConfiguration file( "wiring.xml" ); file.Fill( catalog );
or, a json file:
So, instead of populating manually the catalog, you can do it by loading a xml file:
Catalog catalog; JsonConfiguration file( "wiring.json" ); file.Fill( catalog );
For shake of extendibility you can decide to put your class definitions in shared libraries to get simple plugin mechanism.
The code of your class will remain the same, but you must compile it in a shared library (.dll on windows or .so on unix-like os). You can put declaration and definition in a implementation file:
#include "wallaroo/dynamic_lib.h" // once per library #include "wallaroo/dyn_registered.h" // once per translation unit using namespace wallaroo; class Car : public Part { public: Car(); // other methods declaration here ... }; WALLAROO_DYNLIB_REGISTER( Car ) Car::Car() { ... } // other methods definition here ...
Please note that in this case you use WALLAROO_DYNLIB_REGISTER
instead of WALLAROO_REGISTER
. You must include "dynamic_lib.h" only once per library and "dyn_registered.h" in every translation unit where you use WALLAROO_DYNLIB_REGISTER
.
The current version of wallaroo only support the definition of classes with zero-parameters constructor in shared libraries.
To load the classes definition, you must use the static method Plugin::Load()
at the beginning of your application:
#include "wallaroo/catalog.h" using namespace wallaroo; ... // load classes in shared libraries: Plugin::Load( "car" + Plugin::Suffix() ); // Plugin::Suffix() expands to .dll or .so according to the OS ... Catalog catalog; // this is the container of your objects // populate the catalog with some objects: catalog.Create( "ferrari_f430", "Car" ); catalog.Create( "maserati_granturismo", "Car" ); ...
Alternatively, you can specify the shared libraries to load in the configuration file:
<wallaroo> <plugins> <shared>car</shared> <shared>engine</shared> </plugins> <parts> ... </parts> <wiring> ... </wiring> </wallaroo>
or
{ "wallaroo": { "plugins": { "shared": "car", "shared": "engine" }, "parts": [ ... ], "wiring": [ ... ] } }
then, in the main application, you must specify to load the shared libraries specified in the configuration file:
#include "wallaroo/catalog.h" #include "wallaroo/xmlconfiguration.h" using namespace wallaroo; ... XmlConfiguration file( "configuration.xml" ); // load classes in shared libraries: file.LoadPlugins(); ...
This page has given you a general overview of Wallaroo.
You can obtain more information in the following places:
Wiki: FAQ
Wiki: TableOfContents
Wiki: Tour
Wiki: Tutorial
Wiki: WiringFiles
Wiki: XmlSchema