Generate C++ Code for Export-Function Models That Process Unbounded Variable-Size Data
This example shows how to generate C++ code for a function in an export-function model that processes unbounded variable-size data. For unbounded variable-size signals transmitting unbounded variable-size data, the code generator by default generates std::vector
containers to allocate memory dynamically at run time. These containers enable you to integrate your generated code with third-party applications that use std::vector
to represent variable-size data.
In this example, after code generation, you integrate the generated code with custom main application code to exchange data between their functions.
Example Model
Open the UVSCompMdl
model.
open_system('UVSCompMdl');
The top model UVSCompMdl
contains four components: Data Generator
, Event Scheduler
, Data Processor
, and Data Logger
. The components Data Generator
and Data Processor
are the referenced models DataGenerator
and DataProcessor
.
At each time step of model simulation, the referenced model DataGenerator
outputs a one-dimensional array that is carried by an unbounded variable-size signal. The MATLAB Function block createArray
generates this array, whose size changes based on input values. The Event Scheduler
component schedules the data transfer between DataGenerator
and DataProcessor
.
The referenced model DataProcessor
is an export-function model, in which the Simulink Function block process_data
processes the unbounded variable-size data that it receives as an input.
To process the unbounded variable-size data, the Simulink Function block contains:
The MATLAB Function block
processDataInMATLABFunction
, which amplifies the input array values by a factor of2
and calculates the array length.The subsystem
processDataUsingIteratorSS
, which receives the output of the MATLAB Function block. The subsystem contains a For Iterator Subsystem block that iterates over the length of the input array, amplifies the input value by a factor of10
, and outputs the unbounded variable-size signalprocessedDataOut
.A Bus Creator block, which creates a non-virtual bus
processedDataBus
that includes the unbounded variable-size signalprocessedDataOut
and the array sizeprocessedDataSize
.
The generated data, processed data, and their sizes at each time step of model simulation are logged using Outport blocks. The purpose of the Event Scheduler
and Data Generator
components is to test the behavior of the Data Processor
component through simulation and data logging. Once you are satisfied with the simulation results, you can generate code for the function implemented in Data Processor
.
Configure Model for Simulation and Code Generation
To prepare the top model UVSCompMdl
and its referenced models DataGenerator
and DataProcessor
for simulation and code generation, the model loads dHarness.mat
into the base workspace. Doing so imports a configuration set with the following parameter settings for the top model and referenced models.
These settings enable dynamic memory allocation for a model containing unbounded variable-size signals:
On the Solver pane, set Solver to
discrete (no continuous states)
.On the Data Import/Export pane, set Format to
Dataset
.On the Simulation Target pane, select Dynamic memory allocation in MATLAB functions.
On the Code Generation pane, set Language to
C++
.On the Code Generation pane, in the Interface section, select Support: variable-size signals.
These settings enable generation of std::vector
containers representing unbounded variable-size signals:
On the Code Generation pane, set System target file to
ert.tlc
.On the Code Generation, in the Code Style section, set Dynamic array container type to
std::vector
.
Simulate Top Model
Simulate the top model and review the logged data. For more information about simulation and data logging, see Use Unbounded Variable-Size Signals Between Model Components.
Generate C++ Code for DataProcessor
Model
Once you are satisfied with the logged data of DataProcessor
, you can generate C++ code for the export-function model. To generate code, follow these steps.
1. Open the DataProcessor
model as the top model.
open_system('DataProcessor');
2. Build the model, but do not compile the generated code.
slbuild('DataProcessor',GenerateCodeOnly=true);
### Starting build procedure for: DataProcessor ### Successful completion of code generation for: DataProcessor Build Summary Top model targets: Model Build Reason Status Build Duration =================================================================================================== DataProcessor Information cache folder or artifacts were missing. Code generated. 0h 0m 12.899s 1 of 1 models built (0 models already up to date) Build duration: 0h 0m 15.776s
3. In the header file DataProcessor.h
, the code generator defines dynamic arrays for the input and output of the MATLAB Function block processDataInMATLABFunction
and for the unbounded variable-size signal passing from the For Iterator subsystem as instances of the class template std::vector
. To view the dynamic arrays, open the DataProcessor.h
file.
cfile = fullfile('DataProcessor_ert_rtw','DataProcessor.h'); coder.example.extractLines(cfile,"struct B_DataProcessor_T {", "// External outputs", 1, 0);
struct B_DataProcessor_T { std::vector<real_T> u; // '<S1>/u' std::vector<real_T> TmpSignalConversionAtuOutport1;// '<S1>/u' std::vector<real_T> TmpSignalConversionAtxInport1;// '<S1>/u' std::vector<real_T> TmpSignalConversionAtyInport1;// '<S1>/processDataUsingIteratorSS' std::vector<real_T> assign; // '<S4>/assign' std::vector<real_T> processedData; // '<S1>/processDataInMATLABFunction' };
Integrate Generated Code with Custom Code
To integrate the generated code with custom C++ code and call an entry-point function that accepts or returns std::vector
containers, you must define such containers in the custom code. You can interact with std::vector
containers by accessing their size
method and using the standard C++ array indexing.
In this example, you customize the generated example main application code ert_main.cpp
to call the model-step function process_data
.
1. Open ert_main.cpp
. Include the <iostream>
header.
#include <stdio.h> #include <iostream> #include "DataProcessor.h" // Model header file
2. The function prototype of the generated process_data
function in DataProcessor.cpp
is the following:
cfile = fullfile('DataProcessor_ert_rtw','DataProcessor.cpp'); coder.example.extractLines(cfile,"void", "rty_z", 1, 1);
void DataProcessor::process_data(const std::vector<real_T> &rtu_u, std::vector< real_T> &rty_x, std::vector<real_T> &rty_y, real_T *rty_z)
The function accepts a reference to a std::vector
container of data type real_T
as an input argument, and has three output arguments. Two of the output arguments are also references to std::vector
containers of data type real_T
.
To integrate process_data
with the main application code, in ert_main.cpp
, define an input array myArray
and two output arrays myResult_x
and myResult_y
as class templates.
// Instantiate the input variable by using std::vector template std::vector<real_T> myArray; // Instantiate the result variable by using std::vector template std::vector<real_T> myResult_x; std::vector<real_T> myResult_y; real_T myResult_z;
3. Set the size of myArray
to 100
by using the resize
method and set the input values in the array elements.
// Allocate initial memory for the array myArray.resize(100); // Access array with standard C++ indexing for (int i = 0; i < myArray.size(); i++) { myArray[i] = i; }
4. Use the instance DataProcessor_Obj
of the model class DataProcessor
to call the process_data
function and display the output values of myResult_x
through indexing.
// Pass the input and result arrays to the generated method DataProcessor_Obj.process_data(myArray, myResult_x, myResult_y, &myResult_z); // Print result for (int i = 0; i < myResult_x.size(); i++) { if (i > 0) std::cout << " "; std::cout << myResult_x[i]; if (((i+1) % 10) == 0) std::cout << std::endl; } std::cout << std::endl;
5. Compile the generated code and run the executable.
codebuild('DataProcessor_ert_rtw') !DataProcessor.exe
After integrating the generated code with the main application code in ert_main.cpp
, you can make further changes to the application code you deploy into the target application. The application code and integrated generated code are capable of handling unbounded size data and are suitable for deployment to desktop quality targets. For more information, see Deploy Applications to Target Hardware.