Matlab与C/C++混合编程有很多种方式,分别适用于不同的情况。
Matlab 可以调用C编译器编译的C程序,通过MEX文件的调用,其调用方式与MATALAB的内建函数完全相同,只需要在命令窗口内输入对应的文件名称即可。
简单来说MEX-file是一种预编译的,用其他语言(C/C++,Fortran)编写的函数库,可以直接被Matlab调用。
正如前面提到的,这种方式适用于两种情况:
- 程序中有一部分代码耗时巨大,想通过改写这部分函数提高速度
- 已经有大量C/C++或Fortran的函数库,想直接用Matlab调用,避免重复开发
这两种情况用MEX-file的这种方案来解决都是非常合适的,因为这种调用方式非常方便,你需要注意地只是数据结构的转换。这种方式支持C/C++和Fortran,本文主要讲C/C++。
C语言MEX程序代码文件有 计算子例程(Computational routine)和接口子程序(GatWay routine)两个相互独立的子程序组成。其中,计算子例程 的功能是完成所需要的计算,它和具有相同功能的一般C源程序文件相同;接口子程序 的功能则是计算子程序和MATALAB的接口,用户实现两个不同内存空间中的通信。
这一步可以用Matlab的编辑器也可以用其他你喜欢的编辑器,需要注意的是: 将来在Matlab中调用的函数名即为此处你创建的文件名,而不是文件内的函数名
MEX-file的内容,一个完整的MEX-file应该包括:
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
nlhs:函数左侧,输出参数数目 (Left-hand side)
plhs:函数左侧,指向输出参数的指针
nrhs:函数右侧,输入参数数目
prhs:函数右侧,指向输入参数的指针
例如,使用
[a,b]=test(c,d,e)
细心的你也许已经注意到,prhs[i] 和 plhs[i]都是指向类型mxArray类型数据的指针。 这个类型是在mex.h中定义的,事实上,在Matlab里大多数数据都是以这种类型存在。当然还有其他的数据类型,可以参考Api guide.pdf里的介绍。
为了让大家能更直观地了解参数传递的过程,我们把hello.c改写一下,使它能根据输 入参数的变化给出不同的屏幕输出:
//hello.cpp 2.0
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
int i;
i=mxGetScalar(prhs[0]);
if(i==1)
mexPrintf("hello,world!\n");
else
mexPrintf("大家好!\n");
}
在matlab命令窗口中输入: mex hello.cpp 进行编译后,然后在输入命令:hello(1) 和 hello(0) 会得到如下图所示的结果。
程序hello已经可以根据输入参数来给出相应的屏幕输出。在这个程序里,除了用到了屏幕输出函数mexPrintf(用法跟c里的printf函数几乎完全一样)外,还用到了一个函数mxGetScalar,调用方式如下:
i=mxGetScalar(prhs[0]);
"Scalar"就是 标量 的意思。在Matlab里数据都是以数组的形式存在的,mxGetScalar的作用就是把通过prhs[0]传递进来的mxArray类型的指针指向的数据(标量)赋给C程序里的变量。这个变量本来应该是double类型的,通过强制类型转换赋给了整型变量 i i i。既然有标量,显然还应该有矢量,否则矩阵就没法传了。看下面的程序:
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
double *i;
i=mxGetPr(prhs[0]); //get input parameter
if(i[0]==1)
mexPrintf("hello,world!\n");
else
mexPrintf("大家好!!!!\n");
}
在上面的程序中,通过mxGetPr函数从指向 mxArray 类型数据的 prhs[0] 获得了指向 double类型 的指针。
如果输入的不是单个的数据,而是向量或矩阵,那该怎么处理呢 ?通过mxGetPr只能得到指向这个矩阵的指针,如果我们不知道这个矩阵的确切大小,就 没法对它进行计算。
为了解决这个问题,Matlab提供了两个函数 mxGetM 和 mxGetN来获得传进来参数的行数 和列数。下面程序的功能,就是获得输入的矩阵,把它在屏幕上显示出来:
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
double *data;
int M, N;
int i, j;
data = mxGetPr(prhs[0]); //获得指向矩阵的指针
M = mxGetM(prhs[0]); //获得矩阵的行数
N = mxGetN(prhs[0]); //获得矩阵的列数
for (i=0; i<M; i++)
{
for (j=0; j<N; j++)
{
mexPrintf("%4.3f", data[j*M+i]);
mexPrintf("\n");
}
}
}
对输入数据进行操作,需要通过MEX函数mxGetPr 得到数据的指针地址。 mxGetM 和 mxGetN 得到矩阵数据的行和列 (返回整数)。对于实矩阵,我们可以定义double *data 来对实矩阵数据操作(不过似乎是,plhs, prhs都是指向double类型的指针,所以data定义成double*类型的)
需要注意的是,MATLAB矩阵数据的存储顺序是"从上到下,从左到右",(即按列存取)。另外,MATLAB的指标从1开始,C的指标从0开始,也就是说对于 MATLAB 的 m × n m \times n m×n 的矩阵 A。 A(1,1) 就是 *data,A(2,1) 就是 *(data+1) ,以此类推,A(i, j) 就是 (data+ m(j-1) + (i-1)).
输入数据是在函数调用之前已经在Matlab里申请了内存的,由于mex函数与Matlab共用同一个地址空间,因而在prhs[]里传递指针就可以达到参数传递的目的。
但是,输出参数却需要在mex函数内申请到内存空间,才能将指针放在plhs[]中传递出去。由于返回指针类型必须是 mxArray,所以Matlab专门提供了一个函数:mxCreateDoubleMatrix来实现内存的申请,函数原型如下。
mxArray *mxCreateDoubleMatrix(int m, int n, mxComplexity ComplexFlag)
m:待申请矩阵的行数
n:待申请矩阵的列数
为矩阵申请内存后,得到的是 mxArray 类型的指针,就可以放在plhs[]里传递回去了。但是对这个新矩阵的处理,却要在函数内完成,这时就需要用到前面介绍的 mxGetPr。使用 mxGetPr获得指向这个矩阵中数据区的指针(double类型)后,就可以对这个矩阵进行各种操作和运算了。下面的程序是在上面的show.c的基础上稍作改变得到的,功能是将输入的矩阵进行逆操作。
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
double *inData;
double *outData;
int M,N;
int i,j;
if(nrhs!=1) //输入参数的个数为0
mexErrMsgTxt("USAGE: b=reverse(a)\n");
if(!mxIsDouble(prhs[0])) //输入的参数不是double型
{
mexErrMsgTxt("the Input Matrix must be double!\n");
}
else
{
inData = mxGetPr(prhs[0]);
M = mxGetM(prhs[0]);
N = mxGetN(prhs[0]);
plhs[0] = mxCreateDoubleMatrix(M,N,mxREAL);
outData = mxGetPr(plhs[0]);
for (i=0;i<M;i++)
{
for (j=0;j<N;j++)
{
//outData[j*M+i] = inData[i*M+j]; // 行列转置
outData[j*M+i] = inData[(N-1-j)*M+i]; // 列进行了逆序;第一列变成最后一列
}
}
}
}
执行列的逆序排列:outData[j*M+i] = inData[(N-1-j)*M+i]
执行行列转置:outData[j*M+i] = inData[i*M+j];
当然,Matlab里使用到的并不是只有double类型这一种矩阵,还有字符串类型、稀疏矩阵、结构类型矩阵等等,并提供了相应的处理函数。本文用到编制mex程序中最经常遇到的一些函数,其余的详细情况清参考Apiref.pdf。
需要说明的是,Matlab提供的API中,函数前缀有mex-和mx-两种。带mx-前缀的大多是对mxArray数据进行操作的函数,如mxIsDouble,mxCreateDoubleMatrix等等。而带mex前缀的则大多是与Matlab环境进行交互的函数,如mexPrintf,mexErrMsgTxt等等。了解了这一点,对在Apiref.pdf中查找所需的函数很有帮助。
至此为止,使用C编写mex函数的基本过程已经介绍完了。