Qt与Matlab混合编程中mwArray数组使用详解
原文链接:https://blog.csdn.net/HongAndYi/article/details/79477031
1 内容简介
在《Qt 5.9 与 matlab 2017b 混合编程基本流程》里介绍了MATLAB与C++混合编程的基本流程,流程走通之后,关键就是通过DLL里的函数实现功能了。
MATLAB编译后的函数具有统一的输入输出参数的接口形式,主要是用到mwArray类型数组。在前一博文里没有对mwArray详细介绍,实际使用中还有些细节的问题,在本文里就对mwArray的使用做义工详细的介绍。
主要的内容包括:
(1) mwArray数组的创建,设置实数数据
(2) mwArray主要函数方法
(3) 给mwArray数组传递复数数据
(4) 给mwArray数组传递字符串数据
Qt 5.9 测试项目运行界面如下图所示,用到的工具和环境是:
matlab 2017b
Qt 5.9
MSVC 2015 64位编译器
Windows 7 64位
- 准备m文件,并编译生成DLL
在Matlab里编写三个m函数文件
function C= matAdd(A,B)
% C= matAdd(A,B), 两个矩阵相加
C=A+B;
end
function [C]= matAbs(A)
% 求矩阵各元素的绝对值或模, 若A为复数矩阵,求各个元素的模
C=abs(A);
end
function [C]= matLoadDataFile(filename)
% 从一个文件载入数组
C=load(filename);
end
注意,这三个函数需要分别保存为m文件。
使用deploytool启动MATLAB Compiler,加入这3个m文件,编译为C++ Shared Library。编译项目保存为matBasics.prj,编译后的\for_redistribution_files_only目录下的文件如下图
2 Qt项目里使用matBasics.dll
2.1 Qt项目创建与.pro文件设置
在Qt Creator里创建一个Qt Widget Application,项目名称为test1,主窗口基于QMainWindow。在项目的文件目录下创建一个\include目录,将matBasics.h和matBasics.lib文件复制到此。
通过Add Library 导入库文件matBasics.lib,并添加Matlab相关的库文件和头文件搜索路径。设MATLAB 2017b安装在D:\MATLAB 2017b目录下,设置后的test1.pro文件如下:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = test1
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp \
MainWindow.cpp \
HEADERS += \
MainWindow.h \
include/matBasics.h \
FORMS += \
MainWindow.ui
#用户自定义的MATLAB程序的DLL库
win32: LIBS += -L$$PWD/include/ -lmatBasics
INCLUDEPATH += $$PWD/include
DEPENDPATH += $$PWD/include
# Matlab 运行库的头文件
# .h文件搜索路径
INCLUDEPATH += D:/MATLAB2017b/extern/include
INCLUDEPATH += D:/MATLAB2017b/extern/include/Win64
# 用到的MATLAB 的.lib库文件
INCLUDEPATH += D:/MATLAB2017b/extern/lib/win64/microsoft
DEPENDPATH += D:/MATLAB2017b/extern/lib/win64/microsoft
win32: LIBS += -LD:/MATLAB2017b/extern/lib/win64/microsoft/ -llibmex
win32: LIBS += -LD:/MATLAB2017b/extern/lib/win64/microsoft/ -llibmx
win32: LIBS += -LD:/MATLAB2017b/extern/lib/win64/microsoft/ -llibmat
win32: LIBS += -LD:/MATLAB2017b/extern/lib/win64/microsoft/ -llibeng
win32: LIBS += -LD:/MATLAB2017b/extern/lib/win64/microsoft/ -lmclmcr
win32: LIBS += -LD:/MATLAB2017b/extern/lib/win64/microsoft/ -lmclmcrrt
2.2 matBasics.dll的初始化
在使用matBasics.dll里的函数之前,需要调用matBasics.h里的函数matBasicsInitialize()进行初始化,我们将初始化在窗口的构造函数里完成。下面是构造函数的代码:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
if (matBasicsInitialize()) //必须调用初始化
ui->plainTextEdit->appendPlainText("matlab程序DLL初始化成功.");
else
{
ui->plainTextEdit->appendPlainText("*** matlab程序DLL初始化失败");
return;
}
qsrand(QTime::currentTime().msec()); //初始化随机数种子
}
3 mwArray类的使用
3.1 mwArray类变量的常用构造函数
mwArray类常用的构造函数如下:
mwArray(mwSizenum_rows,mwSizenum_cols,mxClassIDmxID,mxComplexitycmplx=mxREAL)
num_rows表示行数, mwSize是整数类型
num_cols表示列数
mxID是mxClassID类型,表示元素的基本数据类型,常见的有如下的一些取值
mxLOGICAL_CLASS,
mxCHAR_CLASS,
mxDOUBLE_CLASS,
mxSINGLE_CLASS,
mxINT8_CLASS,
mxUINT8_CLASS,
mxINT16_CLASS,
mxUINT16_CLASS,
mxINT32_CLASS,
mxUINT32_CLASS,
mxINT64_CLASS,
mxUINT64_CLASS,
cmplx是mxComplexity类型,有mxREAL和mxCOMPLEX两种取值,标书数组元素是实数或复数,缺省为mxREAL
3.2 mwArray类变量的定义、赋值和常用函数的意义
窗口上“矩阵A的参数”按钮的响应代码如下:
void MainWindow::on_btnParams_clicked()
{// 求矩阵A的各种参数, 即mwArray类的主要函数的作用
int rowCntA=ui->spinBoxA_Row->value();//行数
int colCntA=ui->spinBoxA_Col->value();//列数
//读取矩阵A
int elementCntA=rowCntA*colCntA;//元素个数
double *arrayA=new double[elementCntA]; //一维数组
int N=0;//一维数组的元素索引号
for(int i=0;i<ui->tableA->columnCount();i++) //逐列读取,按列存储
for (int j=0; j<ui->tableA->rowCount();j++)
{
arrayA[N]=ui->tableA->item(j,i)->text().toDouble();
N++;
}
mwArray matrixA(rowCntA,colCntA,mxDOUBLE_CLASS, mxREAL);//定义数组
matrixA.SetData(arrayA,elementCntA); //设置数据
// mwArray类的主要函数的意义
ui->plainTextEdit->clear();
mwSize dims=matrixA.NumberOfDimensions();
//矩阵的维数,标量,值1=一维序列,2=二维数组
ui->plainTextEdit->appendPlainText(tr("mwArray::NumberOfDimensions() 矩阵的维数,标量"));
ui->plainTextEdit->appendPlainText(tr(" 1 表示一维矩阵, 2 表示二维矩阵"));
ui->plainTextEdit->appendPlainText(tr(" matrixA.NumberOfDimensions()=%1").arg(dims));
mwArray arrayDim=matrixA.GetDimensions();//是个一维数组,元素个数=NumberOfDimensions()
ui->plainTextEdit->appendPlainText(tr("\n mwArray::GetDimensions()是矩阵的具体大小,即行数和列数"));
ui->plainTextEdit->appendPlainText(" matrixA.GetDimensions()的结果:");
for(int i=1;i<=dims;i++)
{
int cnt=arrayDim.Get(dims,i);//即使dims=2, i 表示元素索引,也是可行的
// int cnt=arrayDim.Get(1,i); //一维向量
ui->plainTextEdit->appendPlainText(tr(" 第%1维大小 = %2").arg(i).arg(cnt));
}
mwSize elementCnt=matrixA.NumberOfElements();//元素个数
ui->plainTextEdit->appendPlainText(tr("\n mwArray::NumberOfElements() 元素个数=行数*列数"));
ui->plainTextEdit->appendPlainText(tr(" matrixA.NumberOfElements()=%1").arg(elementCnt));
}
这里,主要需要注意以下问题:
(1)mwArray数组的赋值
mwArray:: SetData(mxUint64* buffer, mwSizelen) 用于给数组赋值
其中,buffer必须是一维数组,即便mwArray变量是一个二维数组,len是一维数组的元素个数,等于行数乘以列数。在给二维数组赋值时,buffer必须按列存储数据(见代码内容)
(2)mwSize mwArray::NumberOfDimensions()函数返回一个整数标量,表示数组的维数
返回值为1表示一维数组,2表示二维数组。
程序测试各种情况下都返回2,即便是一个行向量或列向量。
(3)mwArray mwArray ::GetDimensions()返回一个数组,表示数组各维数的大小,对于向量或二维数组,返回值都是两个元素。第1个元素表示行数,第2个元素表示列数。
(4)mwSize mwArray::NumberOfElements() 返回数组的元素个数,等于行数乘以列数
(5)mwArray的其他常用函数
bool IsComplex() const 返回值表示数组是不是复数类型
bool IsEmpty() const 返回值表示数组是否为空
bool IsNumeric() const 返回值表示矩阵是否为数值型
1行1列的数组的参数
1行4列的数组的参数
3行1列的数组的参数
3.3 mwArray数组数据的读取
界面上“C=A”按钮的响应代码如下,其功能是将数组A直接复制给数组C,并读取出C的内容进行显示。
void MainWindow::on_btnAssign_clicked()
{//C=A,用操作符赋值
int rowCnt=ui->spinBoxA_Row->value();
int colCnt=ui->spinBoxA_Col->value();
int elementCnt=rowCnt*colCnt;//元素个数
mwArray matrixA(rowCnt,colCnt,mxDOUBLE_CLASS, mxREAL);//定义数组,2行,3列,double类型
//读取数组A,
double *arrayA;
arrayA=new double[elementCnt];
int N=0;
for(int i=0;i<ui->tableA->columnCount();i++)
for (int j=0; j<ui->tableA->rowCount();j++)
{
// matrixA(j,i)=ui->tableA->item(j,i)->text().toDouble(); //不能如此赋值
arrayA[N]=ui->tableA->item(j,i)->text().toDouble();
N++;
}
matrixA.SetData(arrayA,elementCnt);
//读取结果
// mwArray matrixC(rowCnt,colCnt,mxDOUBLE_CLASS, mxREAL);//定义数组,2行,3列,double类型
mwArray matrixC=matrixA;
ui->spinBoxC_Row->setValue(rowCnt);
ui->spinBoxC_Col->setValue(colCnt);
int dim=2; //数组维数,表示二维数组
N=1;
for(int i=1; i<=colCnt;i++)
for(int j=1; j<=rowCnt;j++)
{
// double value=matrixC(j,i); //直接用数组下标索引,第j行,第i列
// double value=matrixC(N); //直接按元素序号读取,第N个元素
// double value=matrixA.Get(dim,j,i); //按照dim维数数组读出,获取第j行,第i列的数据
double value=matrixA.Get(dim,N); //按照dim维数数组读出,读取序号为N的元素
N++;
QTableWidgetItem *item=new QTableWidgetItem(QString::asprintf("%.0f",value));
item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableC->setItem(j-1,i-1,item);
}
}
这段代码注意以下问题:
(1)matrixA使用了SetData()函数赋值
mwArray matrixA(rowCnt,colCnt,mxDOUBLE_CLASS, mxREAL);
matrixA.SetData(arrayA,elementCnt);
2)matrixC直接用了mwArray 的“=”操作符赋值,即
mwArray matrixC=matrixA;
(3)mwArray数组元素的读取
可以使用mwArray::Get()函数读取数组的元素,
例如,对于二维数组,采用Get()函数读取数据的代码一般是
int dim=2; // 二维数组
double value=matrixA.Get(dim,j,i); //按照dim维数数组读出,第j行, 第i列
也可以不用行号、列号,而用序号读取,如
int dim=2; // 二维数组
double value=matrixA.Get(dim,N); //按照dim维数数组读出,第N个元素
这里的N是按列排列的元素的总的序号。对于二维数组,还是按照行号、列号更直观一些。
也可以直接使用mwArray的“()”操作符读取数组元素,如
double value=matrixC(j,i); //直接用数组下标索引,第j行,第i列
double value=matrixC(N); //直接按元素序号读取, 第N个元素
3.3 mwArray复数型数组赋值
mwArray也可以是复数性数组,定义数组时使用mxCOMPLEX 类型。
mwArray mwArray::real() 获取数组的实部,也是一个mwArray类型数组
mwArray mwArray::imag() 获取数组的虚部,也是一个mwArray类型数组
下面是界面上“A+B*j复数矩阵求模”按钮的响应代码,它将矩阵A作为复数矩阵的实部,矩阵B作为实数矩阵的虚部,然后代用DLL里的matAbs()函数求模。
void MainWindow::on_btnAbs_clicked()
{// A+B*j复数矩阵求模
int rowCntA=ui->spinBoxA_Row->value();
int colCntA=ui->spinBoxA_Col->value();
int rowCntB=ui->spinBoxB_Row->value();
int colCntB=ui->spinBoxB_Col->value();
if ((rowCntA != rowCntB || (colCntA != colCntB)))
{
QMessageBox::critical(this, "错误", "矩阵A和B的维数不一致,不能构造复数矩阵",
QMessageBox::Ok,QMessageBox::NoButton);
return;
}
//读取矩阵A
int elementCntA=rowCntA*colCntA;//元素个数
double *arrayA=new double[elementCntA]; //一维数组
int N=0;//一维数组的元素索引号
for(int i=0;i<ui->tableA->columnCount();i++) //逐列读取,序列化存储到一维数组
for (int j=0; j<ui->tableA->rowCount();j++)
{
arrayA[N]=ui->tableA->item(j,i)->text().toDouble();
N++;
}
mwArray matrixComplex(rowCntA,colCntA,mxDOUBLE_CLASS, mxCOMPLEX);//定义数组,行,列,double类型复数矩阵
matrixComplex.Real().SetData(arrayA,elementCntA); //为复数矩阵的实部赋值
//读取数组B
int elementCntB=rowCntB*colCntB;//元素个数
double *arrayB=new double[elementCntB];
N=0;
for(int i=0;i<ui->tableB->columnCount();i++) //逐列读取,序列化存储到一维数组
for (int j=0; j<ui->tableB->rowCount();j++)
{
arrayB[N]=ui->tableB->item(j,i)->text().toDouble();
N++;
}
matrixComplex.Imag().SetData(arrayB,elementCntB); //为复数矩阵的虚部赋值
//计算, 复数矩阵的模
int rowCntC=rowCntA;
int colCntC=colCntA;
mwArray matrixAbs(rowCntC,colCntC,mxDOUBLE_CLASS, mxREAL);//定义数组,行,列,double类型
int nargout=1;//输出变量个数
matAbs(nargout,matrixAbs,matrixComplex);
//读取结果
ui->spinBoxC_Row->setValue(rowCntC);
ui->spinBoxC_Col->setValue(colCntC);
int dim=2; //按照二维数组读出matrixC
for(int i=1; i<=colCntC;i++)
for(int j=1; j<=rowCntC;j++)
{
double value=matrixAbs.Get(dim,j,i); //二维数组,第j行,第i列的数据
QTableWidgetItem *item=new QTableWidgetItem(QString::asprintf("%.4f",value));
item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableC->setItem(j-1,i-1,item);
}
}
若是要读取复数型数组的内容,也是通过mwArray::real()和mwArray::imag()分别获取其实部和虚部数组,再读取元素的方法就是一样的了。
3.4 mwArray传递字符串数据
创建的matBasics.dll中的函数matLoadDataFile()是接收一个txt文件名,通过load指令载入文件,并作为数组返回。
function [C]= matLoadDataFile(filename)
% 从一个文件载入数组
C=load(filename);
end
在matlab的指令窗口里生成一个随机数矩阵,并保存为文本文件
>> B=10*rand(3,4)
B =
7.0936 6.797 1.19 3.4039
7.5469 6.551 4.9836 5.8527
2.7603 1.6261 9.5974 2.2381
>> save dataA.txt B -ascii
matBasics.h文件里的 matLoadDataFile()函数的原型是:
bool MW_CALL_CONV mlxMatLoadDataFile(int nlhs, mxArray *plhs[], int nrhs, mxArray *prhs[]);
与其他接口函数的参数形式也是一样的,只是输入参数*prhs[] 需要传递字符串表示的文件名,返回数组plhs的行数、列数是未知的。
Qt 编写程序界面上的“打开数组txt文件”按钮的响应代码如下:
void MainWindow::on_btnOpenFile_clicked()
{//传递字符串型数据
QString aFileName=QFileDialog::getOpenFileName(this,"打开文件","","txt文件(*.txt)");
if (aFileName.isEmpty())
return;
ui->plainTextEdit->clear();
ui->plainTextEdit->appendPlainText(aFileName);
//打开文件,并在plainTextEdit里显示
QString str;
QFile aFile(aFileName); //以文件方式读出
if (aFile.open(QIODevice::ReadOnly | QIODevice::Text)) //以只读文本方式打开文件
{
QTextStream aStream(&aFile); //用文本流读取文件
while (!aStream.atEnd())
{
str=aStream.readLine();//读取文件的一行
ui->plainTextEdit->appendPlainText(str); //添加到文本框显示
}
aFile.close();//关闭文件
}
//使用DLL里的matLoadDataFile()打开文件,并返回数组
char* charStr=aFileName.toLocal8Bit().data();
mwArray matrixFilename(charStr);//字符串数据赋值
mwArray matrixC(mxDOUBLE_CLASS, mxREAL);//返回值是未知大小的数组
int nargout=1;//输出变量个数
matLoadDataFile(nargout,matrixC,matrixFilename);
//读取结果数组
mwSize dims=matrixC.NumberOfDimensions();//矩阵的维数,2表示二维数组
mwArray arrayDim=matrixC.GetDimensions();//各维的具体大小
int rowCnt=arrayDim.Get(dims,1); //行数
int colCnt=arrayDim.Get(dims,2); //列数
ui->spinBoxC_Row->setValue(rowCnt);
ui->spinBoxC_Col->setValue(colCnt);
for(int i=1; i<=colCnt;i++)
for(int j=1; j<=rowCnt;j++)
{
double value=matrixC(j,i); //直接用数组下标索引,可行
QTableWidgetItem *item=new QTableWidgetItem(QString::asprintf("%.4f",value));
item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableC->setItem(j-1,i-1,item);
}
}
这段代码先通过Qt的QFileDialog对话框选择文件并显示其内容,
这段代码要注意以下内容:
(1)字符串数据的mwArray数组的赋值
通过QFileDialog::getOpenFileName()获取需要打开的文件名aFileName,aFileName是QString类型,转换为char*类型变量charStr,定义数组matrixFilename时直接在构造函数里赋值。代码如下:
QString aFileName=QFileDialog::getOpenFileName(this,"打开文件","","txt文件(*.txt)");
char* charStr=aFileName.toLocal8Bit().data();
mwArray matrixFilename(charStr);//字符串数据赋值
(2)未知行数、列数的mwArray的定义
调用matLoadDataFile()函数打开的数组的行列数是不确定的,所以定义返回数组matrixC时未指定行数和列数,定义和调用DLL里函数的代码如下:
mwArray matrixC(mxDOUBLE_CLASS, mxREAL);//返回值是未知大小的数组
int nargout=1;//输出变量个数
matLoadDataFile(nargout,matrixC,matrixFilename);
(3)未知大小的mwArray数组的数据读取
matrixC数组的大小预先是不知道的,调用matLoadDataFile()函数返回数组matrixC后,需要通过一些函数获取数组的行数和列数,才可以从数组里完整的读出数据。获取数组大小的代码如下:
mwSize dims=matrixC.NumberOfDimensions();//矩阵的维数,2表示二维数组
mwArray arrayDim=matrixC.GetDimensions();//各维的具体大小
int rowCnt=arrayDim.Get(dims,1); //行数
int colCnt=arrayDim.Get(dims,2); //列数
ui->spinBoxC_Row->setValue(rowCnt);
ui->spinBoxC_Col->setValue(colCnt);
选择文件并打开后的运行效果如下图所示。