MATLAB Examples

Calling Shared Libraries from Simulink

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

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 (also see below).

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.

if isunix
    mex('LDEXT=.so','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.

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
specs.HostLibFiles = {'exlib'};
% 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.c')
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 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
   */

  #ifdef __GNUC__
    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;

  #ifdef __GNUC__
    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
   */

  #ifdef __GNUC__
    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 (not possible to define init/term functions (only through model settings) and problems using 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
persistent initDone

% 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',system_dependent('GetSharedLibExt')]);
% 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(initDone)
    % Initialize
    coder.ceval('exlib_init');
    initDone = 1;
end
% Step
coder.ceval('exlib_print',single(u));
% Terminate
%coder.ceval('exlib_term');

It is not possible to define terminate function in MATLAB Function directly (compare with MATLAB System block below). To define the terminate function for the model, in Model Settings, Simulation Target -> Custom Code -> Terminate function, put exlib_term();.

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

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.

This is a relatively new approach, since support for MATLAB System block in Simulink appeared in R2013b. I personally like this approch a lot, since it allows you to easily define initialize/step/terminate functions, plus you can add in your own auxiliary MATLAB code around those functions - for example, to pre/post process data from step function.

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 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:

if isunix
    libName = 'simlib_test_ert';
else
    libName = ['simlib_test_ert_',computer('arch')];
end
if verLessThan('MATLAB','8.5')
    [~,~] = loadlibrary([libName,system_dependent('GetSharedLibExt')],...
        'simlib_test_ert_ert_shrlib_rtw/simlib_test_ert.h');
else
    [~,~] = coder.loadlibrary([libName,system_dependent('GetSharedLibExt')],...
        'simlib_test_ert_ert_shrlib_rtw/simlib_test_ert.h');
end
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 (current directory in this case)
coder.extrinsic('pwd');
libpath = coder.const(pwd);
% 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('simlib_test_ert_ert_shrlib_rtw/simlib_test_ert.h');

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

8. 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.

I must admit i underestimated MATLAB System block since it first appeared in R2013b. This block might as well become a Legacy Code Tool killer :) It provides the following advantages:

  • Fits very well in initialize/step/terminate paragidm, allowing to maintain all of these functions within the block itself and not model-wide
  • Allows to add custom code around the called external functions
  • Allows you to stay in MATLAB and write just MATLAB code
  • The MATLAB System block is self-contained and doesn't require additional steps to share (no need to compile the S-function, for example)

The drawback of using MATLAB System block is that it might incur additional overhead at its interfaces during code generation. As of 2017, 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.