Deploy MATLAB Function to C++ Application Using MATLAB Data API - MATLAB & Simulink (original) (raw)

This example shows how to package a MATLAB® function and deploy it within a C++ application. It uses the MATLAB Data API for managing data exchange between the MATLAB function and the C++ application. The workflow is supported on Windows®, Linux®, and macOS.

Prerequisites

Create MATLAB Function

Create a MATLAB file named calculateDistance.m with the following code:

function distance = calculateDistance(p1, p2) % This function calculates the Euclidean distance between two points % Inputs: % p1 - a two-element vector [x, y] % p2 - a two-element vector [x, y] % Output: % distance - the Euclidean distance between p1 and p2

% Use arguments block to map C++ type to corresponding MATLAB type
% std::vector<int32_t> <--> (1,2) int32 {mustBeReal}

arguments (Input)
    p1 (1,2) int32 {mustBeReal}
    p2 (1,2) int32 {mustBeReal}
end

arguments (Output)
    distance (1,1) int32 {mustBeReal}
end

% Calculte Euclidean distance
diff = p1 - p2;
diffSq = diff.^2;
sumSq = sum(diffSq);
distance = sqrt(sumSq);

end

Established MATLAB users may find the presence of an arguments block unconventional. Thearguments block lets you represent C++ data types with an equivalent MATLAB type. For instance, if your C++ application employs anint32_t data type representing a value, you can now represent that in MATLAB as an int32. This option is useful in situations where a C++ application has strict type requirements. For details, see Data Type Mappings Between C++ and Strongly Typed MATLAB Code.

In this example, an arguments block with specified type information is used to illuminate subtle nuances. However, remember that incorporating an arguments block is entirely optional. The deployment process remains unchanged even without it. Various parts of this example underscore the areas where this difference manifests. So, if data types aren't crucial, the specification of type information using anarguments block is not necessary.

Test the MATLAB function at the command prompt.

p1 = int32([0, 0]) p2 = int32([3 4]) distance = calculateDistance(p1,p2)

p1 = 1×2 int32 row vector 0 0 p2 = 1×2 int32 row vector 3 4 distance = int32 5

Note

MATLAB

functions that use varargin andvarargout are unsupported.

Package MATLAB Function Using compiler.build.cppSharedLibrary

Create a code archive (.ctf file) and header (.hpp file) from the MATLAB function using the compiler.build.cppSharedLibrary function.

buildResults = compiler.build.cppSharedLibrary("calculateDistance.m",... OutputDir=".\output", Verbose="on");

The function produces a suite of files, as enumerated below, and places them in the specified output directory. Among these, the key files utilized during the integration process are the code archive (.ctf file) containing the MATLAB code and the corresponding header (.hpp file). For information on the other files, see Files Generated After Packaging MATLAB Functions.

P:\MATLAB\WORK\OUTPUT │ GettingStarted.html │ includedSupportPackages.txt │ mccExcludedFiles.log │ readme.txt │ requiredMCRProducts.txt │ unresolvedSymbols.txt │ └───v2 └───generic_interface calculateDistance.ctf calculateDistancev2.hpp readme.txt

To finalize integration, you need the calculateDistance.ctf code archive file and the calculateDistancev2.hpp header file from the generic_interface folder. You can view the header file here:

calculateDistancev2.hpp

#include "MatlabTypesInterface.hpp"
#include <map>

template<size_t nargout>
struct return_type_calculateDistance { typedef void type; };

template<size_t nargout = 1>
typename return_type_calculateDistance<nargout>::type calculateDistance(
    std::shared_ptr<MATLABControllerType> _matlabPtr, 
    std::vector<int32_t> p1, std::vector<int32_t> p2) {
    static_assert(nargout<=1, "Too many outputs specified. Maximum outputs is 1.");
}
template<>
struct return_type_calculateDistance<0> { typedef void type; };

template<>
struct return_type_calculateDistance<1> { typedef int32_t type; };

template<>
void calculateDistance<0>(
    std::shared_ptr<MATLABControllerType> _matlabPtr, 
    std::vector<int32_t> p1, std::vector<int32_t> p2) { 
    matlab::data::ArrayFactory _arrayFactory;
    matlab::data::ArrayDimensions _dims1 = {1, p1.size()};
    matlab::data::ArrayDimensions _dims2 = {1, p2.size()};
    std::vector<matlab::data::Array> _args = 
    {        _arrayFactory.createArray(_dims1, p1.begin(), p1.end()),
        _arrayFactory.createArray(_dims2, p2.begin(), p2.end()) };
    _matlabPtr->feval(u"calculateDistance", 0, _args);
}

