如果想在Matlab中,以Matlab函数的方式调用C程序,那就要用到MEX文件。将C按照一定的格式编写,并编译最终形成MEX文件(后缀为mexw32,不同的Matlab版本这个后缀稍有不同,本文用的是Matlab200b),只要这个MEX文件在Matlab搜索路径中,即可像Matlab工具箱中的函数一样使用了。
Ø 如果Matlab需要调用已有的C算法,不需要将其翻译成m文件,只需要将其编译成MEX文件即可。
Ø 如果为了性能,必须使用C编写核心代码。
使用mex命令可以将C文件编译成二进制的mex文件。mex将源文件编译和连接成可被调用的动态链接库。一旦将C编译成mex文件后就可以像Matlab工具箱中的函数一样使用。
MEX文件由如下部分组成:
Ø C和Matlab之间的接口函数(由mexFunction实现)
Ø 用C编写的实现特定功能的计算程序(由用户编写完成特定的功能)
Ø 与平台相关的预处理宏(Windows平台下与C相关的宏,主要与mxArray类相关)
接口函数是MEX文件的入口点,通过接口Matlab才能调用MEX文件,创建入口函数包括如下步骤:
Ø 接口函数签名:
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]);
Ø 接口参数:
接口必须包括如下参数prhs,nrhs,plhs,和nlhs详细介绍如下表
参数名称 |
参数描述 |
prhs |
输入参数 |
plhs |
输出参数 |
nrhs |
输入参数的个数 |
nlhs |
输出参数的个数 |
Ø 创建使用源文件:
可以将C的源文件和mexFunction入口函数写在一个文件中,也可以将C的源文件和mexFunction分别写成不同的函数。
Ø 使用Matlab库:
Matlab函数库提供了接口函数mexFunction和C计算函数之间的数据转换。
Ø 包含相关头文件
为了使用mex的API需要包含头文件mex.h
#include "mex.h"
Ø MEX文件命名
MEX文件名就是被Matlab调用时的函数名,这个名称是包含入口函数的C的源文件名(结果改造的C源文件,已经包括了入口点函数)。
这部分是原始的未经过改造的C源文件,能够实现用户特定的功能。
mex文件用mwSize代表位数大小,比如矩阵维数大小,数组中元素的个数。
Matlab调用C生成的MEX属于异构程序之间的整合,总的来说需要解决两个问题:接口和数据转换(由mxArray实现)。为了清楚解释整个的MEX文件的组成,下面以一个具体而微的例子说明。
假设有如下的C代码,它的功能是输入x,y,返回结果在z中,代码如下
void arrayProduct(double x, double *y, double *z, int n)
{ int i;
for (i=0; i<n; i++)
z[i] = x * y[i];
}
如果要求Matlab能够调用C代码,就需要将C代码封装到mexFunction入口函数中
void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
/* 在此声明一些变量,并调用C的代码,所有的工作都在这里*/
}
int nlhs, mxArray *plhs[]:这两个参数描述了输出参数信息,nlhs是输出参数个数,具体的输出参数存放在plhs中。
nrhs, const mxArray *prhs[]:这两个参数描述了输入参数的信息,nrhs描述输入参数个数,具体的输入参数存放在prhs中。
需要将C源码中的数据格式进行调整,如下
void arrayProduct(double x, double *y, double *z, mwSize n)
{ mwSize i;
for (i=0; i<n; i++)
z[i] = x * y[i];}
int n需要改为mwSize n,mwSize相当于C语言中的int格式。
if(nrhs!=2) { //检查输入参数是否为两个,如果不是两个报错
mexErrMsgIdAndTxt("MyToolbox:arrayProduct:nrhs",
"Two inputs required.");
}
if(nlhs!=1) { //检查输出参数是否为一个,如果不是一个报错
mexErrMsgIdAndTxt("MyToolbox:arrayProduct:nlhs",
"One output required.");
}
if( !mxIsDouble(prhs[0]) || mxIsComplex(prhs[0]) || mxGetNumberOfElements(prhs[0])!=1 ) {//检查输入的第一个参数是否为标量
mexErrMsgIdAndTxt("MyToolbox:arrayProduct:notScalar",
"Input multiplier must be a scalar.");
}
if(mxGetM(prhs[1])!=1) {//检查输入的第一个参数是否为一维数组
mexErrMsgIdAndTxt("MyToolbox:arrayProduct:notRowVector",
"Input must be a row vector.");
}
为了给void arrayProduct(double x, double *y, double *z, mwSize n)接口传递参数,需要从prhs参数中将每个输入参数取出来,下面的代码需要写在mexFunction接口的开始位置
double multiplier; /*相当于x*/
double *inMatrix; /* 相当于y,维数为1xN */
mwSize ncols; /* 相当于n*/
multiplier = mxGetScalar(prhs[0]);//从prhs中获取x
inMatrix = mxGetPr(prhs[1]);//从prhs中获取y
ncols = mxGetN(prhs[1]);//从prhs中获取n
和处理输入参数类似,对输出参数的处理如下
double *outMatrix; /* 相当于输出参数z*/
plhs[0] = mxCreateDoubleMatrix(1,ncols,mxREAL);//创建输出参数矩阵
outMatrix = mxGetPr(plhs[0]);//获得输出参数矩阵的指针
1.3.1-1.3.5整个过程可以用以下示意图表示为
『注意:以上代码中用到的函数,如mxGetM等的具体功能可以参考Matlab帮助文件,这种函数太多无法一一详细介绍』
完整的C源代码,见arrayProduct.c文件。
1.3中改写好的代码需要编译成MEX,Matlab自带默认的Lcc编译器,如果电脑上装有其它的编译器如VS,也可以选用其它的编译器。
在Matlab的命令行中输入mex –setup,显示如下结果
如果选择y则使用默认的编译器,如果选择n,显示如下图,列出了当前的Matlab支持的外部编译器类型,本文使用Matlab2009,VS的版本是2008SP1,所以可以选择8
下图中的最后一行显示了VS2008SP1的安装位置,如果正确选择y,否则选择n,重新设置VS2008SP1的安装位置,然后按照提示配置完成。
在命令行中输入
mex arrayProduct.c
编译完成后会生成arrayProduct.mexw32文件,可以像Matlab内置函数一样调用它了。
以一个例子展示MEX文件的数据流。
假设有MEX文件myFunction,其有两个输入参数和一个输出参数。Matlab的调用方式为x=myFuncntion(y,z)。
Matlab用以下参数形式,通过接口函数mexFunction调用myFunction。
输入是prhs,其含有两个单元的C数组,所以此时nrhs=2。prhs的第一个元素是一个指向y的mxArray类型的指针,第二个元素是一个指向z的mxArray类型的指针。输出是plhs,是一个含有一个单元的C类型数组,所以此时nlhs=1。
在接口函数中创建一个输出数组,并将plhs[0]指向它。如果没有设置输出参数,而在调用的时候有返回参数,此时Matlab将会报错。
在下面这个例子中,调用的MEX文件为[C,D]=func(A,B)。当在Matlab中调用时,Matlab将输入参数A和B传递给MEX文件,C和D是返回参数。
在func.c中,首先使用mxCreate*函数(meArray的API函数)创建输出参数,然后用plhs[0]和plhs[1]指向创建的输出参数;使用mxGet*函数从输入参数prhs[0]和prhs[1]中获取输入参数。最后调用真正完成特定功能的计算程序计算,并将相应的输入和输出指针传递给该计算程序。
Matlab数据包括:标量数据,向量数据,矩阵数据,字符串数据,元包数据,结构体,对象。这些数据在Matlab中都是以数组形式存储的。在C中Matlab数组用mxArray表示。
要想在Matlab中调用C编译成的MEX文件,需要将Matlab的数据格式转化为C的数据格式。在Matlab中使用mxArray完成这种转化,通过能够通过mxArray类获取Matlab中的数据结构类型,其获取的信息如下
Ø 数据类型
Ø 数据维数
Ø 数据本身
Ø 如果是浮点型的,那么是浮点型的复数还是实数
Ø 如果是稀疏数据,数据的索引和数据值
Ø 如果是结构体或对象,域的个数或者域名是什么
以上的这些信息都是通过mxArray类中的API完成的,在下面的2.5中将会介绍常用的API。
通过指针完成输入和输出参数的传递,例子如下
inMatrix = mxGetPr(prhs[1]);//获得prhs[1]的指针,作为输入参数
plhs[0] = mxCreateDoubleMatrix(1,ncols,mxREAL);//创建一维数组作为输出
outMatrix = mxGetPr(plhs[0]);//获得输出函数额指针
本节将介绍mxArray中常用的API
2.5.1 判断数据类型的mxArray方法
bool mxIsDouble(const mxArray *pm);//如果pm是浮点型返回true
bool mxIsInf(double value);//如果value为无穷大返回true
2.5.2 获取数据维度
size_t mxGetM(const mxArray *pm);//获得pm的行数
size_t mxGetN(const mxArray *pm);//获得pm的列数
size_t mxGetNumberOfElements(const mxArray *pm);//获得pm中元素的个数
2.5.3 获取数据本身
通过指针获取数据,见2.4
mxArray *mxCreateString(const char *str);
见1.2
在这部分,将举例说明如何处理二位数组,字符串如何使用。
在使用MEX文件时可能有些错误信息显示出来,需要用户了解产生这些错误信息的原因,概括如下:
如果运行MEX的Matlab版本和编译它的版本不一致时会导致错误信息,如下
??? Invalid MEX-file <mexfilename>:
The specified module could not be found.
不同的Matlab会支持不同的编译器版本,如果编译器选择的不对会造成编译错误。
如果C代码中的语法不是标准C也可能产生问题。
如果显示如下错误可能是mexFunction入口函数拼写错误造成的,导致找不到入口的错误
Unable to load mex file:
??? Invalid MEX-file
当MEX文件访问被保护、只读或者未被分配的内存区域时会报错。而这种错误却很难被发现。
可以通过以下错误减少这种错误的发生
Ø 重新编译MEX文件,并加上参数检查功能。在编译的时候使用mex的脚本编译选项-argcheck选项,当再有问题时系统将会给出警告。
Ø 再debug下进行调试。
当计算结果错误时,可能是计算程序逻辑错误,或者是某些变量没有初始化。
可能产生错误信息的地方如下图
在MEX文件中如果需要动态分配内存可以用如下的mexArray的API
mwPointer mxCalloc(n, size)
void *mxMalloc(mwSize n);
在MEX文件调用完成后,除了plhs[]参数列表相关内存保留外,所有的MEX文件内部动态分布的内存都自动清除,包括在2.2中用到的内存分配的方法。
一般来说还是推荐用户删除开辟的动态内存,而不是靠系统自动删除动态开辟的内存。
不过如下情况必须考系统自动回收内存(用户不能显示回收)
Ø 输入参数prhs[]内存
Ø 输出参数plhs[]内存
Ø 通过函数mexGetVariablePtr获得的指针
Ø 创建的结构体
不能用mxFree回收mxArray内存空间,而应该使用如下的方式回收
mxArray *temp = mxCreateDoubleMatrix(1,1,mxREAL);
mxFree(temp); /* 错误 */
mxDestroyArray(temp); /*正确 */
因为mxFree删除的是数据结构的头指针,存储数据的空间不会被删除,当退出MEX文件时Matlab还会自动回收临时变量的空间所以还会再一次自动删除temp的指针,这样就会发生错误。
下面的例子将会产生奇怪的错误,因为在MEX文件被调用完成后,其内部除了输入、输出参没有被Matlab自动销毁外,其它临时变量都是由Matlab自动回收内存的。下面的代码首先创建一个mxArray类型的数组,然后将输入参数prhs[0]的值拷贝给temp,这会导致问题,因为MEX文件调用结束后temp将被自动回收,那么prhs将被删除;而之前一再强调prhs的参数是不能被Matlab自动回收的。
mxArray *temp = mxCreateCellMatrix(1,1);
mxSetCell(temp, 0, prhs[0]); /* 错误 */
所以正确的做法是如下,将prhs做一份拷贝操作
mxSetCell(temp, 0, mxDuplicateArray(prhs[0])); /* 正确 */
下面的例子先创建一个空的mxREAL类型的数组temp,然后将double型的数组中元素拷贝给temp,会出现错误,因为mxSetPr要求data必须是用mxCalloc,mxMalloc,mxRealloc动态开辟的
mxArray *temp = mxCreateDoubleMatrix(0,0,mxREAL);
double data[5] = {1,2,3,4,5};
...
mxSetM(temp,1); mxSetN(temp,5); mxSetPr(temp, data); /* 错误 */
所以需要改成如下方式,用memcpy方法实现
mxArray *temp = mxCreateDoubleMatrix(1,5,mxREAL);
double data[5] = {1,2,3,4,5};
...
memcpy(mxGetPr(temp), data, 5*sizeof(double)); /* 正确 */
下面情况会造成内存泄露,泄露空间大小为5*5*8字节,因为double是8个字节。
pr = mxCalloc(5*5, sizeof(double));
... <load data into pr>
plhs[0] = mxCreateDoubleMatrix(5,5,mxREAL);
mxSetPr(plhs[0], pr); /* INCORRECT */
改造的方法如下
plhs[0] = mxCreateDoubleMatrix(5,5,mxREAL);
pr = mxGetPr(plhs[0]);
... <load data into pr>
或者
pr = mxCalloc(5*5, sizeof(double));
... <load data into pr>
plhs[0] = mxCreateDoubleMatrix(5,5,mxREAL);
mxFree(mxGetPr(plhs[0]));
mxSetPr(plhs[0], pr);
输入内存和输出内存的处理由系统控制,即使MEX文件别调用结束后输入输出内存也是存在的,用户不要动态的回收输入和输出内存,也不能由于某些错误操作,误回收输入输出内存。
例如在2.3.2中mxSetCell(temp, 0, prhs[0]); /* 错误 */就会导致在MEX文件别调用结束后prhs自动回收,这就会导致问题,所以要注意。
2.3.4中显示了输出内存处理不当会造成内存泄露。
见演示
所谓Matlab引擎(engine),是指一组Matlab提供的接口函数,支持C/C++、Fortran等语言,通过这些接口函数,用户可以在其它编程环境中实现对Matlab的控制。可以主要功能有:
Ø 打开/关闭一个Matlab对话。
Ø 向Matlab环境发送命令字符串。
Ø 从Matlab环境中读取数据。
Ø 向Matlab环境中写入数据。
与其它各种接口相比,引擎所提供的Matlab功能支持是最全面的。通过引擎方式,应用程序会打开一个新的Matlab进程,可以控制它完成任何计算和绘图操作。对所有的数据结构提供100%的支持。同时,引擎方式打开的Matlab进程会在任务栏显示自己的图标,打开该窗口,可以观察主程序通过engine方式控制Matlab运行的流程,并可在其中输入任何Matlab命令。
数据流主要包括如下
1、启动Matlab引擎
2、将C环境下的数据送入Matlab工作空间
3、将要执行的Matlab脚本送入Matlab空间并执行
4、从Matlab空间中取回结算结果
5、关闭Matlab引擎。
要在C中成功编译Matlab引擎程序,必须完成如下配置
Ø 在编译器中包含引擎头文件engine.h所在的路径
Ø 在编译器中加入Matlab对应的依赖库文件libmx.lib、libmat.lib、libeng.lib,并加入以上库文件所在的路径
Ø 在path路径中加入libeng.dll动态链接库文件所在的路径(切记否则编译可以通过运行会出错)
在调用Matlab引擎之前,首先应在相关文件中加入一行:#include "enging.h",该文件包含了引擎API函数的说明和所需数据结构的定义。可以在C中调用的引擎函数分别如下:
1 引擎的打开和关闭
engOpen-打开Matlab engine
函数声明:
Engine *engOpen(const char *startcmd);
参数startcmd是用来启动Matlab引擎的字符串参数,在Windows操作系统中只能为NULL。
函数返回值是一个Engine类型的指针,它是在engine.h中定义的engine数据结构。
engClose-关闭Matlab 引擎
函数声明:
int engClose(Engine *ep);
参数ep代表要被关闭的引擎指针。
函数返回值为0表示关闭成功,返回1表示发生错误。
例如,通常用来打开/关闭Matlab引擎的代码如下:
Engine *ep; //定义Matlab引擎指针。
if (!(ep=engOpen(NULL))) //测试是否启动Matlab引擎成功。
{
MessageBox("Can't start Matlab engine!" );
exit(1);
}
. …………
engClose(ep); //关闭Matlab引擎。
2 向Matlab发送命令字符串
engEvalString-发送命令让Matlab执行。
函数声明:
int engEvalString(Engine *ep, Const char *string);
参数ep为函数engOpen返回的引擎指针,字符串string为要matlab执行的命令。
函数返回值为0表示成功执行,返回1说明执行失败(如命令不能被Matlab正确解释或Matlab引擎已经关闭了)。
3 获取Matlab命令窗口的输出
要在C中获得函数engEvalString发送的命令字符串被Matlab执行后在matlab窗口中的输出,可以调用engOUtputBuffer函数。
函数声明:
int engOutputBuffer(Engine *ep, char *p, int n);
参数ep为Matlab引擎指针,p为用来保存输出结构的缓冲区,n为最大保存的字符个数,通常就是缓冲区p的大小。该函数执行后,接下来的engEvalString函数所引起的命令行输出结果会在缓冲区p中保存。如果要停止保存,只需调用代码:engOutputBuffer(ep, NULL, 0)。
4 读写Matlab数据
4.1从Matlab引擎工作空间中获取变量。
mxArray *engGetVariable(Engine *ep, const char *name);
参数ep为打开的Matlab引擎指针,name为以字符串形式指定的数组名。
函数返回值是指向name数组的指针,类型为mxArray*(mxArray数据类型在本文第4节详细简介)
4.2 向Matlab引擎工作空间写入变量。
int engPutVariable(Engine *ep, const char *name, const mxArray *mp);
参数ep为打开的Matlab引擎指针,mp为指向被写入变量的指针,name为变量写入后在Matlab引擎工作空间中的变量名。
函数返回值为0表示写入变量成功,返回值为1表示发生错误。
5 调用引擎时显示/隐藏Matlab主窗口
默认情况下,以engine方式调用Matlab的时候,会打开Matlab主窗口,可在其中随意操作。但有时也会干扰应用程序的运行,可用以下设置是否显示该窗口。
int engSetVisible(Engine *ep, bool value);
参数ep为打开的Matlab引擎指针,value为是否显示的标志,取值true(或1)表示显示Matlab窗口,取值false(或0)表示隐藏Matlab窗口。
函数返回值为0表示设置成功,为1表示有错误发生。
要获得当前Matlab窗口的显示/隐藏情况,可以调用函数:
int engGetVisible(Engine *ep, bool *value);
参数ep为打开的Matlab引擎指针,Value为用来保存显示/隐藏情况的变量(采用指针方式传递)。
函数返回值为0表示获取成功,为1表示有错误发生。
Matlab引擎函数中,所有与变量有关的数据类型都是mxArray类型。数据结构mxArray以及大量的mx开头的函数,广泛用于Matlab 引擎程序和Matlab C数学库中。mxArray是一种很复杂的数据结构,与Matlab中的array相对应,我们只需熟悉Matlab的array类型和几个常用的mxArray函数即可。
C中,所有和Matlab的数据交互都是通过mxArray来实现的,在使用mxArray类型的程序中,应包含头文件matrix.h,不过在引擎程序中,一般会包含头文件engine.h,该文件里面已经包含了matrix.h,因此无需重复包含。
1 创建和清除mxArray型数据
Matlab有很多种变量类型,对应于每种类型,基本上都有一个函数用于创建,但它们都有相同的数据结构,就是mxArray。
数组的建立采用mxCreatexxx形式的函数,例如新建一个double类型数组,可用函数mxCreateDoubleMatrix,函数形式如下:
mxArray *mxCreateDoubleMatrix(int m, int n, mxComplexity ComplexFlag);
参数m和n为矩阵的函数和列数。ComplexFlag为常数,用来区分矩阵中元素是实数还是复数,取值分别为mxREAL和mxCOMPLEX。
例如,创建一个3行5列的二维实数数组,可用如下语句:
mxArray *T = mxCreateDoubleMatrix(3, 5, mxREAL);
对应的,要删除一个数组mxDestroyArray,该函数声明如下:
void mxDestroyArray(mxArray *array_ptr);
参数array_ptr为要删除的数组指针。
例如,要删除上面创建的数组T,可用如下语句:
mxDestroyArray(T);
类似的创建函数还有:
mxArray *mxCreateString(const char *str);
创建一个字符串类型并初始化为str字符串。
一般的在VC与Matlab交互中,以上两种类型就够了,其它类型数组的创建这里不再介绍。
2 管理mxArray数据类型
2.1 管理mxArray数据大小
要获得mxArray数组每一维上元素的个数,可以用mxGetM和mxGetN函数。其中mxGetM用来获得数组第一维的元素个数,对于矩阵来说就是行数。
int mxGetM(const mxArray *array_ptr); //返回array_ptr对应数组第一维的元素个数(行数)
int mxGetN(const mxArray *array_ptr); //返回array_ptr对应数组其它维的元素个数,对于矩阵来说是列数。对于多维数组来说是从第2维到最后一维的各维元素个数的乘积。
要获得某一特定维的元素个数,则要用函数:
const int *mxGetDimensions(const mxArray *array_ptr);
该函数返回array_ptr各维的元素个数保存在一个int数组中返回。对于常用的矩阵来说,用mxGetM和mxGetN两个函数就可以了。
另外还可以通过mxGetNumberOfDimensions来获得数组的总的维数,用mxSetM、mxSetN设置矩阵的行数和列数,函数说明如下:
int mxGetNumberOfDimensions(const mxArray *array_ptr); //返回数组的维数
void mxSetM(mxArray *array_ptr, int m); //设置数组为m行
void mxSetN(mxArray *array_ptr, int n); //设置数组为n列
2.2 判断mxArray数组类型
在对mxArray类型的变量进行操作之前,可以验证以下其中的数组的数据类型,比如是否为double数组、整数、字符串、逻辑值等,以及是否为某种结构、类、或者是特殊类型,比如是否为空数组,是否为inf、NaN等。常见的判断函数有:
bool mxIsDouble(const mxArray *array_ptr);
bool mxIsComplex(const mxArray *array_ptr);
bool mxIsChar(const mxArray *array_ptr);
bool mxIsEmpty(const mxArray *array_ptr);
bool mxIsInf(double value);
2.3 管理mxArray数组的数据
对于常用的double类型的数组,可以用mxGetPr和mxGetPi两个函数分别获得其实部和虚部的数据指针,这两个函数的声明如下:
double *mxGetPr(const mxArray *array_ptr); //返回数组array_ptr的实部指针
double *mxGetPi(const mxArray *array_ptr); //返回数组array_ptr的虚部指针
这样,就可以通过获得的指针对mxArray类型的数组中的数据进行读写操作。例如可以用函数engGetVariable从Matlab工作空间读入mxArray类型的数组,然后用mxGetPr和mxGetPi获得数据指针,对并其中的数据进行处理,最后调用engPutVariable函数将修改后的数组重新写入到Matlab工作空间。
1直接在通过字符串执行脚本
2通过函数名执行脚本
这部分的功能是将调用Matlab引擎的C源代码编译成可执行程序
以下命令会将mexfilename.c编译成mexfilename.exe文件,具体执行见演示。
mex('-f', [matlabroot ...
'\bin\win32\mexopts\lccengmatopts.bat'], mexfilename);
【注意:在执行编译后的mexfilename.exe程序时可能会报告无法找到lib*.dll库文件,需要将matlabroot\bin\win32加入到path路径中,这样系统才能找到需要执行的库文件。】