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

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

Prerequisites

Files

Location of Example Files

Example Files _matlabroot_\extern\examples\compilersdk\c_cpp\strongly_typed

Purpose of Each Example File

Files Purpose
+shapes Package containing two classes:MyPosition.m andMyRectangle.m.
MyPosition.m Class within the +shapes package that accepts the X and Y coordinates of a point and creates aMyPosition object.
MyRectangle.m Class within the +shapes package that accepts two points specified as MyPosition objects and creates a MyRectangle object.
calculatearea.m Function that accepts a MyRectangle object as input and calculates the area of the rectangle.
shapes_mda.cpp C++ application code that integrates the code archive (.ctf file) header (.hpp file) generated by packaging the MATLAB code.

Copy the example files to the current work folder.

appDir = fullfile(matlabroot,'extern','examples','compilersdk','c_cpp','strongly_typed'); copyfile(appDir)

Create MATLAB Function and Classes

  1. Examine the code for MyPosition.m,MyRectangle.m, andcalculatearea.m.
    • The +shapes package contains two MATLAB classes: MyPosition.m and MyRectangle.m.
    • The calculatearea.m MATLAB function located outside of the+shapes package accepts aMyRectangle object as input and calculates the area of the rectangle.
      +shapes (Package)
      +shapes
      |- MyPosition.m
      |- MyRectangle.m
      MyPosition.m (Class)
      classdef MyPosition
      properties
      X (1,1) double {mustBeReal}
      Y (1,1) double {mustBeReal}
      end
      end
      MyRectangle.m (Class)
      classdef MyRectangle
      properties
      UpperLeft (1,1) shapes.MyPosition
      LowerRight (1,1) shapes.MyPosition
      end
      methods
      function R = enlarge(R, n)
      arguments
      R (1,1) shapes.MyRectangle
      n (1,1) double {mustBeReal}
      end
      R.UpperLeft.X = R.UpperLeft.X - n;
      R.UpperLeft.Y = R.UpperLeft.Y - n;
      R.LowerRight.X = R.LowerRight.X + n;
      R.LowerRight.Y = R.LowerRight.Y + n;
      end
      function R = show(R)
      arguments
      R (1,1) shapes.MyRectangle
      end
      disp([R.UpperLeft.X R.UpperLeft.Y R.LowerRight.X R.LowerRight.Y]);
      end
      end
      end
      calculatearea.m (Function)
      function r = calculatearea(rect)
      arguments
      rect (1,1) shapes.MyRectangle
      end
      r = (rect.LowerRight.Y - rect.UpperLeft.Y) * (rect.LowerRight.X - rect.UpperLeft.X);
      end
      Established MATLAB users may find it unconventional to see a properties block in a class and an arguments block in a method or function, each detailed with data type information. Both blocks let you represent C++ data types with an equivalent MATLAB type. For instance, if your C++ application employs adouble data type representing a value, you can now represent that in MATLAB as a double. You can also specify a MATLAB object as an argument or property type. For example, the MyRectangle class specifiesshapes.MyPosition as the type for theUpperLeft and LowerRight properties of the MyRectangle class. This option to specify types 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, properties andarguments blocks with data type information are used to illuminate subtle nuances. However, remember that including type information 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 in your deployment, the specification of type information is not necessary.
  2. Create a MATLAB script named runshapes.m with the following code and execute it at the MATLAB command prompt. This script illustrates how the classes and function interact to generate an output.
    runshapes.m
    % Import shapes package
    import shapes.*;
    %% Create Rectangle 1
    % Create MyPosition object for point 1
    p1 = MyPosition;
    p1.X = 10;
    p1.Y = 5;
    % Create MyPosition object for point 2
    p2 = MyPosition;
    p2.X = 50;
    p2.Y = 20;
    % Create MyRectangle object
    r1 = MyRectangle;
    r1.UpperLeft = p1;
    r1.LowerRight = p2;
    % Calculate area of rectangle 1
    a1 = calculatearea(r1);
    % Display rectangle 1 info
    disp('Rectangle 1')
    disp(['Point 1 = ' '(' num2str(p1.X,'%.6f'), ',' num2str(p1.Y,'%.6f') ')'])
    disp(['Point 2 = ' '(' num2str(p2.X,'%.6f'), ',' num2str(p2.Y,'%.6f') ')'])
    disp(['Rectangle ' '(' num2str(p1.X,'%.6f'), ',' num2str(p1.Y,'%.6f') ')',...
    ' -> ' '(' num2str(p2.X,'%.6f'), ',' num2str(p2.Y,'%.6f') ')'])
    %% Create Rectangle 2
    % Create rectangle 2 by enlarging rectangle 1
    r2 = r1.enlarge(10);
    % Get positions of rectangle 2
    q1 = r2.UpperLeft();
    q2 = r2.LowerRight();
    % Calculate area of rectangle 2
    a2 = calculatearea(r2);
    % Display rectangle 2 info
    disp('Rectangle 2')
    disp(['Point 1 = ' '(' num2str(q1.X,'%.6f'), ',' num2str(q1.Y,'%.6f') ')'])
    disp(['Point 2 = ' '(' num2str(q2.X,'%.6f'), ',' num2str(q2.Y,'%.6f') ')'])
    disp(['Rectangle ' '(' num2str(q1.X,'%.6f'), ',' num2str(q1.Y,'%.6f') ')',...
    ' -> ' '(' num2str(q2.X,'%.6f'), ',' num2str(q2.Y,'%.6f') ')'])
    %% Display the area of the two rectangles
    disp(['Area of rectangle r1 = ' num2str(a1)])
    disp(['Area of rectangle r2 = ' num2str(a2)])
    Rectangle 1
    Point 1 = (10.000000,5.000000)
    Point 2 = (50.000000,20.000000)
    Rectangle (10.000000,5.000000) -> (50.000000,20.000000)
    Rectangle 2
    Point 1 = (0.000000,-5.000000)
    Point 2 = (60.000000,30.000000)
    Rectangle (0.000000,-5.000000) -> (60.000000,30.000000)
    Area of rectangle r1 = 600
    Area of rectangle r2 = 2100

