Modelica - Calling of C routines in Function Blocks

Modelica - Calling of C routines in Function Blocks

Inhalt


Topic:.Mdlc.FB_Clang.

Last changed: 2019-04-11

This description should show the principles of calling own C language routines in modelica.


1 Frame to C-call: A Modelica Block, with a simple C routine

Topic:.Mdlc.FB_Clang.Mdlc_Fn.

A Modelica Function is the container for a C routine (C function). A Modelica Function itself can not be a part of a graphical model, only a Function Block can be used. You should define a Block in Modelica: Rigth click in modelica repository, select "New modelica class", in the dialog box select for Specialization: "Block". The download example contains the Block named "MyTestFnC_FB":

block MyTestFnC_FB
  extends Modelica.Blocks.Icons.Block;
  Modelica.Blocks.Interfaces.RealInput u "comment" annotation (Placement(
        transformation(extent={{-120,-10},{-100,10}})));
  Modelica.Blocks.Interfaces.RealOutput y "comment" annotation (Placement(
        transformation(extent={{100,-10},{120,10}})));
  parameter Real k1 = 0.1;
function fn
  input  Real     a;
  input  Real  b;
  output Real     y;
  external "C"  y = mytestFn(a,b);
  annotation( Include ="#include \"myTestFn.h\"", Library = "myTestFn.o");
  //algorithm y := a + b;
end fn;
algorithm
  y := fn(k1, u);
end MyTestFnC_FB;

The block contains the known Input and Output definition and parameter.

The necessity Modelica Function definition is defined inside the block: The function is only necessary for this block. The name of the function can be short, only fn because it is encapsulated.

  external "C"  y = mytestFn(a,b);
  annotation( Include ="#include \"myTestFn.h\"", Library = "myTestFn.o");

That is the immediately calling of the C routine inside the Modelica Function. The Type of the arguments and the return type are determined by the named Modelica Function arguments, Real means double in C.

The annotation(Library = "...") is used to include the given include line in the generated source of the model and the given library in the make file. The OMEdit simulation output window shows:

gcc -o MyTestFnC_Mdl.exe ...  myTestFn.o ...

Both the header and the object should be part of the Modelica working directory for compilation and simulation running, that is %TMP%\OpenModelica\OMEdit\MODELNAME. The mdlc/MyTestFnC/myTestFnAlloc.mk.bat/myTestFnAlloc.mk.bat generates to this directory and copies the header.

The commented line //algorithm y :=... show only an alternative, an immediately algorithm line in the Modelica function instead the C routine invocation.

 algorithm
   y := fn(k1, u);

After the function definition this block contains the Modelica Function call in an algorithm part. It is very simple without when section for special initialize() and sample(). The called function has no internal state. It is a simple function. See difference to the chapter Chapter: 5 C routines with own allocated data and handle-references in the model.

Any model can contain this block in its graphic. It calls the C routine whenever the block is processed. The function is stateless, therefore no timing conditions exist.


2 Building the Object or library

Topic:.Mdlc.FB_Clang.Mdlc_Cfn.

In this simple example the C routine is given in the source myTestFn.c:

#include "myTestFn.h"

double mytestFn(double a, double b){
  return 2*a + b;
}

The header file contains a prototype definition. Because the header is included in

block MyTestFnC_FB
  .....
  annotation( Include ="#include \"myTestFn.h\"", Library = .....);

the Modelica code generation assumes that that header contains a proper prototype definition. If the Include is not given, then the Modelica code generation defines the prototype by itself with the given argument types of the Modelica Function. The part in the generated Modelica file is:

/*
 * The function has annotation(Include=...>) or is builtin
 * the external function definition should be present
 * in one of these files and have this prototype:
 * extern double mytestFn(double (*_a*), double (*_b*));
 */

To compile the following batch file myTestFn.mk.bat is used:

set MDLC_GCC_HOME=%OPENMODELICAHOME%\tools\msys\mingw64\bin
set PATH=%MDLC_GCC_HOME%;%MDLC_GCC_HOME%\..\..\usr\bin;%PATH%

if not exist %TMP%\OpenModelica\OMEdit\MyTestFnC_Mdl mkdir %TMP%\OpenModelica\OMEdit\MyTestFnC_Mdl
%MDLC_GCC_HOME%\gcc.exe -c -v -o %TMP%\OpenModelica\OMEdit\MyTestFnC_Mdl\myTestFn.o myTestFn.c
copy myTestFn.h %TMP%\OpenModelica\OMEdit\MyTestFnC_Mdl\*
pause

It is important to clarify which compiler is used by the Modelica simulation. The gcc is default and built in, but it is not the only one. In this example it is used.

The same compiler should be used for the own compiled parts as for simulation built. The PATH is set to that compiler and the compiler is invoked with absolute path. In my system another MinGW installation is present too, without the PATH setting the other compiler will be used and some sophisticated linker errors may occur: version, settings, 32 or 64 bit compilation etc.

The environment variable OPENMODELICAHOME is known in the system due to the installation of Modelica. It is set in my system with C:\Programs\OpenModelica1.13.264bit\. The MDLC_GCC_HOME is only temporary created and used. I advise to use such temporary variables instead definition in the system level. It does not touch the operation system settings.

The batch call translates and builds the object file in the Modelica working directory starting with %TMP%. You can see which working directory is used by visit the output window while Modelica compilation. I admit that I have touched one of the Modelica tool files:

c:/Programs/OpenModelica1.13.264bit/share/omc/scripts/Compile.bat:
cd /D "%CURRENT_DIR%"
echo currdir=%CD%

The echo command is newly, then the working directory is shown in the Modelica compiler output. It is nice to have.


3 More complex example with allocation, initialization and instance variables

Topic:.Mdlc.FB_Clang.OOFn.

If you want to invoke more complex C operations which are written and used in C already, often they have its own memory and states. As a general delibaration such routines should be 'reentrant'. Because: In a larger model often more as one instance may need. A 'reentrant' style is proper for a single instance too. Hint: The wording 'reentrant' is old conventional and ordinary, more exact may be 'multi-instantiable'.

The C routine in the Chapter: 2 Building the Object or library is stateless. It is only a simple algorithm. But often a routine should use states. The simplest example is a Time delay first order:

y := k * (u - y); //y is the state

A simple way to build internal state variables is the

double myCroutine(double u) {
  static double y;  //state variable NOT RECOMMENDED
  y := k * (u - y); //y is the state
  return y;
}

This is simple and easy, but not recommended and unflexible. Because: If the routine is used in more as one instantiation, it does not run. The same behaviour is given if the state variable is defined as static outside the C function. Often Old-Style C programmer uses static and maybe global data definition, but that is wrong in many cases.

It is necessary in generally to use referenced data for multi-instance-ability ('reentrant-able'). The data are defined outside the C routine with the instance of the function block. Or it is allocated in C in the heap, see next chapter.

That means the C code should not contain static defined data.

One solution with Modelica defined data is shown in the following chapter, but it does not work properly. The Chapter: 5 C routines with own allocated data and handle-references in the model shows a complete solution with core routines in C, with referenced data of the core routines, with allocated data in the Modelica wrapper and with storing of the handle to the data in the Modelica Block instance.


4 Using a modelica record definition for data

Topic:.Mdlc.FB_Clang.record.

Modelica knows the record definition in its textual representation. A record is adequate, will be translated, to a C struct. It can hold the associated data to a C routine. This is the basic feature of ObjectOrientation.

Look at the example Function Block MyTestFnCrecord_FB in Modelica:

block MyTestFnCrecord_FB
  extends Modelica.Blocks.Icons.Block;
  Modelica.Blocks.Interfaces.RealInput u "comment" annotation (Placement(
        transformation(extent={{-120,-10},{-100,10}})));
  Modelica.Blocks.Interfaces.RealOutput y "comment" annotation (Placement(
        transformation(extent={{100,-10},{120,10}})));
  Modelica.Blocks.Interfaces.RealOutput testInit "comment" annotation (Placement(
        transformation(extent={{100,-90},{120,-70}})));
  parameter Real k1 = 0.01;

  record Data
    Real q1, q2;
  end Data;
  Data data;

  function fn
    input Data thiz;
    input  Real u;
    input  Real k;
    output Real y;
    external "C"  y = mytestFnRecord(thiz, u,k);
    annotation( //Include="#include \"myTestFnRecord.h\"",  //Note: cannot include, twice definition
                Library = "myTestFnRecord.o");
  end fn;

//initial equation
//    data.q1 = 5.2;
//    data.q2 = -3;

algorithm
  when initial() then
    data.q1 := 1.2;
    data.q2 := data.q2 +1;
    testInit := data.q2;
    y := 0;
  end when;
  when sample(0, 0.001) then
    y := fn(data, u, k1);
  end when;

end MyTestFnCrecord_FB;

The record contains two double value in C. The line

Data data;

defines the FunctionBlock-instance-specific data for the C operation. It is offered to the C routine with

algorithm
  ...
  y := fn(data, u, k1);

and then

  function fn
    input Data data;
    ...
    external "C"  y = mytestFnRecord(data, u,k);

For the Code generatiion of the Modelica Function definition data is a pointer of the adequate record type. The generated C-source MyTestFnC_Mdl_functions.h (found in the modelica temp directory) contains:

.....
typedef struct MyTestFnCrecord__FB_Data_s {
  modelica_real _q1;
  modelica_real _q2;
} MyTestFnCrecord__FB_Data;
.....
extern double mytestFnRecord(MyTestFnCrecord__FB_Data* /*_thiz*/, double /*_a*/, double /*_b*/);

But up to now for the implementation of the routine in C some problems are given:

The last problem is: The record content seems to be copied before the C routine is called. For this example the same initial values are delivered to my C routine. Records in Modelica should be used for parameter values especially, not for state data.

For all this reasons the following approach may be proper, but it is sometime more complex:


5 C routines with own allocated data and handle-references in the model

Topic:.Mdlc.FB_Clang.CobjO.

That is a consequently approach:

With this opinions the following example is able to download and test.


5.1 Core routines in C

Topic:.Mdlc.FB_Clang.CobjO.core.

The core routines should be independent of Modelica, well testable as units, and fast in a small target system. They are object oriented in C (using struct). Usage of C++ classes is possible. Dealing with references is not slower than using absolute (static global) instances. See the header for core MyTestFnAlloc.h:

#ifndef __myTestFnAlloc_h__
#define __myTestFnAlloc_h__
#include <applstdef_emC.h>  //for some application specific but common definition

/**Data structure for example
 */
typedef struct MyTestFnAlloc_t {

  /**Mark the data with a signum for check. */
  char const* signum;

  /**Some user example data. */
  float q1, q2;

  int32 ct1, ct2;
} MyTestFnAlloc_s;   //Note: _t _s suffix is style for reflection generation and C++-class-usage.

/**The definition of an address for signum of data. */
extern_C char const* signum_MyTestFnAlloc;

/**The constructor for the example class.
 * @arg y0 start value
 */
void ctor_MyTestFnAlloc(MyTestFnAlloc_s* thiz, float y0);

/**The destructor, here empty. */
void dtor_MyTestFnAlloc(MyTestFnAlloc_s* thiz);

/**Step routine for the example class.
 * @arg u input
 * @arg k1 any example parameter
 * @return output
 */
float step_MyTestFnAlloc(MyTestFnAlloc_s* thiz, float u, float k1);

/**Step2 routine for the example class.
 * @arg k1 any example parameter
 * @return output
 */
