企业简介

浙江中控软件技术有限公司位于浙江省杭州市,自20世纪90年代成立以来,一直致力于自动化软件产品(实时数据库、先进控制与优化、节能与优化、安全应急系统、生产执行系统、能源管理中心系统等)的研究开发、工程实施和技术服务。

  • 公司类型:供应商

联系方式
  • 浙江中控软件技术有限公司
  • 地址:浙江省杭州市滨江区六和路309号中控科技园
  • 邮编:310053
  • 电话:0571-86667666
  • 传真:0571-86667616
  • 网址:http://www.soft.supcon.com/
  • Email:soft@supcon.com
  • 联系人:
案例详细
标题ActiveX Scripting技术在先控工程上的应用
技术领域仪器仪表
行业
简介
内容

 
 

    张  军(1981-)



    男,浙江杭州人,助理工程师,本科,毕业于浙江工业大学计算机及应用专业,主要研究方向为自动化软件的研发和应用。



    摘   要:本文介绍了ActiveX Scripting技术,基于ActiveX Scripting技术开发了算法扩展组件,探讨了该组件的系统特点及在先控工程上的应用,为先控工程师提供了脚本编辑,离线调试,最后在线运行这样一套完整的编写自定义扩展算法的操作流程,解决了在特殊工况、特殊装置下运用算法扩展组件编写脚本扩展算法达到辅助控制的目标。

    关键词:ActiveX Scripting;脚本引擎;脚本宿主;离线调试;脚本工程;COM;Automation对象;先进控制

    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控制等标准控制算法就可实现预期控制目标;但也有一小部分情况,如装置改造、扩容或者在某些特殊工况下完全采用标准控制算法并不能达到预期控制目标,这种情况下需要采用自定义算法辅助实现控制目标。

    这就需要应用的软件是可扩充和可定制的,微软提供的ActiveX Scripting技术可使软件扩充变得非常简单,利用脚本引擎(Script Engine)对脚本语言解释和执行的支持,用户可以根据需要使用脚本语言编写自定义扩展算法,并交由软件处理,对于用户来说,就好象自己在编写程序控制应用程序,以完成自己所期望的功能。

1 ActiveX Scripting技术介绍

    ActiveX是Microsoft公司于1996年提出的一项技术,它以COM(Component Object Model,组件对象模型)为基础,使得不同的进程(特别是网络进程)之间可以相互通信。ActiveX控件是Microsoft公司提供的一种用于模块集成的协议,是可移植的软件模块,适用于各种开发语言,因而与开发平台无关。ActiveX Scripting技术是Microsoft 的ActiveX技术的一个组成部分,它主要目的是使应用程序在不被修改的情况下,为各种脚本语言所控制。在软件交互性不断提高的今天,仅仅提供菜单或工具箱的界面已经不能满足用户的需要了,软件的可定制特性已经成为当今软件的一项基本特征,尤其对于一些通用的软件更为如此。大家比较熟悉的Microsoft Office软件,比如Word字处理软件,它不仅提供了界面的任意定制,还提供了方便的Basic语言的可编程特性,用户可以通过编写BASIC语言实现较为复杂的功能扩充。

    ActiveX Scripting体系由一个COM接口族组成,这些接口定义了一个把脚本引擎和脚本宿主连接起来的协议。在ActiveX Scripting的世界里,脚本引擎只是一个组件对象,是ActiveX Scripting技术的实现,它暴露了一套标准的COM接口,能够动态地执行脚本程序,如果应用系统实现了这套标准接口,那么它就可以通过脚本引擎为用户提供对脚本语言的支持,也即实现了脚本宿主的功能。脚本宿主可以将它的Automation接口暴露在脚本引擎的名字空间中,可在动态执行的脚本中像访问程序中的变量那样访问应用程序的对象。

    Automation技术以COM(组件对象模型)为基础,所有的Automation对象都实现了标准的IDispatch接口,通过IDispatch接口暴露对象的属性和方法以便在客户程序中使用这些属性并调用它所支持的方法。Automation对象的客户程序或者宿主程序通过类型库(Type Library)获得对象运行时刻的类型信息,并提供事件处理。宏语言解释器或者脚本引擎根据对象的类型信息,把其中对对象属性和方法的引用解释为对IDispatch接口成员函数Invoke的调用,从而实现对对象的控制。