Create C++ Shared Library Using compiler.build.cppSharedLibrary

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

files = ["calculatearea.m", "+shapes"]; buildResults = compiler.build.cppSharedLibrary(files, OutputDir="output", LibraryName="libshapes", Verbose="on")

The function generates the following files in a folder namedoutput in your current working directory.

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

To finalize integration, you need the libshapes.ctf code archive file and the libshapesv2.hpp header file from thegeneric_interface folder. You can view the header file here:

libshapesv2.hpp

#include "MatlabTypesInterface.hpp" #include

namespace shapes { class MyPosition : public MATLABObject { public:

    // constructors
    MyPosition() : MATLABObject() {}

    MyPosition(std::shared_ptr<MATLABControllerType> matlabPtr) :
        MATLABObject(matlabPtr, u"shapes.MyPosition")
    {}

    MyPosition(std::shared_ptr<MATLABControllerType> matlabPtr, 
        std::vector<matlab::data::Array> _args) {
        m_matlabPtr = matlabPtr;
        matlab::data::Array _result = MATLABCallOneOutputMethod(u"shapes.MyPosition", _args);
        m_object = _result;
    }

    MyPosition(std::shared_ptr<MATLABControllerType> matlabPtr, matlab::data::Array obj) :
        MATLABObject(matlabPtr, obj)
    {}

    // properties
    double getX() { return MATLABGetScalarProperty<double>(u"X"); }
    void setX(double value) { return MATLABSetScalarProperty(u"X", value); }
    double getY() { return MATLABGetScalarProperty<double>(u"Y"); }
    void setY(double value) { return MATLABSetScalarProperty(u"Y", value); }

    // methods
};

}

