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