template<>
int32_t calculateDistance<1>(
    std::shared_ptr<MATLABControllerType> _matlabPtr, 
    std::vector<int32_t> p1, std::vector<int32_t> p2) { 
    matlab::data::ArrayFactory _arrayFactory;
    matlab::data::ArrayDimensions _dims1 = {1, p1.size()};
    matlab::data::ArrayDimensions _dims2 = {1, p2.size()};
    std::vector<matlab::data::Array> _args = 
    {        _arrayFactory.createArray(_dims1, p1.begin(), p1.end()),
        _arrayFactory.createArray(_dims2, p2.begin(), p2.end()) };
    matlab::data::Array _result_mda = _matlabPtr->feval(u"calculateDistance", _args);
    int32_t _result;
    _result = MatlabTypesInterface::convertMDAtoScalar<int32_t>(_result_mda);
    return _result;
}

In the calculateDistancev2.hpp header, the MATLAB function's int32 argument specification mirrors its C++ equivalent, int32_t.

arguments (Input) p1 (1,2) int32 {mustBeReal} p2 (1,2) int32 {mustBeReal} end std::vector<int32_t> p1, std::vector<int32_t> p2)
arguments (Output) distance (1,1) int32 {mustBeReal} end template<> struct return_type_calculateDistance<1> { typedef int32_t type; }; ... ... matlab::data::Array _result_mda = _matlabPtr->feval(u"calculateDistance", _args); int32_t _result; _result = MatlabTypesInterface::convertMDAtoScalar<int32_t>(_result_mda);

When an arguments block detailing type information is not included in your MATLAB function, it results in the production of the following header file:

calculateDistancev2.hpp (Type Agnostic)

#include "MatlabTypesInterface.hpp" #include

template struct return_type_calculateDistance { typedef void type; };

template typename return_type_calculateDistance::type calculateDistance( std::shared_ptr _matlabPtr, matlab::data::Array arg1, matlab::data::Array arg2) { static_assert(nargout<=1, "Too many outputs specified. Maximum outputs is 1."); } template<> struct return_type_calculateDistance<0> { typedef void type; };

template<> struct return_type_calculateDistance<1> { typedef matlab::data::Array type; };

template<> void calculateDistance<0>( std::shared_ptr _matlabPtr, matlab::data::Array arg1, matlab::data::Array arg2) { matlab::data::ArrayFactory _arrayFactory; std::vectormatlab::data::Array _args = { arg1, arg2 }; _matlabPtr->feval(u"calculateDistance", 0, _args); }

template<> matlab::data::Array calculateDistance<1>( std::shared_ptr _matlabPtr, matlab::data::Array arg1, matlab::data::Array arg2) { matlab::data::ArrayFactory _arrayFactory; std::vectormatlab::data::Array _args = { arg1, arg2 }; matlab::data::Array _result_mda = _matlabPtr->feval(u"calculateDistance", _args); matlab::data::Array _result; _result = _result_mda; return _result; }

The primary difference between the header files rests in the type specification for input and output variables. When anarguments block is used with type information specified, the inputs and outputs are categorized asstd::vector<int32_t> and int32_t respectively. Conversely, when an arguments block is absent, the inputs and outputs receive a matlab::data::Array object type designation.

Note

The generated artifacts do not include MATLAB Runtime or an installer. To create an installer using thebuildResults object, see compiler.package.installer.

Integrate MATLAB Code Archive into C++ Application

You can finalize the integration process in your preferred C++ development environment, including MATLAB or alternatives such as Microsoft® Visual Studio® on Windows. This example, however, uses MATLAB as a C++ development environment. For details, see Set Up C++ Development Environment.

To integrate the generated MATLAB code archive (.ctf file) and header (.hpp file) into a C++ application, adhere to these guidelines:

Completing the integration step requires proficient C++ skills for writing application code. You can use the following sample C++ application code as guide when writing your own application.

  1. In the work folder for this example create a new file named DistanceConsoleApp.cpp with the following code.
    DistanceConsoleApp.cpp
    // Include header files

