Modelica external C functions
This post looks into getting external functions working in OpenModelica and, to an extent, Dymola. There are a couple of approaches and I hope to give you a better idea of what to look out for when you come to implement your functions.
You may have noticed that my series on Modelica socket communication stalled somewhat after only one part). Originally, this post was going to be part 2 of the series, but it has become somewhat more general, so I’m posting it as a separate thing.
References
Two good references to get started with external functions are the External Function—Chirp Signal document by Wolfram Research and the section on External Functions from Modelica by Example.
Tools
Throughout this post, I will be assuming that you have either OpenModelica (with the bundled MinGW distribution in Windows or the system-provided GCC in Linux) or Dymola with a separately installed MinGW distribution (I’ve only used Dymola in Windows to date). Using Visual Studio to compile object files for OpenModelica to use has more annoyances than it’s worth dealing with, and both OpenModelica and Dymola are quite happy using MinGW. Whatever makes life simpler can only be a good thing.
So, without further ado, let’s dig into the guts of getting external code working with your Modelica simulation.
Integration of external code into the compiled simulation
I’ve found two main ways that people have suggested for getting external C code compiled into the final simulation. The first way is to separately compile the code into an object file and then instruct the Modelica compiler to link against it, while the second is to tell the Modelica compiler to directly include your source as an input file.
For the purpose of this discussion, let’s assume you have a source file ext_lib.c containing the following demonstration function (we’ll have to pretend it’s a critically important thing that can’t be implemented via Modelica):
int funcname(int n)
{
if (n < 2)
return -1;
else
return n * 4;
}
So let’s take a look at the two different ways of getting access to this function from Modelica.
Separate compilation
Part 1) of my Modelica Socket Communication series gave a rundown on compiling the external code using GCC under Linux (in that case Ubuntu/Mint). Compiling using MinGW is just as easy, though you’ll probably end up using GCC directly rather than libtool. Simply run the following command:
gcc -c ext_lib.c
This will create the ext_lib.o file, which should be copied into your working directory. You then have to create a Modelica function that calls out to your external code:
function ext_funcname
input Integer n;
output Integer result;
external "C" result = funcname(n);
annotation(Library = "ext_lib.o");
end ext_funcname;
While the above technique works with OpenModelica under Windows and Linux, I didn’t have any success with Dymola, which refused to locate the object file regardless of where I placed it or how I referred to it in the annotation. Furthermore, separately compiling the code is an extra step that could be done without when you want to keep the setup process as simple as possible. These factors led me to the second method, which follows.
Letting the Modelica compiler do the work
I discovered this technique after getting frustrated with Dymola as noted above. This is more straightforward as it doesn’t require you to separately compile your external code into an object file. Instead, you tell the Modelica compiler to directly include your source during the simulation compilation. To do this, the annotation must be changed as shown below:
function ext_funcname
input Integer n;
output Integer result;
external "C" result = funcname(n);
annotation(Include = "#include \"ext_lib.c\"");
end ext_funcname;
This will insert the given #include line into the automatically generated C code for your simulation and the Modelica compiler will handle things from there. Note that C99 mode isn’t the default, so you may have to adapt your coding practices. If you have to call out to multiple external functions defined in ext_lib.c you should also use some protection as otherwise you will get errors about multiple definitions:
#ifndef __EXT_LIB_C
#define __EXT_LIB_C
// Your code goes here
#endif
Out of the two options, this second method is now my favoured one for the reasons given previously. In addition to not duplicating a step that the compiler is going to do anyway, you also have the advantage that the object file is guaranteed to be compatible for linking with the other simulation code, as it was generated using the same compiler as the other code and with identical options.
Data type translations
Now that we know how to call the external functions from Modelica, there is another consideration: data type translations between the languages. Anyone who has worked with code from two languages that must directly interact can tell you that this can cause some interesting problems. Luckily, Modelica has a sensible set of mappings that, most of the time, will match with what you would expect.
Assume you have the following function defined in your Modelica code, intended to call an external C function to retrieve some data:
function ext_get_data
input Integer n;
output Real data[n];
output Boolean success;
external "C" success = get_data(size(data, 1), data);
annotation(Include = "#include \"external_file.c\"");
end ext_get_data;
You might expect this to match with the following definition of get_data in the external C code:
bool get_data(int buflen, double buf[]);
In fact, I’ve found that the Dymola/MinGW combination will allow you to use this with no problems. OpenModelica, however, will enforce the data types described in Modelica by Example based on the Modelica function you created. This means that the automatically generated code will include a line such as the following:
extern int get_data(size_t buflen, double buf[]);
Which then gives you an error about conflicting definitions when it comes to compiling your model:
In file included from sim.c:19:
ext_lib.c:382: error: conflicting types for 'get_data'
sim_functions.h:21: note: previous declaration of 'get_data' was here
The solution here, of course, is to double check what type conversions Modelica will automatically perform, particularly when you directly use the return value from an existing function within Modelica (in this case, size). The conversion between Boolean on the Modelica side and int on the C side is also easy to miss.
So that’s all for this post, hopefully it will be of use to anyone looking to use external C functions with Modelica.