Inhalt
Topic:.Mdlc.FB_Clang.
Last changed: 2019-04-11
This description should show the principles of calling own C language routines in modelica.
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.
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.
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.
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 typedef struct ...
definition results from the model content. It is available after the code generation (before simulation start). But the C
source should be translated to an object or library independent of the Modelica model (to test as unit) before.
It may be proper to define a record in Modelica from a given C typedef struct
, reverse to the code generation. In comparison: Simulink knows an 'import' for bus definition for that reason. But that way
should not be possible yet in modelica ?
To use the correct typedef struct
and the correct prototype of the routine it is possible to copy the generated line in an own Headerfile, in the download
example myTestFnRecord.h
. It should be done before the first use, because the C routine is not ready yet. Then write and test the C routine.
For further using the generated code should be compared with the stored lines. A simple text diff tool can be used to compare
the newly generated file MyTestFnC_Mdl_functions.h
(found in the modelica temp directory) with the myTestFnRecord.h
. If the model is not changed, it is consistent. But if the model is changed, firstly it is necessary to update the C source.
If the model is changed and the C source is not updated, a errorneous behavior may occur.
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:
Topic:.Mdlc.FB_Clang.CobjO.
That is a consequently approach:
The C routines allocates its own data. It is possible to test the C routines as modules or units independent of the using model. It means the C routines are core functionalities (core FunctionBlocks) for modelling.
The model manages the references of the allocated data. A Modelica Block should not store the data itself (as in the chapter above) but the reference to the data between the calls. The references itself cannot be stored in the C level because the multi-instance-ability is not given then.
It is possible to associate a reference to the data of one core-C-FunctionBlock to another on model level. This is adequate to associations in a ObjectModelDiagram in UML. Only the direction of the connection is reverse (Uml to the depending object, Model to the using block). It is the base of ObjectOrientation in the model für C core FunctionBlocks.
There is one problem: Modelica does not know a 'reference'-type as pointer value. In a 64-bit-PC-System references have 64
bit and the Integer type (candidate to use) has only 32 bit. It is possible to store a 64-bit-reference in a Real
value (8 byte), but that is confuse. A latent problem may be: The values can be changed by some arithmetic operations. But
that is very wrong.
The solution is: The pointer values are stored in a centralized array of pointer handle2Ptr
. It is existing one time for the whole running model. The 'references' on model level are the indices to the handle2Ptr
array. The array should be large enough for all instances. An amount of about 1000 pointer may be sufficient. That is no
problem for memory effort. 100000 is not a problem too. The number can be defined on demand. This solution is used for Simulink
ObjectOrientation too.
The model accesses the 'internal C'-data via function call from the C level. It is fast.
For code generation for a small target in C the access to data is generated immediately as inline, it is fast. The handle2ptr
array is not necessary if the target system has 32 bit addresses. Then the Integer
reference variables contains the memory address immediately. It is fast.
With this opinions the following example is able to download and test.
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.
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.
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.
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).
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.
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:
The Exception handling can be used, inclusively the asynchon exception on memory access violation. That is important on development and troubleshooting.
Some features in C++, class methods, overridden operations, can be used for PC testing even if C++ should not be used for target compilation.
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:
emc/incApplSpecific/TargetNumericSimple64/applstdef_emC.h
: It defines universal settings for a system without true Exception handling, without using of StringJc
and without a complex ObjectJc
with reflection for a simple well tested target system. Because it is for a 64-bit-Platform the usage of Handle_ptr64_emC
is considered.
incComplSpecific/cc_Gcc64/compl_adaption.h
is included from applstdef_emC.h
and defines basic types depending on compiler features. Especially int32
etc. are available, as soon as bool
and true
, false
for C language (compatible to C++). This file can be adapt to programming styles of existing legacy sources. For example
given typedef int32 ...
from a legacy common header which should be used can be considered or adequate types in user sources such as INT32
can be supported.
emc/sourceApplSpecific/SimpleNumCNoExc/ExcNoStringStacktrcNo_emC.h
: This is the header to define macros for Excpetion handling for the well tested version without true Exception handling.
.../ThreadContextSingle_emC.c
The ThreadContext
is essential for Exception handling and its macros. This is the simple form.
.../ObjectJc_simple.h
: ObjectJc
is a universal base class, here in the simple form.
emc/sourceApplSpecific/applConv/EnhanceRef_simple.h
: It is included but not used here. It is for a concept with a block heap for runtime allocations without fragmentation.
.../assert_THROW.h
some assertion macros forces THROW
.
.../definePrintfMakrosEmpty.h
; Included but not used, able to use PRINTF
macros for 'printf-debugging', here empty.
emc/source/OSAL/os_types_def_common.h
: common types included from compl_adaption.h
ExcThCxtBase_emC.h
: Basic definitions for Exception handling, universal able to use. For true, abort or fake Exception handling.
.../Handle_ptr64_emC.*
: The here used conversion from pointer to 32-bit-handle and vice versa.
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.
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: