Matlab与CC++混合编程Matlab调⽤C函数
Matlab与C/C++混合编程有很多种⽅式,分别适⽤于不同的情况。
1. 程序主体⽤Matlab编写,有⼀些特别耗时的函数⽤C/C++改写来提⾼效率,或者已经有现成的C/C++函数,应⽤到Matlab程序中(本⽂
属于这种情况)
2. 程序主体⽤C/C++编写,部分程序想调⽤Matlab函数减少开发时间,本⽂不涉及这种情况,建议读者⾃⾏查阅Matlab帮助⽂档
⼀点点废话
Matlab有着⾮常详细的帮助⽂档,建议直接阅读其帮助⽂档,市⾯上很多Matlab书籍都是简单的翻译翻译帮助⽂档,例⼦都是照抄,还有很多错误和断章取义的地⽅,参考这样的书籍容易被带上弯路。
打开Matlab,按F1打开帮助,此部分内容在:
MATLAB->Advanced Software Development->MATALB API for Other Languages
什么是MEX-file
简单来说MEX-file是⼀种预编译的,⽤其他语⾔(C/C++,Fortran)编写的函数库,可以直接被Matlab调⽤。
正如前⾯提到的,这种⽅式适⽤于两种情况:
1. 程序中有⼀部分代码耗时巨⼤,想通过改写这部分函数提⾼速度
2. 已经有⼤量C/C++或Fortran的函数库,想直接⽤Matlab调⽤,避免重复开发
这两种情况⽤MEX-file的这种⽅案来解决都是⾮常合适的,因为这种调⽤⽅式⾮常⽅便,你需要注意地只是数据结构的转换。这种⽅式⽀持C/C++和Fortran,本⽂主要将C/C++。
如何创建可供Matlab直接调⽤的MEX-file
1.安装Matlab⽀持的编译器
在Matlab命令窗⼝输⼊:
mex -setup
如果你的电脑已经安装了Matlab⽀持的编译器,这时候你应该会看到设置编译器的提⽰命令,跟着⼀步步下去就可以了。
注意:如果你电脑只安装了⼀个⽀持的编译器,这⼀步会⾃动⽤此编译器进⾏配置,如果有多个⽀持的编译器,Matlab会引导你选择要使⽤哪个编译器。如果你电脑没有安装合适的编译器,会得到⼀个错误,提⽰你安装合适的编译器,并给出⼀个⽀持编译器列表的链接。
2.创建.c/.cpp⽂件
这⼀步可以⽤Matlab的编辑器也可以⽤其他你喜欢的编辑器,需要注意的是:将来在Matlab中调⽤的函数名即为此处你创建的⽂件名,⽽不是⽂件内的函数名
MEX-file的内容
⼀个完整的MEX-file应该包括:
#include <mex.h> MEX-file头⽂件
mexFunction⼊⼝函数(C/C++中的main函数)
输⼊输出的数据的校验
变量的传递
你⾃⼰编写的功能函数
mexFunction⼊⼝函数
void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]);
此函数是MEX-file的⼊⼝函数,形式⽐较固定,起着C/C++语⾔中main函数的作⽤,建议放在整个⽂件的最后。
mexFunction函数中⼀般只做数据的转换和其他函数的调⽤,不做复杂的处理。
prhs -函数右侧,输⼊参数
plhs -函数左侧,输出参数
nrhs -函数右侧,输⼊参数的个数
nlhs -函数左侧,输出参数的个数
例如:在Matlab中⽤[a,b]=myMEX(c,d,e)的形式调⽤的函数,则nrhs==3代表有三个输⼊参数,nlhs==2代表有两个输⼊参数,参数值分别储存
在prhs和plhs中。输⼊输出数据的校验
这⼀部分建议放在mexFunction⾥⾯,校验输⼊输出参数的个数是否符合要求,校验输⼊参数的类型是否符合要求。
这⾥的输⼊参数是只读的,不要尝试更改,不然会引起错误。
创建⼀个可更改的输⼊参数的副本myData并调⽤mxDuplicateArray函数:
mxArray *myData = mxCreateStructMatrix(1,1,nfields,fnames);
mxSetField(myData,0,"myFieldName",mxDuplicateArray(prhs[0]));
对于输⼊参数类型的校验可以⽤mxIsClass中的函数来进⾏:
if(mxIsSparse(prhs[1])||mxIsComplex(prhs[1])||mxIsClass(prhs[1],"char")) {
mexErrMsgTxt("input2 must be full matrix of real values.");
}
完整的mxIsClass函数列表见附录。
3.变量的传递
这⼀部分主要涉及如何将输⼊参数中的数据传出,并且⽤C/C++的数据结构来表⽰,以及如何构建输出参数,将运算结果传回Matlab。
由于Matlab中数据结构种类⽐较多,且⽐较复杂,这⾥并不会⼀⼀涉及,只介绍⼏种⽐较常⽤的数据类型,其他数据类型⼤家可以⾃⾏查阅Matlab帮助⽂档。
以下的⽰例代码都假设你需要传递的输⼊参数是第⼀个,如果为其他,只需修改prhs的⾓标即可
标量的传递
size_t mrows;  //⾏数
size_t ncols;  //列数
double scalar; //接收输⼊参数的变量
mrows = mxGetM(prhs[0]); //获取矩阵⾏数
ncols = mxGetN(prhs[0]); //获取矩阵列数
/*校验输⼊是否是⼀个标量*/
if( !mxIsDouble(prhs[0]) || mxIsComplex(prhs[0]) || !(mrows==1 && ncols==1) ) {
mexErrMsgIdAndTxt( "MATLAB:timestwo:inputNotRealScalarDouble","Input must be a noncomplex scalar double.");
}
scalar = mxGetScalar(prhs[0]); //获取标量值
矩阵的传递
printf输出格式matlabsize_t mrows;    //⾏数
size_t ncols;    //列数
mxArray *inMat;  //接收输⼊参数的指针
mrows = mxGetM(prhs[0]); //获取矩阵⾏数
ncols = mxGetN(prhs[0]); //获取矩阵列数
/*校验输⼊是否是⼀个3*4的double矩阵
矩阵维数的校验也可以去掉(相应的你的处理函数要有处理不同⼤⼩矩阵的能⼒)*/
if( !mxIsDouble(prhs[0]) || mxIsComplex(prhs[0]) || !(mrows==3 && ncols==4) ) {
mexErrMsgIdAndTxt( "MATLAB:timestwo:inputNotRealScalarDouble", "Input must be a noncomplex double matrix.");
}
/*获取输⼊矩阵的指针*/
inMat = mxGetPr(prhs[0]);
为输出变量分配内存并传递给mexFunction的输出参数
mxArray *outMat;
outMat  = mxCreateDoubleMatrix((mwSize)mrows,(mwSize)ncols,mxREAL);
plhs[0] = outMat;
这⾥需要注意的是Matlab中矩阵的储存是列优先的,⽽C语⾔中是⾏优先的,在调⽤矩阵元素时需要注意:
double result;
/* 将iMat中的第 i⾏ j列的元素值赋给result */
result = inMat[j*mrows+i]
为输出变量分配内存并传递给mexFunction的输出参数
mxArray *outMat;
outMat  = mxCreateDoubleMatrix((mwSize)mrows,(mwSize)ncols,mxREAL);
plhs[0] = outMat;
字符串的传递
将输⼊参数转换为C-type的string
string input_buf;
input_buf = mxArrayToString(prhs[0]);
为输出字符串分配内存
string output_buf;
output_buf=mxCalloc(buflen, sizeof(char));
将输出字符串传递给输出参数
plhs[0] = mxCreateString(output_buf);
最后释放内存
mxFree(input_buf);
Structure和Cell类型的传递
Structure和Cell类型的传递其实与其他类型相似,他们是mxArray类型。
mxGetField和mxGetCell函数可以⽤来获取指向Structure和Cell类型内容的mxArray类型的指针。
mxGetNumberOfFields和mxGetNumberOfElements可以⽤来获取Structure的条⽬的个数和元素的个数。
mxGetData函数可以⽤来获取mxArray变量中包含的数据。
因为Matlab中Cell的应⽤⽐Structure频繁,并且这两者结构数据传递⽅式很类似,此处以Cell进⾏讲解:假设我们的输⼊参数Cell中第⼀个元素是⼀个1x3的矩阵,第⼆个元素还是⼀个Cell,这个Cell⾥⾯包含两个1x3的矩阵,在Matlab中构建⽅法如下:
temp    = [];
temp{1} = [1:3];
temp{2} = [4:6];
Cell    = [];
Cell{1} = [1:3];
Cell{2} = temp;
现在我们如果我们想将Cell传⼊MEX-file中进⾏处理,读出Cell中第第⼀个元素[1:3]和第⼆个元素temp,这个元素还是⼀个Cell,这在Matlab中很常见,可以如下操作:
mxArray *mat;  //指向第⼀个元素[1:3]的指针
mxArray *Cell;  //指向第⼆个元素的指针,还是⼀个Cell
size_t nrows;  //⾏数
size_t ncols;  //列数
double *data;    //数据
int i; //循环变量
int j;
/* 获取输⼊Cell的维数 */
mrows = mxGetM(prhs[0]);
ncols = mxGetN(prhs[0]);
/* 输出Cell的维数,这⾥作为⽰例我并没有保存Cell的维数,后⾯获取Cell中元素维数时还是⽤的这两个变量 */
mxPrintf("rows:%d,cols:%d\n",mrows,ncols);
/* 取出Cell中第⼀个元素,此处mat是⼀个指向矩阵的mxArray指针,data储存的是数据 */
mat = mxGetCell(prhs[0],0);
data = (double*)mxGetData(mat);
/* 打印矩阵内的元素 [1:3]*/
mrows = mxGetM(mat);
ncols = mxGetN(mat);
for (i=0;i<mrows;i++)
{
for (j=0;j<ncols;j++)
{
mxPrintf("%f    ",data[j*M+i]);
}
mxPrintf("\n");
}
/* 取出Cell中第⼆个元素还是⼀个Cell 再取出⾥⾯内容的⽅法与上述过程⼀致继续调⽤mxGetCell */
Cell = mxGetCell(prhs[0],1);
关于在Mex-file中构建Cell的⽅法,这⾥不详细讲了,因为个⼈觉得这么做吃⼒不讨好,何不把数据分别传⼊Matlab再重新组织呢?如果你真的想要在MEX-file⾥⾯构建Cell并传出,原理是创建⼀个相应⼤⼩的mxArray,因为Cell本⾝就是mxArrary类型的,然后将这部分内存的地址传给plhs。
MEX-file的编译和调⽤
将Matlab的当前⽬录切换到你MEX-file所在的⽬录,假设你的⽂件名为helloMEX.c,在Matlab命令窗⼝输⼊
mex helloMEX.c
如果得到MEX completed successfully.的提⽰即为编译成功,如果不成功,会显⽰错误的位置和原因,对应修改即可。
编译成功后会得到后缀为.mexw64的⽂件(后缀名与平台相关,此为win64下的后缀名,其他平台不同),将此⽂件添加⼊Matlab的路径中,或者将当前⽬录切换到此⽂件所在⽬录,即可像普通的Matlab函数⼀样调⽤此⽂件。
附录: mxIsClass函数列表
(更详细的介绍参见Matlab帮助⽂档)
mxIsDouble() //Determine whether mxArray represents data as double-precision, floating-point numbers
mxIsSingle() //Determine whether array represents data as single-precision, floating-point numbers
mxIsComplex() //Determine whether data is complex
mxIsNumeric() //Determine whether array is numeric
mxIsInt64() //Determine whether array represents data as signed 64-bit integers
mxIsUint64() //Determine whether array represents data as unsigned 64-bit integers
mxIsInt32() //Determine whether array represents data as signed 32-bit integers
mxIsUint32() //Determine whether array represents data as unsigned 32-bit integers
mxIsInt16() //Determine whether array represents data as signed 16-bit integers
mxIsUint16() //Determine whether array represents data as unsigned 16-bit integers
mxIsInt8() //Determine whether array represents data as signed 8-bit integers
mxIsUint8() //Determine whether array represents data as unsigned 8-bit integers
mxIsChar() //Determine whether input is string array
mxIsLogical() //Determine whether array is of type mxLogical
mxIsLogicalScalar() //Determine whether scalar array is of type mxLogical
mxIsLogicalScalarTrue() //Determine whether scalar array of type mxLogical is true
mxIsStruct() //Determine whether input is structure array
mxIsCell() //Determine whether input is Cell array
mxIsClass() //Determine whether array is member of specified class
mxIsInf() //Determine whether input is infinite
mxIsFinite() //Determine whether input is finite
mxIsNaN() //Determine whether input is NaN (Not-a-Number)
mxIsEmpty() //Determine whether array is empty
mxIsSparse() //Determine whether input is sparse array
mxIsFromGlobalWS() //Determine whether array was copied from MATLAB global workspace
mxAssert() //Check assertion value for debugging purposes
mxAssertS() //Check assertion value without printing assertion text