在工程計算相關項目中,經常利用Matlab來完成計算、算法、繪圖等功能。使用Matlab來完成這些功能很是簡單,Matlab提供的m編程語言功能強大,代碼量少。爲了在本身的C/C++項目中加入這些功能,須要一系列繁瑣的過程,令不少人望之卻步。主要的困難在於:web
l 如何從m文件生成VC可用的C/C++代碼;算法
l 如何設置編譯參數,在VC中編譯這些代碼;編程
l 如何在C/C++語言中設置輸入輸出參數,使之與M代碼生成的C++代碼一同運行;數組
l 如何製做包含matlab運行時庫的安裝程序。網絡
下面結合網絡上面的資料,對以上問題進行了總結,較好的解決了上面的問題。我使用的相關開發環境以下:Matlaba6.5;VC6;WindowsXP。多線程
進入正文以前,要說說寫這篇文章的原由。近幾天發現一個半年前寫的程序出現了莫名其妙的bug。在程序退出時總有一個線程死掉不能退出,致使整個進程不能正常退出,必須從進程管理器中殺掉。因爲該程序一共有7個子線程,我一個個檢查後發現程序在運行時有一個並不是由我建立的多餘子線程。經過Process Viewer和Spy++等工具觀察發現,該線程中有如下幾個窗口IME、TthreadWindow和MSCTFIMEUI,查來查去毫無頭緒。編程語言
首先懷疑是多線程庫有bug,所以仔細重讀了一遍本身封裝的多線庫,還真的發現了幾個bug,可是修正後於事無補。花費了三天時間;ide
而後懷疑是界面庫有問題,仔細比較了使用界面庫和不使用界面庫先後的差異發現界面庫會多啓動兩個界面管理線程,可是都會正常退出,沒有問題。花費一天時間;函數
最後只好懷疑是引入的dll啓動了某個線程。一個個排查dll,終於發現了ago4501.dll和v4501v.dll這兩個可疑的dll。這兩個dll是由Mideva(將m文件轉換爲C/C++代碼的一箇中間工具)引入的。工具
當初使用Mideva就是由於在直接使用matlab的mcc出現了困難,不得已找mideva代替。不少人還信誓旦旦的說Mideva是最適宜VC使用的m代碼轉換工具。不少書和網絡資料都還給出了示例代碼,我就不相信他們沒有碰到這個線程問題,只是避而不談罷了。想起Mideva已經被MathWork公司收購而且再也不支持了,我就決心放棄mideva,繼續使用mcc來生成代碼。全部的歷程都記錄以下。
要在VC中使用m文件,方法有不少種。
最簡單的我認爲仍是使用Mideva,固然若是你可以搞定那個線程問題,而且永遠只使用matlab6之前的版本,你就可使用mideva。這裏就不介紹了。
第二種就是使用Matlab引擎來調用m文件,也比較簡單,可是你必須在目標機器上安裝matlab才行,這每每是不現實的。
第三種使用mcc將m文件編譯成爲C/C++代碼,而後導入Vc編譯,由於經常生成不少源代碼,使用很繁瑣,這個不少網絡資料已經說過。
第四種就是使用mcc將m文件編譯爲頭文件、dll和lib而後導入VC編譯。目前這是最可行的一種方法。本文引用了首發於哈工大紫丁香站BBS的fork (撒哈拉沙漠的沙)寫的解決方法。並作了一些文字上的修改。Fork的例子有些簡單,沒有涉及多維數據參數的構建與輸入,也沒有多字符串組參數的構建,所以我重寫了一個較爲實用的例子來展現他的內容。
例子的內容是經過輸入的數據來展現農做物產量的統計圖,其m代碼以下:
function result = MyStat(mStatMatrix,mNameMatrix,n)
% 畫出柱狀圖來展現各個不一樣季度的農做物產量
% mStatMatrix表明農做物產量矩陣,每行爲一個地區,每行第一列爲小麥產量,第二列爲玉米產量;
% mNameMatrix表明地區名稱字符串數組;
% n表明地區個數
% 返回值爲全部地區糧食總產量
bar(mStatMatrix);
xlabel('地區名稱');
ylabel('產量');
title('農做物產量統計');
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號地區','15號地區','7號地區','9號地區'}
n=4
MyStat(data,name,n)
能夠獲得圖以下:
返回值爲23。
輸入:(格式: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會報錯,由於有些中間文件重名了。
在VC中建一個基於對話框的MFC應用程序,名字爲TestStat,添加一個按鈕,並添加按鈕響應函數,函數內容在第五步中說明。將上面生成的3個文件拷貝到VC工程的TestStat目錄裏。
在VC中選擇:工程--->設置,再選屬性表Link選項,下拉菜單中選擇Input,在對象/庫模塊中加入附錄A中所列出的內容,注意用空格將它們格開而在忽略。庫中加入附錄B中列出的內容;再選擇屬性表C/C++選項,下拉菜單選General,在預處理程序定義中添加附錄C中的內容,原來有的內容要保留,並注意用逗號將它們隔開。再選擇下拉菜單的Precompiled Headers選項,選擇「自動使用預補償頁眉」,在其中添加stdafx.h,肯定。
選擇:工具--->選擇,屬性頁選擇「目錄」,在include files裏面加入:
C:"MATLAB6P5"EXTERN"INCLUDE;
C:"MATLAB6P5"EXTERN"INCLUDE"CPP
注意,根據你的matlab的安裝位置的不一樣,要相應的修改上面的地址。在Library files裏面加入:
C:"MATLAB6P5"EXTERN"LIB"WIN32
C:"MATLAB6P5"EXTERN"LIB"WIN32"MICROSOFT"MSVC60
注意,根據你的matlab的安裝位置的不一樣,要相應的修改上面的地址。
在按鈕響應函數所在文件中添加以下的頭文件:詳細的解釋見下一章參數問題。
......
#include "mystatlib.h"
......
函數響應代碼爲:
mxArray * mStatMatrix = NULL;
mxArray * mNameMatrix = NULL;
mxArray * n;
//給2維數組賦值,是一個3*2數組
mStatMatrix = mxCreateDoubleMatrix(4,2,mxREAL);
int mrows = mxGetM(mStatMatrix); //行數
int ncols = mxGetN(mStatMatrix); //列數
double* data = mxGetPr(mStatMatrix); //矩陣的數據地址
double setdata[4][2] = {{1,2},{3,4},{5,6},{7,8}}; //源數據,也可爲二維數組
for (int i = 0; i < mrows; i++)
{
for (int j = 0; j < ncols; j++)
{
data[j*mrows+i] = setdata[i][j]; //注意這裏的賦值,至關於轉置矩陣賦值
}
}
//建立一個Cell數組來存放字符串數組
int dim[1] ;
dim[0] = 4;
mNameMatrix = mxCreateCellArray(1,dim);
//給Cell數組賦值
for (int x = 0; x < 4; x++)
{
char szTmp[10];
sprintf(szTmp,"地區%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);
在Link---->Input選項中加入一項:MyStatLib.lib。這就是咱們從m文件編譯過來的dll的庫文件。
可獲得如下界面:
附錄A:連接庫
libmmfile.lib libmatlb.lib libmx.lib libmat.lib libmatpm.lib sgl.lib libmwsglm.lib libmwservices.lib libut.lib
附錄B:忽略庫
msvcrt.lib
附錄C: 預處理程序定義
MSVC,IBMPC,MSWIND
附錄D:進一步參考
mxArray的使用參考matlab網站的cmath_ug2b.pdf
Matlab中最常使用的變量有三種,分別是標量、矩陣和元胞數組(Cell Array),咱們只要掌握了這三種變量就能夠對付大部分的需求了。在上面的例子中m函數MyStat(mStatMatrix,mNameMatrix,n)有三個輸入參數,分別是二維矩陣mStatMatrix,元胞數組mNameMatrix和標量n。
mStatMatrix表明農做物產量矩陣,每行爲一個地區,每行第一列爲小麥產量,第二列爲玉米產量;
mNameMatrix表明地區名稱字符串數組;
n表明地區個數。
創建一個標量最簡單,只要將標量的值做爲參數傳入便可:
n = mxCreateScalarDouble(3);
創建多維矩陣比較簡單,可是給矩陣賦值則比較複雜。創建一個雙精度數矩陣的函數以下:
mStatMatrix = mxCreateDoubleMatrix(4,2,mxREAL);
前兩個參數表明二維矩陣是一個4*2的矩陣,最後一個表明這是一個實數矩陣。
給二維矩陣賦值是較爲複雜的,首先要經過mxGetPr函數來獲得矩陣存儲數據的地址。而後經過[]符號來進行地址偏移將適當的值賦值給適當的地址。舉例以下:
int mrows = mxGetM(mStatMatrix); //行數
int ncols = mxGetN(mStatMatrix); //列數
double* data = mxGetPr(mStatMatrix); //矩陣的數據地址
double setdata[4][2] = {{1,2},{3,4},{5,6},{7,8}}; //源數據
for (int i = 0; i < mrows; i++)
{
for (int j = 0; j < ncols; j++)
{
data[j*mrows+i] = setdata[i][j]; //注意這裏的賦值,至關於轉置矩陣賦值
}
}
給多維數組賦值時要特別注意:第一,mxArray的存儲是先列後行的,而C語言是先行後列的,因此在賦值時至關於使用轉置矩陣來賦值;第二要仔細防止下標越界,若是越界則程序運行時會崩潰。
元胞數組是matlab獨有的數據類型。至關於將各類不一樣類型的變量集中到一個數組裏面。此處咱們用元胞數組來存儲多個字符串。
建立元胞數組的函數以下:
mxArray *mxCreateCellArray(int ndim, const int *dims);
參數ndim指示元胞數組的維數,參數dims其實是一個int數組,存儲了各維的長度。下面建立了一個一維數組,長度爲4.
//建立一個Cell數組來存放字符串數組
const int dim[1] = {3};
mNameMatrix = mxCreateCellArray(1,dim);
給Cell數組賦值比較簡單,即便用mxCreateString建立多個字符串而後用mxSetCell將字符串賦值給元胞數組:
for (int x = 0; x < 4; x++)
{
char szTmp[10];
sprintf(szTmp,"地區%d",x+1);
mxArray* m = mxCreateString(szTmp);
mxSetCell(mNameMatrix,x,m);
}
參數準備完畢就能夠調用函數了。Dll中會提供不少可調用的函數,有兩個主要的函數,一個名稱爲XXXInitialize()(XXX即爲庫名稱,本文中是MyStatLibInitialize);第二個是mlfXXX(參數列表)。調用函數分兩步,第一步調用初始化函數MyStatLibInitialize;第二步用設置好的參數調用mlfMystat(mStatMatrix,mNameMatrix,n)。
記得調用完成後用mxDestroyArray刪除mxArray佔用的內存。至此爲止,代碼編寫工做所有結束。程序能夠正常運行了,可是別笑,噩夢剛剛開始~~~~
要使編寫的程序可以在其餘機器順利運行,必須製做安裝程序。於其餘開發庫不一樣,matlab程序的打包顯得比較困難。尤爲是要脫離matlab環境運行的程序顯得更加困難。
根據fork(撒哈拉沙漠的沙)在哈工大紫丁香站BBS上面的文章,我簡要總結了一種較爲簡單的打包方法。
首先獲得matlab運行時庫,其方法是運行「MATLAB6p5"extern"lib"win32」目錄下「mglinstaller.exe」程序,這個程序會在指定目錄產生bin和toolbox兩個目錄,大小是23.7M。這就是matlab的運行時庫;
第二,在製做安裝程序時,將這兩個運行時庫加入安裝資源,在安裝時拷貝到指定目錄C:"MATLAB6p5p1(根據你本身開發程序上的matlab安裝目錄來寫),記住必須拷貝到一樣的目錄,由於mcc生成的代碼中對路徑有硬編碼;
第三,在製做安裝程序時,添加Path路徑的命令,在安裝時設置path爲C:"MATLAB6p5p1"bin"win32(根據你本身開發程序上的matlab安裝目錄來寫);
第四,安裝完成後必須重啓,不然Path路徑不起做用,這一點我很奇怪,由於通常來講不會這樣。