1. 程式人生 > >LabWindows/CVI與Matlab混合程式設計的一種實現方法

LabWindows/CVI與Matlab混合程式設計的一種實現方法

最近一段時間都在學習基於LabWindows/CVI(後文簡稱CVI)開發模擬軟體,由於已有一個不太穩健,但基本框架較為齊備的工程。所以我的工作主要是在這個已有的工程上進行debug、整理修改、開發新功能,從5月開始已經持續了接近三個月。

在之前的開發過程中我就留意到了一點,當在某個具有表格控制元件與圖表控制元件的子介面傳輸資料(資料個數約10^6左右)結束後,退出介面時有5-8s左右的卡頓,期間無法進行任何操作。這讓我開始對基於CVI開發的這個軟體其執行速度有所留意,畢竟誰都希望使用的軟體流暢順滑。

而在實現某個功能完畢,進行模組測試時,發現由於涉及了兩重迴圈,且內部需要進行復數運算(基本都是使用自定義的函式實現的,如求e的複數次方),程式執行速度極慢,10x245000的迴圈,內部基本為複數運算,在CVI中使用C語言耗費了20s才完成,而按相同的思路(也使用兩重迴圈)完成的程式,使用Matlab僅需要2s,在使用行向量直接運算進行優化後,時間縮短到0.2s。因此我產生了在CVI中呼叫Matlab以優化複雜運算速度的需求與想法。

有了這個想法後,我開始查閱相關的書籍和論文,然後發現雖然CVI作為一款極you其dian成guo熟shi的軟體,但相關的資料並不完整,尤其很難面面俱到某個細分小方向的實現細節。

書籍中較為全面介紹與Matlab混合程式設計的,是劉君華主編的《基於LabWindows/CVI的虛擬儀器設計》。其4.2節即為《LabWindows/Cvi與Matlab的介面原理與方法》,在我自行實現混合程式設計的前期,主要參考的就是這個章節的內容。

經歷了這個開發過程後,我發現相比書籍而言更有價值的是很多文獻資料裡都提到而沒有進行深入分析的示範案例(位於安裝目錄\CVI2012\samples\activex\matlab下的工程demoforMATLABinterface)。個人建議直接閱讀這個工程中的主檔案demoforMATLABinterface.c,研究各函式的使用方法。

當然不只是Matlab的混合程式設計,Sample資料夾下的例子都值得細細品味學習。

下面我將從ActiveX服務配置、常用函式及開發小結、測試結果三個方面進行總結。

一、ActiveX服務的配置

由於一般而言sample中的例子由於未在本機上正常連結CVI與Matlab的介面,故無法直接執行。並且報“沒有註冊類”錯誤。這裡給出配置本機適用的ActiveX服務並建立一個可執行版本的demoMATLABinterface工程的全過程。目前暫時我還沒有發現有相關的教程,各位初學者可以參考一下。

軟體開發環境:Matlab 2009a(32-bit),LabWindows/CVI 2012 SP1

系統環境:Windows 7 64位專業版

硬體環境:處理器i7-4790主頻3.6GHz,記憶體16G。

根據多篇論文歸納可知,CVI和Matlab的混合程式設計有引擎、ActiveX服務、編譯器三種主流實現方法,各有優劣。本文主要針對第二種方法進行講解。

需要著重強調。由於CVI是32位軟體(至少2012以及2017均為32-bit),所以使用本方法進行混合程式設計實現時,需使用32位版本的Matlab,如2009a,否則CVI與Matlab的混合程式設計無法正常執行

首先建立空工程,依次選擇“Tool——Create ActiveX Controller ”,彈出名為“ActiveX Controller Wizard”的引導,首先是有點基本介紹的歡迎頁,點next跳過,然後CVI自動索引所有可以使用的ActiveX Server,直接往下拉,選中”Matlab Application(Version 8.5) Type Library“。

:CVI軟體需在matlab安裝完畢後再進行安裝,否則CVI無法找到Matlab。若此處出現“the tyoe library is not intended for use on win32”一般原因即為Matlab版本並非32位,由於CVI自身就是32位軟體。

進入到下一步後,輸入工具字首名(可以簡單理解為所生成的服務控制元件名稱),並選擇.fp檔案的儲存路徑。

進入下一步,點選Advanced Option,在彈出視窗中點選“check all”,然後點選“OK”即可,再下一步即完成ActiveX服務控制元件的生成,空工程中包含了一個名為MLApp.fp的工具檔案,而在資料夾中,出現了一個名為msvc的資料夾以及五個名稱均為MLApp的檔案,副檔名分別為.c .fp .h .obj .sub。

新建一個名為demoMatlab的資料夾,將samples\activex\matlab資料夾中,名為demoforMATLABinterface的.c .cws .h .prj .uir五個檔案,以及chirp.m、cvispiral.m、meshgauss.m三個.m檔案,名為matlabutil的.c與.h兩個檔案拷貝到demoMatlab中,此外,將上段段尾提到的四個名為MLApp的檔案(除MLApp.c)也拷貝到demoMatlab中。

在CVI中開啟prj,由於demoMATLABinterface工程下包含的是名為matlabsrvr7的ActiveX服務,故將matlabsrvr7的.c .h .fp檔案全部移除,通過“Edit——Add Files To Project”將四個名為MLApp的檔案(除MLApp.c)新增進工程,再進行編譯,此時demoforMATLABinterface即可正常執行,正常呼叫matlab完成各種功能,如下所示。

功能實現

工程所包含的檔案如下圖所示。

這裡寫圖片描述

小結

通過ActiveX服務呼叫Matlab最重要的地方在於成功建立CVI與Matlab的介面,一般需要在本機上生成對本機有效的服務控制元件,否則由於不同電腦上CVI以及Matlab版本不同,位數不同,一般情況下均無法直接執行。

而在正常生成服務控制元件後,將 .fp .h .obj .sub四個檔案拷貝至各目標工程所在的資料夾下,併成功新增到工程,即可完成對Matlab的呼叫。

二、常用函式的講解與混合程式設計開發小結

上文講解了ActiveX服務的配置。本節主要講解一下demo中涉及到的一些函式用法注意事項,最後根據我開發相關功能的經歷,進行一個小結。

demo的面板如下所示

這裡寫圖片描述

由圖可一目瞭然,demo通過例子實現了載入Matlab、退出Matlab、退出演示demo、執行Matlab命令、改變Matlab指令碼視窗大小、進行FFT運算、矩陣轉置、解方程並繪圖、傳送序列到Matlab、從Matlab中接收序列、執行M檔案這幾種功能。

下面對其中使用到的函式進行對應的介紹:

1.載入Matlab:MLApp_NewDIMLApp。

由於CVI有一定年頭了,很多書籍介紹混合程式設計時使用的都是較早的版本,載入函式僅有兩個引數,故執行在新版本CVI軟體中時可能出現“過多輸入”的錯誤,此時需要使用新版的MLApp_NewDIMLApp函式。

該函式在ActiveX服務配置後生成的最底層MLApp.c檔案中被定義(再次說明了在本機上第一次使用ActiveX服務時需要對其進行配置的重要性),其在標頭檔案中的宣告和使用如下所示。

//宣告
HRESULT CVIFUNC MLApp_NewDIMLApp (const char *server, int supportMultithreading,
                                  LCID locale, int reserved,
                                  CAObjHandle *objectHandle);
//使用
stat = MLApp_NewDIMLApp(NULL,1,LOCALE_NEUTRAL,0,&hMatlab);  //載入Matlab
if(result != SUCCESS)
{
    MessagePopup("警告","Matlab加載出錯!");
    return 0;
}

函式末尾的hMatlab為全域性的Matlab控制代碼,需提前定義(尤其當你想在其他地方也呼叫Matlab時)

不僅是該函式,大多數函式均會返回一個狀態變數,故一般使用上面這種形式進行程式設計,以對軟體的執行進行更好的控制,當載入錯誤時,能有效報錯,防止軟體無故崩潰

2.改變視窗大小:MinMaxMatlab

該函式在maltbutil.c檔案中被定義,在最底層的MLApp之上進行進一步封裝得到,0表示最小化視窗,1表示最大化視窗。

int MinMaxMatlab(CAObjHandle hMatlab, int minmaxFlag)    
MinMaxMatlab(hMatlab,0);

3.退出Matlab指令碼:MLApp_DIMLAppQuit

該函式在MLApp.c檔案中被定義,也可以對其進行進一步的封裝。

HRESULT CVIFUNC MLApp_DIMLAppQuit (CAObjHandle objectHandle,
                                   ERRORINFO *errorInfo);
stat = MLApp_DIMLAppQuit (*hMatlab, NULL);    

4.執行Matlab命令:RunMatlabCommand

該函式在maltbutil.c檔案中被定義,僅可執行Matlab內部函式相關的命令,無法執行自定義函式命令,否則會報“undefined function or method”錯誤。

int RunMatlabCommand(CAObjHandle hMatlab, char *command)       
result = RunMatlabCommand(hMatlab,"mMatrix=inv(cMatrix);");  
if (result != SUCCESS)
{
    MessagePopup ("ERROR", "Error in sending command to MATLAB");
    return 0;
}

涉及的變數mMatrix與cMatrix無需在CVI中進行宣告。

5.傳送/接收字串:SendString/GetString

該函式在maltbutil.c檔案中被定義,由於Matlab不支援BSTRs,故傳送接收過程中需要使用Fmt函式進行字串與雙精度浮點數的轉換。

Fmt函式示例如下:

Fmt(CVIString,"%s<Hello MATLAB");
Fmt(command,"%s<%s=transpose(%s)",matStringName,matStringName);  
Fmt(command,"%s<%s=char(%s)",matStringName,matStringName); 

傳送接收函式如下所示

int SendString(CAObjHandle hMatlab, char *matStringName, char *CVIString)
int GetString(CAObjHandle hMatlab, char *matStringName, char **cString) 

result = SendString(hMatlab, "matStr", CVIString);
result = GetString(hMatlab,"matStr",&cStr); 

demo給出的例子是,將“Hello MATLAB”字串轉換為雙精度浮點數,變數名為CVIString,傳輸到Matlab中,在Matlab中的名稱為matStr,而後從Matlab中獲得這個字串,變數名為cStr。

在CVI程式中,matStr無需宣告,然而CVIString與cStr是需要宣告的,形式是一維陣列。

6.傳送/接收矩陣

同5,demo矩陣的傳輸演示是將一個2x2的矩陣傳入Matlab,進行轉置運算後接收回來。

int SendMatrix(CAObjHandle hMatlab, char *matlabName, double *matrixReal, 
                    double *matrixImag, size_t dim1, size_t dim2)
int GetMatrix(CAObjHandle hMatlab, char *matlabName, double **matrixReal, 
                    double **matrixImag, size_t *dim1, size_t *dim2)

result = SendMatrix(hMatlab,"cMatrix",(double*)matrix_r,(double*)matrix_i,2,2); 
result = GetMatrix(hMatlab,"mMatrix",&matrixReal,&matrixImag,&dim1,&dim2);

傳輸變數包括Matlab控制代碼,在Matlab中的名稱,矩陣實部數值(也是矩陣),矩陣虛部數值(也是矩陣),行數,列數。其中沒有虛部或者實部時可以用NULL表示。

同4,mMatrix與cMatrix無需在CVI中進行宣告。但傳送矩陣的實部數值matrix_r與虛部數值matrix_i需要在CVI中宣告,形式是二維陣列。接收矩陣的實部數值matrixReal和虛部數值matrixImag為初始化為NULL的指標,也需要提前宣告,行數列數也需要提前確定。

7.執行M檔案指令碼:RunMatlabScript

該函式在matlabutil中定義。

int RunMatlabScript(CAObjHandle hMatlab, char *mFilePath)    
result = RunMatlabScript(hMatlab,"G:\\sig_gen\\demo_0728\\create_chaffsig.m");

在設定路徑時,Windows下的分隔符需使用\\以強制轉義,否則會使得路徑解析出錯,另外由於函式中對路徑長度以及檔名長度最大值僅設為256,故運M檔案指令碼不可設定過深,以防止無法解析。

另外還可利用FileSelectPopupEx等系統相關的函式,實現選擇m檔案執行的操作,這裡不再贅述。

呼叫自定義函式

若要呼叫自定義函式,建議採用以下兩種方式:

1.不足5行的函式,直接使用RunMatlabCommand,拆分為多個語句進行代替。

2.程式較長,但不存在額外呼叫自定義函式的情況(額外呼叫自定義函式指:例如本打算呼叫自定義函式A,A函式中又使用了自定義函式B),則在Matlab中充分測試A函式後,去掉首行的宣告(即function [輸出變數] = 函式名(輸入變數))以及末尾的”end”,將所需要的輸入變數提前構成陣列,直接傳入Matlab,再在Matlab中還原為各引數變數。如下所示:

在CVI中將變數構成陣列

m_out_data[0] = fc;
m_out_data[1] = fs;                                                     
m_out_data[2] = (double)m;                                              
m_out_data[3] = TotalTime*(1e-6);

在M檔案頭部還原

f0 = m_out_data(1);     
fs = m_out_data(2);     
m = m_out_data(3);     
T = m_out_data(4);     

完成運算後再將所需要的資料匯出,由於一般呼叫Matlab的原因是C語言計算訊號的各種變換較慢,所以所需資料一般也為行向量,可以用1xN的矩陣表示。

小結

呼叫Matlab時,需要在CVI程式碼中首先載入Matlab,其次具有一定的技巧地使用自定義函式,最後呼叫完成後需關閉Matlab避免拖累系統。

三、測試結果

在同一計算機上進行相同計算任務,

按上文的思路,CVI使用原方法完成50*245000個迴圈,耗時100s左右。

Matlab環境下執行(也是兩重迴圈),耗時60s

Matlab改為單迴圈時,耗時0.0648s

CVI與Matlab進行混合程式設計後,耗時不足2s。

由上可知,混合程式設計成功地優化了功能,體現了混合程式設計的優越性。

待改進之處

後續打算對另外兩種呼叫方法,以及通過修改Matlab註冊碼使用ActiveX服務三個方面進行研究,並儘快完善本文。

參考資料

劉君華等,《基於LabWindows/CVI的虛擬儀器設計》,電子工業出版社

LabWindows/CVI 自帶的Sample


本文所述工程已上傳到Github,點我跳轉本文例項程式碼

如有疑問,歡迎留言。