float step2_MyTestFnAlloc(MyTestFnAlloc_s* thiz, float k1);


inline void getCt_MyTestFnAlloc(MyTestFnAlloc_s* thiz, int* ct1, int*ct2) { *ct1 = thiz->ct1; *ct2 = thiz->ct2; }

#endif //__myTestFnAlloc_h__

That is the complete header for the example. The style is: Using javadoc documentation in the header, well able to read, common known. Using tag names for struct with _t suffix and _s suffix in name. This is to build a C++ class with an adequate name which extends this struct. This style guides are also necessary for reflection generation, see ../../Inspc/index.html able to use for Modelica data viewing (not ready yet, 2019-04), for PC simulation and for the target system.

Another detail: For the small target system float instead double arithmetic should be used because it is faster and sufficient. The Simulation should use the same arithmetic to check whether it is really ok. Modelica knows double (Real) only, which is proper for physical simulation and the PC platform. This routines uses float adequate the necessities of the target C-code. The adaption is done in the wrapper routines, see next chapter.

The signum in the data is important to prevent run time errors in the model on faulty wiring of blocks. In the small target it is a less effort and able to use for debug on machine level (checking correctness of data). It can be replaced by the basic struct ObjectJc which contains either this signum or a reference to the reflection which are a signum too, the basic for data access and the basic for overridden (dynamic linked, virtual) operations in C.

The implementation is MyTestFnAlloc.c:

#include "myTestFnAlloc.h"

//Any addresse for signum check:
char const* signum_MyTestFnAlloc = "signum_MyTestFnAlloc";

void ctor_MyTestFnAlloc(MyTestFnAlloc_s* thiz, float y0)
{
  thiz->signum = signum_MyTestFnAlloc;
  thiz->q1 = y0;  //any initial value
}

void dtor_MyTestFnAlloc(MyTestFnAlloc_s* thiz)
{
  //nothing to do, empty
}

float step_MyTestFnAlloc(MyTestFnAlloc_s* thiz, float u, float k1)
{ if(++thiz->ct1 == 5) { thiz->ct1 = -5; }
  thiz->q1 += k1 * (u - thiz->q1);
  return thiz->q1;
}

float step2_MyTestFnAlloc(MyTestFnAlloc_s* thiz, float k1)
{
  thiz->ct2 = thiz->ct1;
  thiz->q2 += k1 * (thiz->q1 - thiz->q2);
  return thiz->q2;
}

It is simple. Especially the step routine can be implemented as inline on header level too, for fast machine code. The target compiler can optimized the machine code for very fast run.

The core routines does not determine the storage for the data. It should be organized by the running system. For a small target static data may be proper (not allocated), but allocated were be too. The decision for one of that is done in the runtime environment of the target, not in the core C routines itself.


5.2 The Wrapper for usage in the Modelica Blocks

Topic:.Mdlc.FB_Clang.CobjO.Mdlwrp.

The usage in Modelica Blocks need some organization. Hence a wrapper is used. This wrapper is a slow effort for simulation run. This routines are not necessary in the target system.

The header myTestFnAlloc_Mdlwrp.h:

#ifndef __myTestFnAlloc_Mdlwrp_h__
#define __myTestFnAlloc_Mdlwrp_h__

#undef extern_C
#ifdef __cplusplus
  #define extern_C extern "C"
#else
  #define extern_C extern
#endif

//Note: Java-style comment is used for documentation generation and proper readablility.

/**Allocs, initializes and returns the handle to the memory. */
extern_C int ctorAlloc_MyTestFnAlloc_Mdlwrp(int thizInit, double y0);

/**Frees the memory. */
extern_C void dtorFree_MyTestFnAlloc_Mdlwrp(int hthiz);

/**step routine. */
extern_C double step_MyTestFnAlloc_Mdlwrp(int hthiz, double u, double k1);

/**step2 routine. */
extern_C double step2_MyTestFnAlloc_Mdlwrp(int hthiz, double k1, double stepin);

