Main Content

Export Standalone FMU with External C++ Code

This example shows how to integrate external C++ code into a Simulink® model using S-Function Builder block and export the model to a standalone FMU. Your model can also contain other Simulink blocks which are supported for exporting to an 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/ directories.

// 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 and an Integrator block.

  • Instantiate class multiply in S-Function Output function.

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

% 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, using which you can pack source code into FMU and generate model harness after export. You can also choose the FMI version for the exported FMU by using the FMI Version dropdown from this dialog. Read more about exporting Simulink models to Functional Mock-up Units: Export Simulink Models to Functional Mock-up Units.

Use the exportToFMU function to export the Simulink model to Standalone Co-Simulation FMU. Set the FMI version by using the 'FMIVersion' Name-Value pair and the FMU type by using the 'FMUType' Name-Value pair. The values for 'FMIVersion' can be either '2.0' or '3.0'.

% Export model to Standalone Co-Simulation FMU
exportToFMU('FMUExportWithExternalCPP', 'FMIVersion', '2.0', 'FMUType', 'CS', ...
 '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:

Model                     Build Reason                                         Status                        Build Duration
===========================================================================================================================
FMUExportWithExternalCPP  Information cache folder or artifacts were missing.  Code generated and compiled.  0h 0m 13.957s 

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

A standalone FMU is generated in the Destination folder specified from the export dialog. User can also use Schema -> Access source code from FMU option to pack source code into FMU package. Please note that FMU only accepts target language to be C in R2024a. 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;