摘要:工程中往往需要VC++編寫的程序進(jìn)行大量的數(shù)學(xué)計算,如能在程序中使用Matlab的例程這些問題則能迅速解決。在Windows平臺上Matlab 提供了兩個實現(xiàn)這一目標(biāo)的接口——Matlab Engine和Automation Server,兩者在本質(zhì)上都基于COM技術(shù)。引擎庫通過其輸出函數(shù)對用戶屏蔽了底層的COM細(xì)節(jié),這大大方便了使用。而使用自動化服務(wù)器就要使用COM的自動化接口IDispatch,不過MFC庫的COleDispatchDriver類已使這一過程大大簡化了。兩種方式的差別主要體現(xiàn)在參數(shù)傳遞和返回值處理上,如文中代碼所示。無論哪種方法,都可以將具有強(qiáng)大工程計算能力的Matlab接入VC++程序,從而快捷方面地解決許多數(shù)值計算和圖形輸出問題。
關(guān)鍵詞:Matlab外部接口;混合編程;VC++;COM;自動化
中圖分類號:TP311文獻(xiàn)標(biāo)識碼:A文章編號:1009-3044(2008)36-2792-04
Program with Matlab COM Server in VC++
SHEN Zhi-juan1, YANG Hei-lai1, WANG Shu-fen2, ZHANG Wei-min3
(1.Mechanical Engineering and Automation Department, Beijing University of Aeronautics and Astronautics, Beijing 100083, China; 2.Computing Center, Hebei University of Science and Technology, Tangshan 063009, China; 3.Beingjing Aeronautic Manufacturing Reseach Center, Beijing 100024, China)
Abstract: Programs written in VC++ sometimes need to do calculation intensive jobs, which can be solved swiftly if some Matlab function can be called inside the program. Matlab provides two methods to achieve this: Engine Library and Automation Server, both of which are based on COM. Engine Library made the custom COM interface IEngine transparent to the Client through its export functions while MFC class COleDispatchDriver encapsulates the detail of interacton with the COM Automation interface IDispatch. The given code in this article shows differences of the two method come mainly from the parameter passing and result retrieving process. With either method, Matlab can be linked into the program and thus make the calculation and figure output tasks be achieved faster and easier.
Key words: matlab external interface; hybrid programming; VC++; COM; automation
1 引言
Matlab提供的編程語言——M語言——是以方便數(shù)值計算編程為目的設(shè)計的,所以其基本數(shù)據(jù)結(jié)構(gòu)就是矩陣,且其變量的具體類型和大小可在執(zhí)行時動態(tài)確定,這對編寫數(shù)值計算程序提供了很大的便利。另外,由于M語言可直接調(diào)用Matlab提供的各種數(shù)學(xué)和面向工程應(yīng)用的工具箱函數(shù),所以使用M語言還可以迅速地解決許多計算量很大的工程問題。但另一方面,由于M語言是一種解釋型編程語言,所以它的執(zhí)行效率較低,另外其在開發(fā)應(yīng)用程序界面和對外圍設(shè)備的控制能力上也較差。另一方面,其他的編程環(huán)境,如VC++,則在這些方面提供了很好的支持,但如用其進(jìn)行數(shù)值計算或解決工程問題又會非常不便。故如果對軟件界面、外圍設(shè)備控制、數(shù)值計算三方面都有較高的要求,一個理想的方案是在像VC++這樣的編程環(huán)境中實現(xiàn)界面及外設(shè)控制功能,并在其中調(diào)用Matlab環(huán)境中的模塊,如其工具箱函數(shù),作為計算工具。
在VC++程序中調(diào)用Matlab有兩種方法:一是通過Matlab提供的引擎庫(Engine Library),它的本質(zhì)是一個擁有定制接口IEngine的COM服務(wù)器;二是通過Matlab提供的COM Automation Server。本文將首先介紹COM和Automation技術(shù)的原理和應(yīng)用方法,之后介紹這兩個接口,并隨后給出在C++中使用兩者的具體方法。
2COM與Automation
2.1 COM概述
組件對象模型(Component Object Model,COM) 由Microsoft于1993年創(chuàng)建,它規(guī)定了dll、exe等應(yīng)用模塊之間進(jìn)行交互的步驟和方法。在COM之前,一個應(yīng)用程序(Client)如果要使用dll文件(Server)輸出的功能就要使用Win32 API函數(shù)LoadLibrary,GetProcAddress,要使用exe文件(Server)輸出的功能除了需使用相關(guān)的Win32 API函數(shù)外,還要使用基于Windows消息機(jī)制的DDE協(xié)議。COM則是Microsoft新推出的這些方法的換代品,它解決了不少在使用老方法時產(chǎn)生的問題,如他們本質(zhì)上是面向過程的技術(shù),使用起來比較零散復(fù)雜,且對系統(tǒng)來說,他們是無法管理的。COM則提供了一個面向?qū)ο蟮腟erver功能管理及Client/Server的通訊方法。但這需要應(yīng)用編程者的配合,即COM不僅是Windows操作系統(tǒng)新增的一系列數(shù)據(jù)結(jié)構(gòu)和函數(shù),而且是一系列應(yīng)用與系統(tǒng)交互方式的定義。
在COM中,Server模塊(dll或exe)要向系統(tǒng)注冊其輸出的COM類,即在注冊表中保存COM類的GUID(Global Unique Identifier),Server模塊完整的路徑及文件名等相關(guān)信息。一個Server模塊可擁有多個COM類。于是,Client只要向系統(tǒng)提供其所需COM類的GUID系統(tǒng)就能找到包含這個類的模塊并加載它。在注冊COM類的同時,Server還要注冊每個COM類所包含接口(interface)的GUID。從功能上說,一個接口就是Server輸出的一個功能單元,它由一組功能上密切相關(guān)的函數(shù)指針構(gòu)成;而用C++語言表達(dá),COM接口就是一個僅包含虛函數(shù)的結(jié)構(gòu)體,它本質(zhì)上是一個包含一個函數(shù)指針數(shù)組的內(nèi)存結(jié)構(gòu),這一內(nèi)存結(jié)構(gòu)是由Microsoft定義的標(biāo)準(zhǔn)。Client則是通過GUID向系統(tǒng)提交自己的請求,通過獲得由Server提供的接口指針來調(diào)用Server的輸出函數(shù)[1]。
事實上,接口是COM技術(shù)的核心要素,其最基礎(chǔ)的接口是Microsoft定義的標(biāo)準(zhǔn)接口IUnknown,它包含QueryInterface、AddRef和Release三個函數(shù),這些函數(shù)是Client與Server通信的基礎(chǔ)。而所有其他的接口均需繼承自IUnknown。這也是C++多態(tài)技術(shù)的一個應(yīng)用。除了IUnknown,Microsoft還定義了許多其它接口,這些接口事實上是許多編程技術(shù)的底層基礎(chǔ),如IDispatch接口就是Automation技術(shù)的根本核心。同時需要說明的是,對其制定的大部分接口而言,Microsoft只是定義了接口的數(shù)據(jù)結(jié)構(gòu),接口函數(shù)功能的實現(xiàn)要由Server開發(fā)者完成。
可見,COM是一個Client、Server和操作系統(tǒng)協(xié)力合作。從操作系統(tǒng)的角度看COM是一個記錄COM類的方式和一組用于Client和Server通信的API函數(shù)。對Client來說COM是利用API向系統(tǒng)發(fā)出請求并利用接口指針使用COM類的功能。對Server來說則是利用API注冊自己,實現(xiàn)所有接口函數(shù),這包括由Microsoft定義的接口函數(shù),如Server必須實現(xiàn)IUnknown接口的QueryInterface函數(shù),其作用是為Client提供其所要求的所有接口指針。
2.2 Automation概述
由于接口本質(zhì)上是一個包含一個函數(shù)指針數(shù)組的內(nèi)存結(jié)構(gòu),這一內(nèi)存結(jié)構(gòu)是由Microsoft定義的標(biāo)準(zhǔn),而所有支持COM的編譯系統(tǒng)和語言對接口的處理都符合這一標(biāo)準(zhǔn),所以通過COM,就可實現(xiàn)不同編譯器、不同語言編寫的應(yīng)用順利通信。但是仍有一個問題,就是想Visual Basic這樣的解釋性語言無法學(xué)習(xí)C++定義的接口。舉例來說,如果一個C或C++程序要使用一個組件定義的接口IMath,它就需要聲明接口的結(jié)構(gòu),如:
struct IMath : IUnknown
{
virtual void Add(int a, int b) = 0;
virtual void Multiply(int a, int b) = 0;
virtual void Square(int a) = 0;
};
這樣,在獲得了接口指針,不妨叫pIMath,之后,就可以用“pIMath->Add(a, b);”這樣的語句來使用Server的功能。但Visual Basic卻不支持這樣的語法,即它的解釋程序無法以函數(shù)指針的形式學(xué)習(xí)新的接口定義。考慮到接口將會不斷增長的事實,讓Visual Basic把對所有接口的支持固化在解釋程序中又是不可能的。所以,為了解決這類問題,Microsoft定義了IDispatch接口,其思想是,像VB這類語言的解釋程序只需要支持IDispatch接口,就可以在執(zhí)行時通過Server獲得其需要函數(shù)的相關(guān)信息,從而完成調(diào)用。
整個接口的機(jī)制是Server要為輸出的函數(shù)分配一個數(shù)字ID和一個相對應(yīng)的標(biāo)識字符串。Client只知道標(biāo)識字符串,為了調(diào)用相應(yīng)的函數(shù),Client在獲取IDispatch接口指針后,先調(diào)用其函數(shù)GetIDsOfNames()得到和標(biāo)識字符串相對應(yīng)的數(shù)字ID,再調(diào)用IDispatch的函數(shù)Invoke(),該函數(shù)專門負(fù)責(zé)根據(jù)輸入的數(shù)字ID找到Client要求的函數(shù),并調(diào)用它。Invoke()在調(diào)用函數(shù)時需要傳遞參數(shù),而參數(shù)格式是無法由解釋程序預(yù)知的,所以Invoke()使用了一種特殊的數(shù)據(jù)類型VARIANT來解決這一問題。VARIANT是一個結(jié)構(gòu)體,它可以表示任何類型的數(shù)據(jù)。VARIANT主要由兩部分組成,一是標(biāo)識數(shù)據(jù)類型的vt,二是存儲實際數(shù)據(jù)的union結(jié)構(gòu)。函數(shù)的輸入?yún)?shù)就被存儲在一個VARIANT數(shù)組里被傳遞給Invoke。當(dāng)然,之后解析各個變量并傳遞給輸出函數(shù)的任務(wù)就交由Invoke()完成了。函數(shù)的返回值同樣是以VARIANT形式回傳的。這樣,就解決了高級語言使用COM組件的問題。
所以,自動化(Automation)是建立在COM基礎(chǔ)上的。一個自動化服務(wù)器實際上就是一個實現(xiàn)了IDisPatch接口的COM組件。而一個自動化控制器則是一個通過IDisPatch接口同自動化服務(wù)器進(jìn)行通信的COM客戶。
3 Matlab 提供的COM Server接口
由于出現(xiàn)了Automation技術(shù),訪問COM組件就有了兩種方式:①通過普通的自定義的COM接口訪問組件;②通過調(diào)度接口IDisPatch訪問組件;如果一個組件同時實現(xiàn)了這兩種接口,則稱為雙重接口,那么客戶就可自由的選擇其中的一種方式訪問組件的功能。Matlab就提供了這兩種接口。
3.1 IEngine 接口
IEngine是Matlab提供的定制接口,但通常在程序中不會直接使用它,而是使用Matlab提供的引擎庫(Engine Library)函數(shù)。事實上,COM Server及其接口IEngine可看作是引擎庫在Windows平臺上的實現(xiàn)手段。當(dāng)然,也可以把引擎庫看作Matlab為方便IEngine的使用而提供的函數(shù)庫。在使用引擎庫時,完全不需要COM的知識,只需要把它看成一個非標(biāo)準(zhǔn)的C語言靜態(tài)函數(shù)庫即可。
引擎庫共輸出了9個函數(shù),通過它們可以方便的開始和結(jié)束與Matlab Server的對話,設(shè)置命令窗口(缺省狀態(tài)是顯示命令窗口),與Matlab的工作空間交換變量,執(zhí)行任何可在Matlab命令行執(zhí)行的命令。使用引擎庫和直接在Matlab命令行輸入的方法是極為類似的。
3.2 IDispatch 接口
IDispatch是Matlab為Visual Basic,C#等語言提供的接口,但在C++程序中也可以使用,只不過,這需要進(jìn)行較多和COM相關(guān)的編程。如果使用VC++提供的MFC類COleDispatchDriver來使用Matlab 自動化服務(wù)器,則可以大大簡化這一過程。這些內(nèi)容后面會詳細(xì)討論。
通過IDispatch接口獲得的函數(shù)功能和引擎庫類似,但功能更豐富更靈活些。例如,如果要調(diào)用統(tǒng)計工具箱的方差計算函數(shù)求某一雙精度數(shù)組的方差,使用引擎庫函數(shù)的流程必須是先通過engPutVariable()將數(shù)組存入Matlab,比如起名叫“a”;再用engEvalString()命令執(zhí)行一條語句“b = std ( a )”,這在執(zhí)行命令的同時又在Matlab里建立了變量“b”;最后通過engGetVariable()將變量\"b\"取回。Matlab的自動化服務(wù)器則除了擁有支持上面流程的函數(shù)外,還提供了另一種可能性,即只需調(diào)用函數(shù)Feval()就可以了,因為Feval()可同時接受函數(shù)名,參數(shù)作為輸入,并將Matlab的計算結(jié)果輸出。
4 Matlab COM 接口的VC++調(diào)用
在VC++中使用Server的關(guān)鍵問題有兩類,一類是如何啟動和釋放Server,另一類是如何與Matlab進(jìn)行數(shù)據(jù)交互。下面將分別針對這兩方面,以一個具體的問題——方差的計算,待計算的數(shù)據(jù)保存在變量double Array[20]中——為例,說明調(diào)用Matlab 兩種COM接口的具體操作。下面的代碼均經(jīng)過實際測試使用,其環(huán)境為WindowXP,Visual C++ 6.0和Matlab 7.4.0。
4.1 Server的啟動和釋放
4.1.1 引擎庫
使用引擎庫在創(chuàng)建工程時不需要特別設(shè)置,但之后第一要在源文件中包含engine.h,它其中聲明了引擎庫函數(shù)的原型。engine.h中還包含頭文件matrix.h,其最主要的內(nèi)容是定義了數(shù)據(jù)類型mxArray及對它進(jìn)行操作的函數(shù)原型。mxArray類型是許多引擎庫函數(shù)的參數(shù)類型,它是C++程序和Matlab進(jìn)行數(shù)據(jù)交互的工具。而matrix.h中又包含tmwtypes.h,這一頭文件中也定義了一些數(shù)據(jù)類型。三個頭文件的路徑均為Matlab安裝目錄下的extern\\include。將engine.h包含入源文件有三種方法,其一是在源文件中包含完整的路徑和文件名,如“#include \"D:\\matlab\\extern\\include\\engine.h”;其二是僅包含文件名,但把上述三個頭文件全部拷貝到工程目錄中;其三也是只包含文件名,但要在VC++ 6.0的Tool菜單的Option命令對話框的Directory選項卡中,把頭文件所在路徑添加到include搜索路徑中。這之后,還要為工程加入兩個庫文件——libeng.lib和libmx.lib,其中l(wèi)ibeng.lib實現(xiàn)了引擎庫函數(shù),而libmx.lib則實現(xiàn)了操作mxArray需要的各個函數(shù)。它們路徑均為Matlab安裝目錄下的extern\\lib\\win32\\microsoft。具體操作為右鍵單擊VC++ 6.0左側(cè)Workspace里FileView的工程文件,在彈出菜單中選擇“Add File to Project”,之后在彈出的對話框中轉(zhuǎn)到上述目錄后將文件過濾設(shè)為“Library Files”就可以選擇所要的庫文件了。為方便代碼的移植,則可以先將上述文件復(fù)制到工程所在目錄再進(jìn)行添加。
在做好這些準(zhǔn)備工作后,就可以正式使用引擎庫了。引擎庫啟動可使用如下代碼:
Engine *ep;
if (!(ep = engOpen(\"\\0\"))) {
fprintf(stderr, \"\Can't start MATLAB engine\\");
return;
}
結(jié)束與Matlab的對話可使用:
engClose(ep);
4.1.2 自動化服務(wù)器
在C++程序中直接用Win32 API操作自動化服務(wù)器是比較繁瑣的,所以筆者對自動化服務(wù)器的操作是通過MFC的COleDispatchDriver類完成的。這就要求在創(chuàng)建工程時要加入對MFC類的支持。之后,啟動Class Wizard,單擊其新建類按鈕中的“從類庫(type library)”命令從而打開“從類庫導(dǎo)入”為標(biāo)題的對話框。在對話框中轉(zhuǎn)入Matlab安裝目錄下的bin\\win32文件夾,選擇其中的mlapp.tlb文件就可以生成繼承自COleDispatchDriver類的DIMApp類。
完成上述工作,就可用下面代碼啟動Matlab自動化服務(wù)器:
DIMLApp MatlabAuto;
COleException *e = new COleException;
try{
if (!MatlabAuto. CreateDispatch(\"matlab.application\", e))
throw(e);
}
// Exception handling code.
其中,函數(shù)CreateDispatch()是從基類COleDispatchDriver繼承的。結(jié)束自動化服務(wù)器可用如下代碼:
MatlabAuto.ReleaseDispatch();
函數(shù)ReleaseDispatch()也是從基類COleDispatchDriver繼承的。
4.2 數(shù)據(jù)交換
無論是采用哪種調(diào)用方式,Client都需要一種中間數(shù)據(jù)類型來與Matlab交互數(shù)據(jù)。當(dāng)要把參數(shù)傳遞給Matlab時,Client需要先把自己的數(shù)據(jù)轉(zhuǎn)換成中間類型;當(dāng)從Matlab獲取結(jié)果時,Client要從中間類型中提取原始數(shù)據(jù)。對引擎庫來說,這種中間類型是mxArray;對自動化服務(wù)器來說,這種中間類型是VARIANT。下面通過代碼來說明這兩種方式。
4.2.1 引擎庫
使用引擎庫的代碼如下:
double standard_deviation = 0;
// Create a variable for our data
mxArray *a = mxCreateDoubleMatrix(1, 20, mxREAL);
mxArray *b = NULL;
memcpy((void *)mxGetPr(a), (void *)Array, sizeof(Array));
// Place the variable a into the MATLAB //workspace
engPutVariable(ep, \"a\", a);
// Evaluate function
engEvalString(ep, \"b =std(a);\");
// Get Result
b = engGetVariable(ep, \"b\");
standard_deviation = mxGetPr(b)[0];
cout << \"The standard deviation is \" << standard_deviation << endl;
// Release memory
mxDestroyArray(a);
a= NULL;
mxDestroyArray(b);
b= NULL;
4.2.2 自動化服務(wù)器
使用自動化服務(wù)器的代碼如下:
double standard_deviation = 0;
VARIANT a;// 用于向Matlab傳遞參數(shù)
VariantInit(a);
VARIANT b;// 用于從Matlab獲得結(jié)果
VariantInit(b);
VARIANT c;// 用于從b中得到所需結(jié)果
VariantInit(c);
// 向a內(nèi)寫入數(shù)據(jù)
a.vt = VT_ARRAY | VT_R8;
SAFEARRAY * psa;
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
rgsabound[0].cElements = 20;
psa = SafeArrayCreate(VT_R8, 1, rgsabound);
a.parray = psa;
for(unsigned int i=0; i< rgsabound[0].cElements; i++)
SafeArrayPutElement(a.parray, (long*)i, (Array[i]));
//調(diào)用Matlab,函數(shù)Feval()為由Class Wizard //自動生成的Feval()的重載,原函數(shù)有過多//參數(shù),多余參數(shù)均被賦予同一VT_EMPTY //類的VARIANT變量
MatlabAuto.Feval(\"std\", 1, b, a);
// 此時b.vt的值為8204(0x200C),即VT_A//RRAY(0x2000)與VT_VARIANT(0x000C)//按位相或的結(jié)果,所以獲取結(jié)果時需要變//量c
intj = 0;
SafeArrayGetElement(b.parray, (long*)j, c);
standard_deviation = c.dblVal;
cout << \"The standard deviation is \" << standard_deviation << endl;
//釋放變量a、b的空間
5 總結(jié)
COM是Microsoft公司提供的提高應(yīng)用互操作性能的接口,利用Matlab提供的兩個COM接口——IEngine和IDispatch,在Matlab引擎庫或MFC類COleDispatchDriver的幫助下,VC++程序可很輕松的在程序中使用Matlab豐富的函數(shù)和圖形功能[2],從而加快程序的開發(fā)。但這需要Matlab環(huán)境的運(yùn)行。除了Matlab engine library(引擎庫)和COM Automation Server,Matlab對COM的支持還有MATLAB Builder for .NET。它可以根據(jù)編程人員的要求將一組M文件編譯成可供其它支持COM語言調(diào)用的COM組件,VC++程序使用這些組件就可以脫離Matlab[3],但不是所有的Matlab函數(shù)都可以被編譯。
參考文獻(xiàn):
[1] Shepherd G,Wingo S.MFC internals:inside the microsoft Foundation Class architecture[M].Reading,Mass:Addison-Ewsley,1996:435-497.
[2] 趙啟蒙,高美娟.通過面向?qū)ο缶幊陶{(diào)用MATLAB繪圖的實現(xiàn)[J].大慶石油學(xué)院學(xué)報,2004,28(1):80-82.
[3] 姚光強(qiáng),陳立平.基于COM技術(shù)的C#與Matlab混合編程[J].計算機(jī)工程,2008,34(14):87-89.