We’re here to help you

Menu

Integrating Code in Other Languages

At first, please read the following info/tips/guidelines about generating code from native components in Dezyne.

A component in Dezyne that has no behaviour or system block is considered a native component. This means it is still part of the Dezyne System diagram but all the functionality must be provided by the user in native code.

For C++ we explained that code generation leads to pure virtual classes for these components such that the handwritten code can be placed in a derivative class. In this way code regeneration does not lead to overwriting a handwritten file. For other programming languages this feature is not available. The best way to prevent overwriting handwritten code is to follow this procedure:

  • Create the component in Dezyne and fill out a behaviour block with the event triggers and action code as far as you want

  • Generate code: A rough version or skeleton of the native component will be generated

  • Complete this skeleton with the appropriate implementation

  • In Dezyne, put comment markers around the behaviour block. The component will now be interpreted as native and code generation will have no effect anymore.

Integration steps in C#

Next, let’s consider the actions denoted in the table of required actions to integrate code in C#.

For reference, the relevant table is supplied:

Port type Event type User action

Provides interface (on top of a component)

In

Call Dezyne function from native code

**

Out

Assign native code to system function

Requires interface (on the bottom of a component)

In

Assign native code to system function

**

Out

Call Dezyne function from native code

The Dezyne models that are used in the examples can be found here. Take note that we will be showing the integration steps only; we will not focus on the functional implementation.

Creating instances of the required runtime libraries is done similar to how it is done in C++. In C#, it is easier in the sense that no additional files must be included on a per-file basis. Preparing the runtime libraries is done as follows:

using System;

namespace MyDznApp {
  class main {
    private dzn.Locator dznLoc;
    private dzn.Runtime dznRt;

    public static void Main() {
      dznLoc = new dzn.Locator();
      dznRt = new dzn.Runtime();

      dznLoc.set(dznRt);
    }
  }
}

Take note that Locator and Runtime are located in the dzn namespace. Depending on the execution semantics of your application, you may choose to define the objects within the scope of Main or outside the scope (as can be seen above).

Creating an object to represent the System generated from Dezyne models requires you to pass the dzn.Locator object you created as parameter just like in C++:

using System;

namespace MyDznApp {
  class main {
    private dzn.Locator dznLoc;
    private dzn.Runtime dznRt;
    private AlarmSystem as;

    public static void Main() {
      dznLoc = new dzn.Locator();
      dznRt = new dzn.Runtime();

      dznLoc.set(dznRt);
      as = new AlarmSystem(dznLoc);
    }
  }
}

In C#, events are generated as Action or Func objects, the C# variant of function objects. Action is used for void on events; Func is used for on events that have a return value. To reach an event on a port of a component, the following structure is applied in generated C#:

ComponentName.PortName.PortDirection.EventName, where ComponentName is the name of the data object representing the Component, PortName is the name of the Port you are trying to access, PortDirection is either inport or outport depending on the specification and EventName is the name of the on event you are trying to access.

As an example, let’s invoke the validPincode event on the provided iController port of the AlarmSystem (required action for Provides/in and Requires/out):

using System;

namespace MyDznApp {
  class main {
    private dzn.Locator dznLoc;
    private dzn.Runtime dznRt;
    private AlarmSystem as;

    public static void Main() {
      dznLoc = new dzn.Locator();
      dznRt = new dzn.Runtime();

      dznLoc.set(dznRt);
      as = new AlarmSystem(as);

      as.iController.inport.validPincode();
    }
  }
}

Here, the ComponentName is as; PortName is iController; PortDirection is inport; EventName is validPincode.

Let’s assume the provided IController port has an out event void foo(). According to the table in the introduction of this chapter, Provides/out and Requires/in require you to assign native code to a System function. void foo() will be represented as an Action object, which we must assign native code to. To assign to an Action object, we can use a lambda expression:

using System;

namespace MyDznApp {
  class main {
    private dzn.Locator dznLoc;
    private dzn.Runtime dznRt;
    private AlarmSystem as;

    public static void Main() {
      dznLoc = new dzn.Locator();
      dznRt = new dzn.Runtime();

      dznLoc.set(dznRt);
      as = new AlarmSystem(as);
      as.iController.outport.foo = () => { /* Native implementation of foo() */};

      as.iController.inport.validPincode();
    }
  }
}

For more information on lambda expressions in C#, please refer to this article.

in, out and inout parameters in C#

An in-event in Dezyne with different types of parameters:

in void some_event (int i, out o, inout io);

is translated into c# as follows:

void some_event (int i, out o, ref io);

So there is a one-to-one mapping onto the parameter direction keywords in c#. Note that in the calling of the event also these keywords need to be used as specified in the c# language definition.

Native components in C#

The interface ILED is provided by the LED component, which is a native component and therefore not generated from your Dezyne models. The generated AlarmSystem System component does refer to an object of type LED, so you will have to make this object yourself. Take note that this object should not be contained in a namespace; the generated System refers directly to LED.

A prototype of an LED class that complies to the ILED interface is as follows:

class LED {
  public ILED iLed;

  public LED(dzn.Locator locator, String name = "", dzn.Meta parent = null) {
    iLed = new ILED();

    iLed.inport.setGreen = this.setGreen;
    iLed.inport.setYellow = this.setYellow;
    iLed.inport.setRed = this.setRed;
    iLed.inport.turnOff = this.turnOff;
  }

  public void setGreen() {}
  public void setYellow() {}
  public void setRed() {}
  public void turnOff() {}
}

The LED class has a public member named iLed of type ILED; the name of this member variable has to be the same as the name of the provided interface of the respective component in your Dezyne model. Then, in the constructor the events of ILED that are to be implemented are bound to the implementations within the LED class.

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.