extern_C void getCt_MyTestFnAlloc_Mdlwrp(int hthiz, int* ct1, int* ct2);

#endif //__myTestFnAlloc_Mdlwrp_h__

It defines only the prototypes. The extern_C definition allows using an C++ compiler for the libraries for the C routines: Modelica needs C-compilation. It does not mean that the user C sources should compile with C. C++ can be used. But the linker labels should be proper for C. This is done in C++ with the extern "C" prefix before the prototypes.

Because this header is included in the modelica sources the header should not contains parts of the emC sources, it are not necessary.

The myTestFnAlloc_Mdlwrp.c contains the allocation and conversion to handle-references. To explain show its parts:

#include "myTestFnAlloc_Mdlwrp.h" //first include the own header
#include "myTestFnAlloc.h"        //after them the header of used routines
#include <emC/Handle_ptr64_emC.h>
#include <stdio.h>
#include <stdlib.h>   //for malloc and free
#include "omc/c/ModelicaUtilities.h"

int ctorAlloc_MyTestFnAlloc_Mdlwrp(int thizInit, double y0)
{
  if(thizInit !=0) return thizInit; //it is initialized already, more as one call is possible and acceptable
  //
  MyTestFnAlloc_s* thiz = (MyTestFnAlloc_s*)malloc(sizeof(MyTestFnAlloc_s));
  memset(thiz, 0, sizeof(MyTestFnAlloc_s));
  //
  uint32 hthiz = registerPtr_Handle2Ptr(thiz, "MyTestFnAlloc");
  if(hthiz == 0) { ModelicaError("any Problem with Handle2Ptr"); }
  else {
    ModelicaMessage("ctor myTestFnAlloc_Mdlwrp\n");
    //
    //call the core routine
    ctor_MyTestFnAlloc(thiz, (float)y0);
    //
  }
  //hthiz = -2; //uncomment to force an exception because faulty handle
  return hthiz;
}

Because this source is compiled in an extra make process with possible special settings of the include path it can include both the special user defined sources and some parts from the emC source pool. From the last one only the handle_ptr64_emC,h is necessary for this example.

The ctorAlloc...(...) gets the existing handle thisInit to prevent twice allocation. It is not certain, that an when inital()-part in modelica is processed exactly one time.

It allocates the memory, registers the address and returns the handle. Inside the core constructor is invoked, that is the code in the chapter above.

Because of including ModelicaUtilities.h there is the possibility to output some info or error to the modelica console. Note, that a printf does not work. Especially in the init routine or in unexpected situations it is possible to write an ModelicaMessage(...).

Error handling: If any operation fails, the emc-sources invokes a variant of a THROW-macro, see emC:ExceptionHandling. The macro implementation depends on the Exception settings. They are controlled with the used applstdef_emC.h for the application. For the given compilation a routine in LogException_Mdlc.c is called:

/**Implementation of logging. */
void logSimple_ExceptionJc(int exc, int32 value, int val2, char const* file, int line)
{   //copy the text in any case.
   ModelicaFormatError("Exception in line %d of %s", line, file);
}

That stops the simulation. In this example you can check it with a faulty hthiz. It causes the THROW with abort in the next routines which invokes ptr_Handle2Ptr(hthiz) with the faulty value.

void dtorFree_MyTestFnAlloc_Mdlwrp(int hthiz)
{
  if(hthiz !=0){
    MyTestFnAlloc_s* thiz = (MyTestFnAlloc_s*)clearHandle_Handle2Ptr(hthiz);
    if(thiz !=null && thiz->signum == signum_MyTestFnAlloc) {
      //
      //call the core routine
      dtor_MyTestFnAlloc(thiz);  //call the core routine
      free(thiz);
    }
  }
}

The routine above frees the memory. It gets the handle, convert to the corresponding pointer and invokes free(...).

Step routine

