摘要:針對(duì)傳統(tǒng)的基于COM的Matlab與Delphi混合編程技術(shù),界面容易出現(xiàn)凍結(jié)現(xiàn)象,詳細(xì)討論了其出現(xiàn)的原因,提出了一種基于COM STA線程模型的改進(jìn)的混合編程方法,并以一信號(hào)包絡(luò)提取為例給出了改進(jìn)方法的具體實(shí)現(xiàn)。
關(guān)鍵詞:Delphi;Matlab;混合編程;COM;單線程套間
中圖分類號(hào):TP311 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1009-3044(2013)12-2912-04
Delphi是一種基于Object Pascal語言的快速可視化開發(fā)工具,對(duì)于程序開發(fā)人員來講,使用Delphi開發(fā)應(yīng)用軟件,將會(huì)極大地提高編程效率,但若直接利用Delphi開發(fā)一些復(fù)雜算法特別是科學(xué)計(jì)算,其效率并不會(huì)很高。Matlab作為一種高性能數(shù)值計(jì)算軟件,提供了強(qiáng)大的數(shù)值分析、矩陣運(yùn)算和圖形顯示等功能,被廣泛應(yīng)用于工程計(jì)算、數(shù)值分析、系統(tǒng)仿真等領(lǐng)域,但由于Matlab是一種解釋型編程語言,執(zhí)行效率低,程序界面開發(fā)能力差。如果將Delphi與Matlab相結(jié)合,將會(huì)克服彼此缺點(diǎn),高效地完成編程任務(wù)。
Delphi和Matlab混合編程的常用方法有多種[1-3],其中基于COM組件技術(shù)的編程方法得到了廣泛關(guān)注[4-5]。這種方法是利用Matlab提供的COM Builder工具將編寫好的M文件編譯生成DLL庫,之后供Delphi調(diào)用,該方法可以脫離Matlab環(huán)境,因而極大的方便了應(yīng)用程序的發(fā)布。但其也有缺點(diǎn),其中之一是在應(yīng)用程序中調(diào)用DLL庫時(shí)容易出現(xiàn)用戶界面凍結(jié),使用戶以為程序失去響應(yīng)。為此,該文對(duì)此進(jìn)行研究,提出一種基于COM STA線程模型的改進(jìn)方法。
1 相關(guān)技術(shù)
1.1 COM組件
COM即組件對(duì)象模型(Componet Object Model, COM),是一種以組件為發(fā)布單元的對(duì)象模型,該模型使各應(yīng)用程序組件可以用一種統(tǒng)一的方式進(jìn)行通訊。
在COM標(biāo)準(zhǔn)中,COM對(duì)象被完美地封裝起來,客戶無法訪問對(duì)象的實(shí)現(xiàn)細(xì)節(jié),提供給用戶的唯一訪問途徑是通過COM接口來實(shí)現(xiàn)。COM接口有兩方面的含義:其一,它是一組可供調(diào)用的函數(shù),客戶可以讓該對(duì)象做某些事情;其二,接口是組件及其客戶程序之間的協(xié)議。使用COM編程實(shí)現(xiàn)了與編程語言無關(guān)的軟件重用。
1.2 基于COM組件實(shí)現(xiàn)Delphi與Matlab混合編程
傳統(tǒng)的基于COM組件實(shí)現(xiàn)的Delphi與Matlab混合編程[4-5],其第一步是編寫M文件,之后在Matlab環(huán)境下輸入命令comtool,啟動(dòng)Matlab COM Builder,設(shè)置好相關(guān)參數(shù)并添加好M文件后,就可以編譯生成相應(yīng)的COM組件,并將該COM組件注冊(cè)到Windows的注冊(cè)表中,最后在Delphi中直接調(diào)用該COM組件,實(shí)現(xiàn)混合編程。但這種方法基于的是單線程技術(shù),所開發(fā)的應(yīng)用程序在運(yùn)行過程中容易出現(xiàn)界面凍結(jié)。原因分析如下:
假設(shè)有一個(gè)利用Matlab實(shí)現(xiàn)的COM服務(wù)器,MatrixServer,該服務(wù)器提供有一種方法Mul,功能是實(shí)現(xiàn)矩陣相乘運(yùn)算。
IMatrix = interface
procedure Mul;
end;
假設(shè)該服務(wù)器同通常的應(yīng)用程序一樣,采用的是單線程技術(shù)。考慮3個(gè)客戶(C1,C2,C3),每一客戶都創(chuàng)建一個(gè)Matrix實(shí)例MatrixServer。這3個(gè)客戶都si9MTIjjnM8zi+NTGBUJhg==同時(shí)調(diào)用IMatrix.Mul(假定第一個(gè)是C1,隨后是C2,接著C3)。由于MatrixServer是單線程的,其只能按順序一個(gè)一個(gè)執(zhí)行命令,因此首先處理C1,然后C2,最后處理C3。
由于利用Matlab實(shí)現(xiàn)的大多是一些復(fù)雜的科學(xué)計(jì)算工作,其執(zhí)行過程將花費(fèi)較多的時(shí)間。假設(shè)執(zhí)行完畢C1和C2各需要一分鐘時(shí)間,那么最后完成C3工作將至少需要3分鐘時(shí)間。這是因?yàn)镃3必須等待C1和C2完成之后才能進(jìn)行。這樣就出現(xiàn)界面凍結(jié)。
1.3 COM線程模型
1.3.1 套間
“套間”是指存放一組對(duì)象的地方,該組對(duì)象共享相同的線程行為和要求。COM線程模型包括單線程套間STA、多線程套間MTA和中性套間(Neutral Apartment)。一個(gè)進(jìn)程可以有多個(gè)STA,但最多只能有一個(gè)MTA。
1.3.2 單線程套間STA
STA是COM線程模型中一個(gè)重要的概念。在一個(gè)STA中,有且只有一個(gè)線程,駐留在該STA中的所有對(duì)象都服務(wù)于該線程。由于STA中僅有一個(gè)線程,因而被默認(rèn)序列化,無需考慮多線程編程中的同步問題[7],如圖1所示。
由于一個(gè)STA包含一個(gè)線程,多個(gè)STA包含多個(gè)線程,因而可以利用創(chuàng)建多個(gè)STA在一個(gè)服務(wù)中實(shí)現(xiàn)多線程。
假設(shè)在C1、C2、C3調(diào)用IMatrix.Mul時(shí)分別創(chuàng)建一個(gè)線程實(shí)例,這樣C1、C2、C3將分別工作在服務(wù)線程T1、T2、T3上。這樣C1、C2、C3三個(gè)客戶可以同時(shí)調(diào)用IMatrix.Mul,而無需等待,從而可以消除界面凍結(jié)。圖2所示為利用STA實(shí)現(xiàn)C1、C2、C3同時(shí)調(diào)用IMatrix.Mul。
在使用STA時(shí)需要注意的是,由于STA套間中所有的COM組件代碼都運(yùn)行于主STA(第一個(gè)調(diào)用CoInitialize函數(shù)的線程),如果主線程沒有調(diào)用CoInitialize,那么第一個(gè)調(diào)用CoInitialize的工作線程就會(huì)成為主STA,而工作線程隨時(shí)可能中止,這種情況下,一旦工作線程中止,主STA也就不復(fù)存在了,因此必須在主線程中調(diào)用CoInitialize初始化主STA。初始化STA也可以使用CoInitializeEx(nil,COINIT_APARTMENTTHREADED)函數(shù),它與CoInitialize(nil)等效。
通過查閱Windows注冊(cè)表鍵,由Matlab生成的COM組件,其線程模型ThreadingModel=Both,說明其既支持STA,也支持MTA。
2 利用STA線程模型實(shí)現(xiàn)Delphi調(diào)用COM組件
Delphi調(diào)用COM組件之前需要利用CoInitialize(nil)函數(shù)初始化COM組件,以便讓COM知道如何同調(diào)用線程工作,最后需要調(diào)用CoUnInitialize函數(shù)關(guān)閉COM,卸載在該線程中已裝載DLL,釋放線程中使用的資源,以及關(guān)閉線程中所有打開的RPC連接。
假設(shè)有一個(gè)利用Matlab生成的COM組件signal.dll,在Delphi XE中調(diào)用該組件的方法如下:
1)打開Delphi XE集成開發(fā)環(huán)境,新建一個(gè)包,設(shè)該包名命名為signal,編譯并安裝該包,然后關(guān)閉該包;
2)選擇Component→Import Component→Import a Type Library,出現(xiàn)Import Component對(duì)話框后,找到signal.dll,點(diǎn)擊下一步,在Palette Page中選擇ActiveX,再點(diǎn)擊下一步,選擇“Install to a Existing Package”,繼續(xù)下一步,定位到在第(1)步中安裝的包signal.bpl,單擊完成,即可將signal.dll安裝到Delphi XE中;
3)在所建的Delphi XE工程中,新建一個(gè)Thread Object文件,類名為TSignal,在該文件的Execute過程中輸入以下代碼:
FreeOnTerminate:=True;
try
CoInitialize(nil);
調(diào)用Form1中的Signal1組件;
finally
CoUnInitialize;
end;
在Form1中Button1的OnClick事件中輸入以下代碼:
try
TSignal.Create(False);
except
Application.MessageBox('系統(tǒng)出現(xiàn)異常!','錯(cuò)誤');
end;
通過以上四個(gè)步驟,就可以實(shí)現(xiàn)Delphi與Matlab混合編程,而且在執(zhí)行COM組件所提供函數(shù)的同時(shí),還可以進(jìn)行其它的操作,而不會(huì)出現(xiàn)界面凍結(jié)。
3 應(yīng)用實(shí)例
下面用一個(gè)實(shí)際例子說明如何利用COM STA線程模型實(shí)現(xiàn)Delphi和Matlab混合編程。該實(shí)例實(shí)現(xiàn)的是利用S變換提取信號(hào)包絡(luò)。首先編寫Matlab函數(shù)如下:
%輸入?yún)?shù):fName—文件名,Len—文件長度
%輸出參數(shù):wav—信號(hào)原始波形,stenv—基于S變換提取的信號(hào)包絡(luò)
function [wav, stenv] = stanaly(fName,Len)
[wav Fs] = wavread(fName);
if length(wav) > Len
wav = wav(1:Len);
end
stran = st(wav);
stenv = [];
for i=1:size(stran,2)
stran1 = max(abs(stran(: , i)));
stenv = [stenv stran1];
end
stenv = stenv*max(abs(wav));
stenv = stenv/max(stenv);
按照文獻(xiàn)[4]介紹的方法,制作并注冊(cè)COM組件。設(shè)組件名為TSignal,按上文介紹的方法將TSignal安裝到Delphi IDE中。
在Delphi新建一個(gè)工程,在窗體上添加如下控件:一個(gè)Signal,一個(gè)Chart,兩個(gè)Edit,兩個(gè)Label,一個(gè)UpDown,五個(gè)Button,一個(gè)Timer,一個(gè)OpenDialog,一個(gè)AlWavePlayer,一個(gè)AlAudioOut,一個(gè)SlScope。設(shè)置UpDown控件的Associate屬性為Edit1,Min=7000,Max=10000,設(shè)計(jì)的最終界面如圖3所示。
新建一個(gè)Thread Object單元,單元名為Unit2,類名為TSt。在其Execute過程添加如下代碼:
procedure TSt.Execute;
begin
FreeOnTerminate := True;
try
CoInitialize(nil);
try
fStatusText := '信號(hào)分析—波形分析中,請(qǐng)稍候...';
Synchronize(ShowStatus); //在Form1.Caption中顯示相關(guān)信息
Form1.Signal1.stanaly(2, wav, stenv, FFName, FFLen);
except
Application.MessageBox('系統(tǒng)出現(xiàn)異常!','錯(cuò)誤')
end;
finally
fStatusText := '信號(hào)分析—波形分析完成';
Synchronize(ShowStatus);
CoUnInitialize;
end;
end;
Unit1單元的部分代碼如下:
procedure TForm1.BtnAnalyClick(Sender: TObject);
var
fName : string;
fLen : Integer;
begin
fName := Copy(EdtFName.Text, 1, Length(EdtFName.Text)-4);
fLen := StrToInt(EdtFLen.Text);
wav := VarArrayCreate([1, fLen], VarDouble);//傳出COM的變體數(shù)據(jù):原始波形
stenv := VarArrayCreate([1, fLen], VarDouble);//傳出COM的變體數(shù)據(jù):st變換包絡(luò)
try
Timer1.Enabled := True;
SignalThread := TSt.Create(fName,fLen);
except
Application.MessageBox('系統(tǒng)出現(xiàn)異常!','錯(cuò)誤');
BtnExit.Enabled := True;
end;
end;
procedure TForm1.PlotWave;
var
WavData, EnvData : array of double; //原始波形和信號(hào)包絡(luò)
p1, p2 : Pointer;
i : Integer;
begin
SetLength(WavData,StrToInt(EdtFLen.Text));
SetLength(EnvData,StrToInt(EdtFLen.Text));
p1 := VarArrayLock(wav);
p2 := VarArrayLock(stenv);
Chart1.Series[0].Clear;
Chart2.Series[0].Clear;
try
Move(p1^, WavData[1], StrToInt(EdtFLen.Text) * sizeof(double));
Move(p2^, EnvData[1], StrToInt(EdtFlen.Text) * sizeof(double));
for i := Low(WavData) to High(WavData) do
begin
Chart1.Series[0].AddXY(i, WavData[i]);
Chart2.Series[0].AddXY(i, EnvData[i]);
end;
finally
VarArrayUnlock(wav);
VarArrayUnlock(stenv);
end;
由于篇幅所限,其余代碼省略。
在Delhi IDE中編譯、連接工程,運(yùn)行效果如圖3所示。點(diǎn)擊“分析”按鈕進(jìn)行分析的同時(shí),還可以執(zhí)行其他操作,而不會(huì)出現(xiàn)界面凍結(jié)現(xiàn)象。
4 結(jié)束語
本文詳細(xì)討論了基于COM STA線程模型的Delphi和Matlab混合編程的實(shí)現(xiàn)方法。按照這種方法進(jìn)行應(yīng)用系統(tǒng)的開發(fā),不僅可以充分利用Matlab強(qiáng)大的科學(xué)計(jì)算功能和Delphi靈活的可視化設(shè)計(jì)功能,而且開發(fā)出的系統(tǒng)在運(yùn)行過程中不會(huì)出現(xiàn)界面凍結(jié)現(xiàn)象,避免給用戶程序僵死的假象。采用這種方法可以提高軟件性能,為科學(xué)研究和工程計(jì)算提供更強(qiáng)的技術(shù)支持。該文所介紹的應(yīng)用實(shí)例在Windows XP、Delphi XE和Matlab 7環(huán)境下調(diào)試通過。
參考文獻(xiàn):
[1] 陳新.利用Delphi與Matlab進(jìn)行科學(xué)計(jì)算的實(shí)現(xiàn)[J].四川兵工學(xué)報(bào),2009,30(6):117-118.
[2] 王艷麗.Delphi與Matlab混合編程的5種方法[J].菏澤學(xué)院學(xué)報(bào),2006,28(2):100-102.
[3] 蔣裕豐,何鮮峰,金永強(qiáng).BP網(wǎng)絡(luò)監(jiān)測模型的Matlab&Delphi混合編程[J].水力發(fā)電,2008,34(1):88-91.
[4] 姜銀方,陳建希,李路娜.基于COM的Delphi和Matlab接口編程研究[J].計(jì)算機(jī)應(yīng)用與軟件,2008,25(2):31-34.
[5] 吳小麗,丁維明,程力.Delphi動(dòng)態(tài)調(diào)用Matlab COM組件實(shí)現(xiàn)二者混合編程[J].工業(yè)控制計(jì)算機(jī),2011,24(3):1-3.
[6] Binh Ly.Multithreading in COM[EB/OL][2010-12-28].http://www.techvanguards.com/com/concepts/multithreading.asp.