Main Content

Export Standalone FMU with External C++ Code

This example shows how to import external C++ code into Simulink® model using S-Function Builder and export it to a standalone FMU. S-Function Builder block lets user import C/C++ code into Simulink Semantic by building an S-function wrapper for external code. This example demonstrates this process in a phased approach by implementing a C++ multiply class, integrating C++ code with S-Function Builder, and exporting the model as a standalone FMU.

Implement Multiply Class with C++ Code

The following code implements a class multiply to be integrated into Simulink model. Class multiply takes a gain value in the constructor and multiplies it with an input value when the user calls member function double multiply::apply(double). The following implementation can be found in include/ and src/ directory.

// multiply class header, the following code is defined in include/multiply.hpp

class EXPORT multiply {
public:
    multiply(double init);
    ~multiply() = default;

    double apply(int val);
private:
    double gain; 
};

// multiply class source, the following code is defined in src/multiply.cpp

#include "multiply.hpp"

multiply::multiply(double init) {
    gain = init;
}

double multiply::apply(double val) {
    return val * gain;
}

Import External C++ Code with S-Function Builder

This section shows the process to integrate external C++ code into Simulink model using S-function Builder block:

  • Open Simulink model with an S-Function Builder block.

  • Instantiate class multiply in S-Function Output function.

  • Add C++ code path to S-Function Builder block Libraries Table.

modelWithSFcnBuilder.png

% Open example model with S-Function Builder block
open_system('FMUExportWithExternalCPP');

% Open S-Function Builder block dialog
open_system('FMUExportWithExternalCPP/S-Function Builder');

Use S-Function Builder block to include multiply.hpp and instantiate C++ class multiply in corresponding wrapper functions. The wrapper functions below instantiate and destroy an instance of class multiply in void cppwrapper_Start_wrapper(const real_T*, const int_T, void**) and void cppwrapper_Outputs_wrapper(const real_T*, real_T*, const real_T*, const int_T, void**). S-Function Builder block uses a dialog parameter defined in Parameter Table to instantiate an instance of class multiply and creates a PWork to store the pointer.

// add the following code to include header
#include "multiply.hpp"

// add the following code to void cppwrapper_Start_wrapper(const real_T*, const int_T, void**)
// the code below takes parameter from S-Function Builder block dialog to instantiate class multiply and store it in a PWork vector
    real_T val = p0[0];
    multiply* mulPtr = new multiply(val);
    pW[0] = mulPtr;
   
// add the following code to void cppwrapper_Outputs_wrapper(const real_T*, real_T*, const real_T*, const int_T, void**)
// gain input value
    multiply* mulPtr = static_cast<multiply*>(pW[0]);
    y0[0] = mulPtr->apply(u0[0]);

// add the following code to void cppwrapper_Terminate_wrapper(const real_T*, const int_T, void**)
// instance of class multiply is destroy when simulation terminates
    multiply* mulPtr = static_cast<multiply*>(pW[0]);
    delete mulPtr;

A complete example code is shown below:

S-Function Builder block requires the include and source directory to build external C++ code. User can define C++ file path and entry in Libraries table of S-Function builder and specify a target language from the Language setting combobox. For S-Function Bluilder block reference, please see: Create an S-Function Builder Block and Specify Settings.

In this example, we add the following path to S-Function Builder Libraries Table. Click Build button on the S-Function Builder dialog to build the code.

sfunction_library_table.png

% add c++ source and header to library table
handle = getSimulinkBlockHandle('FMUExportWithExternalCPP/S-Function Builder');
Simulink.SFunctionBuilder.add(handle,"LibraryItem","LibraryItemTag","INC_PATH","LibraryItemValue",fullfile(pwd, 'include'));
Simulink.SFunctionBuilder.add(handle,"LibraryItem","LibraryItemTag","SRC_PATH","LibraryItemValue",fullfile(pwd, 'src'));
Simulink.SFunctionBuilder.add(handle,"LibraryItem","LibraryItemTag","ENTRY","LibraryItemValue",'multiply.cpp');

% build s-function
Simulink.SFunctionBuilder.build(handle);
Generating 'cppwrapper.cpp' ....Please wait
Compiling 'cppwrapper.cpp' ....Please wait
### 'cppwrapper.cpp' created successfully
### 'cppwrapper_wrapper.cpp' created successfully
### S-function 'cppwrapper.mexa64' created successfully

The Libraries Table also allows user to specify external shared/static library referenced by custom code. For more information on how to add external libraries, see Use the Libraries Table to Specify External Code and Paths.

Note: Shared libraries dependencies in standalone FMU may have symbol clashing, library loading order conflicts, and data racing issues which result in simulation error.

Export Simulink Model as Standalone FMU

To build and export your model to a standalone FMU, click drop-down button for Save from Simulation tab and select Standalone FMU.

The figure below shows Export Standalone FMU dialog, user can pack source code into FMU and generate model harness after export. Read more about the Standalone FMU export functionality: Export Simulink Model to Standalone FMU.

fmuexportdialog.png

% Export model to Standalone Co-Simulation FMU 2.0
exportToFMU2CS('FMUExportWithExternalCPP', 'CreateModelAfterGeneratingFMU', 'on');
Setting System Target to FMU 'Co-Simulation' for model 'FMUExportWithExternalCPP'.
Setting Hardware Implementation > Device Type to 'MATLAB Host' for model 'FMUExportWithExternalCPP'.
### 'GenerateComments' is disabled for 'Co-Simulation' FMU Export.

Build Summary

Top model targets built:

Model                     Action                        Rebuild Reason                                    
==========================================================================================================
FMUExportWithExternalCPP  Code generated and compiled.  Code generation information file does not exist.  

1 of 1 models built (0 models already up to date)
Build duration: 0h 0m 19.9s
### Model was successfully exported to 'Co-Simulation' FMU: '/tmp/Bdoc24a_2528353_479333/tpf67a1df1/simulinkcompiler-ex17580767/FMUExportWithExternalCPP.fmu'.

A standalone FMU is generated in the Destination folder specified from the export dialog. User can also use Access source code from FMU option to pack source code into FMU package. Please note that FMU only accepts target landguage to be C in R2022a. Mixed compilng C++ in S-Function Builder block and Simulink model results S-Function Builder block generate wrapper function with C calling convention ("extern C").

// the following code marks function name in C++ have C linkage
extern "C" {
    void cppwrapper_Start_wrapper(const real_T*, const int_T, void**);
    void cppwrapper_Outputs_wrapper(const real_T*, real_T*, const real_T*, const int_T, void**);
    void cppwrapper_Terminate_wrapper(const real_T*, const int_T, void**);
}
% Close all model
bdclose FMUExportWithExternalCPP;
bdclose FMUExportWithExternalCPP_harness;