double step_MyTestFnAlloc_Mdlwrp(int hthiz, double u, double k1)
{ if(hthiz == 0) return -1; //not initalized
  MyTestFnAlloc_s* thiz = (MyTestFnAlloc_s*)ptr_Handle2Ptr(hthiz);
  if(thiz !=null && thiz->signum == signum_MyTestFnAlloc) {
    //
    //call the core routine
    return step_MyTestFnAlloc(thiz, (float)u, (float)k1);
    //
  }
  else {
    ModelicaError("faulty instance for step_MyTestFnAlloc_Mdlwrp");
    return -1;
  }
}

It gets the handle as integer and converts it to the pointer. The error situation hthiz == 0 is not expected but possible on errors in the modelica source parts. For that reason a special return value may be sufficient, a ModelicaError(...) may be possible here and more proper.

Before calling the step routine the signum is tested. If the value for the hthiz handle is changed because any non-obvious situation, this test fails. Then the simulation is aborted with the given error message. There is no remaining case where the core routine are called with a faulty thiz.

The other routines are adequate, see orignal sources.


5.3 Modelica Block for this C routines

Topic:.Mdlc.FB_Clang.CobjO.block.

The modelica block MyTestFnAlloc_FB.mo contains:

block MyTestFnAlloc_FB

 extends Modelica.Blocks.Icons.Block;
 Modelica.Blocks.Interfaces.RealInput u "comment" annotation (Placement(
       transformation(extent={{-120,-10},{-100,10}})));
 Modelica.Blocks.Interfaces.RealOutput y "comment" annotation (Placement(
       transformation(extent={{100,-10},{120,10}})));
 Modelica.Blocks.Interfaces.IntegerOutput testInit "comment" annotation (Placement(
       transformation(extent={{100,-90},{120,-70}})));
 //parameter Real k1 = 0.01;
 parameter Real k1 = 0.005;
 parameter Real y0 = 1.2;
 Integer ct;
 protected Integer thiz;

The variable thiz is declared as protected instance variable.

 function ctorAlloc
   input Integer thizInit;
   input Real y0;
   output Integer thiz;
   external "C"  thiz = ctorAlloc_MyTestFnAlloc_Mdlwrp(thizInit, y0);
   annotation
   ( Include = "#include \"myTestFnAlloc_Mdlwrp.h\""
   , Library = "libmyTestFnAlloc.a"
   );
 end ctorAlloc;
 function dtor
   input Integer thiz;
   external "C"  dtorFree_MyTestFnAlloc_Mdlwrp(thiz);
 end dtor;
 function step
   input Integer thiz;
   input Real u;
   input Real k1;
   output Real y;
   external "C"  y = step_MyTestFnAlloc_Mdlwrp(thiz, u, k1);
 end step;

The three necessary functions are defined inside the block. The annotation is only necessary for one function because it is included in the make process for all.

algorithm
 when initial() then
   thiz := ctorAlloc(thiz, y0);
   Modelica.Utilities.Streams.print("Info from: " + getInstanceName());
   thizout := thiz;
   y := 0;  //only formally, modelica warning
   ct := 0;
 end when;
 when terminal() then
   dtor(thiz);
 end when;
 when sample(0, Tstep) then
   y := step(thiz, u, k1);
   ct := ct +1;
 end when;

end MyTestFnAlloc_FB;

The last part shows the algorithm of the block. The when ... clauses checks init and terminate. The sample checks a specified sample time. It is a parameter.


5.4 Building the library with core C sources and wrapper

Topic:.Mdlc.FB_Clang.CobjO.mk.

The library is for Modelica simulation. For the target system the sources are used in another build process.

Because there are more as one source file, in opposite to the examples above, a make system is proper to use. There are some well known possibilities: Using an IDE to build a library for example with Eclipse-CDT, using a make file (nmake). In Eclipse CDT the same gcc compiler tools should be used as in Modelica. To do so, the PATH to this toolchain (%OPENMODELICAHOME%\tools\msys\mingw64\bin) can be set before start of eclipse.exe in a batch call.

