Call shared libraries from Similink

Hi, Habr!
I present to you the translation of an article by my colleague Mikhail on methods for invoking shared libraries in Simulink. Why was it created at all? The fact is that many companies already have many legacy models that we would like to reuse and we are often asked questions: “How can I integrate legacy into Simulink? And if my legacy is in the form of a DLL? ” Therefore, the original article was written.
Under the cat, several ways to call shared libraries in Simulink are discussed.

All changes to the code were made with the permission of Mikhail and made so as not to overload the article.


This article shows how to use functions from shared libraries in Simulink models.
Let's say we need to embed the exlib library in Simulink. Its code is contained in the sources of exlib.c and exlib.h . This is a very simple library containing three functions:
void exlib_init (void) - opens a file for writing.
void exlib_print (float data) - writes data to a file.
void exlib_term (void) - closes the file.

Library assembly


First, compile the library.
This step is not necessary if the library is precompiled and delivered as a binary file. In this case, you can immediately go to step 3, but remember that to work with the library you need to get the .dll file (or .so for Linux) and the corresponding header file (.h) from the library provider. For Windows, you will also need a .lib file, which is not a static library, but the so-called import library. This import library is required for implicit linking.
First, we will use the MATLAB mex command, which calls the current active host compiler to compile the shared library (exlib.dll on Windows and exlib.so on Linux). It is important to make sure that the command
mex -setup


to select a supported compiler .
Now use mex to compile the library:

Code for building a library
mingw = strfind(mex.getCompilerConfigurations('C','Selected').Name,'MinGW64 Compiler');
if isunix
    % GCC
    mex('LDEXT=.so','LINKEXPORT=','LINKEXPORTVER=','LINKLIBS=','exlib.c');
elseif mingw
    % MinGW builds static shared library, so dll and lib files are the same
    % loadlibrary uses the dll file, while legacy code tool looks for the lib file
    mex('LDEXT=.lib','LINKEXPORT=','LINKEXPORTVER=','LINKLIBS=','exlib.c');
    mex('LDEXT=.dll','LINKEXPORT=','LINKEXPORTVER=','LINKLIBS=','exlib.c');
else
    % Visual
    mex('LDEXT=.dll','LINKEXPORT=','LINKEXPORTVER=','CMDLINE300="del exlib.exp exlib.dll.manifest"','exlib.c');
end
Building with 'gcc'.
MEX completed successfully.



Checking the compiled library


After compilation, you need to make sure that the resulting library can be loaded and used in MATLAB:

Code for checking the library
% Load the library
[~,~] = loadlibrary(['exlib',system_dependent('GetSharedLibExt')],'exlib.h');
% Display functions available in the library
libfunctionsview('exlib');
% Initialize
calllib('exlib','exlib_init');
% Step
for i = 1:10
    calllib('exlib','exlib_print',single(i));
end
% Terminate
calllib('exlib','exlib_term');
% Unload the library
unloadlibrary('exlib');
% Show contents of generated file
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000


Now that we’ve made sure everything works, let's get started with Simulink!

Invoking a shared library using S functions


S-functions allow you to use C-code in Simulink. This approach allows the user to develop any C code needed to load the library and call its functions. Then, this code is compiled into a binary file that is recognized by Simulink and linked to a shared library. This binary is called an S function. Simulink has a block for calling this S function. There are several approaches for creating S-functions in Simulink.

Using the Legacy Code Tool


The first approach is to use the Legacy Code Tool , a tool that helps you automatically create S-functions according to specifications created using MATLAB. In this example, the S-function is linked to the import library (on Windows we need the corresponding * .lib file) and the library functions are called. This approach is called implicit linking.
First, you need to initialize the structure for the Legacy Code Tool
Code for initializing the Legacy Code Tool structure
specs = legacy_code('initialize');
% Prototype for the initialization function
specs.StartFcnSpec = 'exlib_init()';
% Prototype for the step function
specs.OutputFcnSpec = 'exlib_print(single u1)';
% Prototype for the terminate function
specs.TerminateFcnSpec = 'exlib_term()';
% Shared library to link with (.so on Linux and .dll on Windows)
specs.HostLibFiles = {['exlib',strrep(system_dependent('GetSharedLibExt'),'.dll','.lib')]};
% We must supply header file when linking with shared library, otherwise
% compiler might make wrong assumptions about function's prototype.
specs.HeaderFiles = {'exlib.h'};
specs.SFunctionName = 'sfun_exlib';



Create an S-function, compile and link it:
legacy_code('generate_for_sim',specs);
### Start Compiling sfun_exlib
    mex('sfun_exlib.c', '-I/tmp/simulink_shrlib_fex', '/tmp/simulink_shrlib_fex/exlib.so')