namespace shapes { class MyRectangle : public MATLABObject { public:

    // constructors
    MyRectangle() : MATLABObject() {}

    MyRectangle(std::shared_ptr<MATLABControllerType> matlabPtr) :
        MATLABObject(matlabPtr, u"shapes.MyRectangle")
    {}

    MyRectangle(std::shared_ptr<MATLABControllerType> matlabPtr, 
        std::vector<matlab::data::Array> _args) {
        m_matlabPtr = matlabPtr;
        matlab::data::Array _result = MATLABCallOneOutputMethod(u"shapes.MyRectangle", _args);
        m_object = _result;
    }

    MyRectangle(std::shared_ptr<MATLABControllerType> matlabPtr, matlab::data::Array obj) :
        MATLABObject(matlabPtr, obj)
    {}

    // properties
    shapes::MyPosition getUpperLeft() {
        matlab::data::Array obj = MATLABGetScalarProperty<matlab::data::Array>(u"UpperLeft");
        return shapes::MyPosition(m_matlabPtr, obj);
    }
    void setUpperLeft(shapes::MyPosition obj) {
        MATLABSetScalarProperty(u"UpperLeft", matlab::data::Array(obj));
    }
    shapes::MyPosition getLowerRight() {
        matlab::data::Array obj = MATLABGetScalarProperty<matlab::data::Array>(u"LowerRight");
        return shapes::MyPosition(m_matlabPtr, obj);
    }
    void setLowerRight(shapes::MyPosition obj) {
        MATLABSetScalarProperty(u"LowerRight", matlab::data::Array(obj));
    }

    // methods
private:
    template<size_t nargout>
    struct return_type_show { typedef void type; };

public:
    template<size_t nargout = 1>
    typename return_type_show<nargout>::type show() {
        static_assert(nargout <= 1, "Too many outputs specified. Maximum outputs is 1.");
    }

private:
    template<size_t nargout>
    struct return_type_enlarge { typedef void type; };

public:
    template<size_t nargout = 1>
    typename return_type_enlarge<nargout>::type enlarge(double n) {
        static_assert(nargout <= 1, "Too many outputs specified. Maximum outputs is 1.");
    }

};
template<>
struct MyRectangle::return_type_show<0> { typedef void type; };

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

template<>
void MyRectangle::show<0>() {
    matlab::data::ArrayFactory _arrayFactory;
    std::vector<matlab::data::Array> _args = {
        m_object };
    m_matlabPtr->feval(u"show", 0, _args);
}

template<>
matlab::data::Array MyRectangle::show<1>() {
    matlab::data::ArrayFactory _arrayFactory;
    std::vector<matlab::data::Array> _args = {
        m_object };
    matlab::data::Array _result_mda = m_matlabPtr->feval(u"show", _args);
    matlab::data::Array _result;
    _result = _result_mda;
    return _result;
}

template<>
struct MyRectangle::return_type_enlarge<0> { typedef void type; };

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

template<>
void MyRectangle::enlarge<0>(double n) {
    matlab::data::ArrayFactory _arrayFactory;
    std::vector<matlab::data::Array> _args = {
        m_object,
        _arrayFactory.createArray<double>({1,1}, {n}) };
    m_matlabPtr->feval(u"enlarge", 0, _args);
}

template<>
matlab::data::Array MyRectangle::enlarge<1>(double n) {
    matlab::data::ArrayFactory _arrayFactory;
    std::vector<matlab::data::Array> _args = {
        m_object,
        _arrayFactory.createArray<double>({1,1}, {n}) };
    matlab::data::Array _result_mda = m_matlabPtr->feval(u"enlarge", _args);
    matlab::data::Array _result;
    _result = _result_mda;
    return _result;
}

}

template struct return_type_calculatearea { typedef void type; };

template typename return_type_calculatearea::type calculatearea( std::shared_ptr _matlabPtr, shapes::MyRectangle rect) { static_assert(nargout <= 1, "Too many outputs specified. Maximum outputs is 1."); } template<> struct return_type_calculatearea<0> { typedef void type; };

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

