Calling Shared Libraries from Simulink

This paper discusses several approaches for calling shared libraries from Simulink models.

NOTE: This example is best viewed in MATLAB's built-in browser. Simply download this FileExchange submission and open the HTML file in MATLAB.

Consider the example library called exlib. Its code is provided in files exlib.c and exlib.h. This is a very simple library that consists of three functions:

void exlib_init(void) opens file for writing.

void exlib_print(float data) writes supplied data into the file.

void exlib_term(void) closes the file.

Contents

1. Compiling the example library

To start off, let's compile the library.

Obviously, you don't need this step if your library is precompiled and ships to you as a binary file. In this case you can go straight to step 3, but note that you need to get .dll (or .so for Linux) file and the according header file (.h) from your library provider. For Windows, you will also need the .lib file - which is not the static library, but the so called import library. You need the import library in case you are going to do implicit linking.

We are using MATLAB mex command which in turn calls the chosen host compiler to compile the shared library (exlib.dll on Windows, and exlib.so on Linux). It is important to make sure that you ran the

mex -setup

command first, to choose the supported host compiler.

Little trick: Using mex to compile the shared 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.

2. Making sure the library can be loaded

We can test this shared library from MATLAB (though it's not a scope of this paper) to make sure it works.

% 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');

Now that the shared library is available, let's get it working with Simulink!

3. Calling shared library using C S-function

C S-function is a way to bring C code into Simulink. This approach allows user to write whatever C code is needed to load their library and call functions in it. This C code is then compiled into a binary file that is recognized by Simulink and linked against the shared library. This binary file is called S-function. There is an according block in Simulink to call this S-function. There are several approaches for creating of C S-functions in Simulink.

3.1. Using Legacy Code Tool

One approach is to use Legacy Code Tool that helps users create S-functions automatically from supplied specifications expressed with MATLAB code. In this example, we link with import library (we need a *.lib file for that on Windows) and call C functions from that library. This approach is also called a load-time linking or implicit linking.

% Initialize structure for Legacy Code Tool
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 S-function, compile and link in one step:

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

Create Simulink block (if needed) using the generated S-function:

legacy_code('slblock_generate',specs);

Let's 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

3.2. Using manually written S-Function

Another approach is to manually write our own S-Function to load shared library and call C functions from that library. With this approach, we will be using explicit linking, also referred to as run-time linking. The easiest is to adapt the S-Function automatically generated by Legacy Code Tool previously. In this case, we will have to modify mdlStart, mdlOutputs and mdlTerminate functions.

Here is how these three functions look like after modification:

ctext = fileread('sfun_exlib_dyn.c');
match = regexp(ctext, 'void mdlStart.*?\n\}', 'match'); fprintf('%s\n\n',match{1});
match = regexp(ctext, 'void mdlOutputs.*?\n\}', 'match'); fprintf('%s\n\n',match{1});
match = regexp(ctext, 'void mdlTerminate.*?\n\}', 'match'); fprintf('%s',match{1});
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 manually written S-Function:

if isunix
    mex('sfun_exlib_dyn.c','-ldl');
else
    mex('sfun_exlib_dyn.c');
end
Building with 'gcc'.
MEX completed successfully.

Let's run the simulation:

open_system('simlib_test_dyn');
snapnow;
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

3.3. Using S-Function Builder

Yet another approach is to use S-Function Builder. This is a special block from Simulink library that in some sense stands between Legacy Code Tool and manually written S-Functions from complexity point of view. S-Function Builder provides a graphical user interface, where you describe characteristics of your S-Function and then it generates S-Function automatically for you.

TODO (need to figure out how to use global file pointer).

4. Calling shared library using MATLAB Function

MATLAB Function allows you to use MATLAB language to describe your algorithm in Simulink.

In order to call shared library from MATLAB Function, we will be using coder.ceval function, which can only be used from within a MATLAB Function. Apparently, you don't need to have MATLAB Coder installed for coder.ceval to work.

This is how the MATLAB Function looks like:

% Display MATLAB Function
open_system('simlib_test_mlf');
open_system('simlib_test_mlf/MATLAB Function');
doc = matlab.desktop.editor.getActive;
disp(doc.Text);
close_system('simlib_test_mlf');
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

There is no good way to call terminate function when using MATLAB Function (compare to MATLAB System block below).

One way to define terminate function for the model is to put exlib_term(); in Model Settings, Simulation Target -> Custom Code -> Terminate function. If the Import custom code option is set, you have to specify all code dependencies in the Simulation Target panel (rather than using coder.cinclude and coder.updateBuildInfo). If this option is not set, you can combine Simulation Target settings, coder.cinclude and coder.updateBuildInfo.

Another way is to maintain the dependencies using coder.cinclude and coder.updateBuildInfo, and call exlib_term() conditionally in MATLAB Function. This is how I chose to do it in this example.

Let's run the simulation:

open_system('simlib_test_mlf');
snapnow;
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

5. Calling shared library using Stateflow

If you need to use your shared library functions in a Stateflow chart, it makes sense to call them directly from Stateflow. There is a nice example in Stateflow documentation that shows just how to do that.

Calling external function in Stateflow is as easy as typing the function's name in the chart, when you want to use it:

open_system('simlib_test_sf');
snapnow;
open_system('simlib_test_sf/Chart');
snapnow;

You need to adjust Model Settings so that Stateflow knows where to look for these external functions. In Simulation Target -> Custom Code -> Libraries pane, put exlib.lib (or exlib.so on Linux). In Simulation Target -> Custom Code -> Header File, put #include "exlib.h". One potential pitfall is forgeting to include the terminate function. In Simulation Target -> Custom Code -> Terminate function, put exlib_term();.

Let's 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 is relevant for Stateflow charts that use C Action Language. If you are using Stateflow with MATLAB Action Language, the approach is the same as with coder.ceval and MATLAB Function, described above.

6. Calling shared library using MATLAB System block

MATLAB System block allows you to use System Object in Simulink. More information about this block can be found in documentation.

Support for MATLAB System block appeared in Simulink in R2013b. Since then, I've seen many people using this approch, since it allows to easily define initialize/step/terminate functions. You can add in your own auxiliary MATLAB code around those functions - for example, to pre/post process data from step function - all without writing a single line of C code.

Here is how System Object for use in MATLAB System block looks like:

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

Let's run the simulation:

open_system('simlib_test_mls');
snapnow;
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

7. Calling shared library using C Caller block

C Caller block allows you to call C functions directly from Simulink. More information about this block can be found in documentation.

This is the newest approach, which first appeared in MATLAB R2018b. Its main purpose is to make calls to C functions and libraries ridiculously easy in Simulink. But it comes with some limitations that you can read about in the block's documentation.

After I've added the exlib.so/exlib.lib to the Simulation Target -> Libraries pane and #include "exlib.h" to the Simulation Target -> Header file pane in model settings, it was just a matter of clicking "Refresh custom code" in C Caller block to be able to see all the functions in the library.

After I've chosen the exlib_print function, port specification dialog was populated automatically:

Don't forget to add calls to exlib_init and exlib_term functions using the same Simulation Target pane. Alternatively, drop in a couple of other C Caller blocks to call initialization and termination functions directly. If you choose to use C Caller blocks for init and term, you will probably find Initialize Function and Terminate Function blocks very useful. You might also find this Stateflow example useful: Schedule Subsystems to Execute at Specific Times

Let's run the simulation:

open_system('simlib_test_ccaller');
snapnow;
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

8. Calling shared library generated by Embedded Coder

You can use Embedded Coder to automatically generate C code from your Simulink model and package it as a shared library. There is also an example in documentation that shows how to automatically generate shared library and call it from external application written in C.

Note that if you just want to run your algorithm or part of your algorithm as a C code within the same Simulink model, then it's better done with Simulink Coder's S-Function target or Software-in-the-Loop simulation.

However, if you already ship Embedded Coder generated shared library for example for HIL simulator, you might want to integrate the very same library into a larger model maintained by your collegue or customer (and protect the IP).

Embedded Coder generated shared library is nothing different from the example shared library we used throughout the paper. Consider a simple model that has two inputs and one output:

open_system('simlib_test_ert');
snapnow;

After building the model, you will get .dll file (.so on Linux), .lib file (import library for the .dll) and .exp file (export file for linking with .dll).

if isunix
    set_param('simlib_test_ert','CustomHeaderCode','#include <stddef.h>');
end
rtwbuild('simlib_test_ert');
### Starting build procedure for model: simlib_test_ert
### Successful completion of build procedure for model: simlib_test_ert

The generated shared library exports the following symbols:

buildDir = RTW.getBuildDir('simlib_test_ert').BuildDirectory;
cgDir = RTW.getBuildDir('simlib_test_ert').CodeGenFolder;
if isunix
    libName = 'simlib_test_ert';
else
    libName = ['simlib_test_ert_',computer('arch')];
end
fullLibName = fullfile(cgDir,[libName,system_dependent('GetSharedLibExt')]);
[~,~] = coder.loadlibrary(fullLibName,fullfile(buildDir,'simlib_test_ert.h'));
libfunctions(libName,'-full');
unloadlibrary(libName);
Functions in library simlib_test_ert:

ex_init
double ex_step(double, double)
simlib_test_ert_terminate


By default, inputs and outputs are exported as global symbols, and initialize/step/terminate functions follow the model naming convention. You can configure function prototypes, symbol names and storage if necessary (but that's way out of this paper's scope). In this model, i've set the model step function prototype to be Out1 = ex_step(In1, In2).

To call these functions you only have to apply one of the above methods. For example, I can use MATLAB Function (for simplicity, I only call the step function) to achieve that:

% Display MATLAB Function
open_system('simlib_test_callert');
open_system('simlib_test_callert/MATLAB Function');
doc = matlab.desktop.editor.getActive;
disp(doc.Text);
close_system('simlib_test_callert');
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);

Let's simulate and observe results:

open_system('simlib_test_callert');
sim('simlib_test_callert');
snapnow;

9. Conclusion

This paper describes various approaches for calling shared libraries from Simulink models. Both implicit (load-time) and explicit (run-time) linking was considered. All methods have their pros and cons and their fitness depends on your specific workflow, requirements and aims.

Legacy Code Tool approach is the best fit when implementing load-time linking with a shared library, since in this case we can just call functions from the library directly, and the linker will take care of the rest.

MATLAB System block is another approach which provides the following advantages:

The drawback of using MATLAB System block is that it might incur additional overhead at its interfaces during code generation. As of year 2019, Legacy Code Tool and S-functions are still preferred for production code generation.

Manually written S-function is the best fit when implementing run-time linking with a shared library. In this case we have to use functions like LoadLibrary / dlopen, GetProcAddress / dlsym, FreeLibrary / dlclose to be able to call our functions.

C Caller block, which appeared in R2018b, is by far the easiest way to call C functions, but it comes with its limitations.