Package MATLAB Function Using C++ Shared Library Compiler App with MATLAB Data API - MATLAB & Simulink (original) (raw)

Supported platforms: Windows®, Linux®, Mac

This example shows how to use the C++ Shared Library Compiler app to package MATLAB® functions into a C++ shared library. The provided C++ application demonstrates passing matrices between the MATLAB functions and the C++ application using the MATLAB Data API.

Before R2025a: See Generate a C++ MATLAB Data API Shared Library and Build a C++ Application (R2024b).

Prerequisites

Create MATLAB Functions

Write MATLAB code that you want to package. This example uses these files in_`[matlabroot](../../matlab/ref/matlabroot.html)`_\extern\examples\compilersdk\c_cpp\matrix:

MATLAB Functions addmatrix.m eigmatrix.m multiplymatrix.m
C++ MATLAB Data API Application Code matrix_mda.cpp

At the MATLAB command prompt, copy the contents of the matrix folder that ships with MATLAB to a new folder named MatrixProject.

copyfile(fullfile(matlabroot,'extern','examples', ... 'compilersdk','c_cpp','matrix'),"MatrixProject")

Examine and test addmatrix.m, multiplymatrix.m, andeigmatrix.m.

function a = addmatrix(a1, a2) %ADDMATRIX Add two matrices % This function adds the two matrices passed as input. This function is % used to demonstrate the functionality of MATLAB Compiler SDK.

a = a1 + a2;

function e = eigmatrix(a1) %EIGMATRIX Returns the eigenvalues of the given matrix % This function returns the eigenvalues of the input matrix. This % function is used to demonstrate the functionality of MATLAB Compiler SDK.

try
    %Tries to calculate the eigenvalues and return them.
    e = eig(a1);
catch
    %Returns a -1 on error.
    e = -1;

end

function m = multiplymatrix(a1, a2) %MULTIPLYMATRIX Multiplies two matrices % This function multiplies the two matrices passed as input. This % function is used to demonstrate the functionality of MATLAB Compiler SDK.

m = a1*a2;

Create Project and Compiler Task

Create a compiler task for your C++ shared library using the C++ Shared Library Compiler. Compiler tasks allow you to compile files in a project for a specific deployment target.

To open the app, on the Apps tab, expand theApps gallery. In theApplication Deployment section, click C++ Shared Library Compiler.

Application Deployment section of the Apps gallery

You can also open the app using the cppSharedLibraryCompiler function at the MATLAB Command Window.

Create compiler task dialog box with the text 'To deploy your MATLAB code, you need a MATLAB project to organize code and a compiler task to handle deployment.' The option 'Start a new project and create a compiler task' is selected.

After you open the app, the Create Compiler Task dialog box prompts you to add a task to a new or an existing MATLAB project. For this example, select Start a new project and create a compiler task and create a new project named MatrixProject in the MatrixProject folder. For more information on creating and using MATLAB projects, see Create Projects.

A new compiler task named CppSharedLib1 opens in the Editor.

You can create more C++ compiler tasks or package code for other deployment targets by opening the Compiler Task Manager or going to the Manage Tasks tab and creating a new compiler task.

Specify Build Options

You can specify options for the C++ shared library and its installer before packaging to customize the building and packaging process. For instance, you can obfuscate the MATLAB code or specify the method of including MATLAB Runtime in the generated installer.

Add the MATLAB functions to the C++ shared library. All files must be located in the project root folder to be added to the project. For this example, in the Exported Functions section of the compiler task, click Add File and select addmatrix.m, multiplymatrix.m, andeigmatrix.m. In the Project panel, the files now have the labelsDesign and Exported Function File.

Exported file section of the compiler task with no file selected and a button labeled Add Exported Function

In the Package Info section, replace the string My C++ Package with the name for your C++ shared library,libmatrix.

To choose a different output location for the generated files, update the paths in theOutput Locations section.

In the C++ API Selection section, choose the API to use for exchanging data between the C++ application and the MATLAB functions. For this example, select the MATLAB Data API. For more information, see Choose C++ Deployment Option.

C++ API Selection section with the option 'Create interface that uses the MATLAB Data API for C++' selected

View Code and Package C++ Shared Library

To view code that contains instructions on building and packaging your component, click the arrow next to Export Build Script and select Show Code. On the right, a window displays a deployment script with the compiler.build.cppSharedLibrary and compiler.package.installer functions that corresponds to your build options. You can convert this code to a MATLAB script file by clicking the Export Build Script button. Running the generated build script is equivalent to clicking the Build and Package button.

Two buttons labeled Export Build Script and Build and Package

To create the C++ shared library and an installer, click Build and Package. To create only the C++ shared library, click the arrow next toBuild and Package and select Build.

The compiler generates files in the _`<compilertaskname>`_/output folder in your project folder. 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.

If you created an installer, the package subfolder contains the installer for your shared library files along with MATLAB Runtime.

Integrate MATLAB Code Archive into C++ Application

After creating the C++ shared library, write source code for a C++ application in your preferred 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:

There are two C++ applications included with this example:matrix_mda.cpp, which uses the MATLAB Data API, and matrix_mwarray.cpp, which uses themwArray API. For this example, use the filematrix_mda.cpp.

matrix_mda.cpp

/*==============================================================
 *
 * MATRIX_MDA.CPP
 * Sample driver code that uses the MATLAB Data API interface 
 * (introduced in R2018a) and MATLAB Data API to call a C++ 
 * shared library created using the MATLAB Compiler SDK.
 * Demonstrates passing matrices via the MATLAB Data API.
 * Refer to the MATLAB Compiler SDK documentation for more 
 * information.
 *
 * Copyright 2017-2023 The MathWorks, Inc.
 *
 *============================================================*/

// Include the header file required to use the MATLAB Data API
// interface for the C++ shared library generated by the
// MATLAB Compiler SDK.
#include "MatlabCppSharedLib.hpp"
#include <iostream>
#include <numeric> // for iota

namespace mc = matlab::cpplib;
namespace md = matlab::data;

std::u16string convertAsciiToUtf16(const std::string & asciiStr);

template <typename T>
void writeMatrix(std::ostream & ostr, const md::TypedArray<T> & matrix, 
    md::MemoryLayout layoutOfArray = md::MemoryLayout::ROW_MAJOR);

int mainFunc(std::shared_ptr<mc::MATLABApplication> app,
    const int argc, const char * argv[]);

// The main routine. On the Mac, the main thread runs the system code, and
// user code must be processed by a secondary thread. On other platforms, 
// the main thread runs both the system code and the user code.
int main(const int argc, const char * argv[])
{
    int ret = 0;
    try {
        auto mode = mc::MATLABApplicationMode::IN_PROCESS;
        std::vector<std::u16string> options = {u"-nojvm"};
        auto matlabApplication = mc::initMATLABApplication(mode, options);
        ret = mc::runMain(mainFunc, std::move(matlabApplication),  argc, argv);
        // Calling reset() on matlabApplication allows the user to control
        // when it is destroyed, which automatically cleans up its resources.
        // Here, the object would go out of scope and be destroyed at the end 
        // of the block anyway, even if reset() were not called.
        // Whether the matlabApplication object is explicitly or implicitly
        // destroyed, initMATLABApplication() cannot be called again within
        // the same process.
        matlabApplication.reset();
    } catch(const std::exception & exc) {
        std::cerr << exc.what() << std::endl;
        return -1;
    }
    return ret;
}

int mainFunc(std::shared_ptr<mc::MATLABApplication> app,
    const int argc, const char * argv[])
{
    try {
        const std::u16string U16STR_CTF_NAME = u"libmatrix.ctf";
        
        // The path to the CTF (library archive file) passed to 
        // initMATLABLibrary or initMATLABLibraryAsync may be either absolute
        // or relative. If it is relative, the following will be prepended
        // to it, in turn, in order to find the CTF:
        // - the directory named by the environment variable 
        // CPPSHARED_BASE_CTF_PATH, if defined
        // - the working directory
        // - the directory where the executable is located
        // - on Mac, the directory three levels above the directory
        // where the executable is located
        
        // If the CTF is not in one of these locations, do one of the following:
        // - copy the CTF
        // - move the CTF
        // - change the working directory ("cd") to the location of the CTF
        // - set the environment variable to the location of the CTF
        // - edit the code to change the path
        auto lib = mc::initMATLABLibrary(app, U16STR_CTF_NAME);
        md::ArrayFactory factory;
        const size_t NUM_ROWS = 3;
        const size_t NUM_COLS = 3;
        md::TypedArray<double> doubles = factory.createArray<double>({NUM_ROWS, NUM_COLS}, 
            {1.0, 2.0, 3.0,
             4.0, 5.0, 6.0,
             7.0, 8.0, 9.0}); 
             
        // Note that the matrix is interpreted as being in column-major order 
        // (the MATLAB convention) rather than row-major order (the C++ 
        // convention). Thus, the output from the next two lines of code will 
        // look like this:
        //     The original matrix is:
        //     1 4 7
        //     2 5 8
        //     3 6 9
        // If you want to work with a matrix that looks like this:
        //   1 2 3
        //   4 5 6
        //   7 8 9
        // you can either store the data as follows:
        //   md::TypedArray<double> doubles = 
        //     factory.createArray<double>({NUM_ROWS, NUM_COLS},
        //       {1.0, 4.0, 7.0,
        //        2.0, 5.0, 8.0,
        //        3.0, 6.0, 9.0}); 
        // or apply the MATLAB transpose function to the original matrix.
        std::cout << "The original matrix is: " << std::endl;
        writeMatrix<double>(std::cout, doubles);
        
        std::vector<md::Array> matrices{doubles, doubles};
        std::cout << "The sum of the matrix with itself is: " << std::endl;
        auto sum = lib->feval("addmatrix", 1, matrices);
        // The feval call returns a vector (of length 1) of md::Array objects.
        writeMatrix<double>(std::cout, sum[0]);

        std::cout << "The product of the matrix with itself is: " << std::endl;
        auto product = lib->feval("multiplymatrix", 1, matrices);
        writeMatrix<double>(std::cout, product[0]);

        std::cout << "The eigenvalues of the original matrix are: " << std::endl;
        std::vector<md::Array>single_matrix{doubles};
        auto eigenvalues = lib->feval("eigmatrix", 1, single_matrix);
        writeMatrix<double>(std::cout, eigenvalues[0]);

        // This part of the code shows how createBuffer and createArrayFromBuffer
        // can be used to convert from row-major to column-major order.
        auto colMajorMatrixBuffer = factory.createBuffer<int>(6);
        // The following call writes the values 100, 101, 102, 103, 104, 105
        // into colMajorMatrixBuffer.
        std::iota(colMajorMatrixBuffer.get(), colMajorMatrixBuffer.get() + 6, 100);
        auto colMajorMatrixArray = factory.createArrayFromBuffer({2, 3},
            std::move(colMajorMatrixBuffer), md::MemoryLayout::COLUMN_MAJOR);
        // OUTPUT:
        // The original contents of the column-major matrix are:
        //      100 102 104
        //      101 103 105
        std::cout << "The original contents of the column-major matrix are: " << std::endl;
        writeMatrix<int>(std::cout, colMajorMatrixArray);
        std::vector<md::Array> colMajorMatrixArrays{colMajorMatrixArray,
            colMajorMatrixArray};
        
        // OUTPUT:
        // The sum of the column-major matrix with itself is:
        // 200 204 208
        // 202 206 210
        std::cout << "The sum of the column-major matrix with itself is: " << std::endl;
        auto sumOfColMajorMatrixArrays = lib->feval("addmatrix", 1, colMajorMatrixArrays);
        // The feval call returns a vector (of length 1) of md::Array objects.
        writeMatrix<int>(std::cout, sumOfColMajorMatrixArrays[0]);
        
        auto rowMajorMatrixBuffer = factory.createBuffer<int>(6);
        std::iota(rowMajorMatrixBuffer.get(), rowMajorMatrixBuffer.get() + 6, 100);
        auto rowMajorMatrixArray = factory.createArrayFromBuffer({3, 2},
            std::move(rowMajorMatrixBuffer), md::MemoryLayout::ROW_MAJOR);
        // OUTPUT:
        // The original contents of the row-major matrix are:
        // 100 101
        // 102 103
        // 104 105
        std::cout << "The original contents of the row-major matrix are: " << std::endl;
        writeMatrix<int>(std::cout, rowMajorMatrixArray);
        std::vector<md::Array> rowMajorMatrixArrays{rowMajorMatrixArray, rowMajorMatrixArray};
        
        // OUTPUT:
        // The sum of the row-major matrix with itself is:
        // 200 202
        // 204 206
        // 208 210
        std::cout << "The sum of the row-major matrix with itself is: " << std::endl;
        auto sumOfRowMajorMatrixArrays = lib->feval("addmatrix", 1, rowMajorMatrixArrays);
        // The feval call returns a vector (of length 1) of md::Array objects.
        writeMatrix<int>(std::cout, sumOfRowMajorMatrixArrays[0]);
    } catch(const std::exception & exc) {
        std::cerr << exc.what() << std::endl;
        return -1;
    }
    return 0;
}

std::u16string convertAsciiToUtf16(const std::string & asciiStr)
{
    return std::u16string(asciiStr.cbegin(), asciiStr.cend());
}

template <typename T>
void writeMatrix(std::ostream & ostr, const md::TypedArray<T> & matrix, 
    md::MemoryLayout layoutOfArray /*= md::MemoryLayout::ROW_MAJOR*/)
{
    md::ArrayDimensions dims = matrix.getDimensions();
    if (dims.size() != 2)
    {
        std::ostringstream ostrstrm;
        ostrstrm << "Number of dimensions must be 2; actual number: " << dims.size();
        throw std::runtime_error(ostrstrm.str());
    }

    switch(layoutOfArray)
    {
        case md::MemoryLayout::ROW_MAJOR:
            for (size_t row = 0; row < dims[0]; ++row)
            {
                for (size_t col = 0; col < dims[1]; ++col)
                {
                    std::cout << matrix[row][col] << " ";
                }
                std::cout << std::endl;
            }
            break;

        case md::MemoryLayout::COLUMN_MAJOR:
            for (size_t col = 0; col < dims[1]; ++col)
            {
                for (size_t row = 0; row < dims[0]; ++row)
                {
                    std::cout << matrix[row][col] << " ";
                }
                std::cout << std::endl;
            }
            break;

        default:
            std::cout << "WARNING: invalid layout passed to writeMatrix." << std::endl;
            break;
    }
    std::cout << std::endl;
}

The matrix application performs these actions.

Copy and paste the generated code archive libmatrix.ctf from thev2\generic_interface folder into the project folder that contains your C++ application.

Compile and link the application using mbuild at the system command prompt.

The compiler generates an executable file named matrix_mda for your C++ application.

Run the application from the system command prompt. To test your application in MATLAB before deployment, run the executable using the bang (!) operator.

The application outputs the results of matrix calculations using the packaged MATLAB functions.

The original matrix is:
1 4 7
2 5 8
3 6 9

The sum of the matrix with itself is:
2 8 14
4 10 16
6 12 18

The product of the matrix with itself is:
30 66 102
36 81 126
42 96 150

The eigenvalues of the original matrix are:
16.1168
-1.11684
-1.57673e-16

The original contents of the column-major matrix are:
100 102 104
101 103 105

The sum of the column-major matrix with itself is:
200 204 208
202 206 210

The original contents of the row-major matrix are:
100 101
102 103
104 105

The sum of the row-major matrix with itself is:
200 202
204 206
208 210

To run the C++ application outside of MATLAB, you must install MATLAB Runtime. For details, see Download and Install MATLAB Runtime. If you create an installer usingBuild and Package, the installer contains a version of MATLAB Runtime that matches the version of MATLAB used to compile the C++ shared library.

To deploy the C++ application, distribute the executable file and MATLAB Runtime to the end user.

See Also

C++ Shared Library Compiler | compiler.build.cppSharedLibrary | compiler.package.installer | mbuild

Topics