編寫 Matlab mexFunction (C mex)

 

資料一 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[] )編輯器

{ide

}函數

四個參數分別用來輸出和輸入數據: nlhs 輸出參數個數,plhs 輸出參數指針 (nrhs和prhs是輸入參數相關的)。測試

注意: 咱們對輸出和輸入參數的操做都是經過指針的方式進行的。(這點很容易理解,由於咱們的計算結果是須要傳遞給MATLAB的,實際上咱們傳遞的不是數據,而是指針。MATLAB能夠經過這些指針,訪問內存中的數據。)ui

b. 操做輸入數據

對輸入數據進行操做,須要經過MEX函數mxGetPr 獲得數據的指針地址。 mxGetM 和 mxGetN 獲得矩陣數據的行和列 (返回整數)。對於實矩陣,咱們能夠定義 double *M; 來對實矩陣數據操做(不過彷佛是,plhs, prhs都是指向double類型的指針,因此下面的這個M等,都要定義成double*類型的)。如:spa

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出錯信息發佈函數mexErrMsgTxtmexWarnMsgTxt

兩函數的具體格式以下:

#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 獲取行維和列維函數mxGetMmxGetN

其格式以下:

#include "matrix.h"

int mxGetM(const mxArray *array_ptr);

int mxGetN(const mxArray *array_ptr);

與之相關的還有:

mxSetM:設置矩陣的行維

mxSetN:設置矩陣的列維

2.6 獲取矩陣實部和虛部函數mxGetPrmxGetPi

其格式以下:

#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文件應該有點熟悉了,具體還要到實際應用中慢慢體會。

相關文章
相關標籤/搜索