本文主要介紹VC與Matlab混合編程的兩種方法,並詳細介紹了VC向Matlab傳遞復雜數據:結構體的方法,有詳細代碼說明。
Matlab 主要面對科學計算、可視化以及交互式程序設計的高科技計算環境,但由於Matlab開發平台上開發的程序不能脫離Matlab運行環境,因而在處理一些實際應用問題時顯得靈活性不足,而VC++則在一定程度上能夠彌補這一漏洞,因此,將二者結合共用,各獻其長,可以為科研工作和工程開發提供更為強大的技術支持。
Matlab作為控制系統設計的一種通用工具,它可以很方便的和VC進行連接。一般而言,Matlab與VC混合編程的實現方法有很多種,這里主要介紹以下兩種:
1. VC調用Matlab Engine的方式:
Matlab Engine是一組Matlab提供的接口函數,支持C/C++語言,Matlab Engine采用C/S(客戶機/服務器)模式,Matlab作為后台服務器,而C/C++程序作為前台客戶機,向Matlab Engine傳遞執行命令和數據信息,從Matlab Engine接收執行結果。用戶可以在前台應用程序中調用這些接口函數,實現對Matlab Engine的控制。采用這種方法幾乎能利用Matlab全部功能,但是需要在機器上安裝Matlab軟件,缺點是執行效率較低。下面簡單介紹下這種方式的實現步驟:
1.1 API接口介紹
先來介紹一組Matlab提供的引擎API接口:(僅作簡單功能介紹,詳細參數說明請參考Matlab幫助)
Engine* engOpen(const char* startcmd)啟動Matlab引擎
int engClose(Engine* ep) 關閉Matlab引擎
int engEvalString(Engine* ep, const char* string)執行Matlab表達式
mxArray* engGetArray(Engine* ep, const char* name)獲取一個變量數組的值
int engPutArray(engine* ep, const mxArray* mp)設置一個變量數組的值
int engPutVariable(Engine *ep, const char *name, const mxArray *pm)同上
mxArray *engGetVariable(Engine *ep, const char *name)獲取一個變量
int engOutputBuffer(Engine* eP,char* p,int n)獲取輸出字符串
1.2 VC環境配置
要想在VC集成環境下調用Matlab引擎實現VC和Matlab的混合編程,一般需要經過以下幾個必要的步驟:(以下以Matlab2008a和VS2005版本為例)
(1)添加include路徑:將“\extern\include”(在Matlab的安裝路徑下)路勁添加到VC編譯器的include下
(2)添加lib路徑:將“\extern \lib\win32\microsoft” (在Matlab的安裝路徑下) 路徑添加到VC編譯器的lib下
(3)加載lib:需要加載至少libmx.lib、libmat.lib、libeng.lib三個庫
(4)include頭文件:在要使用 engine 函數的地方包含engine.h頭文件
1.3 引擎調用
接下來就可以在VC中調用Matlab引擎了,簡單的示例代碼如下:
#include "engine.h" #pragma comment(lib, "libeng.lib") #pragma comment(lib, "libmx.lib") #pragma comment(lib, "libmat.lib") void TestDeno() { Engine* pEng = NULL; if (!(pEng = engOpen(NULL))) { printf("Open matlab enging fail!"); return; } //call Engine plot A*sin(t)+B A=2 B=1 mxArray *A = NULL; double init = 2; A = mxCreateDoubleMatrix(1, 1, mxREAL); memcpy((void*) mxGetPr(A), (void*)&init, sizeof (double)); engPutVariable(pEng, "A", A); init = 1; memcpy((void*) mxGetPr(A), (void*)&init, sizeof (double)); engPutVariable(pEng, "B", A); mxDestroyArray(A); Sleep(3*60*1000); engEvalString(pEng, "t=0:0.2:7;plot(t,A*sin(t)+B);"); if(NULL != pEng) { engClose(pEng); } }
示例代碼通過VC調用Matlab引擎,繪制正弦曲線,相對簡單,就不再詳細解釋,效圖如下:
2. VC調用Matlab DLL的方式
DLL是一個可執行的二進制文件。把很多通用的功能放在DLL中,可以供各種應用程序調用,這樣可以很好的減少外部存儲空間的占有量,並實現代碼的共享。Matlab也支持將m程序編譯成dll,供其他語言(包括VC、VB、Fortran等)調用,下面先簡單介紹下Matlab DLL的編譯步驟:
2.1 Matlab DLL制作
(1)配置Matlab的編譯器跟,要將mex和mbuil兩個都配置成本機上安裝的VC,mbuild的配置方法同mex
(2)編譯m函數成為dll,在matlab命令空間中使用mcc命令編譯m文件:(mcc的使用參考Matlab幫助)
示例:將一個Matlab函數MyFun 編譯成libMyFun 的命令:
mcc –W cpplib:libMyFun –T link:lib MyFun
參數說明:-W控制編譯之后的封裝格式,cpplib表示c++的lib,冒號之后是編譯輸出lib名,-T表示目標,link:lib表示連接到lib的目標,MyFun是待編譯的m文件名,編譯成功之后,會輸出三個文件:libMyFun.lib libMyFun.dll libMyFun.h
2.2 DLL在VC中調用
要想在VC集成環境下調用Matlab的DLL,實現VC和Matlab的混合編程,一般需要經過以下幾個必要的步驟:(以下以Matlab2008a和VS2005版本為例)
(1)VC環境配置,同前面VC調用Matlab引擎方式類似,也需要配置VC的編譯環境,其中include路徑、lib路徑和前一種方式相同,加載的lib變更為:mclmcrrt.lib、libmx.lib、libmat.lib、mclmcr.lib四個,include的頭文件變更為:mclmcr.h、matrix.h、mclcppclass.h三個。
(2)DLL中函數的調用,先看一下m函數編譯成C++Dll之后的函數聲明,例:matlab函數:function [y, out] = CaculateFun(x, in)編譯之后對應c++的聲明:void MW_CALL_CONVCaculateFun(int nargout, mwArray& y, mwArray& out, const mwArray& x, const mwArray& in)參數int nargout指定調用時,輸出參數的個數,緊跟的后續nargout個參數y、out為輸出參數,后續再剩下的多個參數x、in就為輸入參數。
(3)注意事項:在VC中調用dll中的函數之前,需要先調用函數libFunInitialize初始化,在調用完函數以后,需要再調用函數libFunTerminate和mclTerminateApplication終止。
准備好了上述步驟,就可以在VC中調用DLL中函數的了,簡單的示例代碼如下:
// include matlab sys head file #include "mclmcr.h" #include "matrix.h" #include "mclcppclass.h" // include lib head file #include "libCaculateFun.h" // link matlab sys lib #pragma comment(lib, "mclmcrrt.lib") #pragma comment(lib, "libmx.lib") #pragma comment(lib, "libmat.lib") #pragma comment(lib, "mclmcr.lib") // link lib #pragma comment(lib, "libCaculateFun.lib") void TestDeno() { // init lib if (!(libCaculateFunInitialize())) { std::cout<<"Could not init lib !"<<endl; return -1; } double xxxx[2] = {0}; double inin = 0; double yyyy[2] = {0}; double outo = 0; // 為函數參數分配內存空間 mwArray mwXX(1, 2, mxDOUBLE_CLASS); mwArray mwIn(1, 1, mxDOUBLE_CLASS); mwArray mwYY(1, 2, mxDOUBLE_CLASS); mwArray mwOut(1, 2, mxDOUBLE_CLASS); // 為輸入參數賦值 mwXX.SetData(&xxxx, 2); mwIn.SetData(&inin, 1); // 調用計算函數 CaculateFun(2, mwYY, mwOut, mwXX, mwIn); // 獲取輸出參數 outo = mwOut.Get(1, 1); // lib Terminate libCaculateFunTerminate(); // MCR Terminate mclTerminateApplication(); }
示例代碼通過VC調用Matlab DLL,將變量xxxx的值付給yyyy,將變量inin的值付給outo,相對簡單,就不再詳細解釋。
3. VC向Matlab函數傳遞參數
前面在介紹兩種混合編程的方式時,示例代碼中已經涉及到了參數的傳遞問題,對於VC中的簡單數據類型:變量、數組等,網上已經有很多資料介紹,前面示例代碼中也有所體現,這里不再詳細說明。
關於復雜數據結構:用戶自定義結構體類型,處理起來比較麻煩,網上能找到的資料也較少,由於前面提及的兩種方式,在傳遞參數時,使用的數據類型是不一致的:引擎方式只能是mxArray類型,而DLL方式的參數只能是mwArray,因此需要分別介紹將結構體轉換成這兩種類型的方法:
先來定義兩個結構體:
// 三維坐標系中的點 struct Postion { double x; double y; double z; }; // 名字標示的一個坐標點 struct Coordinate { struct Postion pos; char name[ARRAYSIZE]; };
分別介紹將如上兩個結構體轉成Matlab能使識別的Array的方法
(1)結構體轉換成mxArray:解釋比較多余,就直接上代碼吧:
mxArray *Staruct2mxArray(struct Postion *pStaruct) { mxArray *pm, *mx; mwSize m = 1, n = 1; const int nfields = 3; const char *fieldnames[3] = {"x","y","z"}; const mwIndex index = 0; pm = mxCreateStructMatrix(m, n, nfields, fieldnames); mx = mxCreateNumericMatrix(m, n, mxDOUBLE_CLASS, mxREAL); memcpy(mxGetPr(mx), &(pStaruct->x), sizeof(double)); mxSetFieldByNumber(pm, index, 0, mx); mx = mxCreateNumericMatrix(m, n, mxDOUBLE_CLASS, mxREAL); memcpy(mxGetPr(mx), &(pStaruct->y), sizeof(double)); mxSetFieldByNumber(pm, index, 1, mx); mx = mxCreateNumericMatrix(m, n, mxDOUBLE_CLASS, mxREAL); memcpy(mxGetPr(mx), &(pStaruct->z), sizeof(double)); mxSetFieldByNumber(pm, index, 2, mx); return pm; } mxArray *Staruct2mxArray(struct Coordinate *pStaruct) { mxArray *pm, *mx; mwSize m = 1, n = 1; const int nfields = 2; const char *fieldnames[2] = {"pos","name"}; const mwIndex index = 0; pm = mxCreateStructMatrix(m, n, nfields, fieldnames); mx = Staruct2mxArray(&(pStaruct->pos)); mxSetFieldByNumber(pm, index, 0, mx); n = ARRAYSIZE; mx = mxCreateNumericMatrix(m, n, mxCHAR_CLASS, mxREAL); memcpy(mxGetPr(mx), pStaruct->name, sizeof(char)*ARRAYSIZE); mxSetFieldByNumber(pm, index, 1, mx); return pm; }
(2)結構體轉換成mwArray:
mwArray Staruct2mwArray(struct Postion *pStaruct) { mwSize m = 1, n = 1; const int nfields = 3; const char *fieldnames[3] = {"x","y","z"}; mwArray pm(m, n, nfields, fieldnames); mwArray mx(pStaruct->x); pm(fieldnames[0], 1, 1) = mx; mwArray my(pStaruct->y); pm(fieldnames[1], 1, 1) = my; mwArray mz(pStaruct->z); pm(fieldnames[2], 1, 1) = mz; return pm; } mwArray Staruct2mwArray(struct Coordinate *pStaruct) { mwSize m = 1, n = 1; const int nfields = 2; const char *fieldnames[2] = {"pos","name"}; const mwIndex index = 0; mxArray pm(m, n, nfields, fieldnames); mxArray mpos = Staruct2mwArray(&(pStaruct->pos)); pm(fieldnames[0], 1, 1) = mpos; mxArray mname(pStaruct->name); pm(fieldnames[1], 1, 1) = mname; return pm; }
(3)mxArray轉換成結構體:
struct Coordinate *mwArray2Staruct(mwArray pm) { // 代碼僅處理 name 字段 pos 字段是三個簡單類型省略 struct Coordinate *pStaruct = new struct Coordinate; const int nfields = 2; const char *fieldnames[2] = {"pos","name"}; mwArray mwStr = pm.Get(fieldnames[1], 1, 1); char *str = strdup(mwStr.ToString()); memcpy(pStaruct->name,str,sizeof(char)*ARRAYSIZE); return pStaruct; }
(4)mwArray轉換成結構體:
struct Coordinate *mxArray2Staruct(mxArray *pm) { // 代碼僅處理 name 字段 pos 字段是三個簡單類型省略 struct Coordinate *pStaruct = new struct Coordinate; mxArray * mxname = mxGetFieldByNumber(pm, 1, 1) mxArray * str = mxGetPr(mxname); memcpy(pStaruct->name,str,sizeof(char)*ARRAYSIZE); return pStaruct; }