Building with 'gcc'.
MEX completed successfully.
### Finish Compiling sfun_exlib
### Exit


Finally, create the Simulink block:
legacy_code('slblock_generate',specs);


Run the simulation:
open_system('simlib_test');
snapnow;
sim('simlib_test');
% Observe the results:
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000


Writing S Functions Manually


The second approach is to write your own S-functions for loading a shared library and calling functions from this library. This approach will use explicit linking, also called runtime linking. The easiest solution is to adapt the S-function that was automatically generated earlier by the Legacy Code Tool. In this example, the mdlStart , mdlOutputs, and mdlTerminate functions are modified .
Here's how these functions look after modification:
View code
void mdlStart(SimStruct *S)
{
  /*
   * Load the dynamic library
   */

  #if defined(__GNUC__) && !defined(__MINGW32__)
    void (*exlib_init_ptr)(void);
    dllHandle = dlopen("./exlib.so",RTLD_LAZY);
    exlib_init_ptr = dlsym(dllHandle, "exlib_init");
  #else
    exlib_init_type exlib_init_ptr = NULL;
    dllHandle = LoadLibrary("exlib.dll");
    exlib_init_ptr = (exlib_init_type)GetProcAddress(dllHandle,"exlib_init");
  #endif

  exlib_init_ptr();
}


void mdlOutputs(SimStruct *S, int_T tid)
{
  real32_T *u1 = 0;

  #if defined(__GNUC__) && !defined(__MINGW32__)
    void (*exlib_print_ptr)(float);
    exlib_print_ptr = dlsym(dllHandle,"exlib_print");
  #else
    exlib_print_type exlib_print_ptr = NULL;
    exlib_print_ptr = (exlib_print_type)GetProcAddress(dllHandle,"exlib_print");
  #endif

  /*
   * Get access to Parameter/Input/Output/DWork/size information
   */
  u1 = (real32_T *) ssGetInputPortSignal(S, 0);

  /*
   * Call the function from library
   */
  exlib_print_ptr( *u1);
}


void mdlTerminate(SimStruct *S)
{
  /*
   * Unload the dynamic library
   */

  #if defined(__GNUC__) && !defined(__MINGW32__)
    void (*exlib_term_ptr)(void);
    exlib_term_ptr = dlsym(dllHandle,"exlib_term");
    exlib_term_ptr();
    dlclose(dllHandle);
  #else
    exlib_term_type exlib_term_ptr = NULL;
    exlib_term_ptr = (exlib_term_type)GetProcAddress(dllHandle,"exlib_term");
    exlib_term_ptr();
    FreeLibrary(dllHandle);
  #endif
}



Compile the resulting S-function:
if isunix
    mex('sfun_exlib_dyn.c','-ldl');
else
    mex('sfun_exlib_dyn.c');
end
Building with 'gcc'.
MEX completed successfully.


And run the simulation:
open_system('simlib_test_dyn');
sim('simlib_test_dyn');
% Observe the results:
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000


Using S-Function Builder


Another approach is to use the S-Function Builder block . This is a special unit that can be seen as a cross between the Legacy Code Tool and hand-written S-functions in terms of complexity. S-Function Builder provides a graphical interface that describes the characteristics of your S-function, and the S-function is created automatically.
There are some performance limitations and problems with using S-Function Builder. Currently, this is considered an obsolete approach to creating S-functions. It is recommended to use the C function block, which appeared in the release of R2020a and is discussed later.

Invoking a shared library using MATLAB Function


The MATLAB Function block allows you to use the MATLAB language to describe a custom algorithm in Simulink.
To call the shared library from the MATLAB function, use the coder.ceval function , which can only be used in the body of the MATLAB Function (and not MATLAB). Coder.ceval does not require MATLAB Coder to work.
Code for the MATLAB Function block calling the shared library:
View code
function fcn(u)
%#codegen

% Keep track of initialization and runtime count
persistent runTimeCnt

% Generate library path on the fly (current directory in this case)
coder.extrinsic('pwd','system_dependent');
libpath = coder.const(pwd);
% Shared library to link with
libname = coder.const(['exlib',strrep(system_dependent('GetSharedLibExt'),'.dll','.lib')]);
% Add the external library. Mark it as precompiled, so it won't appear as
% makefile target during code generation.
coder.updateBuildInfo('addLinkObjects',libname,libpath,1000,true,true);
coder.updateBuildInfo('addIncludePaths',libpath);
coder.cinclude('exlib.h');

if isempty(runTimeCnt)
    % Initialize
    coder.ceval('exlib_init');
    runTimeCnt = 0;
end
% Step
coder.ceval('exlib_print',single(u));
runTimeCnt = runTimeCnt+1;
% Terminate on the 10th step
if (runTimeCnt == 11)
    coder.ceval('exlib_term');
end



