資料一 MATLAB的MEX文件編寫和調試
1. MEX的編寫格式
寫MEX程序其實就是寫一個DLL程序,所以你可以使用C,C++,Fortran等多種編程語言來寫。
編寫MEX程序的編輯器可以使用MATLAB的代碼編輯器,也可使用自己的C++編輯器,如VS2008等。
用MATLAB的編輯器的好處是,MEX函數會加粗高亮顯示,這給程序編寫帶來便利,可惜無法動態調試。如用VC即可編譯也可調試,比較方便。mex的編譯結果實際上就是一個帶輸出函數mexFunction 的dll文件,所以會用VC編寫和調試dll,就會用VC編寫和調試MEX程序。
a. MEX文件格式
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[] )
{
}
四個參數分別用來輸出和輸入數據: nlhs 輸出參數個數,plhs 輸出參數指針 (nrhs和prhs是輸入參數相關的)。
注意: 我們對輸出和輸入參數的操作都是通過指針的方式進行的。(這點很容易理解,因為我們的計算結果是需要傳遞給MATLAB的,實際上我們傳遞的不是數據,而是指針。MATLAB可以通過這些指針,訪問內存中的數據。)
b. 操作輸入數據
對輸入數據進行操作,需要通過MEX函數mxGetPr 得到數據的指針地址。 mxGetM 和 mxGetN 得到矩陣數據的行和列 (返回整數)。對於實矩陣,我們可以定義 double *M; 來對實矩陣數據操作(不過似乎是,plhs, prhs都是指向double類型的指針,所以下面的這個M等,都要定義成double*類型的)。如:
double *M;
int m, n;
M = mxGetPr(prhs[0]); // 指針指向第一個參數的數據地址
m = mxGetM(prhs[0]);
n = mxGetN(prhs[0]);
需要注意的是,MATLAB矩陣數據的存儲順序是"從上到下,從左到右"。也就是說對於MATLAB的m x n的矩陣A。 A(1,1) 就是 *M,A(2,1) 就是 *(M+1) ,以此類推,A(i, j) 就是 *(M + m*(j-1) + (i-1)).
注意: MATLAB的指標從1開始,C的指標從0開始。
c. 輸出數據操作
對於輸出數據,我們需要首先分配內存空間,有專門的mex函數可以使用,如:
plhs[0] = mxCreateDoubleMatrix(m, n, mxREAL); //生成m x n 的實矩陣。
同輸入數據一樣,要對輸出數據操作,我們也需要一個指向數據的指針變量,如
double *A;
A = mxGetPr( plhs[0]);
下面介紹一下如何使用VS2008編寫MEX並編譯調試。
2. VC中編寫MEX
打開Visual Studio 2008/2010/2012/2013, 新建項目, 選擇MFC DLL.
a. 配置項目屬性
打開項目屬性配置頁,選擇配置屬性目錄,然后分別進行如下操作
VC++目錄 -> 包含目錄 加入MATLAB安裝目錄下的 \extern\include 路徑。
VC++目錄 -> 庫目錄 加入MATLAB的 \extern\lib\win32\microsoft 路徑。
連接器 -> 輸入 -> 附加依賴項 輸入libmx.lib libeng.lib libmat.lib libmex.lib
b. 編輯輸出函數
在項目源文件的. def 中EXPORTS段加入mexFunction, 如:
EXPORTS ; 此處可以是顯式導出 mexFunction
c. 編寫MEX文件
項目文件中新建一個C++文件 如 mexproc.cpp,里面按前面介紹的格式編寫代碼即可。
d. VC編譯MEX
像編譯其他程序那樣直接編譯即可,成功會生成dll文件。如果編譯鏈接時出錯,根據錯誤提示,檢查一下lib和h的路徑是否正確,有無缺少lib文件,代碼是否有語法錯誤等。
3. VC中調試MEX
要調試MEX程序就要先編譯,再調用它。所以我們需要在MATLAB中調用這個函數,並在VC的MEX程序相應位置處下斷點即可。調用的函數名就是dll的主文件名,你可以根據自己的需要改名。
我們用mymexfun.dll為例,先在VC的 mexFunction 函數代碼段開始處F9下斷。然后Ctrl+Alt+P附加MATLAB.exe進程。這樣就可以運行命令調試程序了。我們可以在MATLAB的命令行里輸入命令:
[輸出變量] = mymexfun(輸入變量)
程序一旦被調用,就會被斷在我們的斷點處。接着你就可以像調試C++程序那樣調試MEX程序了。(如果命令找不到,檢查一下matlab當前路徑,和path路徑。)
在MATLAB中編譯MEX可以輸入: mex 文件名.cpp
MATLAB上編譯MEX時,你可以選擇不同的編譯器如lc, gcc等。也可以在編譯時附加lib和h文件。關於mex的命令詳解請參考MATLAB幫助文檔。
資料二 深入
在使用MATLAB編譯C/C++代碼時,C/C++代碼中要使用一個mexFunction函數,那么這個函數是如何定義,在編譯時又是如何實現的呢?下面我將使用實例進行說明。
如一個簡單的函數:
double add(double x, double y)
{
return x + y;
}
mexFunction的定義為:
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
}
可以看到,mexFunction是沒返回值的,它不是通過返回值把結果傳回Matlab的,而是通過對參數plhs的賦值。mexFunction的四個參數皆是說明Matlab調用MEX文件時的具體信息,如這樣調用函數時:
>> b = 1.1; c = 2.2;
>> a = add(b, c)
mexFunction四個參數的意思為:
nlhs = 1,說明調用語句左手面(lhs-left hand side)有一個變量,即a。
nrhs = 2,說明調用語句右手面(rhs-right hand side)有兩個自變量,即b和c。
plhs是一個數組,其內容為指針,該指針指向數據類型mxArray。因為現在左手面只有一個變量,即該數組只有一個指針,plhs[0]指向的結果會賦值給a。
prhs和plhs類似,因為右手面有兩個自變量,即該數組有兩個指針,prhs[0]指向了b,prhs[1]指向了c。要注意prhs是const的指針數組,即不能改變其指向內容。
因為Matlab最基本的單元為array,無論是什么類型也好,如有double array、 cell array、 struct array……所以a,b,c都是array,b = 1.1便是一個1x1的double array。而在C語言中,Matlab的array使用mxArray類型來表示。所以就不難明白為什么plhs和prhs都是指向mxArray類型的指針數組。
完整的add.c如下:
#include "mex.h"//使用mex文件必須包含頭文件 //執行具體工作的C函數 double add(double x, double y) { return x+y; } //MEX文件接口函數 void mexFunction(int nlhs, mxArray *plhs[], int nrhs, mxArray *prhs[]) { double *a; double b,c; plhs[0]=mxCreateDoubleMatrix(1,1,mxREAL); a=mxGetPr(plhs[0]);//得到第一個接收輸出變量的地址 b=*(mxGetPr(prhs[0])); c=*(mxGetPr(prhs[1])); *a=add(b,c); }
mexFunction的內容是什么意思呢?我們知道,如果這樣調用函數時:
>> output = add(1.1, 2.2);
在未涉及具體的計算時,output的值是未知的,是未賦值的。所以在具體的程序中,我們建立一個1x1的實double矩陣(使用 mxCreateDoubleMatrix函數,其返回指向剛建立的mxArray的指針),然后令plhs[0]指向它。接着令指針a指向plhs [0]所指向的mxArray的第一個元素(使用mxGetPr函數,返回指向mxArray的首元素的指針)。同樣地,我們把prhs[0]和prhs [1]所指向的元素(即1.1和2.2)取出來賦給b和c。於是我們可以把b和c作自變量傳給函數add,得出給果賦給指針a所指向的mxArray中的元素。因為a是指向plhs[0]所指向的mxArray的元素,所以最后作輸出時,plhs[0]所指向的mxArray賦值給output,則 output便是已計算好的結果了。
實際上mexFunction是沒有這么簡單的,我們要對用戶的輸入自變量的個數和類型進行測試,以確保輸入正確。如在add函數的例子中,用戶輸入char array便是一種錯誤了。
從上面的講述中我們總結出,MEX文件實現了一種接口,把C語言中的計算結果適當地返回給Matlab罷了。當我們已經有用C編寫的大型程序時,大可不必在 Matlab里重寫,只寫個接口,做成MEX文件就成了。另外,在Matlab程序中的部份計算瓶頸(如循環),可通過MEX文件用C語言實現,以提高計算速度。
一個簡單的MEX文件例子:用m文件建立一個1000×1000的Hilbert矩陣。
% mextest.m
tic m=1000; n=1000; a=zeros(m,n); for i=1:1000 for j=1:1000 a(i,j)=1/(i+j); end end toc
在matlab中新建一個Matlab_1.cpp 文件並輸入以下程序:
#include "mex.h" //該函數是mexfunction調用的唯一一個計算子程序 void hilb(double *y,int n) { int i,j; for(i=0;i<n;i++) for(j=0;j<n;j++) *(y+j+i*n)=1/((double)i+(double)j+1); } void mexFunction(int nlhs,mxArray *plhs[],int nrhs,const mxArray *prhs[]) { double x,*y; int n; if (nrhs!=1) mexErrMsgTxt("One inputs required."); if (nlhs != 1) mexErrMsgTxt("One output required."); if (!mxIsDouble(prhs[0])||mxGetN(prhs[0])*mxGetM(prhs[0])!=1) mexErrMsgTxt("Input must be scalars."); x=mxGetScalar(prhs[0]); plhs[0]=mxCreateDoubleMatrix(x, x, mxREAL); n=mxGetM(plhs[0]); y=mxGetPr(plhs[0]); hilb(y, n); }
該程序是一個C語言程序,它也實現了建立Hilbert矩陣的功能。在MATLAB命令窗口輸入以下命令:mex Matlab_1.cpp,即可編譯成功。進入該文件夾,會發現多了一個文件:Matlab_1.mexw32,其中Matlab_1.mexw32即是MEX文件。運行下面程序:
tic a=Matlab_1(1000); toc
由上面實驗看出,同樣功能的MEX文件比m文件快得多。
MEX文件的組成與參數
MEX文件的源代碼一般由兩部分組成:
(1)計算過程。該過程包含了MEX文件實現計算功能的代碼,是標准的C語言子程序。
(2)入口過程。該過程提供計算過程與MATLAB之間的接口,以入口函數mxFunction實現。在該過程中,通常所做的工作是檢測輸入、輸出參數個數和類型的正確性,然后利用mx-函數得到MATLAB傳遞過來的變量(比如矩陣的維數、向量的地址等),傳遞給計算過程。
MEX文件的計算過程和入口過程也可以合並在一起。但不管那種情況,都要包含#include "mex.h",以保證入口點和接口過程的正確聲明。注意,入口過程的名稱必須是mexFunction,並且包含四個參數,即:
void mexFunction(int nlhs,mxArray *plhs[],int nrhs,const mxArray *prhs[])
其中,參數nlhs和nrhs表示MATLAB在調用該MEX文件時等式左端和右端變量的個數,例如在MATLAB命令窗口中輸入以下命令:
[a,b,c]=Matlab_1(d,e,f,g)
則nlhs為3,nrhs為4。
MATLAB在調用MEX文件時,輸入和輸出參數保存在兩個mxArray*類型的指針數組中,分別為prhs[]和plhs[]。prhs[0]表示第一個輸入參數,prhs[1]表示第二個輸入參數,…,以此類推。如上例中,d→prhs[0],e→prhs[1],f→prhs[2],f→prhs[3]。同時注意,這些參數的類型都是mxArray *。
接口過程要把參數傳遞給計算過程,還需要從prhs中讀出矩陣的信息,這就要用到下面的mx-函數和mex-函數。
第三部分 MEX函數
1 MEX文件的組成與參數
MEX文件的源代碼一般由兩部分組成:
(1)計算過程。該過程包含了MEX文件實現計算功能的代碼,是標准的C語言子程序。
(2)入口過程。該過程提供計算過程與MATLAB之間的接口,以入口函數mxFunction實現。在該過程中,通常所做的工作是檢測輸入、輸出參數個數和類型的正確性,然后利用mx-函數得到MATLAB傳遞過來的變量(比如矩陣的維數、向量的地址等),傳遞給計算過程。
MEX文件的計算過程和入口過程也可以合並在一起。但不管那種情況,都要包含#include "mex.h",以保證入口點和接口過程的正確聲明。注意,入口過程的名稱必須是mexFunction,並且包含四個參數,即:
void mexFunction(int nlhs,mxArray *plhs[],int nrhs,const mxArray *prhs[])
其中,參數nlhs和nrhs表示MATLAB在調用該MEX文件時等式左端和右端變量的個數,例如在MATLAB命令窗口中輸入以下命令:
[a,b,c]=Matlab_1(d,e,f,g)
則nlhs為3,nrhs為4。
MATLAB在調用MEX文件時,輸入和輸出參數保存在兩個mxArray*類型的指針數組中,分別為prhs[]和plhs[]。prhs[0]表示第一個輸入參數,prhs[1]表示第二個輸入參數,…,以此類推。如上例中,d→prhs[0],e→prhs[1],f→prhs[2],f→prhs[3]。同時注意,這些參數的類型都是mxArray *。
接口過程要把參數傳遞給計算過程,還需要從prhs中讀出矩陣的信息,這就要用到下面的mx-函數和mex-函數。
2 常用的mex-函數和mx-函數
在MATLAB6.5版本中,提供的mx-函數有106個,mex-函數有38個,下面我們僅介紹常用的函數。
2.1入口函數mexFunction
該函數是C MEX文件的入口函數,它的格式是固定的:
void mexFunction(int nlhs,mxArray *plhs[],int nrhs,const mxArray *prhs[])
說明:MATLAB函數的調用方式一般為:[a,b,c,…]=被調用函數名稱(d,e,f,…),nlhs保存了等號左端輸出參數的個數,指針數組plhs具體保存了等號左端各參數的地址,注意在plhs各元素針向的mxArray內存未分配,需在接口過程中分配內存;prhs保存了等號右端輸入參數的個數,指針數組prhs具體保存了等號右端各參數的地址,注意MATLAB在調用該MEX文件時,各輸入參數已存在,所以在接口過程中不需要再為這些參數分配內存。
2.2出錯信息發布函數mexErrMsgTxt,mexWarnMsgTxt
兩函數的具體格式如下:
#include "mex.h"
void mexErrMsgTxt(const char *error_msg);
void mexWarnMsgTxt(const char *warning_msg);
其中error_msg包含了要顯示錯誤信息,warning_msg包含要顯示的警告信息。兩函數的區別在於mexErrMsgTxt顯示出錯信息后即返回到MATLAB,而mexWarnMsgTxt顯示警告信息后繼續執行。
2.3 mexCallMATLAB和mexEvalString
兩函數具體格式如下:
#include "mex.h"
int mexCallMATLAB(int nlhs, mxArray *plhs[],
int nrhs, mxArray *prhs[], const char *command_name);
int mexEvalString(const char *command);
mexCallMATLAB前四個參數的含義與mexFunction的參數相同,command_name可以MATLAB內建函數名、用戶自定義函數、M文件或MEX文件名構成的字符串,也可以MATLAB合法的運算符。
mexEvalString用來操作MATLAB空間已存在的變量,它不返回任何參數。
mexCallMATLAB與mexEvalString差異較大,請看下面的例子。
【例2】試用MEX文件求5階完全圖鄰接矩陣 的特征值及對應的特征向量。
5階完全圖的鄰接矩陣為:(這里找不到圖片了,抱歉。不過不會影響您對本文的理解。)
下面是求該矩陣的MEX文件。
Matlab_2.cpp
#include "mex.h" void mexFunction(int nlhs,mxArray *plhs[],int nrhs,const mxArray *prhs[]) { double x; mxArray *y,*z,*w; int n; if (nrhs!=1) mexErrMsgTxt("One inputs required."); if (nlhs != 3) mexErrMsgTxt("Three output required."); if (!mxIsDouble(prhs[0])||mxGetN(prhs[0])*mxGetM(prhs[0])!=1) mexErrMsgTxt("Input must be a scalar."); x=mxGetScalar(prhs[0]); plhs[0]=mxCreateDoubleMatrix(x,x,mxREAL); plhs[1]=mxCreateDoubleMatrix(x,x,mxREAL); plhs[2]=mxCreateDoubleMatrix(x,x,mxREAL); n=mxGetM(plhs[0]); y=plhs[0]; z=plhs[1]; w=plhs[2]; //利用mexCallMATLAB計算特征值 mexCallMATLAB(1,&plhs[1],1,prhs,"ones"); mexCallMATLAB(1,&plhs[2],1,prhs,"eye"); mexCallMATLAB(1,&plhs[0],2,&plhs[1],"-"); mexCallMATLAB(2,&plhs[1],1,&plhs[0],"eig"); //演示mexEvalString的功能 mexEvalString("y=y*2"); mexEvalString("a=a*2"); }
在MATLAB命令窗口輸入以下命令:
>> mex Matlab_2.cpp >> clear >> a=magic(5) a = 17 24 1 8 15 23 5 7 14 16 4 6 13 20 22 10 12 19 21 3 11 18 25 2 9 >> [y,z,w]=Matlab_2(5) ??? Undefined function or variable 'y'. a = 34 48 2 16 30 46 10 14 28 32 8 12 26 40 44 20 24 38 42 6 22 36 50 4 18 y = 0 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 0 z = 0.8333 -0.1667 -0.1667 0.2236 0.4472 -0.1667 0.8333 -0.1667 0.2236 0.4472 -0.1667 -0.1667 0.8333 0.2236 0.4472 -0.5000 -0.5000 -0.5000 0.2236 0.4472 0 0 0 -0.8944 0.4472 w = -1 0 0 0 0 0 -1 0 0 0 0 0 -1 0 0 0 0 0 -1 0 0 0 0 0 4
由上面可以看出,K5的特征值為–1和4,其中–1是四重根。MATLAB提供了mexGetVariable、mexPutVariable函數,以實現MEX空間與其它空間交換數據的任務,具體可以參看MATLAB幫助文檔。
2.4建立二維雙精度矩陣函數mxCreateDoubleMatrix
其格式具體如下:
#include "matrix.h"
mxArray *mxCreateDoubleMatrix(int m, int n, mxComplexity ComplexFlag);
其中m代表行數,n代表列數,ComplexFlag可取值mxREAL 或mxCOMPLEX。如果創建的矩陣需要虛部,選擇mxCOMPLEX,否則選用mxREAL。
類似的函數有:
mxCreateCellArray |
創建n維元胞mxArray |
mxCreateCellMatrix |
創建二維元胞mxArray |
mxCreateCharArray |
創建n維字符串mxArray |
mxCreateCharMatrixFromStrings |
創建二維字符串mxArray |
mxCreateDoubleMatrix |
創建二維雙精度浮點mxArray |
mxCreateDoubleScalar |
創建指定值的二維精度浮點mxArray |
mxCreateLogicalArray |
創建n維邏輯mxArray,初值為false |
mxCreateLogicalMatrix |
創建二維邏輯mxArray,初值為false |
mxCreateLogicalScalar |
創建指定值的二維邏輯mxArray |
mxCreateNumericArray |
創建n維數值mxArray |
mxCreateNumericMatrix |
創建二維數值mxArray,初值為0 |
mxCreateScalarDouble |
創建指定值的雙精度mxArray |
MxCreateSparse |
創建二維稀疏mxArray |
mxCreateSparseLogicalMatrix |
創建二維稀疏邏輯mxArray |
MxCreateString |
創建指定字符串的1 n的串mxArray |
mxCreateStructArray |
創建n維架構mxArray |
mxCreateStructMatrix |
創建二維架構mxArray |
2.5 獲取行維和列維函數mxGetM、mxGetN
其格式如下:
#include "matrix.h"
int mxGetM(const mxArray *array_ptr);
int mxGetN(const mxArray *array_ptr);
與之相關的還有:
mxSetM:設置矩陣的行維
mxSetN:設置矩陣的列維
2.6 獲取矩陣實部和虛部函數mxGetPr、mxGetPi
其格式如下:
#include "matrix.h"
double *mxGetPr(const mxArray *array_ptr);
double *mxGetPi(const mxArray *array_ptr);
與之相關的函數還有:
mxSetPr:設置矩陣的實部
mxSetPi:設置矩陣的虛部
【例3】實現字符串的倒序輸出。
#include "mex.h" void revord(char *input_buf,int buflen,char *output_buf) { int i; //實現字符串倒序 for(i=0;i<buflen-1;i++) *(output_buf+i)=*(input_buf+buflen-i-2); } void mexFunction(int nlhs,mxArray *plhs[],int nrhs,const mxArray *prhs[]) { //定義輸入和輸出參量的指針 char *input_buf,*output_buf; int buflen,status; //檢查輸入參數個數 if(nrhs!=1) mexErrMsgTxt("One input required."); else if(nlhs>1) mexErrMsgTxt("Too many output arguments.");
//檢查輸入參數是否是一個字符串 if(mxIsChar(prhs[0])!=1) mexErrMsgTxt("Input must be a string.");
//檢查輸入參數是否是一個行變量 if(mxGetM(prhs[0])!=1) mexErrMsgTxt("Input must a row vector.");
//得到輸入字符串的長度 buflen=(mxGetM(prhs[0])*mxGetN(prhs[0]))+1; //為輸入和輸出字符串分配內存 input_buf=mxCalloc(buflen,sizeof(char)); output_buf=mxCalloc(buflen,sizeof(char)); //將輸入參量的mxArray結構中的數值拷貝到C類型字符串指針 status=mxGetString(prhs[0],input_buf,buflen); if(status!=0) mexWarnMsgTxt("Not enough space. String is truncated."); //調用C程序 revord(input_buf,buflen,output_buf); plhs[0]=mxCreateString(output_buf); }
這個程序中需要注意的地方是mxCalloc函數,它代替了標准C程序中的calloc函數用於動態分配內存,而mxCalloc函數采用的是MATLAB的內存管理機制,並將所有申請的內存初始化為0,因此凡是C代碼需要使用calloc函數的地方,對應的Mex文件應該使用mxCalloc函數。同樣,凡是C代碼需要使用realloc函數的地方,對應的Mex文件應該使用mxRealloc函數。
在MATLAB命令窗口中對revord.cpp程序代碼編譯鏈接:
>> mex revord.cpp
在MATLAB命令窗口中對C-MEX文件revord.dll進行測試:
>> x='I am student.';
>> revord(x)
ans =
.tneduts ma I
[原作者贈言] 終於寫完了,相信大家對mex文件應該有點熟悉了,具體還要到實際應用中慢慢體會。