We’re here to help you

Menu

Using Dezyne in your C++ Environment

Creating the native C++ environment

image

The first step to start integrating your Dezyne models in an actual application is to generate code from all the models. This can be done in two ways, namely through the Dezyne application or by use of the command line client. In the Dezyne application, right-click on your *.dzn files and navigate to the Generate Code menu item. There you can select multiple programming languages to generate code for. Select a location where the generated code should be stored and Dezyne will generate code for you. Later on in this tutorial, we will discuss how to automate this process by use of the Dezyne command line client and a makefile in Breakdown of example makefile: automating Dezyne features.

Up until now, we have approached the application as an event-based system. In order to run as a program on the Raspberry, though, we must have some kind of (infinite) loop that will provide the system with events. From here on after, this loop will be referred to as the event loop. Let’s start by creating a main function to contain this event loop for our application.

int main(int argc, char* argv[]) {
   while (true) {
     // Dezyne interaction
   }
}

The above main function will run until cancelled and each iteration of the while(true) loop will perform some kind of interaction with Dezyne later on. This is the easiest way of facilitating the event loop required by the Dezyne-generated code.

Including and configuring the dzn runtime

Before the generated code can be used in combination with the event loop you just created, you must also download the Dezyne runtime to use in your project. The runtime can be found in Window → Show Views → Runtime. Once there, select the programming language you want to use and right click to download the associated files. (Just like code generation, downloading the runtime can be done by use of the command line client and a makefile; see Breakdown of example makefile: automating Dezyne features. This approach can be helpful in an environment that makes use of version control.)

The minimum required components of the Dezyne runtime that must be included in your application are the actual runtime and the Dezyne locator. The Dezyne runtime is a library of primitives used by the generated code; the locator is a mechanism to inject dependencies into a Dezyne system and its components. The runtime is an example of such a dependency that can be injected into the locator.

The files that must be included, namely runtime.hh and locator.hh, are located in the dzn subfolder. The inclusion of the runtime files should be done using ‘ <> ’ include tags as opposed to ‘ “” ’ include tags; this is because of how code is generated from Dezyne models. This has some implications for compilation but those will be covered later in Compiling your application.

When the files are included you can instantiate the Dezyne locator and runtime objects in your created main. The minimal setup that does all of the above will look as follows:

#include <dzn/runtime.hh>
#include <dzn/locator.hh>
int main(int argc, char* argv[]) {
   dzn::locator loc;
   dzn::runtime rt;
   loc.set(rt);

   while (true ) {
     // Dezyne interaction
   }
}

Near the end of this tutorial, in some extra materials you will find a chapter dedicated to an in-depth look at the Dezyne runtime with more information on how you can customize some of the components of the runtime to your liking: Using the Dezyne locator to distribute (runtime) objects.

Inspecting the generated system

Up until this point you should have completed the following tasks:

  • Generated code from your Dezyne models

  • Created a main function with an (infinite) loop

  • Downloaded and integrated the Dezyne runtime in your main loop

Before we start on writing implementations for the various components of the Alarm System, it is important to have a closer look at the generated code from the System component.

Recall that a System component is used to specify how components within the system interact with one another and how they are connected to the native environment outside of the system. This specification of connection to the outside world is why using a System component is so important; it is where and how your handwritten code will interact with the code generated from verified models.

The iteration of the Alarm System from the introductory tutorial we will be using for this can be found here. If you generate code from these models, you should end up with a pair of a '.hh' and a '.cc' file for each .dzn file:

AlarmSystem.cc
AlarmSystem.hh
Controller.cc
Controller.hh
ILED.cc
ILED.hh

As the name of the System component specified in the Dezyne model is AlarmSystem, the generated files have been named AlarmSystem.cc and AlarmSystem.hh as well. Let’s start by having a look at AlarmSystem.hh. This file contains a definition of a struct AlarmSystem which shares some similarities with the System component as specified in Dezyne, which are highlighted in the snippet below:

struct AlarmSystem
{
  dzn::meta dzn_meta;
  dzn::runtime& dzn_rt;

  const dzn::locator& dzn_locator;
  Controller controller;
  LED led;

  IController& iController;

  AlarmSystem(const dzn::locator&);
  void check_bindings() const ;
  void dump_tree(std::ostream& os= std::clog) const ;
};

image

Most notably, it specifies which ports can be accessed on the AlarmSystem object (iController, in this case). This directly maps to what ports are available on the boundaries of the system. Another important factor is the definition of the AlarmSystem constructor, which requires a dzn::locator object to be passed as parameter. This is the case for all components and systems generated from Dezyne models, but luckily the System component will take care of distributing the dzn::locator for you!

The other file that was generated from the System component, AlarmSystem.cc, contains the implementation of functions declared in the header file. The constructor generated for the system looks as follows:

AlarmSystem::AlarmSystem(const dzn::locator& dezyne_locator) :
dzn_meta{"","AlarmSystem",0,0,{},{&controller.dzn_meta,&led.dzn_meta},{[this]
{iController.check_bindings();}}}
, dzn_rt(dezyne_locator.get<dzn::runtime>())>
, dzn_locator(dezyne_locator)
, controller(dezyne_locator)
, led(dezyne_locator)
, iController(controller.iController)
{
  controller.dzn_meta.parent = &dzn_meta;
  controller.dzn_meta.name = "controller";
  led.dzn_meta.parent = &dzn_meta;
  led.dzn_meta.name = "led";
  connect(led.iLed, controller.iLed);
  dzn::rank(iController.meta.provides.meta, 0);
}

The contents of this file look a lot more daunting, but again you can relate some parts of the implementation to lines in the actual Dezyne model. For instance, the binding of ports that was done by controller.iLed <⇒ led.iLed in Dezyne can be traced to an invocation of the connect(led.iLed, controller.iLed) function.

In the implementation of the System’s constructor most of the magic happens, as a chain of constructors of components within the system is set off and the dzn::locator container (and as such, the dzn::runtime) is spread across the system. Using a System component for this purpose ensures that every component is using the same runtime, which is essential for Dezyne’s functionality. For every Dezyne application you create, make sure to embed the components in a System component.

Interaction with the AlarmSystem object in your event loop is how the logic modeled in Dezyne will perform in your fully integrated application. Later in the tutorial you will see how interaction with the System on its exposed ports will work, but to conclude this chapter on preparing the native C++ environment let’s do exactly that; prepare the main loop for use by creating the System object.

Most of the initialization steps were already completed in the earlier draft of your main function; the runtime files were included and the respective objects were created. What remains is to create an object of the AlarmSystem struct type and pass the locator to it. Then, before entering the event loop, it is recommended to invoke check_bindings() on the System. This is a Dezyne functionality that ensures that all ports have been bound properly. The result will look like this:

#include <dzn/runtime.hh>
#include <dzn/locator.hh>

int main(int argc, char* argv[]) {
  dzn::locator loc;
  dzn::runtime rt;
  loc.set(rt);
  AlarmSystem as(loc);
  as.check_bindings();

  while (true ) {
    // Dezyne interaction
  }
}

If you have questions that weren’t answered by this Guide,
let our support team help you out.

Enjoy this article? Don't forget to share.