This approach has one drawback - the lack of a good way to call the completion function at the end of the simulation (compared to the MATLAB System block).
You can define the completion function for the model by setting exlib_term (); in the model settings in the category Simulation Target -> Custom Code -> Terminate function .

NOTE . If the “Import custom code” parameter is set in the model settings, then you need to specify all the code dependencies in the “Simulation Target” panel (instead of using coder.cinclude and coder.updateBuildInfo ). If this parameter is not set, then you can combine the settings from Simulation Target, coder.cinclude and coder.updateBuildInfo.


Another way is to maintain dependencies usingcoder.cinclude and coder.updateBuildInfo , and call exlib_term () by convention, as demonstrated in the example above.
Run the simulation:
open_system('simlib_test_mlf');
sim('simlib_test_mlf');
% Observe the results:
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000


Invoking a shared library from Stateflow


If you want to use the shared library functions in Stateflow diagrams , then these functions should be called directly from Stateflow. Stateflow documentation has examples that show how to do this.
Calling an external function in Stateflow is very simple - you need to specify the name of the function in the Stateflow diagram:


Additionally, you need to configure the model parameters so that Stateflow knows where to look for these external functions. In the settings of Simulation Target -> Custom Code -> Libraries, you need to enter exlib.lib (or exlib.so in Linux) . In Simulation Target -> Custom Code -> Header File you need to enter #include "exlib.h" . It is also important not to forget to specify the completion function. ATSimulation Target -> Custom Code -> Terminate function must specify exlib_term (); .
Run the simulation:
if isunix
    set_param('simlib_test_sf','SimUserLibraries','exlib.so');
end
sim('simlib_test_sf');
% Observe the results:
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000


Note that the information in this section applies to Stateflow diagrams that use the C action language . If the Stateflow diagram uses the MATLAB action language , then coder.ceval is required , as is the case with the MATLAB Function.

Invoking a shared library using the MATLAB System block


The MATLAB System block allows you to use system objects in Simulink. More information about this block can be found in the documentation.
Support for system objects appeared in Simulink in the release of R2013b. Many people use system objects because they make it easy to define the initialization, simulation, and completion functions. Also, system objects can use the auxiliary MATLAB code for these functions — for example, for pre- and post-processing data of a step function — all without writing C code.
Here's what the system object used in the MATLAB System block looks like:
System Object Code
classdef exlib < matlab.System
    % Call exlib shared library
    %
    % This example shows how to call shared library from Simulink using
    % MATLAB System block.
    properties (Nontunable,Access=private)
        libName = exlib.getLibName;
        libPath = pwd;
        libHeader = 'exlib.h';
    end
    
    methods (Static)
        function libName = getLibName
            if isunix
                libName = 'exlib.so';
            else
                libName = 'exlib.lib';
            end
        end
    end
    
    methods (Access=protected)
        function setupImpl(obj, ~)
            % Initialize.
            coder.updateBuildInfo('addLinkObjects',obj.libName,obj.libPath,1000,true,true);
            coder.updateBuildInfo('addIncludePaths',obj.libPath);
            coder.cinclude(obj.libHeader);
            coder.ceval('exlib_init');
        end
        
        function stepImpl(~, u)
            % Step.
            coder.ceval('exlib_print',u);
        end
        
        function releaseImpl(~)
            % Terminate.
            coder.ceval('exlib_term');
        end
    end
end



Run the simulation:
open_system('simlib_test_mls');
sim('simlib_test_mls');
% Observe the results:
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000


Invoking shared libraries using the C Caller block


The C Caller block allows you to call C functions directly from Simulink. More information about this block can be found in the documentation.
This is a relatively new approach that first appeared in MATLAB R2018b. Its main goal is to make function calls and C libraries in Simulink extremely simple. But it has limitations, which you can read about in the documentation for this block.
After exlib.so/exlib.lib has been added to Simulation Target -> Libraries and #include "exlib.h" in Simulation Target -> Header in the model settings, just click the "Refresh custom code" button in the C Caller block, to see all the functions contained in the library.
After selecting the exlib_print function , the port specification dialog is filled in automatically:

And again, you need to add the exlib_init and exlib_term function calls to the Simulation Target. You can also add a couple of other C Caller blocks to directly call the initialization and termination functions. These C Caller blocks need to be placed in the Initialize Function and Terminate Function subsystems. You can also consider the following example from Stateflow: Schedule Subsystems to Execute at Specific Times
Run the simulation:
open_system('simlib_test_ccaller');
if isunix
    set_param('simlib_test_ccaller','SimUserLibraries','exlib.so');
end
sim('simlib_test_ccaller');
% Observe the results:
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000


Invoking shared libraries using the C Function block