template<> void calculatearea<0>(std::shared_ptr _matlabPtr, shapes::MyRectangle rect) { matlab::data::ArrayFactory _arrayFactory; std::vectormatlab::data::Array _args = { rect }; _matlabPtr->feval(u"calculatearea", 0, _args); }

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

For an in-depth discussion of how the MATLAB classes and function and mapped to C++ in the header file, seeMap MATLAB Classes and Functions to C++.

For details on data type mappings, see Data Type Mappings Between C++ and Strongly Typed MATLAB Code.

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 namedshapes_mda.cpp with the following code:
    shapes_mda.cpp
    // Include header files

#include // for abs
#include
#include "MatlabCppSharedLib.hpp"
#include "output/cpp/v2/generic_interface/libshapesv2.hpp"
using namespace shapes;
// 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;
}
// This is an example of a new function that can be written in the C++ driver code without having to
// modify the MATLAB code.
static double calculateperimeter(MyRectangle rectangle)
{
// Get the positions of the upper left and lower right corners.
MyPosition upperLeft = rectangle.getUpperLeft();
MyPosition lowerRight = rectangle.getLowerRight();
// Calculate the width and height of the rectangle.
double width = abs(lowerRight.getX() - upperLeft.getX());
double height = abs(upperLeft.getY() - lowerRight.getY());
// Calculate and return the perimeter.
return 2 * (width + height);
}
// 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"libshapes.ctf");
std::shared_ptr matlabPtr(std::move(libPtr));

    // Create a rectangle from two points  
    MyPosition p1(matlabPtr);  
    p1.setX(10);  
    p1.setY(5);  
    std::cout << "Rectangle 1\n";  
    p1.show();  
    MyPosition p2(matlabPtr);  
    p2.setX(50);  
    p2.setY(20);  
    p2.show();  
    MyRectangle r1(matlabPtr);  
    r1.setUpperLeft(p1);  
    r1.setLowerRight(p2);  
    r1.show();  
    // Call MATLAB function to calculate first rectangle area and print result  
    matlab::data::TypedArray<double> area1 = calculatearea(matlabPtr, r1);  
    std::cout << "Area of rectangle r1 = " << area1[0] << "\n";  
    std::cout << "Perimeter of rectangle r1 = " << calculateperimeter(r1) << std::endl;  
      
    // Create second rectangle by enlarging the first one  
    MyRectangle r2(matlabPtr, r1.enlarge(10.0));  
    MyPosition p1r2(matlabPtr, r2.getUpperLeft());  
    MyPosition p2r2(matlabPtr, r2.getLowerRight());  
    std::cout << "Rectangle 2\n";  
    p1r2.show();  
    p2r2.show();  
    r2.show();  
    // Call MATLAB function to calculate second rectangle area and print result  
    matlab::data::TypedArray<double> doubles = calculatearea(matlabPtr, r2);  
    std::cout << "Area of rectangle r2 = " << doubles[0] << "\n";  
    std::cout << "Perimeter of rectangle r2 = " << calculateperimeter(r2) << std::endl;  
}  
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;
} 2. Compile and link the application by executing the mbuild function at the MATLAB command prompt.
mbuild -v shapes_mda.cpp -outdir output\bin

Handling the 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\strongly_typed\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 the MATLAB command prompt. This does not require a MATLAB Runtime installation.

!output\bin\shapes_mda.exe

Rectangle 1 Point (10.000000, 5.000000) Point (50.000000, 20.000000) Rectangle (10.000000, 5.000000) -> (50.000000, 20.000000) Area of rectangle r1 = 600 Rectangle 2 Point (0.000000, -5.000000) Point (60.000000, 30.000000) Rectangle (0.000000, -5.000000) -> (60.000000, 30.000000) Area of rectangle r2 = 2100

See Also

arguments | properties

Topics