For this example another approach is used: Zmake. This is a possibility to invoke the compiler immediately without an IDE and to check dependencies with really conditional compilation on changed sources. A standard ..make.exe checks only whether a source is newer than the object, not whether it is changed. The approach with Zmake is: Development in an IDE (Visual Studio, Eclipse) with a test environment and debug possibilities, and deployment for a target system (in this case the Modelica lib is the target) using Zmake. Compiler and linker errors are shown immediately from the compiler output. Because the sources are tested before, only basicly compiler errors are able to expect. For example because a faulty include path. The C(++) sources for test on PC with the IDE and to deploy for the target are the same. The deployment-compilation via Zmake is more simple. It can be more simple adapted to target compiler conditions.

You can use the myTestFnAlloc.mk.bat which calls the already generated myTestFnAlloc.zmk.bat without using Zmake and JZtxtcmd. If you are intent to change sources, you can use or adapt the myTestFnAlloc.jz.cmd which is the Zmake script file. For running a JZtxtcmd script only only the ZBNF/zbnfjax/zbnf.jar is necessary. This Java archive file contains all necessities in about 1 MByte. Of course a Java JRE is necessary, version 8.x

The file myTestFnAlloc.jz.cmd is a batch file (.cmd) and contains after

exit /B

==JZtxtcmd==

the JZtxtcmd script. Such a script works well with Java. Tooling for translation processes or other PC software is often written in Java.

The script names all input files in Fileset whereby wildcards (*) can be used too. All conditions to compile are set in some variables. The Dependency check with decision whether it should be compiled is done by the class org.vishia.checkDeps_C.CheckDependencyFile. This class looks for dependencies in the source file and all depending files by itself and delete the object file if new compilation is necessary. Changed included files will be checked too, except not found included files. That are the system includes, which are not necessary to check.

The compilation itself may be able to do in this ZJtxtcmd script execution. It is possible to invoke the gcc while creating a new process in Windows (or Linux) from Java. But it is faster in case of many compiler invocation to do this in a generated batch file. The last one has the additional advantage for staticly analyze-ability (which sources, which options etc), whereby each source is one line. It is more clarified what the compilation process is going to do than a make file.

It means, except checking dependencies and deleting objects, the JZtxtcmd script only generates the myTestFnAlloc.zmk.bat file. The outputs to generate are written in

                  <:>
===================if not exist <&objTmp>\<&input.localnameW()>.o (
===================  if not exist <&objTmp>\<&input.localdirW()> mkdir <&objTmp>\<&input.localdirW()>
===================  if exist <&objTmp>\<&input.localnameW()>.o del <&objTmp>\<&input.localnameW()>.o
===================  echo gcc -o <&objTmp>/<&input.localname()>.o <&input.file()> >>err.txt
===================  echo gcc <&input.file()>
===================  g++ .exe -c <&cOptions> %INCLUDEPATH% -o <&objTmp>/<&input.localname()>.o <&input.file()> 1>>out.txt 2>>err.txt
===================)
===================if not exist <&objTmp>\<&input.localnameW()>.o echo !!!!error^^^^^^^^!!!! >>err.txt
===================<.>

This immediately output text can contain 'placeholder' for values from Java data (JZtxtcmd script data) written in <&....> whereby access to data or Java operation invocation is possible. File designations are contained in special Java classes, from there determined parts can be gotten. <&input.localdirW()> gets the directory of an input file written with \ (W =^ Windows), without W with slash. <&input.localname()>.o builds the name of the Object file with a path part. The local path starts on a position marked with ':' inside the path or it is the given path in the fileset.

The JZtxtcmd approach is developed about 10 years ago and used in many projects. It is open source (Java language).


6 Handle connection in the model, multiple instances

Topic:.Mdlc.FB_Clang.exmpl.


