在參考文獻基礎上。補充和完善了。
Matlab與C/C++混合編程接口及應用
摘要:Matlab具有很強的數值計算和分析等能力,而C/C++是目前最為流行的高級程序設計語言,兩者互補結合的混合編程在科學研究和工程實踐中具有非常重要的意義。從Matlab調用C/C++代碼及C/C++調用m文件兩方面,深入地研究了它們之間混合編程的原理和實現機制,並且給出了具體條件下的混合編程方法和步驟。實驗表明,給出的Matlab與C/C++混合編程接口及應用方法是有效、實用的。
1引言
Matlab是當前應用最為廣泛的數學軟件,具有強大的數值計算、數據分析處理、系統分析、圖形顯示甚至符號運算等功能[1]。利用這一完整的數學平台,用戶可以快速實現十分復雜的功能,極大地提高工程分析計算的效率[2][3]。但與其他高級程序[3]相比,Matlab程序是一種解釋執行程序,不用編譯等預處理,程序運行速度較慢[4]。
C/C++語言是目前最為流行的高級程序設計語言之一[5]。它可對操作系統和應用程序以及硬件進行直接操作,用C/C++語言明顯優於其它解釋型高級語言,一些大型應用軟件如 Matlab 就是用C語言開發的。
在工程實踐中,用戶經常遇到Matlab與C/C++混合編程的問題。本文基於Matlab 6.5和VC6.0開發環境,在Windows平台下就它們之間的混合編程問題進行深入研究並舉例說明。
2 Matlab調用C/C++
Matlab調用C/C++的方式主要有兩種:利用MEX技術和調用C/C++動態連接庫。
在Matlab與C/C++混合編程之前,必須先對Matlab的編譯應用程序mex和編譯器mbuild進行正確的設置[1]:
對Matlab編譯應用程序mex的設置:Mex –setup.
對Matlab編譯器mbuild的設置:Mbuild –setup.
2.1調用C/C++的MEX文件
MEX是Matlab Executable的縮寫,它是一種“可在Matlab中調用的C(或Fortran)語言衍生程序”[6]。MEX文件的使用極為方便,其調用方式與Matlab的內建函數完全相同,只需在Matlab命令提示符下鍵入MEX文件名即可。
一個C/C++的MEX源程序通常包括4個組成部分,其中前3個是必須包含的內容,第4個則根據所實現的功能靈活選用:(1)#include “mex.h”;(2)MEX文件的入口函數mexFunction, MEX文件導出名必須為mexFunction函數;(3)mxArray;(4)API函數
通過簡單的例子說明C/C++的MEX源程序編寫和調用過程:
#include "mex.h"
void timeSTwo(double y[], double x[])
{ y[0] = 2.0*x[0]; }
void mexFunction(int nlhs, mxArray * plhs[], int nrhs, const mxArray *prhs[])
{
double *x,*y; int mrows, ncols;
if( nrhs!=1) mexErrMsgTxt("One input required.");
else if( nlhs>1) mexErrMsgTxt("Too manyoutput arguments");
mrows = mxGetM( prhs[0] ); ncols = mxGetN(prhs[0]);
if( !mxIsDouble(prhs[0]) || mxIsComplex( prhs[0] ) || !( mrows ==1 && ncols==1 ) )
mexErrMsgTxt( "Input must be a noncomplex scalar double." );
plhs[0] = mxCreateDoubleMatrix ( mrows, ncols, mxREAL );
x = mxGetPr( prhs[0] ); y = mxGetPr( plhs[0] ); timestwo(y,x); }
可在matlab中編譯,也可以直接在C++環境中編譯:
1).(在matlab中)用指令mex timestwo.c編譯此文件,然后在MATLAB命令行下調用生成的MEX文件即可。2). (在VC2008中)和一般c++一樣編譯后,就會產生dll,這樣可以直接在Matlab中用了,或者copy且更改后綴名.mexw32即可。(因為Matlab R2010b以后版本可能不支持調用dll為后綴的mex文件了)
2.2調用C/C++動態連接庫(即:一般普通的C程序dll沒有用mex的接口函數)
Matlab提供對動態連接庫DLL文件的接口[7]。利用該接口,可在Matlab中調用動態連接庫導出的函數。Matlab對DLL的接口支持各種語言編寫的DLL文件。在調用DLL文件之前,需要准備函數定義的頭文件。對於C/C++語言開發的DLL文件,可使用源程序中相應的頭文件;而對於其他語言開發的DLL,則要手工准備等效的C語言函數定義頭文件。
在Matlab中利用動態連接庫接口技術通常需要完成以下4個步驟:
(1)打開動態連接庫文件;(2)為調用函數准備數據;(3)調用動態連接庫文件中導出的函數;(4)關閉動態連接庫文件。
為了實現以上步驟,用到的Matlab函數有:loadlibrary, loadlibrary, calllib, libfunctions, lipointer, libstruct, libisloaded。下面舉例說明Matlab調用C/C++動態連接庫的方法和步驟:
a.在VC環境下,新建工程->win32動態連接庫->工程名Test1->empty工程->完成;
b.新建->C++源文件->添加a.cpp,內容為:#include "a.h"
_declspec(dllexport) int add(int a, int b) { return a+b; }
c.新建->C/C++頭文件->添加a.h,內容為: _declspec(dllexport) int add(int a,intb); 然后編譯生成Test1.dll動態連接庫文件,將Test1.dll和a.h拷到Matlab 工作目錄下。
d.在Matlab命令行下,調用Test.dll:>>loadlibrary(‘Test1’,’a.h’); >>x=7;
>>y=8; >>calllib(‘Test1’,‘add’,x,y); Ans=15 >>unloadlibrary(‘Test1’).
調用DLL動態連接庫的方法,為Matlab重用工程實踐中積累的大量實用C/C++代碼提供了一種簡潔方便的方法。與調用MEX文件相比,該方法更加簡便實用。但是這個接口之支持C,不支持C++庫和函數的重載,這種情況下,推薦用MEX-file,若實在要用這種方法(調用C/C++動態連接庫),則對於C++要做一些更改,詳見http://www.mathworks.de/help/techdoc/matlab_external/f43202.html#bq__4nu-1,
3 C/C++調用Matlab
在工程實踐中,C/C++調用Matlab的方法主要有調用Matlab計算引擎、包含m文件轉換的C/C++文件,以及調用m文件生成的DLL文件。
3.1利用Matlab計算引擎
Matlab的引擎庫為用戶提供了一些接口函數,利用這些接口函數,用戶在自己的程序中以計算引擎方式調用Matlab文件。該方法采用客戶機/服務器的方式,利用Matlab引擎將Matlab和C/C++聯系起來。在實際應用中,C/C++程序為客戶機,Matlab作為本地服務器。
C/C++程序向Matlab計算引擎傳遞命令和數據信息,並從Matlab計算引擎接收數據信息[2]。
Matlab提供了以下幾個C語言計算引擎訪問函數供用戶使用[8]:engOpen,engClose, engGetVariable,engPutVariable,engEvalString,engOutputBuffer,engOpenSingleUse, engGetVisible,engSetVisible。
下面以C語言編寫的、調用Matlab引擎計算方程x3 ?2x+5=0根的源程序example2.c為例,說明C/C++調用Matlab計算引擎編程的原理和步驟:
#include <windows.h> #include <stdlib.h>
#include <stdio.h> #include "engine.h"
int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow )
{
Engine *ep; mxArray *P=NULL,*r=NULL;
char buffer[301]; double poly[4] = { 1,0,-2,5 };
if ( !(ep =engOpen(NULL) ) )
{ fprintf( stderr,"\nCan't start MATLAB engine\n" ); return EXIT_FAILURE; }
P = mxCreateDoubleMatrix( 1, 4, mxREAL); mxSetClassName( P, "p" );
memcpy( ( char * ) mxGetPr( P ), (char *) poly, 4*sizeof(double) );
engPutVariable( ep, P ); engOutputBuffer( ep, buffer, 300 );
engEvalString( ep, "disp(['多項式',poly2str(p,'x'),'的根']),r=roots(p)" );
MESSageBox(NULL,buffer,"example2展示MATLAB引擎的應用",MB_OK);
engClose( ep ); mxDestroyArray( P ); return EXIT_SUCCESS;
}
在Matlab下運行example2.exe: mex -f example2.c。運行結果如圖1所示:
利用計算引擎調用Matlab的特點是:節省大量的系統資源,應用程序整體性能較好,但不能脫離Matlab的環境運行,且運行速度較慢,但在一些特別的應用[9](例如需要進行三維圖形顯示)時可考慮使用。
3.2利用mcc編譯器生成的cpp 和hpp文件
Matlab自帶的C++Complier--mcc,能將m文件轉換為C/C++代碼。因此,它為C/C++程序調用m文件提供了另一種便捷的方法。下面舉例說明相應步驟:
a.新建example3.m: function y=exmaple3(n) y=0; for i=1:n y=y+i; end
保存后在命令窗口中輸入:mcc -t -L Cpp -h example3.
則在工作目錄下生成example3.cpp和example3.hpp兩個文件。
b.在VC中新建一個基於對話框的MFC應用程序Test2,添加一個按鈕,並添加按鈕響應函數,函數內容見f步。將上面生成的兩個文件拷貝到VC工程的Test2目錄下。
c.在VC中選擇:工程->設置,選擇屬性表Link選項,下拉菜單中選擇Input,在對象 / 庫模塊中加入lIBMmfile.lib libmatlb.lib libmx.lib libmat.lib libmatpm.lib sgl.lib libmwsglm.lib libmwservices.lib,(后三個為使用Matlab圖形庫時,需加入)注意用空格分開;而在忽略庫中加入 msvcrt.lib;
d.選擇屬性表C/C++選項,下拉菜單選General,在預處理程序定義中保留原來有的內容,並添加MSVC,IBMPC,MSWIND,並用逗號隔開。選擇下拉菜單的Precompiled Headers 選項,在“自動使用預補償頁眉”中添加stdafx.h,然后確定。
e.選擇:工具-> 選項,屬性頁選擇“目錄”,在include files加入: C:\MATLAB6p5p1\extern\include, C:\MATLAB6p5p1\extern\include\cpp;然后在 Library files里面加入: C:\MATLAB6p5p1\bin\win32, C:\MATLAB6p5p1\extern\ lib\win32\microsoft\msvc60;注意根據用戶的Matlab安裝位置,修改相應目錄。
f.在響應函數中添加頭文件:#include "matlab.hpp" #include "example3.hpp"函數響應代碼為:
int i; mwArray n; n=10; n=example3(n); i=n.ExtractScalar(1);
CString str; str.Format( "example3的返回值是:%d", I ); AfxMessageBox( str );
g.編譯,連接,執行,結果如圖2所示。
3.3利用mcc編譯器生成的DLL 文件
Matlab的C++ Complier不僅能夠將Matlab的m文件轉換為C/C++的源代碼,還能產生完全脫離Matlab運行環境的獨立可執行DLL程序。從而可以在C/C++程序中,通過調用DLL實現對 Matlab代碼的調用。下面通過一個簡單的例子說明C/C++調用m文件生成的DLL:
a.建立m文件example4.m: function result = example4(para)
x=[1 para 3]; y=[1 3 1]; plot( x,y ); result=para*2; end.然后在命令窗口中輸入:
mcc -t -W libhg: example4 -T link: lib -h libmmfile.mlib libmwsglm.mlib example4則在工作目錄下會生成example4 .dll、example4 .lib和example4 .h三個文件。
b.在VC中新建一個基於對話框的應用程序Test3,然后添加一個按鈕及按鈕響應函數,函數內容見d步,再將生成的3個文件拷貝到Test2工程目錄下。
c.VC編譯環境的設置如同3.2節c、d步;
d.在按鈕函數文件添加如下的頭文件:#include "example4 .h",函數響應代碼為:
mxArray*para=mxCreateDoubleScalar(2); mxArray* result; example4Initialize();
result =mlfExample4(para); CString str;
str.Format( "%f",mxGetScalar(result) ); AfxMessageBox(str);
e.編譯,連接,執行,結果如圖3所示。
利用mcc編譯器生成的DLL動態連接庫文件,只需在C/C++編譯環境中將其包含進來,調用導出函數即可實現原m文件的功能,極大地方便了用戶的代碼設計。
4結束語
本文從Matlab調用C/C++代碼和C/C+調用m文件兩方面,詳細地研究了Matlab與C/C++混合編程技術。對於Matlab調用C/C++代碼,給出了常用的MEX技術和調用C/C++動態連接庫的方法,並對它們進行比較。針對用戶在實際中經常遇到的C/C++調用Matlab問題,通過研究給出了常用的三種方法及其特點:利用Matlab計算引擎的方法,混合編程后的可執行程序脫離不了Matlab的運行環境,運行速度很慢;利用mcc編譯器將m文件轉化為C/C++文件的方法,雖然能獨立於Matlab運行環境,可在C/C++環境中包含生成的文件非常繁瑣;但是m文件生成的DLL為用戶提供了一種簡潔方便的C/C++調用Matlab代碼的方法。除 Matlab自帶的mcc外,Matcom 也能將M文件編譯為C/C++文件和DLL文件[2][8],但混合編程原理一樣,在此省略。
2007-11-25 15:28 12073人閱讀評論(13)收藏舉報
通過把耗時長的函數用c語言實現,並編譯成mex函數可以加快執行速度。Matlab本身是不帶c語言的編譯器的,所以要求你的機器上已經安裝有VC,BC或Watcom C中的一種。如果你在安裝Matlab時已經設置過編譯器,那么現在你應該就可以使用mex命令來編譯c語言的程序了。如果當時沒有選,就在Matlab里鍵入mex -setup,下面只要根據提示一步步設置就可以了。需要注意的是,較低版本的在設置編譯器路徑時,只能使用路徑名稱的8字符形式。比如我用的VC裝在路徑C:/PROGRAM FILES/DEVSTUDIO下,那在設置路徑時就要寫成:“C:/PROGRA~1”這樣設置完之后,mex就可以執行了。為了測試你的路徑設置正確與否,把下面的程序存為hello.c。
/*hello.c*/
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{ mexPrintf("hello,world!/n");
}
假設你把hello.c放在了C:/TEST/下,在Matlab里用CD C:/TEST/ 將當前目錄改為C:/ TEST/(注意,僅將C:/TEST/加入搜索路徑是沒有用的)。現在敲:
mex hello.c
如果一切順利,編譯應該在出現編譯器提示信息后正常退出。如果你已將C:/TEST/加
入了搜索路徑,現在鍵入hello,程序會在屏幕上打出一行:
hello,world!
看看C/TEST/目錄下,你會發現多了一個文件:HELLO.DLL。這樣,第一個mex函數就算完成了。分析hello.c,可以看到程序的結構是十分簡單的,整個程序由一個接口子過程 mexFunction構成。
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
前面提到過,Matlab的mex函數有一定的接口規范,就是指這
nlhs:輸出參數數目
plhs:指向輸出參數的指針
nrhs:輸入參數數目
例如,使用
[a,b]=test(c,d,e)
調用mex函數test時,傳給test的這四個參數分別是
2,plhs,3,prhs
其中:
prhs[0]=c
prhs[1]=d
prhs[2]=e
當函數返回時,將會把你放在plhs[0],plhs[1]里的地址賦給a和b,達到返回數據的目的。
細心的你也許已經注意到,prhs[i]和plhs[i]都是指向類型mxArray類型數據的指針。 這個類型是在mex.h中定義的,事實上,在Matlab里大多數數據都是以這種類型存在。當然還有其他的數據類型,可以參考Apiguide.pdf里的介紹。
為了讓大家能更直觀地了解參數傳遞的過程,我們把hello.c改寫一下,使它能根據輸
入參數的變化給出不同的屏幕輸出:
//hello.c 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");
}
將這個程序編譯通過后,執行hello(1),屏幕上會打出:
hello,world!
而hello(0)將會得到:
大家好!
現在,程序hello已經可以根據輸入參數來給出相應的屏幕輸出。在這個程序里,除了用到了屏幕輸出函數mexPrintf(用法跟c里的printf函數幾乎完全一樣)外,還用到了一個函數:mxGetScalar,調用方式如下:
i=mxGetScalar(prhs[0]);
"Scalar"就是標量的意思。在Matlab里數據都是以數組的形式存在的,mxGetScalar的作用就是把通過prhs[0]傳遞進來的mxArray類型的指針指向的數據(標量)賦給C程序里的變量。這個變量本來應該是double類型的,通過強制類型轉換賦給了整形變量i。既然有標量,顯然還應該有矢量,否則矩陣就沒法傳了。看下面的程序:
//hello.c 2.1
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[],
int nrhs, const mxArray *prhs[])
{
int *i;
i=mxGetPr(prhs[0]);
if(i[0]==1)
mexPrintf("hello,world!/n");
else
mexPrintf("大家好!/n");
}
這樣,就通過mxGetPr函數從指向mxArray類型數據的prhs[0]獲得了指向double類型的指針。
但是,還有個問題,如果輸入的不是單個的數據,而是向量或矩陣,那該怎么處理呢 ?通過mxGetPr只能得到指向這個矩陣的指針,如果我們不知道這個矩陣的確切大小,就
沒法對它進行計算。
為了解決這個問題,Matlab提供了兩個函數mxGetM和mxGetN來獲得傳進來參數的行數 和列數。下面例程的功能很簡單,就是獲得輸入的矩陣,把它在屏幕上顯示出來:
//show.c 1.0
#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");
}
}
編譯完成后,用下面的命令測試一下:
a=1:10;
b=[a;a+1];
show(a)
show(b)
需要注意的是,在Matlab里,矩陣第一行是從1開始的,而在C語言中,第一行的序數為零,Matlab里的矩陣元素b(i,j)在傳遞到C中的一維數組大data后對應於data[j*M+i] 。
輸入數據是在函數調用之前已經在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的基礎上稍作改變得到的,功能是將輸
//reverse.c 1.0
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[],
int nrhs, const mxArray *prhs[])
{
double *inData;
double *outData;
int M,N;
int i,j;
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[(N-1-j)*M+i];
}
當然,Matlab里使用到的並不是只有double類型這一種矩陣,還有字符串類型、稀疏矩陣、結構類型矩陣等等,並提供了相應的處理函數。本文用到編制mex程序中最經常遇到的一些函數,其余的詳細情況清參考Apiref.pdf。
通過前面兩部分的介紹,大家對參數的輸入和輸出方法應該有了基本的了解。具備了這些知識,就能夠滿足一般的編程需要了。但這些程序還有些小的缺陷,以前面介紹的re由於前面的例程中沒有對輸入、輸出參數的數目及類型進行檢查,導致程序的容錯性很差,以下程序則容錯性較好
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
double *inData;
double *outData;
int M,N;
//異常處理
//異常處理
if(nrhs!=1)
mexErrMsgTxt("USAGE: b=reverse(a)/n");
if(!mxIsDouble(prhs[0]))
mexErrMsgTxt("the Input Matrix must be double!/n");
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[(N-1-j)*M+i];
}
在上面的異常處理中,使用了兩個新的函數:mexErrMsgTxt和mxIsDouble。MexErrMsgTxt在給出出錯提示的同時退出當前程序的運行。MxIsDouble則用於判斷mxArray中的數據是否double類型。當然Matlab還提供了許多用於判斷其他數據類型的函數,這里不加詳述。
需要說明的是,Matlab提供的API中,函數前綴有mex-和mx-兩種。帶mx-前綴的大多是對mxArray數據進行操作的函數,如mxIsDouble,mxCreateDoubleMatrix等等。而帶mex前綴的則大多是與Matlab環境進行交互的函數,如mexPrintf,mxErrMsgTxt等等。了解了這一點,對在Apiref.pdf中查找所需的函數很有幫助。
至此為止,使用C編寫mex函數的基本過程已經介紹完了。