The latest addition to integrating external code into Simulink is the C Function block . He appeared in the R2020a.
It resembles the C Caller block in terms of ease of use, but allows you to create a C wrapper around imported functions (thus, this block resembles S-Function Builder). But, most likely, the main scenario for using the C Function block is not to call existing C functions, but to write small pieces of C code, if such code is needed in the application. For example, you may need access to hardware registers or the compiler's built-in functions.
Do not forget to add exlib.so/exlib.lib to the settings "Simulation Target -> Libraries" and #include "exlib.h"in the settings "Simulation Target -> Header file" in the model settings.
After that, in the settings of the C Function block, you need to add a character for the input data with the data type single and specify the output, start and end code:


open_system('simlib_test_cfunction');
if isunix
    set_param('simlib_test_cfunction','SimUserLibraries','exlib.so');
end
sim('simlib_test_cfunction');
% Observe the results:
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000


Invoking shared libraries generated using Embedded Coder


One of the scenarios for using Embedded Coder is the automatic generation of C-code from the Simulink model and the packaging of this code in a shared library. The documentation also has an example that shows how to automatically generate a shared library and call it from an external application written in C.
Note that if you just need to run the algorithm or part of the algorithm as C code in the same Simulink model, then it’s better to use S-functions of Simulink Coder or software-in-the-loop simulation. However, if the shared library generated by Embedded Coder is used for semi-natural modeling, it may be necessary to integrate the same library into the larger model used by other developers and thus protect their intellectual property.
Working with a shared library generated by Embedded Coder is no different from working with a shared library, which was used in the article. Consider a simple model that has two inputs and one output:
open_system('simlib_test_ert');
snapnow;



After assembling the model, we get a .dll file (.so on Linux), a .lib file (an import library for .dll), and a .exp file (an export file for communicating with .dll).
if isunix
    set_param('simlib_test_ert','CustomHeaderCode','#include <stddef.h>');
end
rtwbuild('simlib_test_ert');
### Starting build procedure for: simlib_test_ert
### Successful completion of build procedure for: simlib_test_ert


The generated shared library exports the following characters:
ex_init
double ex_step(double, double)
simlib_test_ert_terminate


By default, inputs and outputs are exported as global characters, and the initialization, step, and completion functions follow the model naming convention. If necessary, you can configure function prototypes, symbol names, and more (a discussion of these settings is beyond the scope of this article). In this model, the prototype of the step function of the model is defined as Out1 = ex_step (In1, In2) .
To call these functions, you need to use one of the methods listed above. For example, you can use the MATLAB Function (for simplicity we only call the step function):
View code
function y = fcn(u1, u2)
%#codegen

% Generate library path on the fly
coder.extrinsic('RTW.getBuildDir','fullfile');
buildDir = coder.const(RTW.getBuildDir('simlib_test_ert'));
libpath = coder.const(buildDir.CodeGenFolder);
incpath = coder.const(fullfile(buildDir.BuildDirectory,'simlib_test_ert.h'));
% Shared library to link with
if isunix
    ext = '.so';
    libname = ['simlib_test_ert',ext];
else
    ext = '.lib';
    libname = ['simlib_test_ert_',computer('arch'),ext];
end
% Add the external library. Mark it as precompiled, so it won't appear as
% makefile target during code generation.
coder.updateBuildInfo('addLinkObjects',libname,libpath,1000,true,true);
coder.updateBuildInfo('addIncludePaths',libpath);
coder.cinclude(incpath);

% Initialize output
y = 0;
% Step
y = coder.ceval('ex_step',u1,u2);



Run the simulation and look at its results:
open_system('simlib_test_callert');
sim('simlib_test_callert');
snapnow;



findings


This article describes various approaches to invoking shared libraries from Simulink models. Both implicit and explicit links were considered. All methods have their pros and cons, and their suitability depends on the specific workflow, requirements and goals.
The Legacy Code Tool approach works best when implementing an implicit link with a shared library, because in this case we can just call functions directly from the library, and the linker will take care of the rest.
The MATLAB System block is another approach that provides the following benefits:
  • Excellent compliance with the paradigm of initialization, step and completion functions, allowing you to maintain all these functions within the block itself, and not on the scale of the model

  • MATLAB MATLAB
  • MATLAB System (, S-)

The disadvantage of using the MATLAB Function block is that its use can lead to an additional overhead during code generation. Thus, the Legacy Code Tool and S functions are still preferred for generating production code.
A hand-written S function is best suited for implementing an explicit link to a shared library. In this case, you need to use functions such as LoadLibrary / dlopen , GetProcAddress / dlsym , FreeLibrary / dlclose in order to be able to call functions.
The C Caller block introduced in R2018b is by far the easiest way to call C functions, but it has its limitations. The same can be said for the C Function block.which is the newest addition to the R2020a.

Download sources and models here

All Articles