matlab 與C 接口

如果我有一個用C語言寫的函數,實現了一個功能,如一個簡單的函數:

double add(double x, double y)

{

return x + y;

}

 
現在我想要在Matlab中使用它,比如輸入:
 
>> a = add(1.1, 2.2)

    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"   
--> "del "test.exp""   
--> "del "test.lib""

也就是說,雖然在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?

 
(2)選擇y,MATLAB將自動搜索計算機上已安裝的外部編譯器的類型、版本及所在路徑,並列出來讓用戶選擇:

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):

 
(4)選擇y,結束MATLAB編譯器的配置。

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和mexString

兩函數具體格式如下:

#include "mex.h"

int mexCallMATLAB(int nlhs, mxArray *plhs[],

int nrhs, mxArray *prhs[], const char *command_name);

int mexString(const char *command);

mexCallMATLAB前四個參數的含義與mexFunction的參數相同,command_name可以MATLAB內建函數名、用戶自定義函數、M文件或MEX文件名構成的字符串,也可以MATLAB合法的運算符。

mexString用來操作MATLAB空間已存在的變量,它不返回任何參數。

mexCallMATLAB與mexString差異較大,請看下面的例子。

【例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");

    //演示mexString的功能

    mexString("y=y*2");

    mexString("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。

 類似的函數有:

 

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

 

 

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章