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 commandmex -setup
to select a supported compiler .Now use mex to compile the library:Code for building a librarymingw = strfind(mex.getCompilerConfigurations('C','Selected').Name,'MinGW64 Compiler');
if isunix
mex('LDEXT=.so','LINKEXPORT=','LINKEXPORTVER=','LINKLIBS=','exlib.c');
elseif mingw
mex('LDEXT=.lib','LINKEXPORT=','LINKEXPORTVER=','LINKLIBS=','exlib.c');
mex('LDEXT=.dll','LINKEXPORT=','LINKEXPORTVER=','LINKLIBS=','exlib.c');
else
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
[~,~] = loadlibrary(['exlib',system_dependent('GetSharedLibExt')],'exlib.h');
libfunctionsview('exlib');
calllib('exlib','exlib_init');
for i = 1:10
calllib('exlib','exlib_print',single(i));
end
calllib('exlib','exlib_term');
unloadlibrary('exlib');
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 ToolCode for initializing the Legacy Code Tool structurespecs = legacy_code('initialize');
specs.StartFcnSpec = 'exlib_init()';
specs.OutputFcnSpec = 'exlib_print(single u1)';
specs.TerminateFcnSpec = 'exlib_term()';
specs.HostLibFiles = {['exlib',strrep(system_dependent('GetSharedLibExt'),'.dll','.lib')]};
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');
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 codevoid mdlStart(SimStruct *S)
{
#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
u1 = (real32_T *) ssGetInputPortSignal(S, 0);
exlib_print_ptr( *u1);
}
void mdlTerminate(SimStruct *S)
{
#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');
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 codefunction fcn(u)
persistent runTimeCnt
coder.extrinsic('pwd','system_dependent');
libpath = coder.const(pwd);
libname = coder.const(['exlib',strrep(system_dependent('GetSharedLibExt'),'.dll','.lib')]);
coder.updateBuildInfo('addLinkObjects',libname,libpath,1000,true,true);
coder.updateBuildInfo('addIncludePaths',libpath);
coder.cinclude('exlib.h');
if isempty(runTimeCnt)
coder.ceval('exlib_init');
runTimeCnt = 0;
end
coder.ceval('exlib_print',single(u));
runTimeCnt = runTimeCnt+1;
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');
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');
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 Codeclassdef exlib < matlab.System
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, ~)
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)
coder.ceval('exlib_print',u);
end
function releaseImpl(~)
coder.ceval('exlib_term');
end
end
end
Run the simulation:open_system('simlib_test_mls');
sim('simlib_test_mls');
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 TimesRun the simulation:open_system('simlib_test_ccaller');
if isunix
set_param('simlib_test_ccaller','SimUserLibraries','exlib.so');
end
sim('simlib_test_ccaller');
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');
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 codefunction y = fcn(u1, u2)
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'));
if isunix
ext = '.so';
libname = ['simlib_test_ert',ext];
else
ext = '.lib';
libname = ['simlib_test_ert_',computer('arch'),ext];
end
coder.updateBuildInfo('addLinkObjects',libname,libpath,1000,true,true);
coder.updateBuildInfo('addIncludePaths',libpath);
coder.cinclude(incpath);
y = 0;
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