#include
#include "MatlabCppSharedLib.hpp"
#include "P:\MATLAB\work\output\v2\generic_interface\calculateDistancev2.hpp"
// Start MATLAB Runtime, initialize it, and return an object to it
std::shared_ptrmatlab::cpplib::MATLABApplication setup()
{
auto mode = matlab::cpplib::MATLABApplicationMode::IN_PROCESS;
std::vectorstd::u16string options = { u"-nojvm" };
std::shared_ptrmatlab::cpplib::MATLABApplication matlabApplication =
matlab::cpplib::initMATLABApplication(mode, options);
return matlabApplication;
}
// Initialize the code archive (.ctf file), specify input arguments, call the MATLAB function,
// and print the result
int mainFunc(std::shared_ptrmatlab::cpplib::MATLABApplication app, const int argc, const char* argv[])
{
try {
auto libPtr = matlab::cpplib::initMATLABLibrary(app, u"calculateDistance.ctf");
std::shared_ptr matlabPtr(std::move(libPtr));
// Specify inputs using std::vector; no need to create matlab::data::Array objects
std::vector p1 = { 0, 0 };
std::vector p2 = { 3, 4 };
// Specify output type as int32_t, call MATLAB function, and print result
int32_t distance = calculateDistance(matlabPtr, p1, p2);
std::cout << "Euclidean distance between ["
<< p1[0] << ", " << p1[1] << "] and [" << p2[0] << ", " << p2[1] << "] is: "
<< distance << "\n";
}
catch (const std::exception& exc) {
std::cerr << exc.what() << std::endl;
return -1;
}
return 0;
}
// Call setup() to initialize MATLAB Runtime, use runMain() to run mainFunc(),
// and reset MATLAB Runtime after completion
int main(const int argc, const char* argv[])
{
int ret = 0;
try {
auto matlabApplication = setup();
ret = matlab::cpplib::runMain(mainFunc, std::move(matlabApplication), argc, argv);
matlabApplication.reset();
}
catch (const std::exception& exc) {
std::cerr << exc.what() << std::endl;
return -1;
}
return ret;
}
When an arguments block detailing type information is not included in your MATLAB function, your C++ application code must be written as follows:
DistanceConsoleApp.cpp (Type Agnostic)
// Include header files
#include
#include "MatlabCppSharedLib.hpp"
#include "P:\MATLAB\work\output\v2\generic_interface\calculateDistancev2.hpp"
// Start MATLAB Runtime, initialize it, and return an object to it
std::shared_ptrmatlab::cpplib::MATLABApplication setup()
{
auto mode = matlab::cpplib::MATLABApplicationMode::IN_PROCESS;
std::vectorstd::u16string options = { u"-nojvm" };
std::shared_ptrmatlab::cpplib::MATLABApplication matlabApplication =
matlab::cpplib::initMATLABApplication(mode, options);
return matlabApplication;
}
// Initialize the code archive (.ctf file), specify input arguments, call the MATLAB function,
// and print the result
int mainFunc(std::shared_ptrmatlab::cpplib::MATLABApplication app, const int argc, const char* argv[])
{
try {
auto libPtr = matlab::cpplib::initMATLABLibrary(app, u"calculateDistance.ctf");
std::shared_ptr matlabPtr(std::move(libPtr));
Specify inputs as matlab::data::Array objects; cannot use std::vector
matlab::data::ArrayFactory factory;
matlab::data::TypedArray p1 = factory.createArray({ 1,2 }, { 0.0, 0.0 });
matlab::data::TypedArray p2 = factory.createArray({ 1,2 }, { 3.0, 4.0 });
// Call MATLAB function and print result
matlab::data::TypedArray distance = calculateDistance(matlabPtr, p1, p2);
std::cout << "Euclidean distance between ["
<< p1[0] << ", " << p1[1] << "] and [" << p2[0] << ", " << p2[1] << "] is: "
<< distance[0] << "\n";
}
catch (const std::exception& exc) {
std::cerr << exc.what() << std::endl;
return -1;
}
return 0;
}
// Call setup() to initialize MATLAB Runtime, use runMain() to run mainFunc(),
// and reset MATLAB Runtime after completion
int main(const int argc, const char* argv[])
{
int ret = 0;
try {
auto matlabApplication = setup();
ret = matlab::cpplib::runMain(mainFunc, std::move(matlabApplication), argc, argv);
matlabApplication.reset();
}
catch (const std::exception& exc) {
std::cerr << exc.what() << std::endl;
return -1;
}
return ret;
}
In C++ application code, the distinction between including anarguments block with type information and not doing so lies in the specification of inputs and outputs to the MATLAB function. When an arguments block detailing type information is included, the use of C++ Standard Library and primitive types is permissible for input and output specification. On the other hand, when anarguments block detailing type information is not included, inputs and outputs must be defined asmatlab::data::Array objects. 2. Compile and link the application by executing the mbuild function at the MATLAB command prompt.
mbuild -v DistanceConsoleApp.cpp -outdir output\bin

Handle Code Archive (.ctf file)

To ensure your C++ application can access the code archive (.ctf file) containing MATLAB code, place the file in a location accessible to the executable. For this example we are going to do this by setting theCPPSHARED_BASE_CTF_PATH environment variable in the MATLAB desktop environment.

setenv("CPPSHARED_BASE_CTF_PATH","P:\MATLAB\work\output\v2\generic_interface")

If you're using Visual Studio, see Set Environment Variables in Visual Studio.

For a complete list of code archive (.ctf file) placement options, see Code Archive (.ctf file) Placement.

Run C++ Application

For testing purposes, you can run the application from MATLAB command prompt. This does not require a MATLAB Runtime installation.

!output\bin\DistanceConsoleApp.exe

Euclidean distance between [0, 0] and [3, 4] is: 5

Subtleties of Deploying to C++ Using MATLAB Data API

See Also

mbuild | compiler.build.cppSharedLibrary | arguments

Topics