張 軍(1981-)
男,浙江杭州人,助理工程師,本科,畢業(yè)于浙江工業(yè)大學(xué)計(jì)算機(jī)及應(yīng)用專業(yè),主要研究方向?yàn)樽詣踊浖难邪l(fā)和應(yīng)用。
摘 要:本文介紹了ActiveX Scripting技術(shù),基于ActiveX Scripting技術(shù)開發(fā)了算法擴(kuò)展組件,探討了該組件的系統(tǒng)特點(diǎn)及在先控工程上的應(yīng)用,為先控工程師提供了腳本編輯,離線調(diào)試,最后在線運(yùn)行這樣一套完整的編寫自定義擴(kuò)展算法的操作流程,解決了在特殊工況、特殊裝置下運(yùn)用算法擴(kuò)展組件編寫腳本擴(kuò)展算法達(dá)到輔助控制的目標(biāo)。
關(guān)鍵詞:ActiveX Scripting;腳本引擎;腳本宿主;離線調(diào)試;腳本工程;COM;Automation對象;先進(jìn)控制
Abstract: This article describes the ActiveX Scripting technology. Based on ActiveX Scripting technology, we extend the algorithm component and explore the characteristics of the components of the system and its application in control engineering We provide a complete operation procedure of script editing, off-line debugging, and on-line running for control engineer to compile self-defined extended algorithm. Thus, we can edit script extended algorithms in special conditions and special equipment to achieve auxiliary control.
Key words: ActiveX Scripting; The script engine; Script Host; off-line debugging; script works; COM; Automation object; advanced process control
先控工程絕大部分情況下,采用預(yù)測控制或PID控制等標(biāo)準(zhǔn)控制算法就可實(shí)現(xiàn)預(yù)期控制目標(biāo);但也有一小部分情況,如裝置改造、擴(kuò)容或者在某些特殊工況下完全采用標(biāo)準(zhǔn)控制算法并不能達(dá)到預(yù)期控制目標(biāo),這種情況下需要采用自定義算法輔助實(shí)現(xiàn)控制目標(biāo)。
這就需要應(yīng)用的軟件是可擴(kuò)充和可定制的,微軟提供的ActiveX Scripting技術(shù)可使軟件擴(kuò)充變得非常簡單,利用腳本引擎(Script Engine)對腳本語言解釋和執(zhí)行的支持,用戶可以根據(jù)需要使用腳本語言編寫自定義擴(kuò)展算法,并交由軟件處理,對于用戶來說,就好象自己在編寫程序控制應(yīng)用程序,以完成自己所期望的功能。
1 ActiveX Scripting技術(shù)介紹
ActiveX是Microsoft公司于1996年提出的一項(xiàng)技術(shù),它以COM(Component Object Model,組件對象模型)為基礎(chǔ),使得不同的進(jìn)程(特別是網(wǎng)絡(luò)進(jìn)程)之間可以相互通信。ActiveX控件是Microsoft公司提供的一種用于模塊集成的協(xié)議,是可移植的軟件模塊,適用于各種開發(fā)語言,因而與開發(fā)平臺無關(guān)。ActiveX Scripting技術(shù)是Microsoft 的ActiveX技術(shù)的一個組成部分,它主要目的是使應(yīng)用程序在不被修改的情況下,為各種腳本語言所控制。在軟件交互性不斷提高的今天,僅僅提供菜單或工具箱的界面已經(jīng)不能滿足用戶的需要了,軟件的可定制特性已經(jīng)成為當(dāng)今軟件的一項(xiàng)基本特征,尤其對于一些通用的軟件更為如此。大家比較熟悉的Microsoft Office軟件,比如Word字處理軟件,它不僅提供了界面的任意定制,還提供了方便的Basic語言的可編程特性,用戶可以通過編寫B(tài)ASIC語言實(shí)現(xiàn)較為復(fù)雜的功能擴(kuò)充。
ActiveX Scripting體系由一個COM接口族組成,這些接口定義了一個把腳本引擎和腳本宿主連接起來的協(xié)議。在ActiveX Scripting的世界里,腳本引擎只是一個組件對象,是ActiveX Scripting技術(shù)的實(shí)現(xiàn),它暴露了一套標(biāo)準(zhǔn)的COM接口,能夠動態(tài)地執(zhí)行腳本程序,如果應(yīng)用系統(tǒng)實(shí)現(xiàn)了這套標(biāo)準(zhǔn)接口,那么它就可以通過腳本引擎為用戶提供對腳本語言的支持,也即實(shí)現(xiàn)了腳本宿主的功能。腳本宿主可以將它的Automation接口暴露在腳本引擎的名字空間中,可在動態(tài)執(zhí)行的腳本中像訪問程序中的變量那樣訪問應(yīng)用程序的對象。
Automation技術(shù)以COM(組件對象模型)為基礎(chǔ),所有的Automation對象都實(shí)現(xiàn)了標(biāo)準(zhǔn)的IDispatch接口,通過IDispatch接口暴露對象的屬性和方法以便在客戶程序中使用這些屬性并調(diào)用它所支持的方法。Automation對象的客戶程序或者宿主程序通過類型庫(Type Library)獲得對象運(yùn)行時刻的類型信息,并提供事件處理。宏語言解釋器或者腳本引擎根據(jù)對象的類型信息,把其中對對象屬性和方法的引用解釋為對IDispatch接口成員函數(shù)Invoke的調(diào)用,從而實(shí)現(xiàn)對對象的控制。
圖1是腳本宿主和腳本引擎之間的協(xié)作過程。
圖1 腳本宿主和腳本引擎之間的協(xié)作過程
從圖1中可以看到IActiveScriptParse、IActiveScript、IActiveScriptSite這三個腳本引擎暴露的接口在整個協(xié)作過程中扮演了非常重要的角色。
(1)創(chuàng)建必要的受控對象,這里的受控對象指的是Automation對象并且是在腳本文件中將會被引用到的。
(2)創(chuàng)建腳本引擎對象,獲取IActiveScript接口指針。
(3)通過IActiveScript接口查詢IActiveScriptParse接口,調(diào)用其中的ParseScriptText方法將腳本代碼加載到腳本引擎中。
(4)向腳本引擎注冊命名對象名稱,在第一步為創(chuàng)建的每個Automation都定義一個命名項(xiàng),然后通過IActiveScript接口中的AddNamedItem接口方法將命名項(xiàng)添加到腳本引擎中。當(dāng)腳本文件中需要引用Automation對象時,這一步是不能省略的,否則腳本引擎將無法解析該對象。
(5)啟動引擎,運(yùn)行腳本。即將腳本引擎狀態(tài)設(shè)置為連接狀態(tài)即可,可通過調(diào)用IActiveScript接口中的SetScriptState方法來完成。
(6)腳本運(yùn)行時,如遇到命名對象,則腳本引擎會調(diào)用由腳本宿主重新實(shí)現(xiàn)的IActiveScriptSite接口中的GetItemInfo方法,它根據(jù)傳入的命名項(xiàng)字符串與已注冊的命名對象名稱進(jìn)行比較,并返回對應(yīng)的Automation對象的IDispatch接口指針。
(7)通過連接點(diǎn)機(jī)制實(shí)現(xiàn)Automation對象與相關(guān)腳本的事件通知,如Automation對象觸發(fā)了一個鼠標(biāo)雙擊動作,則與雙擊事件相關(guān)的腳本代碼將被執(zhí)行。
(8)在腳本引擎的執(zhí)行過程中,如遇到腳本引用了Automation對象的方法或?qū)傩詴r,則腳本引擎會通過第六步拿到的該對象的IDispatch接口指針調(diào)用Invoke方法來實(shí)現(xiàn)與該對象的交互。
2 算法擴(kuò)展組件
2.1 系統(tǒng)介紹
算法擴(kuò)展組件采用ActiveX Scripting技術(shù)為先控工程師提供了編寫擴(kuò)展算法腳本的環(huán)境,實(shí)現(xiàn)了離線編輯腳本,離線調(diào)試腳本,最后在線運(yùn)行腳本這樣一套完整的編寫自定義擴(kuò)展算法的操作流程。
算法擴(kuò)展組件由腳本開發(fā)環(huán)境、腳本在線運(yùn)行監(jiān)視環(huán)境、算法擴(kuò)展組件(以COM組件形式發(fā)布)三部分組成。
算法擴(kuò)展組件的模塊層次如圖2所示。
圖2 算法擴(kuò)展組件模塊層次示意圖
算法擴(kuò)展組件實(shí)現(xiàn)了腳本引擎暴露的IActiveScriptSite、IActiveScriptSiteWindow、IDebugSessionProvider、IApplicationDebugger、IDebugExpressionCallBack接口,并將這些接口包裝成IScriptHost、IScriptDebug等接口暴露給腳本開發(fā)環(huán)境,用戶通過腳本開發(fā)環(huán)境訪問算法擴(kuò)展組件提供的這些接口服務(wù),使之成為腳本宿主和調(diào)試器于一體的應(yīng)用系統(tǒng);腳本在線運(yùn)行監(jiān)視環(huán)境通過訪問算法擴(kuò)展組件暴露的IScriptPrj接口提供對腳本工程的加載、卸載、啟動、停止等功能。
2.2 離線調(diào)試
算法擴(kuò)展組件內(nèi)部定義了一些與先控平臺相關(guān)的Automation對象,使之能在腳本中被引用,從而針對特定裝置編寫特定算法實(shí)現(xiàn)特定控制;如果將編寫的算法腳本直接加載到腳本引擎,那么它的安全性和有效性是無法保障的,考慮到先進(jìn)控制的高可靠性和高安全性要求,需要事先對算法腳本進(jìn)行調(diào)試,算法擴(kuò)展組件提出了離線調(diào)試的概念,即將腳本調(diào)試器集成到腳本開發(fā)環(huán)境中,腳本在調(diào)試過程中所引用的Automation對象并不與先控平臺連接,中間所發(fā)生的數(shù)據(jù)處理和數(shù)據(jù)交互交由算法擴(kuò)展組件的IScriptVar接口完成,這樣對腳本引擎來說IScriptVar就是一個虛擬的Automation對象,并且對它是透明的。在現(xiàn)場環(huán)境中這種調(diào)試模式有效屏蔽了與現(xiàn)場數(shù)據(jù)的交互,避免因所寫算法存在缺陷將數(shù)據(jù)誤寫而引起控制異常。
圖3顯示了在離線調(diào)試下腳本引擎與Automation對象的交互過程。
圖3 離線調(diào)試示意圖
2.3 腳本工程
算法擴(kuò)展組件定義了腳本工程的概念,即將所編寫的腳本代碼與該腳本相關(guān)的組態(tài)信息(比如腳本的觸發(fā)方式以及它的觸發(fā)周期等)打包成一個腳本工程。通過算法擴(kuò)展組件為每個在線加載的腳本工程派發(fā)一個后臺線程來處理,以實(shí)現(xiàn)多腳本工程的并發(fā)運(yùn)行,當(dāng)其中一個腳本工程運(yùn)行出現(xiàn)異常時不影響其他工程的運(yùn)行。腳本在線運(yùn)行監(jiān)視環(huán)境被集成到原有的在線操作平臺中,并以腳本工程為操作單元,實(shí)現(xiàn)對腳本工程的加載、卸載、啟動、停止等功能。
圖4顯示了腳本工程的加載、運(yùn)行示意圖。
圖4 腳本工程加載、運(yùn)行示意圖
2.4 腳本算法庫
將多年先控工程實(shí)施過程中積累的常用腳本算法提煉到腳本算法庫中,便于工程師提取,并運(yùn)用相應(yīng)的組態(tài)功能,對某個腳本算法稍作參數(shù)的改動就可應(yīng)用于新的先控工程中,減少工程師重新編寫腳本或粘貼/復(fù)制等重復(fù)勞動。同時腳本算法庫是可維護(hù)的,什么時候把一個什么樣的腳本算法添加到算法庫中完全由工程師決定。
3 腳本調(diào)試器的實(shí)現(xiàn)
在介紹腳本調(diào)試的實(shí)現(xiàn)之前,先介紹一下腳本調(diào)試技術(shù)。
微軟為腳本調(diào)試框架定義了5個模塊,分別是腳本宿主(Host)、腳本引擎(Language Engine)、進(jìn)程調(diào)試管理器(Process Debug Manager)、本機(jī)調(diào)試管理器(Machine Debug Manager)、調(diào)試器(Application Debugger),其框架定義如圖5所示。
圖5 腳本調(diào)試框架圖
(1)Host:負(fù)責(zé)為腳本創(chuàng)建運(yùn)行環(huán)境,提供腳本編譯時和運(yùn)行時的錯誤信息處理及其他一些腳本事件處理,如當(dāng)腳本終止運(yùn)行時、當(dāng)腳本狀態(tài)改變時等等。
(2)Language Engine:負(fù)責(zé)解析和執(zhí)行腳本代碼并提供腳本調(diào)試狀態(tài)下的功能,如枚舉調(diào)用堆棧、表達(dá)式計(jì)算、編譯時和運(yùn)行時的錯誤通知等等。例程名為VBScript.dll。
(3)PDM:負(fù)責(zé)管理在Active Scripting Framework中各種和進(jìn)程相關(guān)的問題,一個進(jìn)程通常能支持一個或多個腳本應(yīng)用程序,而PDM就是用來管理這些應(yīng)用程序的。它能跟蹤到正在運(yùn)行的這些應(yīng)用程序和進(jìn)程,并且能跟蹤到所有的線程和他們的父線程,同時協(xié)調(diào)MDM、調(diào)試器和腳本引擎之間的通信。例程名為PDM.dll。
(4)MDM:負(fù)責(zé)管理所有正在本機(jī)上運(yùn)行的應(yīng)用程序。例程名為mdm.exe。
(5)Application Debugger:負(fù)責(zé)提供調(diào)試時的所有用戶界面功能,如調(diào)用堆棧窗口、即時窗口、監(jiān)視窗口及斷點(diǎn)設(shè)置等。
根據(jù)對上述各模塊的描述,要實(shí)現(xiàn)調(diào)試器,就需要自己構(gòu)建Host和Application Debugger框架與腳本引擎通信。
通過實(shí)現(xiàn)IActiveScriptSite、IActiveScriptSiteWindow接口構(gòu)建Host框架。在Host框架中創(chuàng)建一個PDM實(shí)例,并獲取默認(rèn)應(yīng)用程序?qū)ο笾羔槨?BR>
::CoCreateInstance(CLSID_ProcessDebugManager,
NULL,
CLSCTX_ALL,
IID_IProcessDebugManager,
(LPVOID*) &m_pProcessDebugManager
);
m_pProcessDebugManager->GetDefaultApplication(&m_pDebugApplication);
通過實(shí)現(xiàn)IApplicationDebugger、IDebugSessionProvider接口構(gòu)建調(diào)試器框架調(diào)試器框架類的定義大致如下:
class CScriptDebug : public IApplicationDebugger,
public IDebugSessionProvider
{
……
}
一旦調(diào)試器框架類被實(shí)例化之后,通過QI查詢IDebugSessionProvider接口,該接口有一個重要的方法StartDebugSession,該方法負(fù)責(zé)將應(yīng)用程序?qū)ο蠛驼{(diào)試器關(guān)聯(lián)起來,以支持調(diào)試功能,所以該方法是必須要實(shí)現(xiàn)的,實(shí)現(xiàn)方式如下:
STDMETHODIMP CScriptDebug::StartDebugSession(
IRemoteDebugApplication __RPC_FAR *pda)
{
HRESULT hr;
hr = pda->ConnectDebugger(this);
return SUCCEEDED(hr) ? S_OK : E_FAIL;
}
調(diào)試器中設(shè)置的斷點(diǎn)如何映射到腳本引擎中呢?首先需要一個IActiveScript接口指針,這可以通過以下代碼得到:
// 創(chuàng)建語言引擎實(shí)例
CLSID clsid;
IActiveScript* pActiveScript;
HRESULT hr = CLSIDFromProgID(L"VBScript", &clsid);
hr = ::CoCreateInstance(clsid,
NULL,
CLSCTX_ALL,
IID_IActiveScript,
(LPVOID*) &pActiveScript
);
進(jìn)而通過QueryInterface查詢IActiveScriptDebug接口,該接口中有一個方法EnumCodeContextsOfPosition,該方法根據(jù)當(dāng)前設(shè)置的斷點(diǎn)位置獲取相應(yīng)的代碼上下文的枚舉器(IEnumDebugCodeContexts)接口指針,通過該枚舉器枚舉IDebugCodeContext接口,IDebugCodeContext接口有一個方法SetBreakPoint,該方法負(fù)責(zé)將當(dāng)前的斷點(diǎn)映射到腳本引擎對應(yīng)的代碼上下文中。
當(dāng)調(diào)試器中設(shè)置的斷點(diǎn)被映射到腳本引擎對應(yīng)的代碼上下文后,接下來的問題是當(dāng)腳本引擎開始執(zhí)行腳本時,遇到斷點(diǎn)怎么通知調(diào)試器?這就需要用到IDebugApplication這個接口,該接口繼承自IRemoteDebugApplication。
IDebugApplication接口實(shí)例是有PDM負(fù)責(zé)創(chuàng)建的并注冊給腳本引擎,因此當(dāng)腳本引擎命中斷點(diǎn)時,IDebugApplication接口中的HandleBreakPoint將會被調(diào)用,進(jìn)而會調(diào)用到調(diào)試器重載的onHandleBreakPoint方法,該方法的簽名如下:
onHandleBreakPoint(
/* [in] */ IRemoteDebugApplicationThread __RPC_FAR *prpt,
/* [in] */ BREAKREASON br,
/* [in] */ IActiveScriptErrorDebug __RPC_FAR *pError)
從該方法中可獲取到RemoteDebugApplicationThread接口指針,通過該指針可以獲取調(diào)用堆棧信息、屬性信息、進(jìn)行表達(dá)式計(jì)算、獲取當(dāng)前腳本語句所在的行號。
因此當(dāng)一個斷點(diǎn)到來時,就可以根據(jù)中斷原因進(jìn)行相應(yīng)的處理;如果中斷原因是BREAKREASON_ERROR,那么第三個參數(shù)將會被用到,否則其他情況下該參數(shù)總是為空。
一旦斷點(diǎn)被命中,應(yīng)用程序就被阻塞等待調(diào)試器給它的喚醒指令。具體執(zhí)行喚醒指令的是ResumeFromBreakPoint這個方法,其簽名如下:
HRESULT ResumeFromBreakPoint(
IRemoteDebugApplicationThread* prptFocus,
BREAKRESUMEACTION bra,
ERRORRESUMEACTION era
);
該方法由IRemoteDebugApplication接口實(shí)現(xiàn),遺憾的是并不能通過PDM直接獲取到該接口指針,但是能獲取到IRemoteDebugApplicationThread這個接口指針,進(jìn)而再通過這個接口調(diào)用其GetApplication方法就能獲取IRemoteDebugApplication接口,在獲取到IRemoteDebugApplication接口指針后,就可以調(diào)用ResumeFromBreakPoint方法來向應(yīng)用程序發(fā)出喚醒指令。
前面說過onHandleBreakPoint中的IRemoteDebugApplicationThread接口指針非常重要,因?yàn)橥ㄟ^它能獲取調(diào)用堆棧信息,該接口下有一個方法EnumStackFrames,該方法能返回一個IEnumDebugStackFrames類型的接口指針,它被用來枚舉線程中的調(diào)用堆棧,枚舉的結(jié)果是返回一個DebugStackFrameDescriptor類型的結(jié)構(gòu)體,該結(jié)構(gòu)體定義如下:
typedef struct tagDebugStackFrameDescriptor
{
IDebugStackFrame __RPC_FAR *pdsf;
DWORD dwMin;
DWORD dwLim;
BOOL fFinal;
IUnknown __RPC_FAR *punkFinal;
} DebugStackFrameDescriptor;
其中第一個參數(shù)是IDebugStackFrame類型的接口指針,通過該接口中的方法能獲取到具體的調(diào)用堆棧信息,而其他參數(shù)則是在對調(diào)用堆棧進(jìn)行排序時才有用。
獲取腳本上下文屬性可通過IDebugProperty接口來實(shí)現(xiàn),那么怎么獲取該接口呢,前面提到tagDebugStackFrameDescriptor這個結(jié)構(gòu)有一個IDebugStackFrame接口,該接口有一個方法GetDebugProperty,通過這個方法就能獲取到IDebugProperty接口指針了,獲取IDebugProperty接口后再調(diào)用其中的EnumMembers方法來枚舉屬性的成員,其方法簽名如下:
EnumMembers (
DBGPROP_INFO_FLAGS dwFieldSpec,
UINT nRadix,
REFIID refiid,
IEnumDebugPropertyInfo** ppEnum
);
通過此方法,就能獲取到一個IEnumDebugPropertyInfo枚舉器接口指針,有了這個接口指針就能枚舉所有屬性信息,微軟定義了如下屬性信息結(jié)構(gòu):
typedef struct tagDebugPropertyInfo
{
DBGPROP_INFO_FLAGS m_dwValidFields;
BSTR m_bstrName;
BSTR m_bstrType;
BSTR m_bstrValue;
BSTR m_bstrFullName;
DBGPROP_ATTRIB_FLAGS m_dwAttrib;
IDebugProperty __RPC_FAR *m_pDebugProp;
} DebugPropertyInfo;
從該結(jié)構(gòu)中能獲取到屬性名、屬性類型、屬性值等一些需要的信息,同時通過結(jié)構(gòu)中的m_pDebugProp成員可遞歸枚舉其所有子屬性信息。
4 結(jié)束語
ActiveX Scripting技術(shù)為開發(fā)的軟件提供了強(qiáng)大的可擴(kuò)展性,以該技術(shù)開發(fā)的算法擴(kuò)展組件為先控工程師提供了友好的操作界面,支持對腳本關(guān)鍵字著色、大綱折疊、行號顯示、撤消/重做、斷點(diǎn)設(shè)置、StepIn、StepOut、StepOver等三種調(diào)試模式;支持調(diào)試狀態(tài)下的調(diào)用堆棧查看、即時窗口查看、表達(dá)式計(jì)算;支持對腳本語法錯誤或運(yùn)行時錯誤的信息提示并實(shí)現(xiàn)相應(yīng)的錯誤信息定位;實(shí)現(xiàn)了在線運(yùn)行環(huán)境下根據(jù)組態(tài)信息自動控制腳本的執(zhí)行和停止。
算法擴(kuò)展組件改變了工程師完全依賴第三方腳本編輯器來編寫及調(diào)式腳本的狀況,根據(jù)先控工程特點(diǎn)定制的離線調(diào)試功能解決了之前調(diào)試狀態(tài)下直接連接先控平臺的缺陷;腳本內(nèi)容和相應(yīng)的組態(tài)信息被算法擴(kuò)展組件中包裝成一個腳本工程,并通過加密增強(qiáng)了腳本算法內(nèi)容的安全性。腳本算法庫便于工程師提取常用腳本算法,減輕了一部分工作量。
2009年8月投入試用之后工程師可在不使用第三方腳本調(diào)試器情況下,使用算法擴(kuò)展組件完成腳本編輯、調(diào)試、運(yùn)行等一整套完整的操作流程,運(yùn)行效果良好。
參考文獻(xiàn):
[1]呂思偉,潘愛民. ActiveX Scripting技術(shù)介紹[EB/OL].http://www.vckbase.com/article/atl/0003.htm.
[2] microsoft.Windows 腳本技術(shù)[EB/OL]. http://download.csdn.net/source/160096.
[3] Mike Pellegrino. Active Scripting APIs: Add Powerful Custom Debugging to Your Script-Hosting App[EB/OL]. MSDN Magzine,2000.
[4] Steve Wampler. ActiveX Scripting in MFC[EB/OL].http://download.csdn.net/source/894113.
[5] Mark Baker. Active Scripting Newsletter[EB/OL]. http://www.ddj.com/windows/184405549.
[6] BRENT RECTOR, CHRIS SELLS. 深入解析ATL[M].潘愛民,新語,譯.北京:中國電力出版社,2001(10):347-393.
轉(zhuǎn)自《自動化博覽》