m文件轉(zhuǎn)換為C/C++文件的編譯、繪圖、參數(shù)、打包問題總結(jié)
在工程計算相關(guān)項目中,常常利用Matlab來完成計算、算法、繪圖等功能。使用Matlab來完成這些功能非常簡單,Matlab提供的m編程語言功能強大,代碼量少。為了在自己的C/C++項目中加入這些功能,需要一系列繁瑣的過程,令很多人望之卻步。主要的困難在于:
l 如何從m文件生成VC可用的C/C++代碼;
l 如何設(shè)置編譯參數(shù),在VC中編譯這些代碼;
l 如何在C/C++語言中設(shè)置輸入輸出參數(shù),使之與M代碼生成的C++代碼一同運行;
l 如何制作包含matlab運行時庫的安裝程序。
下面結(jié)合網(wǎng)絡(luò)上面的資料,對以上問題進行了總結(jié),較好的解決了上面的問題。我使用的相關(guān)開發(fā)環(huán)境如下:Matlaba6.5;VC6;WindowsXP。
1 引子
進入正文之前,要說說寫這篇文章的起因。近幾天發(fā)現(xiàn)一個半年前寫的程序出現(xiàn)了莫名其妙的bug。在程序退出時總有一個線程死掉不能退出,導(dǎo)致整個進程不能正常退出,必須從進程管理器中殺掉。由于該程序一共有7個子線程,我一個個檢查后發(fā)現(xiàn)程序在運行時有一個并非由我創(chuàng)建的多余子線程。通過Process Viewer和Spy++等工具觀察發(fā)現(xiàn),該線程中有以下幾個窗口IME、TthreadWindow和MSCTFIMEUI,查來查去毫無頭緒。
首先懷疑是多線程庫有bug,因此仔細重讀了一遍自己封裝的多線庫,還真的發(fā)現(xiàn)了幾個bug,但是修正后于事無補。花費了三天時間;
然后懷疑是界面庫有問題,仔細比較了使用界面庫和不使用界面庫前后的差別發(fā)現(xiàn)界面庫會多啟動兩個界面管理線程,但是都會正常退出,沒有問題。花費一天時間;
最后只好懷疑是引入的dll啟動了某個線程。一個個排查dll,終于發(fā)現(xiàn)了ago4501.dll和v4501v.dll這兩個可疑的dll。這兩個dll是由Mideva(將m文件轉(zhuǎn)換為C/C++代碼的一個中間工具)引入的。
當初使用Mideva就是因為在直接使用matlab的mcc出現(xiàn)了困難,不得已找mideva代替。很多人還信誓旦旦的說Mideva是最適宜VC使用的m代碼轉(zhuǎn)換工具。很多書和網(wǎng)絡(luò)資料都還給出了示例代碼,我就不相信他們沒有碰到這個線程問題,只是避而不談罷了。想起Mideva已經(jīng)被MathWork公司收購并且不再支持了,我就決心放棄mideva,繼續(xù)使用mcc來生成代碼。所有的歷程都記錄如下。
2 M文件轉(zhuǎn)換為C/C++文件
要在VC中使用m文件,方法有很多種。
最簡單的我認為還是使用Mideva,當然如果你能夠搞定那個線程問題,并且永遠只使用matlab6以前的版本,你就可以使用mideva。這里就不介紹了。
第二種就是使用Matlab引擎來調(diào)用m文件,也比較簡單,但是你必須在目標機器上安裝matlab才行,這往往是不現(xiàn)實的。
第三種使用mcc將m文件編譯成為C/C++代碼,然后導(dǎo)入Vc編譯,因為常常生成很多源代碼,使用很繁瑣,這個很多網(wǎng)絡(luò)資料已經(jīng)說過。
第四種就是使用mcc將m文件編譯為頭文件、dll和lib然后導(dǎo)入VC編譯。目前這是最可行的一種方法。本文引用了首發(fā)于哈工大紫丁香站BBS的fork (撒哈拉沙漠的沙)寫的解決方法。并做了一些文字上的修改。Fork的例子有些簡單,沒有涉及多維數(shù)據(jù)參數(shù)的構(gòu)建與輸入,也沒有多字符串組參數(shù)的構(gòu)建,因此我重寫了一個較為實用的例子來展示他的內(nèi)容。
2.1 例子
例子的內(nèi)容是通過輸入的數(shù)據(jù)來展示農(nóng)作物產(chǎn)量的統(tǒng)計圖,其m代碼如下:
function result = MyStat(mStatMatrix,mNameMatrix,n)
% 畫出柱狀圖來展示各個不同季度的農(nóng)作物產(chǎn)量
% mStatMatrix代表農(nóng)作物產(chǎn)量矩陣,每行為一個地區(qū),每行第一列為小麥產(chǎn)量,第二列為玉米產(chǎn)量;
% mNameMatrix代表地區(qū)名稱字符串數(shù)組;
% n代表地區(qū)個數(shù)
% 返回值為所有地區(qū)糧食總產(chǎn)量
bar(mStatMatrix);
xlabel('地區(qū)名稱');
ylabel('產(chǎn)量');
title('農(nóng)作物產(chǎn)量統(tǒng)計');
legend('小麥','玉米',1);
totalnum = 0;
for i=1:n
text(i,max(mStatMatrix(i,1),mStatMatrix(i,2))+0.25,mNameMatrix(i));
totalnum = totalnum + mStatMatrix(i,1)+mStatMatrix(i,2);
end
set(gcf,'Menubar','none');
result = totalnum;
在matlab中輸入如下命令:
data=[1,2;3,4;5,6;1,1]
name={'1號地區(qū)','15號地區(qū)','7號地區(qū)','9號地區(qū)'}
n=4
MyStat(data,name,n)
可以得到圖如下:

返回值為23。
2.2 Mcc生成代碼
輸入:(格式:mcc -t -W libhg:<庫名稱> -T link:lib -h libmmfile.mlib libmwsglm.mlib 文件名)
mcc -t -W libhg:MyStatLib -T link:lib -h libmmfile.mlib libmwsglm.mlib MyStat
然后你會在你的工作目錄下找到MyStatLib.dll,MyStatLib.lib,MyStatLib.h三個文件。這三個文件就是VC編程所需要的。一個有趣的bug是,你的庫名稱不能和m文件名稱相同,否則mcc會報錯,因為有些中間文件重名了。
2.3 在VC中添加
在VC中建一個基于對話框的MFC應(yīng)用程序,名字為TestStat,添加一個按鈕,并添加按鈕響應(yīng)函數(shù),函數(shù)內(nèi)容在第五步中說明。將上面生成的3個文件拷貝到VC工程的TestStat目錄里。
2.4 設(shè)置VC
在VC中選擇:工程--->設(shè)置,再選屬性表Link選項,下拉菜單中選擇Input,在對象/庫模塊中加入附錄A中所列出的內(nèi)容,注意用空格將它們格開而在忽略。庫中加入附錄B中列出的內(nèi)容;再選擇屬性表C/C++選項,下拉菜單選General,在預(yù)處理程序定義中添加附錄C中的內(nèi)容,原來有的內(nèi)容要保留,并注意用逗號將它們隔開。再選擇下拉菜單的Precompiled Headers選項,選擇“自動使用預(yù)補償頁眉”,在其中添加stdafx.h,確定。
2.5 設(shè)置頭文件和庫文件路徑
選擇:工具--->選擇,屬性頁選擇“目錄”,在include files里面加入:
C:"MATLAB6P5"EXTERN"INCLUDE;
C:"MATLAB6P5"EXTERN"INCLUDE"CPP
注意,根據(jù)你的matlab的安裝位置的不同,要相應(yīng)的修改上面的地址。在Library files里面加入:
C:"MATLAB6P5"EXTERN"LIB"WIN32
C:"MATLAB6P5"EXTERN"LIB"WIN32"MICROSOFT"MSVC60
注意,根據(jù)你的matlab的安裝位置的不同,要相應(yīng)的修改上面的地址。
2.6 添加響應(yīng)代碼
在按鈕響應(yīng)函數(shù)所在文件中添加如下的頭文件:詳細的解釋見下一章參數(shù)問題。
......
#include "mystatlib.h"
......
函數(shù)響應(yīng)代碼為:
mxArray * mStatMatrix = NULL;
mxArray * mNameMatrix = NULL;
mxArray * n;
//給2維數(shù)組賦值,是一個3*2數(shù)組
mStatMatrix = mxCreateDoubleMatrix(4,2,mxREAL);
int mrows = mxGetM(mStatMatrix); //行數(shù)
int ncols = mxGetN(mStatMatrix); //列數(shù)
double* data = mxGetPr(mStatMatrix); //矩陣的數(shù)據(jù)地址
double setdata[4][2] = {{1,2},{3,4},{5,6},{7,8}}; //源數(shù)據(jù),也可為二維數(shù)組
for (int i = 0; i < mrows; i++)
{
for (int j = 0; j < ncols; j++)
{
data[j*mrows+i] = setdata[i][j]; //注意這里的賦值,相當于轉(zhuǎn)置矩陣賦值
}
}
//創(chuàng)建一個Cell數(shù)組來存放字符串數(shù)組
int dim[1] ;
dim[0] = 4;
mNameMatrix = mxCreateCellArray(1,dim);
//給Cell數(shù)組賦值
for (int x = 0; x < 4; x++)
{
char szTmp[10];
sprintf(szTmp,"地區(qū)%d",x+1);
mxArray* m = mxCreateString(szTmp);
mxSetCell(mNameMatrix,x,m);
}
//給n賦值
n = mxCreateScalarDouble(4);
MyStatLibInitialize();
mlfMystat(mStatMatrix,mNameMatrix,n);
mxDestroyArray(mNameMatrix);
mxDestroyArray(mStatMatrix);
mxDestroyArray(n);
2.7 添加自己的庫
在Link---->Input選項中加入一項:MyStatLib.lib。這就是我們從m文件編譯過來的dll的庫文件。
2.8 編譯鏈接執(zhí)行
可得到以下界面:

2.9 附錄
附錄A:鏈接庫
libmmfile.lib libmatlb.lib libmx.lib libmat.lib libmatpm.lib sgl.lib libmwsglm.lib libmwservices.lib libut.lib
附錄B:忽略庫
msvcrt.lib
附錄C: 預(yù)處理程序定義
MSVC,IBMPC,MSWIND
附錄D:進一步參考
mxArray的使用參考matlab網(wǎng)站的cmath_ug2b.pdf
3 參數(shù)問題
Matlab中最常使用的變量有三種,分別是標量、矩陣和元胞數(shù)組(Cell Array),我們只要掌握了這三種變量就可以對付大部分的需求了。在上面的例子中m函數(shù)MyStat(mStatMatrix,mNameMatrix,n)有三個輸入?yún)?shù),分別是二維矩陣mStatMatrix,元胞數(shù)組mNameMatrix和標量n。
mStatMatrix代表農(nóng)作物產(chǎn)量矩陣,每行為一個地區(qū),每行第一列為小麥產(chǎn)量,第二列為玉米產(chǎn)量;
mNameMatrix代表地區(qū)名稱字符串數(shù)組;
n代表地區(qū)個數(shù)。
3.1 mxArray標量
建立一個標量最簡單,只要將標量的值作為參數(shù)傳入即可:
n = mxCreateScalarDouble(3);
3.2 mxArray矩陣
建立多維矩陣比較簡單,但是給矩陣賦值則比較復(fù)雜。建立一個雙精度數(shù)矩陣的函數(shù)如下:
mStatMatrix = mxCreateDoubleMatrix(4,2,mxREAL);
前兩個參數(shù)代表二維矩陣是一個4*2的矩陣,最后一個代表這是一個實數(shù)矩陣。
給二維矩陣賦值是較為復(fù)雜的,首先要通過mxGetPr函數(shù)來得到矩陣存儲數(shù)據(jù)的地址。然后通過[]符號來進行地址偏移將適當?shù)闹蒂x值給適當?shù)牡刂贰Ee例如下:
int mrows = mxGetM(mStatMatrix); //行數(shù)
int ncols = mxGetN(mStatMatrix); //列數(shù)
double* data = mxGetPr(mStatMatrix); //矩陣的數(shù)據(jù)地址
double setdata[4][2] = {{1,2},{3,4},{5,6},{7,8}}; //源數(shù)據(jù)
for (int i = 0; i < mrows; i++)
{
for (int j = 0; j < ncols; j++)
{
data[j*mrows+i] = setdata[i][j]; //注意這里的賦值,相當于轉(zhuǎn)置矩陣賦值
}
}
給多維數(shù)組賦值時要特別注意:第一,mxArray的存儲是先列后行的,而C語言是先行后列的,所以在賦值時相當于使用轉(zhuǎn)置矩陣來賦值;第二要仔細防止下標越界,如果越界則程序運行時會崩潰。
3.3 元胞數(shù)組
元胞數(shù)組是matlab獨有的數(shù)據(jù)類型。相當于將各種不同類型的變量集中到一個數(shù)組里面。此處我們用元胞數(shù)組來存儲多個字符串。
創(chuàng)建元胞數(shù)組的函數(shù)如下:
mxArray *mxCreateCellArray(int ndim, const int *dims);
參數(shù)ndim指示元胞數(shù)組的維數(shù),參數(shù)dims實際上是一個int數(shù)組,存儲了各維的長度。下面創(chuàng)建了一個一維數(shù)組,長度為4.
//創(chuàng)建一個Cell數(shù)組來存放字符串數(shù)組
const int dim[1] = {3};
mNameMatrix = mxCreateCellArray(1,dim);
給Cell數(shù)組賦值比較簡單,即使用mxCreateString創(chuàng)建多個字符串然后用mxSetCell將字符串賦值給元胞數(shù)組:
for (int x = 0; x < 4; x++)
{
char szTmp[10];
sprintf(szTmp,"地區(qū)%d",x+1);
mxArray* m = mxCreateString(szTmp);
mxSetCell(mNameMatrix,x,m);
}
3.4 調(diào)用m代碼中的函數(shù)
參數(shù)準備完畢就可以調(diào)用函數(shù)了。Dll中會提供很多可調(diào)用的函數(shù),有兩個主要的函數(shù),一個名稱為XXXInitialize()(XXX即為庫名稱,本文中是MyStatLibInitialize);第二個是mlfXXX(參數(shù)列表)。調(diào)用函數(shù)分兩步,第一步調(diào)用初始化函數(shù)MyStatLibInitialize;第二步用設(shè)置好的參數(shù)調(diào)用mlfMystat(mStatMatrix,mNameMatrix,n)。
記得調(diào)用完成后用mxDestroyArray刪除mxArray占用的內(nèi)存。至此為止,代碼編寫工作全部結(jié)束。程序可以正常運行了,但是別笑,噩夢剛剛開始~~~~
4 打包
要使編寫的程序能夠在其他機器順利運行,必須制作安裝程序。于其他開發(fā)庫不同,matlab程序的打包顯得比較困難。尤其是要脫離matlab環(huán)境運行的程序顯得更加困難。
根據(jù)fork(撒哈拉沙漠的沙)在哈工大紫丁香站BBS上面的文章,我簡要總結(jié)了一種較為簡單的打包方法。
首先得到matlab運行時庫,其方法是運行“MATLAB6p5"extern"lib"win32”目錄下“mglinstaller.exe”程序,這個程序會在指定目錄產(chǎn)生bin和toolbox兩個目錄,大小是23.7M。這就是matlab的運行時庫;
第二,在制作安裝程序時,將這兩個運行時庫加入安裝資源,在安裝時拷貝到指定目錄C:"MATLAB6p5p1(根據(jù)你自己開發(fā)程序上的matlab安裝目錄來寫),記住必須拷貝到同樣的目錄,因為mcc生成的代碼中對路徑有硬編碼;
第三,在制作安裝程序時,添加Path路徑的命令,在安裝時設(shè)置path為C:"MATLAB6p5p1"bin"win32(根據(jù)你自己開發(fā)程序上的matlab安裝目錄來寫);
第四,安裝完成后必須重啟,否則Path路徑不起作用,這一點我很奇怪,因為一般來說不會這樣。