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