如何讓matlab用mex與C連接使用
如果我有一個用C語言寫的函數,實現了一個功能,如一個簡單的函數: double add(double x, double y) { return x + y; }
3.3000 要得出以上的結果,那應該怎樣做呢? 解決方法之一是要通過使用MEX文件,MEX文件使得調用C函數和調用Matlab的內置函數一樣方便。MEX文件是由原C代碼加上MEX文件專用的接口函數後編譯而成的。可以這樣理解,MEX文件實現了一種接口,它把在Matlab中調用函數時輸入的自變量通過特定的接口調入了C函數,得出的結果再通過該接口調回Matlab。該特定接口的操作,包含在mexFunction這個函數中,由使用者具體設定。 所以現在我們要寫一個包含add和mexFunction的C文件,Matlab調用函數,把函數中的自變量(如上例中的1.1和2.2)傳給 mexFunction的一個參數,mexFunction把該值傳給add,把得出的結果傳回給mexFunction的另一個參數,Matlab通過該參數來給出在Matlab語句中調用函數時的輸出值(如上例中的a)。 值得注意的是,mex文件是與平臺有關的,以我的理解,mex文件就是另類的動態鏈接庫。在matlab6.5中使用mex -v 選項,你可以看到最後mex階段有類似如下的信息: --> "del _lib94902.obj" 也就是說,雖然在matlab6.5生成的是dll文件,但是中間確實有過lib文件生成。 比如該C文件已寫好,名爲add.c。那麼在Matlab中,輸入: >> mex add.c 就能把add.c編譯爲MEX文件(編譯器的設置使用指令mex -setup),在Windows中,MEX文件類型爲mexw32,即現在我們得出add.mexw32文件。現在,我們就可以像調用M函數那樣調用 MEX文件,如上面說到的例子。所以,通過MEX文件,使用C函數就和使用M函數是一樣的了。 我們現在來說mexFunction怎樣寫。 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,const 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便是已計算好的結果了。 上面說的一大堆指向這指向那,什麼mxArray,初學者肯定都會被弄到頭暈眼花了。很抱歉,要搞清楚這些亂糟糟的關係,只有多看多練。 實際上mexFunction是沒有這麼簡單的,我們要對用戶的輸入自變量的個數和類型進行測試,以確保輸入正確。如在add函數的例子中,用戶輸入char array便是一種錯誤了。 從上面的講述中我們總結出,MEX文件實現了一種接口,把C語言中的計算結果適當地返回給Matlab罷了。當我們已經有用C編寫的大型程序時,大可不必在 Matlab裏重寫,只寫個接口,做成MEX文件就成了。另外,在Matlab程序中的部份計算瓶頸(如循環),可通過MEX文件用C語言實現,以提高計算速度。 以上是對mex文件的初步認識,下面詳細介紹如何用c語言編寫mex文件: 1 爲什麼要用C語言編寫MEX文件 MATLAB是矩陣語言,是爲向量和矩陣操作設計的,一般來說,如果運算可以用向量或矩陣實現,其運算速度是非常快的。但若運算中涉及到大量的循環處理,MATLAB的速度的令人難以忍受的。解決方法之一爲,當必須使用for循環時,把它寫爲MEX文件,這樣不必在每次運行循環中的語句時MATLAB都對它們進行解釋。 2 編譯器的安裝與配置 要使用MATLAB編譯器,用戶計算機上應用事先安裝與MATLAB適配的以下任何一種ANSI C/C++編譯器: 5.0、6.0版的MicroSoft Visual C++(MSVC) 5.0、5.2、5.3、5.4、5.5版的Borland C++ LCC(由MATLAB自帶,只能用來產生MEX文件) 下面是安裝與配置MATLAB編譯器應用程序MEX的設置的步驟: (1)在MATLAB命令窗口中運行mex –setup,出現下列提示: Please choose your compiler for building external interface (MEX) files: Would you like mex to locate installed compilers [y]/n?
Select a compiler: [1] Borland C++Builder version 6.0 in C:\Program Files\Borland [2] Digital Visual Fortran version 6.0 in C:\Program Files\Microsoft Visual Studio [3] Lcc C version 2.4 in D:\MATLAB6P5P1\sys\lcc [4] Microsoft Visual C/C++ version 6.0 in C:\Program Files\Microsoft Visual Studio [0] None Compiler: (3)選擇其中一種(在這裏選擇了3),MATLAB讓用戶進行確認: Please verify your choices: Compiler: Lcc C 2.4 Location: D:\MATLAB6P5P1\sys\lcc Are these correct?([y]/n):
3 一個簡單的MEX文件例子 【例1】用m文件建立一個1000×1000的Hilbert矩陣。 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" //計算過程 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.asv和Matlab_1.dll,其中Matlab_1.dll即是MEX文件。運行下面程序: tic a=Matlab_1(1000); toc elapsed_time = 0.0470 由上面看出,同樣功能的MEX文件比m文件快得多。 4 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-函數。 5 常用的mex-函數和mx-函數 在MATLAB6.5版本中,提供的mx-函數有106個,mex-函數有38個,下面我們僅介紹常用的函數。 5.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文件時,各輸入參數已存在,所以在接口過程中不需要再爲這些參數分配內存。 5.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顯示警告信息後繼續執行。 5.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階完全圖鄰接矩陣 的特徵值及對應的特徵向量。 下面是求該矩陣的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幫助文檔。 5.4建立二維雙精度矩陣函數mxCreateDoubleMatrix 其格式具體如下: #include "matrix.h" mxArray *mxCreateDoubleMatrix(int m, int n, mxComplexity ComplexFlag); 其中m代表行數,n代表列數,ComplexFlag可取值mxREAL 或mxCOMPLEX。如果創建的矩陣需要虛部,選擇mxCOMPLEX,否則選用mxREAL。 類似的函數有:
5.5 獲取行維和列維函數mxGetM、mxGetN 其格式如下: #include "matrix.h" int mxGetM(const mxArray *array_ptr); int mxGetN(const mxArray *array_ptr); 與之相關的還有: mxSetM:設置矩陣的行維 mxSetN:設置矩陣的列維 5.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 原博客地址爲http://blog.163.com/wang_pw/blog/static/24841039201091071243173/ |