精品文档面向过程的编译和解释环境在工控组态软件系统中的应用赵立伟张春施寅(北京交通大学计算机与信息技术学院,北京100044)摘要:本文分析了开放式工控组态软件系统的优势和不足,介绍了自定义的Child-C语言编译和解释系统。使用该系统可以较大地增强组态软件系统在流程控制和应急处理方面的能力。同时还引进了事件的概念。文章重点介绍了在解释执行环境中处理二进制可执行代码的一种有效方案。关键词:编译;解释执行;事件;COM;自动化;工控;组态TheApplicationofProcessfacedCompilerandInterpreterinIndustryControlConfigurationSoftwareZHAOLi-wei,ZHANGChun,ShiYin(CollegeofComputerScience&InformationTechnology,BeijingJiaotongUniversity,Beijing100044,China)Abstract:Thepapershowstheadvantagesanddeficienciesofopenindustrycontrolconfigurationsoftware.AsystemofChild-Clanguagecompilerandinterpreterwereintroduced.Bythehelpofthissystem,theabilityofprocesscontrollingandemergencyhandlingcanbeelevatedgreatly.Italsousestheconceptofevent.Thepapermainlyintroducesasolutionwhichshowshowtocarryingoutbinaryexecutablecodeininterpreterlanguagesystem.Keywords:Compile;Interpret;Event;COM;Automatic;IndustryControl;Configuration1基于COM的工控组态软件的框架分析传统的工控组态软件[1]一般可以分为两部分:组态设计系统和组态运行系统。组态设计系统可以按照实际工业流程的需要选择工控功能模块,设置各个模块间的关联和数据流向等系统参数,从而建立一套完整的控制系统。组态运行系统建立在组态设计系统基础之上,它按照设计系统设定的参数启动相应的工控功能模块,并负责模块之间数据的传输和并发控制等。基于COM的工控组态软件[2]以面向对象的柔性设计与控制理论为基础[3],将面向对象技术应用于控制领域,把控制领域中的功能模块和对应的控制数据抽象封装成COM组件,按照开放式软件平台的原则将这些COM组件以插件的形式被加载到软件系统中。COM组件是与语言无关的二进制组件,使用任何语言开发的符合接口规定的COM组件都可以在组态软件系统中使用,因此提高了组态软件的开放性和收缩性,在系统设计阶段,设计人员可以像搭积木一样快速而直观的搭建出一条控制流水线。每个COM组件是被单独开发的高度抽象的功能模块,它偏重于数据的计算处理而缺乏对系统环境变化的应变能力,组件之间的数据交互能力也较差。它与系统和其它的控制组件的耦合性较小,要完全同其它组件融合在一起并在大的系统环境中良好地运转和适度灵活的应变系统的变化是比较困难的。这就需要在组态设计系统中使用辅助控制语言对这些组件的活动加以控制,规范控制流程。如图1所示,在组态设计系统中引入事件(Event)的概念,在事件的响应函数中使用辅助控制语言规范控制流程和组件的活动,经过编译后生成中间代码提交给组态运行系统。相应地在组态运行系统中建立解释环境对中间代码进行解释执行,从而达到规范流程控制的目的。精品文档图1编译和解释运行原理图2COM组件和自动化对象COM是用于开发分布式软件模型的组件化程序设计模型,它是建立在二进制可执行代码级基础上的。支持IDispatch接口的COM组件对象称为自动化对象,该接口允许将一个函数的名称以字符串的形式提交给组件,组件根据函数的名称自动调用相应的函数。该接口提供的这种机制可实现对COM组件的统一调度。本文介绍的编译器和解释器的工作就建立在该机制之上,它为如何在解释执行环境中启动二进制可执行代码提供了很好的解决方案。3编译器和解释器体系的框架分析3.1控制语言功能的选择C语言功能丰富,表达能力强,目标程序效率高,可移植性好,控制灵活,普及面广,非常适用于工业控制领域。C语言功能强,语法规则也很庞大,但是要构造一个完整的C语言编译器用于流程的控制则显得过于浪费。我们构造了C语言语法规则的一个子集——Child-C,该子集保留了C语言的分支、循环、赋值、常量和变量的定义以及函数调用等基本语法规则,去掉了汇编语言的处理能力。3.2中间代码编译器与二进制代码编译器边界的划分由上面的分析可以看出,整个控制系统要实现的功能代码要经过中间代码编译器与二进制代码编译器两种编译器共同编译。如图2所示,我们可以把完整编译器所具备的功能看成是一个大的集合,在这个集合中可以找到一条分界线:分界线左边的功能是中间代码编译器必须具备的,它需要我们自己设计实现;分界线右边的功能是二进制代码编译器所具备的,可以编译出高效的具有数据运算处理等功能的二进制机器码,它不需要我们动手实现。这条分界线是可以移动的,我们需要尽量使它向左移动,压缩中间代码编译器的功能,从而减少开发的工作量。把复杂的数据处理能力和底层访问能力放到右边。图2两类编译器的分界3.3运行时存储空间组织Child-C能够处理的数据类型除了C语言中传统的长短整形、浮点、实数、字符、数组、指针组态设计系统组件1组件n....…..关联事件1事件2……事件m编译器中间代码1中间代码2……中间代码m组态运行系统解释器组件1组件n....…..事件槽循环、分支、函数调用等底层I/O、数学处理等分界线中间代码编译器二进制代码编译器器精品文档和结构体外还有COM对象。如图3a所示,它描述了操作系统、组态软件运行系统和解释器运行时三者存储空间之间的关系。它们是包含的关系,解释器是被组态软件运行系统建立的,所以它实际上属于组态软件运行系统的一部分。如图3b所示,解释器运行时存储空间可以分为6部分。第一部分占64个字节,它存储了两组共16个虚拟寄存器,名称为R0,R1,R2…依次类推。第一组包含8个寄存器,它用来完成除了变长字符串之外的所有中间指令数据操作。第二组包含8个寄存器,它专门用来完成变长字符串相关指令的操作。第二部分占12个字节,它存储了3个32位的无符号整数,分别记录了全局堆基址、全局变量基址和运行时堆基址。第三部分是COM组件地址映像区,该区的作用将在后面详细描述。第四部分是全局堆存储区,它用来存储Child-C代码中用new操作符动态申请的数据空间以及所有的变长字符串数据,该区的大小是在解释器启动前设定的,解释器一但运行该值就不能改变。第五部分为全局变量存储区,该区存储的是可以被所有函数访问的全局参数,如控制环境的平均温度和湿度等。第六部分为运行时栈存储区,Child-C编写的函数全部运行在这个空间之上,它不但存储函数体定义的形参、变量和返回值,还要存储过程调用和返回时所需的控制链数据。ab图3存储空间关系图(a)和运行时存储空间示意图(b)3.4中间代码操作数的定义Child-C程序被编译成中间代码指令被解释器执行,如以下的Child-C语句执行相加操作:inta,b,c;a=b+c;它被编译为如下几条中间代码指令:LODR0Ox000000104;①--加载变量a到寄存器R0,Ox00000010是变量a在运行时堆存储区内的偏移地址,4表示加载的数据为4个字节LODR1Ox000000144;②--加载变量b到寄存器R1ADD③--R1+R0-R2,三个寄存器的意义是固定的STRR2Ox00000018④—将寄存器R2的值保存到变量c中其中指令③的ADD指令是二元操作数指令,但是它的操作数固定为R0和R1寄存器,计算结果也固定放到R3中。3.5字符串的兼容和特殊处理C语言中的字符串是通过字符数组进行处理的,Child-C继承了这种字符串处理方法。字符数组是长度固定的字符串,而在使用Child-C与COM组件交互时经常要处理COM标准的BSTR类型变长字符串,为了兼容该数据类型,在Child-C中增加了bstr类型的变长字符串数据类型。bstr由头尾两部分组成:头部信息由head和len两个32位无符号整形组成,头部信息代表了字符串实体,它全局堆基址组态软件系统存储区虚拟寄存器组全局变量基址运行时堆基址COM地址映像区全局堆存储区全局变量存储区运行时堆存储区COM存储区运行时存储空间操作系统存储区0x000000000x000000400x0000004C精品文档可以作为局部和全局变量存储,head是一个指针,它指向字符数据在全局堆中的首地址,len记录了字符串的长度。尾部信息保存在全局堆存储区中,字符信息在该区中并不是连续的,这也适应了字符串长度变化的需要。bstr可以直接代替BSTR类型,中间代码指令集中有专门的转换指令COMTOC和CTOCOM进行数据转换。当需要将bstr转换成BSTR时,编译器自动添加COMTOC指令,该指令根据bstr的头信息读取字符数据组装成BSTR数据提交给COM组件,而CTOCOM是一个逆过程。这两条指令使用特设的第二组虚拟寄存器进行转换操作。3.6COM组件的访问COM对象的访问需要进行特殊处理,它是在解析运行环境建立之前就已经存在的。如图3a所示,它存储在组态软件系统的存储空间中,它不会随着解释运行环境的消亡而消亡。而除此之外的数据都存储在解释器的存储空间中,它们会随着解释运行环境的消亡而消亡。为了便于编译器和解释器统一存储空间的处理,需要将COM组件映像到解释器存储空间中,图3b中的COM组件地址映像区就用来保存COM组件地址的映像。在编译器和解释器的运行过程中,所有对COM组件的访问都被映像到该区域,从而避免了存储空间的交叉访问。3.7解释器环境中对COM组件方法的调用COM组件的各种函数已经被编译成可被计算机直接执行的机器代码,自动化COM对象允许使用IDispatch接口的统一调度函数Invoke()调用组件的函数,被执行的函数可以当作一个字符串参数被传送。因此在构造的中间代码指令集中有一条INVOKE指令,该指令格式为:INVOKEComOffsetFuncOffsetParaOffset;该指令有三个操作数:ComOffset为要访问的COM组件在COM组件地址映像区的偏移,FuncOffset为被调用函数的名称bstr变量地址,ParaOffset是该函数参数的个数和参数的存储单元地址,该存储区之后是一个4字节的单元,它用来保存Invoke()函数执行的返回值。就是说,对COM组件的所有操作,解释器只需通过执行Invoke()函数就能完成。4事件体系的构造Child-C函数是通过事件触发而被调用的。事件包含两种类型,一种是组件运行系统定义的事件,该类型事件属于整个系统,它与具体组件无关,但可以被COM组件和系统触发。另一种是COM组件自定义的事件,该类型事件属于COM组件,它只能被组件自身触发。事件发生时触发解释器解释运行事件的相应函数。事件的处理工作全部交给事件槽来处理,事件槽实际上是一个可链接对象接收器[5],它可以接受系统和COM组件触发的事件,同时它还维护了一张事件映像表,该表记录了所有添加了响应函数的事件和该事件响应函数的入口地址以及有关参数信息。当有事件到达时,首先检查事件映射表该事件是否有被注册,如果有则根据响应函数的参数信息分配存储空间并调用响应函数。5结束语在实际的生产应用中可以证明,Child-C完全可以胜任绝大部分流程控制的需要,提高了工控组态软件的灵活性。解释器环境开发难度小但是程序执行效率低,自动化COM组件的函数调度机制可以很好的解决这种矛盾,该机