图1是脚本宿主和脚本引擎之间的协作过程。

           

                  图1   脚本宿主和脚本引擎之间的协作过程

    从图1中可以看到IActiveScriptParse、IActiveScript、IActiveScriptSite这三个脚本引擎暴露的接口在整个协作过程中扮演了非常重要的角色。

    (1)创建必要的受控对象,这里的受控对象指的是Automation对象并且是在脚本文件中将会被引用到的。

    (2)创建脚本引擎对象,获取IActiveScript接口指针。

    (3)通过IActiveScript接口查询IActiveScriptParse接口,调用其中的ParseScriptText方法将脚本代码加载到脚本引擎中。

    (4)向脚本引擎注册命名对象名称,在第一步为创建的每个Automation都定义一个命名项,然后通过IActiveScript接口中的AddNamedItem接口方法将命名项添加到脚本引擎中。当脚本文件中需要引用Automation对象时,这一步是不能省略的,否则脚本引擎将无法解析该对象。

    (5)启动引擎,运行脚本。即将脚本引擎状态设置为连接状态即可,可通过调用IActiveScript接口中的SetScriptState方法来完成。

    (6)脚本运行时,如遇到命名对象,则脚本引擎会调用由脚本宿主重新实现的IActiveScriptSite接口中的GetItemInfo方法,它根据传入的命名项字符串与已注册的命名对象名称进行比较,并返回对应的Automation对象的IDispatch接口指针。

    (7)通过连接点机制实现Automation对象与相关脚本的事件通知,如Automation对象触发了一个鼠标双击动作,则与双击事件相关的脚本代码将被执行。

    (8)在脚本引擎的执行过程中,如遇到脚本引用了Automation对象的方法或属性时,则脚本引擎会通过第六步拿到的该对象的IDispatch接口指针调用Invoke方法来实现与该对象的交互。

2 算法扩展组件

    2.1 系统介绍


    算法扩展组件采用ActiveX Scripting技术为先控工程师提供了编写扩展算法脚本的环境,实现了离线编辑脚本,离线调试脚本,最后在线运行脚本这样一套完整的编写自定义扩展算法的操作流程。

    算法扩展组件由脚本开发环境、脚本在线运行监视环境、算法扩展组件(以COM组件形式发布)三部分组成。

    算法扩展组件的模块层次如图2所示。

                    

                          图2   算法扩展组件模块层次示意图

    算法扩展组件实现了脚本引擎暴露的IActiveScriptSite、IActiveScriptSiteWindow、IDebugSessionProvider、IApplicationDebugger、IDebugExpressionCallBack接口,并将这些接口包装成IScriptHost、IScriptDebug等接口暴露给脚本开发环境,用户通过脚本开发环境访问算法扩展组件提供的这些接口服务,使之成为脚本宿主和调试器于一体的应用系统;脚本在线运行监视环境通过访问算法扩展组件暴露的IScriptPrj接口提供对脚本工程的加载、卸载、启动、停止等功能。

    2.2 离线调试

    算法扩展组件内部定义了一些与先控平台相关的Automation对象,使之能在脚本中被引用,从而针对特定装置编写特定算法实现特定控制;如果将编写的算法脚本直接加载到脚本引擎,那么它的安全性和有效性是无法保障的,考虑到先进控制的高可靠性和高安全性要求,需要事先对算法脚本进行调试,算法扩展组件提出了离线调试的概念,即将脚本调试器集成到脚本开发环境中,脚本在调试过程中所引用的Automation对象并不与先控平台连接,中间所发生的数据处理和数据交互交由算法扩展组件的IScriptVar接口完成,这样对脚本引擎来说IScriptVar就是一个虚拟的Automation对象,并且对它是透明的。在现场环境中这种调试模式有效屏蔽了与现场数据的交互,避免因所写算法存在缺陷将数据误写而引起控制异常。
图3显示了在离线调试下脚本引擎与Automation对象的交互过程。

             

                             图3   离线调试示意图

    2.3 脚本工程

    算法扩展组件定义了脚本工程的概念,即将所编写的脚本代码与该脚本相关的组态信息(比如脚本的触发方式以及它的触发周期等)打包成一个脚本工程。通过算法扩展组件为每个在线加载的脚本工程派发一个后台线程来处理,以实现多脚本工程的并发运行,当其中一个脚本工程运行出现异常时不影响其他工程的运行。脚本在线运行监视环境被集成到原有的在线操作平台中,并以脚本工程为操作单元,实现对脚本工程的加载、卸载、启动、停止等功能。
图4显示了脚本工程的加载、运行示意图。

         

                         图4   脚本工程加载、运行示意图

    2.4 脚本算法库

    将多年先控工程实施过程中积累的常用脚本算法提炼到脚本算法库中,便于工程师提取,并运用相应的组态功能,对某个脚本算法稍作参数的改动就可应用于新的先控工程中,减少工程师重新编写脚本或粘贴/复制等重复劳动。同时脚本算法库是可维护的,什么时候把一个什么样的脚本算法添加到算法库中完全由工程师决定。

3 脚本调试器的实现

    在介绍脚本调试的实现之前,先介绍一下脚本调试技术。

    微软为脚本调试框架定义了5个模块,分别是脚本宿主(Host)、脚本引擎(Language Engine)、进程调试管理器(Process Debug Manager)、本机调试管理器(Machine Debug Manager)、调试器(Application Debugger),其框架定义如图5所示。

                   

                               图5   脚本调试框架图

    (1)Host:负责为脚本创建运行环境,提供脚本编译时和运行时的错误信息处理及其他一些脚本事件处理,如当脚本终止运行时、当脚本状态改变时等等。

    (2)Language Engine:负责解析和执行脚本代码并提供脚本调试状态下的功能,如枚举调用堆栈、表达式计算、编译时和运行时的错误通知等等。例程名为VBScript.dll。

    (3)PDM:负责管理在Active Scripting Framework中各种和进程相关的问题,一个进程通常能支持一个或多个脚本应用程序,而PDM就是用来管理这些应用程序的。它能跟踪到正在运行的这些应用程序和进程,并且能跟踪到所有的线程和他们的父线程,同时协调MDM、调试器和脚本引擎之间的通信。例程名为PDM.dll。

    (4)MDM:负责管理所有正在本机上运行的应用程序。例程名为mdm.exe。

    (5)Application Debugger:负责提供调试时的所有用户界面功能,如调用堆栈窗口、即时窗口、监视窗口及断点设置等。

    根据对上述各模块的描述,要实现调试器,就需要自己构建Host和Application Debugger框架与脚本引擎通信。

    通过实现IActiveScriptSite、IActiveScriptSiteWindow接口构建Host框架。在Host框架中创建一个PDM实例,并获取默认应用程序对象指针。

::CoCreateInstance(CLSID_ProcessDebugManager,

                               NULL,

                               CLSCTX_ALL,

                               IID_IProcessDebugManager,

                               (LPVOID*) &m_pProcessDebugManager

                               ); 

m_pProcessDebugManager->GetDefaultApplication(&m_pDebugApplication); 

     通过实现IApplicationDebugger、IDebugSessionProvider接口构建调试器框架调试器框架类的定义大致如下:

class CScriptDebug : public IApplicationDebugger,

public IDebugSessionProvider

{

 ……

}

    一旦调试器框架类被实例化之后,通过QI查询IDebugSessionProvider接口,该接口有一个重要的方法StartDebugSession,该方法负责将应用程序对象和调试器关联起来,以支持调试功能,所以该方法是必须要实现的,实现方式如下:

STDMETHODIMP CScriptDebug::StartDebugSession( 

  IRemoteDebugApplication __RPC_FAR *pda)

 {
  HRESULT hr;

  hr = pda->ConnectDebugger(this);

  return SUCCEEDED(hr) ? S_OK : E_FAIL;

 }

      调试器中设置的断点如何映射到脚本引擎中呢?首先需要一个IActiveScript接口指针,这可以通过以下代码得到:

 // 创建语言引擎实例

 CLSID clsid;

 IActiveScript* pActiveScript;

 HRESULT hr = CLSIDFromProgID(L"VBScript", &clsid);

 hr = ::CoCreateInstance(clsid,

                                               NULL,

                                               CLSCTX_ALL,

                                               IID_IActiveScript,

                                               (LPVOID*) &pActiveScript

                                               );

    进而通过QueryInterface查询IActiveScriptDebug接口,该接口中有一个方法EnumCodeContextsOfPosition,该方法根据当前设置的断点位置获取相应的代码上下文的枚举器(IEnumDebugCodeContexts)接口指针,通过该枚举器枚举IDebugCodeContext接口,IDebugCodeContext接口有一个方法SetBreakPoint,该方法负责将当前的断点映射到脚本引擎对应的代码上下文中。

    当调试器中设置的断点被映射到脚本引擎对应的代码上下文后,接下来的问题是当脚本引擎开始执行脚本时,遇到断点怎么通知调试器?这就需要用到IDebugApplication这个接口,该接口继承自IRemoteDebugApplication。

    IDebugApplication接口实例是有PDM负责创建的并注册给脚本引擎,因此当脚本引擎命中断点时,IDebugApplication接口中的HandleBreakPoint将会被调用,进而会调用到调试器重载的onHandleBreakPoint方法,该方法的签名如下:

onHandleBreakPoint( 

  /* [in] */ IRemoteDebugApplicationThread __RPC_FAR *prpt,

  /* [in] */ BREAKREASON br,

  /* [in] */ IActiveScriptErrorDebug __RPC_FAR *pError)

    从该方法中可获取到RemoteDebugApplicationThread接口指针,通过该指针可以获取调用堆栈信息、属性信息、进行表达式计算、获取当前脚本语句所在的行号。

    因此当一个断点到来时,就可以根据中断原因进行相应的处理;如果中断原因是BREAKREASON_ERROR,那么第三个参数将会被用到,否则其他情况下该参数总是为空。

    一旦断点被命中,应用程序就被阻塞等待调试器给它的唤醒指令。具体执行唤醒指令的是ResumeFromBreakPoint这个方法,其签名如下:

 HRESULT ResumeFromBreakPoint(

                        IRemoteDebugApplicationThread* prptFocus, 

  BREAKRESUMEACTION        bra,

                        ERRORRESUMEACTION        era

                               );

    该方法由IRemoteDebugApplication接口实现,遗憾的是并不能通过PDM直接获取到该接口指针,但是能获取到IRemoteDebugApplicationThread这个接口指针,进而再通过这个接口调用其GetApplication方法就能获取IRemoteDebugApplication接口,在获取到IRemoteDebugApplication接口指针后,就可以调用ResumeFromBreakPoint方法来向应用程序发出唤醒指令。

    前面说过onHandleBreakPoint中的IRemoteDebugApplicationThread接口指针非常重要,因为通过它能获取调用堆栈信息,该接口下有一个方法EnumStackFrames,该方法能返回一个IEnumDebugStackFrames类型的接口指针,它被用来枚举线程中的调用堆栈,枚举的结果是返回一个DebugStackFrameDescriptor类型的结构体,该结构体定义如下:

 typedef struct  tagDebugStackFrameDescriptor

 {
     IDebugStackFrame __RPC_FAR *pdsf;

     DWORD dwMin;

     DWORD dwLim;

     BOOL fFinal;

     IUnknown __RPC_FAR *punkFinal;

 } DebugStackFrameDescriptor;

    其中第一个参数是IDebugStackFrame类型的接口指针,通过该接口中的方法能获取到具体的调用堆栈信息,而其他参数则是在对调用堆栈进行排序时才有用。

    获取脚本上下文属性可通过IDebugProperty接口来实现,那么怎么获取该接口呢,前面提到tagDebugStackFrameDescriptor这个结构有一个IDebugStackFrame接口,该接口有一个方法GetDebugProperty,通过这个方法就能获取到IDebugProperty接口指针了,获取IDebugProperty接口后再调用其中的EnumMembers方法来枚举属性的成员,其方法签名如下:

 EnumMembers (

                                DBGPROP_INFO_FLAGS dwFieldSpec,

                                UINT nRadix,

                                REFIID refiid,

                                IEnumDebugPropertyInfo** ppEnum

                                );

    通过此方法,就能获取到一个IEnumDebugPropertyInfo枚举器接口指针,有了这个接口指针就能枚举所有属性信息,微软定义了如下属性信息结构:


 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;

    从该结构中能获取到属性名、属性类型、属性值等一些需要的信息,同时通过结构中的m_pDebugProp成员可递归枚举其所有子属性信息。

4 结束语

    ActiveX Scripting技术为开发的软件提供了强大的可扩展性,以该技术开发的算法扩展组件为先控工程师提供了友好的操作界面,支持对脚本关键字着色、大纲折叠、行号显示、撤消/重做、断点设置、StepIn、StepOut、StepOver等三种调试模式;支持调试状态下的调用堆栈查看、即时窗口查看、表达式计算;支持对脚本语法错误或运行时错误的信息提示并实现相应的错误信息定位;实现了在线运行环境下根据组态信息自动控制脚本的执行和停止。

    算法扩展组件改变了工程师完全依赖第三方脚本编辑器来编写及调式脚本的状况,根据先控工程特点定制的离线调试功能解决了之前调试状态下直接连接先控平台的缺陷;脚本内容和相应的组态信息被算法扩展组件中包装成一个脚本工程,并通过加密增强了脚本算法内容的安全性。脚本算法库便于工程师提取常用脚本算法,减轻了一部分工作量。

    2009年8月投入试用之后工程师可在不使用第三方脚本调试器情况下,使用算法扩展组件完成脚本编辑、调试、运行等一整套完整的操作流程,运行效果良好。

    参考文献:

    [1]吕思伟,潘爱民. ActiveX Scripting技术介绍[EB/OL].http://www.vckbase.com/article/atl/0003.htm. 

    [2] microsoft.Windows 脚本技术[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.







                                                        转自《自动化博览》