MATLAB實現整合系列第三期:Compiler SDK(下)C++環境為例

Fred Liu
16 min readJan 12, 2024

--

接續上次的流程,這裡的轉擇點是在Library Compiler中這次選擇使用C++ Shared Library,將轉出C++的dll,在C++中建立專案後呼叫dll做使用,因此在前段的部分請參考Compiler SDK(上)。

此次選擇C++ Shared Library

記得Support Packages要添加。

然後有一點比較重要的是,目前在包裝成C++ dll的API上有兩種方式,分別是:

MATLAB Data API(C++11) (since R2018a)
mwArray API(C++03)

mwArray API是比較舊也早期的用法,但也是目前大多數人所使用的方式,不過官方有列出一系列使用MATLAB Data API的好處,可以參考以下連結:

在Library Compiler的APP上要更改的話,會在下面那的API selection列中

關於MATLAB Data API(C++11) (since R2018a)與mwArray API(C++03)

大多人比較常使用,也是比較長年下來的使用方式都是使用mwArray居多,這是基於在C++03框架下發展出來的API,如果使用的話會產生出來的檔案如下:

mwArray API(C++03)

而在較新一點的版本後提供的則是MATLAB Data API的方式,是基於在C++11的情況下所發展,所以實際上是比傳統的mwArray在整合上擁有更多的優勢,尤其是在管理runtime的生命週期上來得更加容易。

MATLAB Data API(C++11)

以下是將官網的介紹機翻:

MATLAB Compiler SDK 提供了兩種將 MATLAB 函式部署到 C++ 應用程式中的方式:

  1. 使用 MATLAB Data API 部署到 C++ 應用程式(C++11)
  2. 使用 mwArray API 部署到 C++ 應用程式(C++03)

由於 MATLAB Compiler SDK 使用兩種不同的 C++ 應用程式 API 與已部署的 MATLAB 函式進行交互,這兩種部署選項基於用於在 C++ 應用程式和已部署的 MATLAB 函式之間交換資料的 API 有所不同。

選擇 C++ 部署選項的關鍵在於了解每個選項的功能,並認識這些功能如何滿足你的開發需求。兩個選項都提供了一套完整的 API,用於處理應用程式管理和資料操作。

MATLAB Data API 相對於 MWArray API 的優勢

MathWorks 建議使用更現代的 MATLAB Data API,而不是 MWArray API 部署到 C++ 應用程式。使用 MATLAB Data API 相對於 MWArray API 的優勢包括:

  1. 減少資料複製操作。
  2. 無需顯式管理 MATLAB Runtime 實例和函式庫的生命週期,因為 C++ API 提供了安全的終止方式。
  3. Runtime 實例可以在 C++ 應用程式的進程中或外部運行,已部署的 MATLAB 函式可以同步或異步執行。
  4. 支援 C++11 功能、型別安全性和多線程安全性。
  5. 支援 MATLAB 代碼中的強型別。
  6. 自 R2018a 開始,複雜資料以 MATLAB 內部和 C++ 語言共用的交錯格式存儲,無需進行內部轉換。

產生的工件差異

當你將 MATLAB 函式或類別提供給 compiler.build.cppSharedLibrary 函式或 Library Compiler 應用程式時,對於兩種部署選項,生成的主要成果是不同的。

假設你有一個名為 calculateDistance 的 MATLAB 函式,儲存在一個名為 calculateDistance.m 的檔案中,並且希望在 C++ 應用程式中部署,讓我們來檢查這兩種部署替代方案的結果。

Visual Studio整合

在此段落會介紹兩種不同的API方式如何在Visual Studio上做整合,詳細的文件可以參考以下的段落:

https://www.mathworks.com/help/compiler_sdk/cxx/cpp-development-environment.html

  1. 基於MWArray API進行整合:

首先先創建專案,選擇C++並選擇Console APP來做創建。

切換成64位元環境

在專案的參數中做設定(點選紅圈處)

C/C++ > General > Additional Include中加入以下資訊

如果是有MATLAB環境:
C:\Program Files\MATLAB\R2023b\extern\include

如果是Runtime環境
C:\Program Files\MATLAB\MATLAB Runtime\R2023b\extern\lib\win64\microsoft

然後也要將轉出來的h檔放進這個路徑中(例如)
D:\Fred\CompilerSDK\yolo_c_mwarray

Linker > General > Additional Library Directories中加入以下資訊

在MATLAB環境:
C:\Program Files\MATLAB\R2023b\extern\lib\win64\microsoft

在Runtime環境
C:\Program Files\MATLAB\MATLAB Runtime\R2023b\extern\lib\win64\microsoft

將轉出來的h檔放進這個路徑中(例如)
D:\Fred\CompilerSDK\yolo_c_mwarray

Linker > Input > Delay Loaded Dlls 中加入以下資訊

<compilerSDKGeneratedLibrary>.lib
mclmcrrt.lib

Solutiom Explorer中加入.h與.dll & .lib等檔案。

建置好以上環境後,就可以撰寫一下的範例程式碼, 來呼叫dll中的function來做執行,而這邊的操作方式是在main function中先初始化啟動mcr,
"mclmcrInitialize();",並將主要調用dll的功能放在mclRunMain中做啟動,最後在使用mclTerminateAPPlocation,來關掉mcr的功能。


#include <iostream>
#include "YOLOv4DetectvC_04.h"

int run_main(int argc, const char** argv)
{
if (!mclInitializeApplication(nullptr, 0))
{
std::cerr << "Could not initialize the application." << std::endl;
return -1;
}
std::cout << "run mcr " << std::endl;;

if (!YOLOv4DetectvC_04Initialize())
{
std::cerr << "Could not initialize the YOLOV4." << std::endl;
return -2;
}

mwArray num;
try
{
YOLOv4Detect(1, num);
std::cout << "end" << std::endl << num << std::endl;
}
catch (const mwException & e)
{
std::cout << e.what() << std::endl;
}
mclTerminateApplication();
return 0;
}

int main(int argc, const char** argv)
{
mclmcrInitialize();

std::cout << "in try" << std::endl;

return mclRunMain((mclMainFcnType)run_main, argc, argv);
}

2. 基於MATLAB Data API進行整合:

在使用新版的MATLAB Data API進行整合時,在C++的環境設置上會跟先前的mwArray有所不一樣,以及在做相關變數的設定與呼叫上也不太相同,因此可以當作是兩者不同的技術來做使用。

本文後續會使用以下的範例來做操作,並且將環境設定與範例參考放在下方連結。

環境設定:Setting Up C++ Development Environment

範例參考:Deploy to C++ Applications Using MATLAB Data API (C++11)

首先先創建專案,選擇C++並選擇Console APP來做創建。

切換成64位元環境

在專案的參數中做設定(點選紅圈處)

C/C++ > General > Additional Include中加入以下資訊

如果是有MATLAB環境:
C:\Program Files\MATLAB\R2023b\extern\include

如果是Runtime環境
C:\Program Files\MATLAB\MATLAB Runtime\R2023b\extern\include

Linker > General > Additional Librry Directories中加入以下資訊

如果是有MATLAB環境:
C:\Program Files\MATLAB\R2023b\extern\include

如果是Runtimae環境
C:\Program Files\MATLAB\MATLAB Runtime\R2023b\extern\lib\win64\microsoft

Linker > Input> Additional Dependencies中加入以下資訊

delayimp.lib
libMatlabCppSharedLib.lib
libMatlabDataArray.lib

Linker > Input> Delay Loaded中加入以下資訊

libMatlabDataArray.dll

以下是在MATLAB中所使用的程式碼:

截圖
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

使用MATLAB Data API include的情況請加入以下
#include “MatlabCppSharedLib.hpp”

接下來就是完整在C++中所建立的程式碼內容:

#include <iostream>
#include "MatlabCppSharedLib.hpp"
#include "D:\Fred\CompilerSDK\calculateDistance\for_redistribution_files_only\v2\generic_interface\calculateDistancev2.hpp"


std::shared_ptr<matlab::cpplib::MATLABApplication> setup()
{
auto mode = matlab::cpplib::MATLABApplicationMode::IN_PROCESS;
std::vector<std::u16string> options = { u"-nojvm" };
std::shared_ptr<matlab::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_ptr<matlab::cpplib::MATLABApplication> app, const int argc, const char* argv[])
{
try {
auto libPtr = matlab::cpplib::initMATLABLibrary(app, u"calculateDistance.ctf");
std::shared_ptr<MATLABControllerType> matlabPtr(std::move(libPtr));

// Specify inputs using std::vector; no need to create matlab::data::Array objects

std::vector<int32_t> p1 = { 0, 0 };
std::vector<int32_t> 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: "
<< matlabPtr << "\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;
}

執行後就可以得到輸出的數值如下圖:

細部拆解整體的用法,在MATLAB階段就需要將變數的格式先做好定義,在C++環境中則是先啟動Runtime,所以設置了setup的函式,設定相關的runtime啟動方式與參數,以及下方mainFunc則是設定如何建立變數,以及呼叫Fnc做使用,詳細的一些變數設置與內容,未來可能再開一篇文章做深度介紹。

--

--