Bild: mdl The model in the download file contains two instances Testobj and TestObj2 of the above described Block MyTestFnAlloc_FB with different parameters. It shows that the Block is multi-instanceable.

The blocks step2 and getCt uses the thiz handle of Testobj for second operations with the same instance Testobj. It is a method or operation of this class = FunctionBlock MyTestFnAlloc_FB. The connection is done in the init phase, because the thizout output is set in

algorithm
 when initial() then
   .....
   thizout := thiz;

It is sufficient. But because that the connected FBlocks have not an information about the calculation order. Therefore a stepin Input is defined for the step2 and getCt FBlock. The model should use that wiring to determine the order. In the shown variant firstly getCt is calculated, and after them step2. Hence the both ct1, ct2 have different values in result. If the wiring is changed to forward Testobj to step2 and step2 to getCt then both ct values are equal. The step2 contains internally in C the line:

   thiz->ct2 = thiz->ct1;

and the TestObj counts thiz->ct1. The stepIn signal is wired till the wrapper C routine in myTestFnAlloc_Mdlwrp.c but not used for the core algorithm. Hence it determines the model and the order of calculation but does not need any time for a target machine code.


7 C++ can be used, Exception handling can be used, content of emC

Topic:.Mdlc.FB_Clang.cpp.

Though Modelica needs a C interface for the C-Language functions the internal code can be compiled with a C++ compiler. The make file in Chapter: 5.4 Building the library with core C sources and wrapper uses g++ instead of gcc. Whether all features of C++ language is used or not, this other question depends on decisions for a target system compiler. C++ for PC usage has some important advantages:

Unfortunately there is not a common standard for C and lightweight C++ for embedded systems. But usual C code can compile with C++, only a few unneccesary features of C does not compile to C++.

The Exception handling can be used in C either with longjmp or the code is considered as well tested for the target usage. The sophisticated exception possibilities of C++ should not be used, they are not applicable for C. But a Java-adequate thinking for C and C++ is possible. This is described in emC:ExceptionHandling as part of the emC source pool. The used emC/Handle_ptr64_emC.c has some THROW statements in error cases. With the given simple solution the THROW invokes in LogException_Mdlc.c:

/**Implementation of logging. */
void logSimple_ExceptionJc(int exc, int32 value, int val2, char const* file, int line)
{ //copy the text in any case.
  ModelicaFormatError("Exception in line %d of %s", line, file);
}

This is the only logging of an exception-strategy of emC instead a true Exception handling. But because calling Modelica Error the simulation is aborted on any THROW in the software. It means, the first THROW stops the simulation. That is proper for this usage. With a little more effort a true Exception handling can be used instead with emC features. The effort for sources from emC is kept low for the example because it is not the topic here.

The usage of any emC sources is not necessary in generally for the concept of C(++)-Function Blocks in Modelica like shown here. The Handle_Ptr64 mechanism is easy and can be adapted to any other basic C system. But it is recommended because some other features. The here used emC sources are:

The header files are commented in well readable Javadoc style. All emc-Files are open source with LPGL, hosted on www.github.com/JzHartmut/emc. The system is described on emC.


8 Modelica-Example and Download

Topic:.Mdlc.FB_Clang..

The ../Download folder contains FB_Clang_2019mmdd.zip. This file can be unzipped to any location. It contains the shown model (above) and the modelica FBlocks in FB_Clang/mdlc/MyTestFnC. All sources to build the C-objects and library inclusively the used zbnf.jar for the Zmake approach are contained. The compiled library and Objects are stored in the same directory as the model FB_Clang/mdlc/MyTestFnC. For first test without compilation you can copy the *.o, *.h and *.a in the Modelica working directory for this model (%TMP%\OpenModelica\OMEdit\MyTestFnC_Mdl). To build it newly the *.mk.bat should be invoked. They should run because the OPENMODELICAHOME environment variable should be set in your system due to the Modelica installation. To build the model for simulation the header files

If the model runs the following outputs are created: