您的位置:首页 > 编程语言 > MATLAB

C++/MATLAB 联合编程:C++中调用MATLAB编译出来的动态库

2020-07-16 22:50 169 查看

欢迎关注我的微信公众号:MatlabGUI QtCPP等学习记录

目录

前言mwArrayBrief构造函数成员方法C++ 调用 MATLAB 实例前期准备无返回值有返回值注意事项

前言

之前我写过一篇 MATLAB与C语言的混合编程,那个是用 mex 命令 来编译 .C 文件 生成 .mexw64 文件来给 MATLAB 调用。注意啊,是在 MATLAB中 调用 C函数。

现在,在这偏推送里讲的是:在 C++中 调用 MATLAB函数 !

其实,我之前也没怎么学在C++中调用MATLAB,突然学这个是因为 上上周老师催着要一个原型发给客户。但是核心的算法目前仅仅是用MATLAB验证了正确性,还没有用C++写。如果核心的算法用 C++ 来写的话估计要花好久(毕竟也就学了一个学期多点的C++)。所以为了赶时间,我就打算用 MATLAB 把核心的算法打包成动态,然后拿去给C++调用。

简单展示下还没做完的东西吧:

不多说废话了,进入正题。

mwArray

关于 mwArray 的构造函数 以及 其成员方法 部分,完全是按照官网上的 文档复制过来的,有一些关于稀疏矩阵的方法我这里没放,我平时也没解过大型的方程组,所以稀疏矩阵这块也没怎么用,也就没有花时间去了解它。

Brief

Class used to pass input/output arguments to C++ functions generated by MATLAB Compiler SDK

用于将输入/输出参数传递给由MATLAB Compiler SDK生成的C ++函数的类。

使用mwArray类将输入/输出参数传递给由MATLAB生成的 C++ 接口函数。在MATLAB中所有的数据均由数组表示,这个类就是是对MATLAB中数组的封装。mwArray类提供必一些构造函数,方法以及运算符,用于数组的创建和初始化 以及 简单索引方式取值。

构造函数

1. 创建 空的 数组

[code]/// 空的 数组,元素类型预定为 double; 构造函数:mwArray(mxClassID mxID)
mwArray a(mxDOUBLE_CLASS);

/// Note:mxClassID有以下若干种(下同)
mxDOUBLE_CLASS

mxSINGLE_CLASS

mxINT8_CLASS

mxUINT8_CLASS

mxINT16_CLASS

mxUINT16_CLASS

mxINT32_CLASS

mxUINT32_CLASS

mxINT64_CLASS

mxUINT64_CLASS

2. 二维数组

[code]/// 二维数组 构造函数:mwArray(mwSize num_rows, mwSize num_cols, mxClassID mxID, mxComplexity cmplx = mxREAL)
//  行 和 列 都是 mwSize 类型的值,mwSize 是 size_t 的 typedef,
//  size_t 在win64种是 unsigned __int64 的typedef;在其他中是 unsigned int 的typedef
mwSize num_rows = 3;
mwSize num_cols = 3;
mwArray b(num_rows, num_cols, mxDOUBLE_CLASS);

3. 三维数组/多维数组

[code]/// 三维数组 mwArray(mwSize num_dims, const mwSize* dims, mxClassID mxID, mxComplexity cmplx = mxREAL)
mwSize num_dims = 3;                // 数组的维数
const mwSize dims[3] = {5, 5, 3};   // 每个维度具体的值
mwArray c(num_dims, dims, mxDOUBLE_CLASS);

其实 二维数组 也可以用这样的方式来创建

4. 一维字符数组(字符串)

[code]/// 一维字符数组 mwArray(const char* str)
std::string str = "Mitch Hong";
mwArray d(str.c_str());

5. 多行字符串

[code]/// 多行字符串,不要求每行字符个数相等 mwArray(mwSize num_strings,const char ** str)
const mwSize num_strings = 3;
const char* strs[num_strings] = {"Mitch", "Hong", "Miao"};
mwArray e(num_strings, strs);

6. 结构体数组

[code]/// 二维 结构体数组 mwArray(mwSize num_rows, mwSize num_cols, int num_fields, const char** fieldnames)
//  Note: 结构体的每个 域值 也 必须是 mwArray 类型。在后面 方法30 中写了一个 设置结构体域值的例子
num_rows = 2;  // 前面声明且定义过,这里直接赋值
num_cols = 2;  // 前面声明且定义过,这里直接赋值
int num_fields = 2;
const char* fields[2] = {"Name", "Sex"};
mwArray f(num_rows, num_cols, num_fields, fields);

这个可能稍微难理解一点,多看几眼这个构造函数,这个构造函数就是说明结构体数组有 几行 几列,几个域名,每个域名具体是什么。对于结构体值的设置,看一下下面的第30个成员方法。

7. 多维结构体数组

略,我都没碰见过,就不写这个了。

8. 拷贝构造函数(深拷贝)

mwArray(const mwArray& arr)

9. 标量

[code]/// 标量 实数:mwArray(<type> re);虚数:mwArray(<type> re, <type> im)
mwArray g(mxDouble);
mwArray h(mxDouble, mxDouble);

成员方法

基本复制的官网上的,基本都没改。

1. 深拷贝

mwArray Clone() const

[code]mwArray a(2, 2, mxDOUBLE_CLASS);
mwArray b = a.Clone();

2. 浅拷贝(公用同一个地址的值)

mwArray SharedCopy() const

[code]mwArray a(2, 2, mxDOUBLE_CLASS);
mwArray b = a.SharedCopy();

3. 把数组 序列化 为 字节。

mwArray Serialize() const

[code]mwArray a(2, 2, mxDOUBLE_CLASS);
mwArray b = a.Serialize();
std::cout << "3.Serialize():\n" << b << std::endl << std::endl;

反序列化成原始形势:mwArray::Deserialize().

4. 确定数组的类型

mxClassID ClassID() const

[code]mwArray a(2, 2, mxDOUBLE_CLASS);
mxClassID id = a.ClassID();
std::cout<< "4.ClassID():\nid == mxDOUBLE_CLASS: " << (id == mxDOUBLE_CLASS) << std::endl << std::endl;

5. 确定数组类型元素的大小(以字节为单位)

size_t ElementSize()const

[code]mwArray a(2, 2, mxDOUBLE_CLASS);
int size = a.ElementSize();
std::cout << "5.ElementSize(): " << size << std::endl << std::endl;

6. 确定数组元素的个数

mwSize NumberOfElements() const

[code]mwArray a(2, 2, mxDOUBLE_CLASS);
int n = a.NumberOfElements();
std::cout << "6.NumberOfElements(): " << n << std::endl << std::endl;

7. 确定数组的维数

mwSize NumberOfDimensions() const

[code]mwArray a(2, 2, mxDOUBLE_CLASS);
int n = a.NumberOfDimensions();
std::cout << "7.NumberOfDimensions(): " << n << std::endl << std::endl;

8. 确定结构体数组中的字段数(如果这个mwArray变量不是结构体类型则返回0)

int NumberOfFields() const

[code]const char* fields[] = {"a", "b", "c"};
mwArray a(2, 2, 3, fields);
int n = a.NumberOfFields();
std::cout << "8.NumberOfFields(): " << n << std::endl << std::endl;

9. 获取 结构体的 第 index 个字段名

mwString GetFieldName(int index)
;如果mwArray变量不是结构体 则抛出异常

[code]const char* fields[] = {"a", "b", "c"};
mwArray a(2, 2, 3, fields);
mwString tempname = a.GetFieldName(1);
std::cout << "9.GetFieldName(1): " << tempname << std::endl << std::endl;

10. 返回数组每个维度的大小(比如 几行几列):

mwArray GetDimensions() const
。返回的其实也是数组

[code]mwArray a(2, 2, mxDOUBLE_CLASS);
mwArray dims = a.GetDimensions();
std::cout << "10.GetDimensions(): " << dims << std::endl << std::endl;

11. 判断数组是否为空

bool IsEmpty() const

[code]mwArray a;
bool b = a.IsEmpty();
std::cout << "11.IsEmpty(): " << b << std::endl << std::endl;

12. 判断是否是 数值数组

bool IsNumeric() const

[code]mwArray a(2, 2, mxDOUBLE_CLASS);
bool b = a.IsNumeric();
std::cout << "12.IsNumeric(): " << b << std::endl << std::endl;

13. 判断是否是 复数 数组

bool IsComplex() const

[code]mwArray a(2, 2, mxDOUBLE_CLASS, mxCOMPLEX);
bool b = a.IsComplex();
std::cout << "13.IsComplex(): " << b << std::endl << std::endl;

14. 判断两个数组是否相等(逐字节判断)

bool Equals(const mwArray&amp; arr) const

[code]mwArray a(2, 2, mxDOUBLE_CLASS);
mwArray b(2, 2, mxDOUBLE_CLASS);
bool c = a.Equals(b);
std::cout << "14.Equals(): " << c << std::endl << std::endl;

所以就算你用相同的数据来初始化两个不同类型的数组(如mxINT_CLASS 和 mxDOUBLE_CLASS),其结果也可能是不相等。

也就是这个方法只能用于两个像同类型的数组来判断是否相等

15. 把mwArray数组转化成字符串输出

mwString ToString()const

[code]mwArray a(2, 2, mxDOUBLE_CLASS, mxCOMPLEX);
a.Real() = 1.0;
a.Imag() = 2.0;
std::cout << "15.ToString(): " << a.ToString() << std::endl << std::endl;

16. 把一个 实数矩阵 转化为 复数矩阵

void MakeComplex()
;原地修改;如果不是数值矩阵会抛出异常

[code]double rdata[4] = {1.0, 2.0, 3.0, 4.0};
double idata[4] = {10.0, 20.0, 30.0, 40.0};
mwArray a(2, 2, mxDOUBLE_CLASS);
a.SetData(rdata, 4);
a.MakeComplex();
a.Imag().SetData(idata, 4);

std::cout << "16.MakeComplex(): " << a << std::endl << std::endl;

17. 根据索引获取数组的中的值 mwArray Get(mwSize num_indices, …)

[code]double data[9] = {1.0, 2.0, 3.0, 4.0, 5, 6, 7, 8, 9};
double x;
mwArray a(3, 3, mxDOUBLE_CLASS);
a.SetData(data, 9);

x = a.Get(1,7);             // 按列数,第7个元素值(7)
std::cout << "17.Get(): " << x << std::endl;
x = a.Get(2, 1, 3);     // 第一行,第3列的元素值(7)
std::cout << x << std::endl;
x = a.Get(2, 2, 3);     // 第二行,第3列的元素值(8)
std::cout << x << std::endl << std::endl;

Note: 索引从 1 开始!,第一个参数可以是数组的维度,紧接着后面的系列参数表示 取 第几行第几列 或者 第几页的元素值。以二维数组为例,第一个参数正常写成 2 ,后面两个参数分别代表 行 和 列。
如果第一个参数是 1,则应该只输入两个参数才对。假设第2个参数是 n , 那么将按照列来数 第 n 个元素值

18. 根据 域名 和 索引 获取结构体数组中 第 索引 处的 域名下的值

mwArray Get(const char* name, mwSize num_indices, …)

[code]const char* fields[] = {"a", "b", "c"};
mwArray a(3, 3, 3, fields);
mwArray c = a.Get("b", 2, 2, 3);  // 第二行,第三列,处的结构体的 域名为 "b" 的阈值值

std::cout << "18.Get(): a(2, 3).b" << c << std::endl << std::endl;

Note:后面索引的问题,同上

19. 获取数组的 实部 和 虚部

mwArray Real()
mwArray Imag()

[code]double rdata[4] = {1.0, 2.0, 3.0, 4.0};
double idata[4] = {10.0, 20.0, 30.0, 40.0};
mwArray a(2, 2, mxDOUBLE_CLASS, mxCOMPLEX);
a.Real().SetData(rdata, 4);
a.Imag().SetData(idata, 4);
std::cout << "19.Real() & Imag(): \n" << "a.Real(): "  << a.Real() << std::endl;
std::cout << "a,Imag(): " << a.Imag() << std::endl;

20. 用一个mwArray数组的值来赋值给另一个mwArray(用的同一个地址上的值);

void Set(const mwArray&amp; arr)

[code]mwArray a(2, 2, mxDOUBLE_CLASS);
mwArray b(2, 2, mxINT16_CLASS);
mwArray c(1, 2, mxCELL_CLASS);
c.Get(1,1).Set(a);
c.Get(1,2).Set(b);

std::cout << "20. Set(): " << c << std::endl << std::endl;

21. 从mwArray 中拷贝 len 个数值到 一个C标准数组中

void GetData(* buffer, mwSize len) const

[code]double rdata[4] = {1.0, 2.0, 3.0, 4.0};
double data_copy[4] ;
mwArray a(2, 2, mxDOUBLE_CLASS);
a.SetData(rdata, 4);
a.GetData(data_copy, 4);

std::cout << "21. GetData(): " << data_copy[0] << " " << data_copy[1] << " " << data_copy[2] << " " << data_copy[3] << std::endl << std::endl;

22. 从 mwArray 字符数组中拷贝 len 个 字符 到一个 char[] 数组中

void GetCharData(mxChar* buffer, mwSize len) const

[code]mxChar data[6] = {'H', 'e' , 'l' , 'l' , 'o' , '\0'};
mxChar data_copy[6] ;
mwArray a(1, 6, mxCHAR_CLASS);
a.SetCharData(data, 6);
a.GetCharData(data_copy, 6);
std::cout << "22. GetCharData(): " << data_copy << std::endl << std::endl;

23. 给mwArray 数组赋值,从一个 标准 C 数组中 拷贝前 len 个数值 到 mwArray 数组中

void SetData(* buffer, mwSize len)

[code]double rdata[4] = {1.0, 2.0, 3.0, 4.0};
double data_copy[4] ;
mwArray a(2, 2, mxDOUBLE_CLASS);
a.SetData(rdata, 4);
a.GetData(data_copy, 4);  // 把 a 中的值又拷贝给了 data_copy
std::cout << "23. SetData(): " << a << std::endl << std::endl;

24. 给逻辑数组赋值 void SetLogicalData(mxLogical* buffer, mwSize len)

[code]mxLogical data[4] = {true, false, true, false};
mxLogical data_copy[4] ;
mwArray a(2, 2, mxLOGICAL_CLASS);
a.SetLogicalData(data, 4);
a.GetLogicalData(data_copy, 4);  // 把 a 中的值又拷贝给了 data_copy
std::cout << "24. SetLogicalData(): " << a << std::endl << std::endl;

25. 给mwArray字符数组赋值 void SetCharData(mxChar* buffer, mwSize len)

[code]mxChar data[6] = {'H', 'e' , 'l' , 'l' , 'o' , '\0'};
mxChar data_copy[6] ;
mwArray a(1, 6, mxCHAR_CLASS);
a.SetCharData(data, 6);
a.GetCharData(data_copy, 6);  // 把 a 中的值又拷贝给了 data_copy

std::cout << "25. SetCharData(): " << a << std::endl << std::endl;

26. 判断数值是否有限

[code]static bool mwArray::IsFinite(double x)

27. 判断数值是否无穷

[code]static bool mwArray::IsInf(double x)

28. 判断数值是否 NaN

[code]static bool mwArray::IsNaN(double x)

29. mwArray operator()(mwIndex i1, mwIndex i2, mwIndex i3, …, )

[code]double data[4] = {1.0, 2.0, 3.0, 4.0};
double x;
mwArray a(2, 2, mxDOUBLE_CLASS);
a.SetData(data, 4);
x = a(1,1);
x = a(1,2);
x = a(2,2);

std::cout << "29. :" << x << std::endl << std::endl;

和MATLAB中一样的获取值的方法

30. mwArray operator()(const char* name, mwIndex i1, mwIndex i2, mwIndex i3, …, )

[code]const char* fields[] = {"a", "b", "c"};
int index[2] = {1, 1};
mwArray a(1, 1, 3, fields);

a("a", 1, 1) = mwArray("Mitch");   // 结构体的 域值 也必须是 mwArray!
a("b", 1, 1) = mwArray("Hong");   // 结构体的 域值 也必须是 mwArray!
a("c", 1, 1) = mwArray("Miao");   // 结构体的 域值 也必须是 mwArray!

mwArray b = a("a", 1, 1);  // b 是 "Mitch"

std::cout << "30. :" << "a: " << a << std::endl << "b: " << b << std::endl << std::endl;

Note:获取结构体的值

31. 给mwArray数组中的某个元素设置某个标量

mwArray&amp; operator=(const&amp; x)

[code]mwArray a(2, 2, mxDOUBLE_CLASS);
a(1,1) = 1.0;
a(1,2) = 2.0;
a(2,1) = 3.0;
a(2,2) = 4.0;

std::cout << "31. :" << a;

C++ 调用 MATLAB 实例

这里只是举了两个简单的例子,通过MATLAB来画图。

前期准备

首先设置MATLAB的编译器 主要设置C++编译器,装好 VS2017,然后

[code]mbuild -setup

分别点下那两个箭头,然后就好了。如果你用的MinGW编译器则配置起来多几步,这里就不多说了,Win中还是最好用VS的编译器吧。

还有就是你需要引入 MATLAB 的库,和 MCR 那些相关的,之前在 搭建VS2017_QT_MATLAB开发环境 这偏推送种写过,习惯用VS的同学可以看这篇推送去。不过,我现在用的是 CLion ,用 CMake 来引入 MATLAB 的库,相关的 CMakeLists.txt 的写法如下

[code]###################################### MATLAB库 ######################################
# 把包含 MATLAB 那6个lib所在的头文件的目录进来(固定的)
include_directories("C:/Program Files/Polyspace/R2019a/extern/include")
include_directories("C:/Program Files/Polyspace/R2019a/extern/include/win64")

# 把我们用 MATLAB 生成的那些lib的头文件的目录包含进来(我就放在当前目录下)
INCLUDE_DIRECTORIES("./")

# 把我们用 MATLAB 生成的那些 lib所在的 目录 链接 进来(我也放在当前目录下)
link_directories(./)

# 把我们用MATLAB生成的那些 lib 所 依赖的 MATLAB的库目录路径 链接进来
link_directories("C:/Program Files/Polyspace/R2019a/extern/lib/win64/microsoft")
# 链接 我们用MATLAB生成的那个lib 所依赖的 6个库文件 链接程序中,供那个库使用
link_libraries(
        "C:/Program Files/Polyspace/R2019a/extern/lib/win64/microsoft/libmex.lib"
        "C:/Program Files/Polyspace/R2019a/extern/lib/win64/microsoft/libmx.lib"
        "C:/Program Files/Polyspace/R2019a/extern/lib/win64/microsoft/libmat.lib"
        "C:/Program Files/Polyspace/R2019a/extern/lib/win64/microsoft/libeng.lib"
        "C:/Program Files/Polyspace/R2019a/extern/lib/win64/microsoft/mclmcr.lib"
        "C:/Program Files/Polyspace/R2019a/extern/lib/win64/microsoft/mclmcrrt.lib"
)

add_executable(target main.cpp)

target_link_libraries(target PUBLIC 生成的MATLAB库.lib)

Note:如果生成的 MATLAB库对应的lib文件没有和 主CMakeList.txt放一块的话,就必须要使用绝对路径!然后链接到目标文件中。

无返回值

MATLAB代码:

[code]function ShowXY(x, y, XRange, YRange)

ax = axes('Box', 'on', 'XLim', XRange, 'YLim', YRange);
plot(x, y, 'Parent', ax)

end

使用 mcc 命令来编译 m代码

[code]mcc -W cpplib:MyLib -T link:lib ShowXY.m

看一下生成的这个MyLib头文件

拖到最后,你会看到一个 CPP API;

可以看到 无输出的 C++ 的接口和原来的M文件的函数接口一摸一样。在它前面还有个 C API,那个是C的接口,这里用不上。

但是,前面还有有两个 C API 是需要用的:

分别是启动这个库的函数,和终结这个库的函数!

C++代码

[code]#include "MyLib.h"
#include <iostream>

int main()
{
    /// 调用 MATLAB 生成的库 前,先初始化这个库
    if (!MyLibInitialize())
    {
        return -1;
    }

    /// 准备数据
    auto* x = new double[30];
    auto* y = new double[30];
    double XRange[2] = {0, 10};
    double YRange[2] = {0, 20};

    double step = 0.1;
    for (int i = 0; i < 30; ++i)
    {
        x[i] = step * i;
        y[i] = x[i] * x[i];
    }

    /// 把数据转化成 mwArray
    mwArray x_mwArray(1, 30, mxDOUBLE_CLASS);
    mwArray y_mwArray(1, 30, mxDOUBLE_CLASS);
    x_mwArray.SetData(x, 30);
    y_mwArray.SetData(y, 30);

    mwArray XRange_mwArray(1, 2, mxDOUBLE_CLASS);
    mwArray YRange_mwArray(1, 2, mxDOUBLE_CLASS);
    XRange_mwArray.SetData(XRange, 2);
    YRange_mwArray.SetData(YRange, 2);

    /// 调用 MATLAB 生成的 库函数
    ShowXY(x_mwArray, y_mwArray, XRange_mwArray, YRange_mwArray);

    /// 需要 阻止主线程的结束,不然这个窗口也只是一闪而过
    std::cin.get();
    std::cin.get();

    /// 最后程序结束前终止这个库
    MyLibTerminate();

    return 0;
}

结果:

有返回值

这里,返回两个变量值

MATLAB代码

[code]function [mean_x, mean_y] = ShowXY(x, y, XRange, YRange)

ax = axes('Box', 'on', 'XLim', XRange, 'YLim', YRange);
plot(x, y, 'Parent', ax)

mean_x = mean(x);
mean_y = mean(y);

end

相同的操作编译成 库,库的名字还和上面一样, 具体的操作略

假设,MATLAB函数有 nargout 个 返回值,那么有返回值的 接口和没返回值得接口区别在于:有返回值的接口的前 nargout+1 个参数都是用来描述MATLAB函数的返回值。紧接着后面的若干个参数才是MATLAB函数的输入。具体看下面的代码实例

C++ 代码

[code]#include "MyLib.h"

int main()
{
    /// 调用 MATLAB 生成的库 前,先初始化这个库
    if (!MyLibInitialize())
    {
        return -1;
    }

    /// 准备输入数据
    auto* x = new double[30];
    auto* y = new double[30];
    double XRange[2] = {0, 10};
    double YRange[2] = {0, 20};

    double step = 0.1;
    for (int i = 0; i < 30; ++i)
    {
        x[i] = step * i;
        y[i] = x[i] * x[i];
    }

    /// 把数据转化成 mwArray
    mwArray x_mwArray(1, 30, mxDOUBLE_CLASS);
    mwArray y_mwArray(1, 30, mxDOUBLE_CLASS);
    x_mwArray.SetData(x, 30);
    y_mwArray.SetData(y, 30);

    mwArray XRange_mwArray(1, 2, mxDOUBLE_CLASS);
    mwArray YRange_mwArray(1, 2, mxDOUBLE_CLASS);
    XRange_mwArray.SetData(XRange, 2);
    YRange_mwArray.SetData(YRange, 2);

    /// 准备 MATLAB 函数的输出
    int nargout = 2;
    mwArray meanX_mwArray(mxDOUBLE_CLASS);  // 空的就行,只是要声明一下这个 变量
    mwArray meanY_mwArray(mxDOUBLE_CLASS);

    /// 调用 MATLAB 生成的 库函数
    ShowXY(nargout, meanX_mwArray, meanY_mwArray, x_mwArray, y_mwArray, XRange_mwArray, YRange_mwArray);

    /// 打印 MATLAB 函数的  输出
    std::cout << "meanX_mwArray:\n" << meanX_mwArray << std::endl;
    std::cout << "meanY_mwArray:\n" << meanY_mwArray << std::endl;

    /// 需要 阻止主线程的结束,不然这个窗口也只是一闪而过
    std::cin.get();
    std::cin.get();

    /// 最后程序结束前终止这个库
    MyLibTerminate();

    return 0;
}

结果:

注意事项

调用MATLAB库生成多个MATLAB的figure

如果你调用MATLAB所生成的库函数生成多个 MATLAB 中的figure,可能会崩溃死。是因为:可能会多次高频地触发MATLAB的figure的

WindowButtonMotionFunction
。这会造成崩溃!

解决办法,网上说的我试了,反正我没成功。后面是采用MATLAB的

print
函数保存figure窗口中的图形到图片中,然后显示图片就行,一样的效果!

最好现在MATLAB中测试一下函数有没有问题,再编译!

--- mwArray 的官网链接

https://ww2.mathworks.cn/help/compiler_sdk/cxx/mwarray.html

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: