代码生成器(共8篇)
代码生成器 篇1
1 前言
Think PHP是笔者常用的一个轻量级国产PHP开发框架, 但在开发过程中, 发现有大量类似但不完全重复的代码编写工作, 比如重复编写一些简单的数据库增删改查的操作, 编写前台表单、样式表等等。除了数据库设计不同之外, 界面、操作步骤等基本类似, 显得重复而没有必要。因此, 考虑设计一个通用的代码生成器来完成这些工作, 从而有效地减少开发工作量, 将精力集中于核心业务逻辑。
2设计思路
通过分析关系数据库操作的一些共性, 根据面向对象的思想和分层架构设计, 预置了一些与数据库相关的界面模板与操作的常用代码模板, 根据具体项目需要和设定, 不用写一行代码, 就能完成特定数据库表的增删改查的操作和相关界面的模板设计工作。主要包括以下功能:
1.1 生成CURD操作的HTML界面及后台代码
CURD是一个数据库技术中的缩写词, 它代表了数据库的创建 (Create) 、更新 (Update) 、读取 (Read) 和删除 (Delete) 操作。设计流程是:在系统界面中, 选择数据库类型 (My Sql或Sql Lite) , 列出所有数据库, 选择相应的数据库、数据表, 并设置好表间关系, 及相关查询代码及显示要求后, 点击确定, 基于模板自动生成关于该数据表的增加、删除、修改、浏览的基本HTML显示界面, 以及对应的后台数据库控制器操作代码, 并且按照think PHP的MVC模式的要求分目录存放。之后, 简单做一些修改就能满足项目需要。
1.2 生成通用的注册登录模块
任何一个系统, 都会有一些近似的注册登录等基本功能, 为此, 将其整合在了代码生成器中。主要包括新用户注册、登录、密码修改、密码找回等功能。
1.3 生成通用的后台管理模块
后台管理模板比较用常见, 不过为了更加方便, 将其与代码生成器的封装、整合在一起, 有利于后期的维护, 能够最大程度的简化劳动。
3 代码生成器实现的关键步骤
3.1 解决模板代码的冲突问题
Think Php是MVC模式架构的框架, 其最终生成的代码是基于模板机制的。而代码生成器的原理, 也是利用预置的模板生成符合当前项目需要的文件。因此, 预置的模板实际上是模板之模板。设计的关键是需要注意模板代码的冲突问题。在代码生成器中主要传入的模板变量是关于数据库的结构信息, 如表名、字段名、主键等, 而最终生成的代码还必须能够通过模板解析。例如, 在最终HTML界面中需要的是形如{$rs.name}的变量, 在代码生成器的模板中则需要写成"$"."rs.$a Field Name"的形式。
3.2 了解文件系统、数据库系统的操作方法
主要通过定义了下面的一些功能函数实现:
获取子目录列表function get Dir List ($dir) 创建目录function create Dir ($fullpath)
读取module名称列表function get Module Name List ()
获取表名列表function get Table Name List ()
获取字段列表function get Table Field Arr ($table)
获取表的主键function get Key Field Name ($table)
把带下划线的表名转换为驼峰命名 (首字母大写)
function table Name To Model Name ($table Name) 把带下划线的列名转换为驼峰命名 (首字母小写)
function Field Name To Var Name ($Field Name)
3.3 生成Html模板界面
生成Html界面时, 需要设置数据字段的显示方式, 即设置用Input、Radio、Check Box等哪一种表单元素来显示字段值, 对此, 需要利用自定义函数判断。
4 结语
在进行Web程序开发时, 不同的站点或系统都会有一些共同的操作, 如果每次都要重新编写代码既费时间, 又费精力。通过代码生成器, 能够实现通用模块代码的自动生成, 特别是实现数据表的基类代码的自动生成, 包括生成属性、添加、修改、删除、查询、存在性、Model类构造等基础代码片断, 增强了代码的可复用性、可维护性, 使得在开发过程中可以节省大量机械录入的时间和重复劳动, 而将精力集中于核心业务逻辑, 更加轻松地进行项目开发。因此, 设计一个通用的代码生成器显得非常有必要, 可以有效地减少了Web开发的工作量, 且提高了自已开发时间和维护成本, 给开发的工作带来了便利。
摘要:在基于ThinkPHP框架的多个web站点开发过程中, , 发现有大量类似但不完全重复的代码编写工作, 为了便于开发, 减少重复劳动, 本文基于ThinkPHP设计了一种代码生成器, 通过简单配置, 能够自动生成一些通用的界面与操作代码, 从而可以节省大量机械录入的时间和重复劳动, 将精力集中于核心业务逻辑的开发。
关键词:ThinkPHP框架,代码生成器,模板
参考文献
[1]王池李隐峰基于ThinkPHP的微课教学竞赛系统设计《电子科技》2014.10
[2]郭柏乔基于ThinkPHP框架的高校自主招生网上报名系统设计与实现《广州城市职业学院学报》2014.02
代码生成器 篇2
XSD 文件描述了允许包含在 XML 文档中以便被该文档视为有效的内容。由于需要以类型安全的方式对数据进行处理(这些数据最终将被序列化为 XML 数据以供使用),因此产生了各种将 XSD 转换为类的方法。我们可以回想一下,XSD 并“不是”作为一种描述对象及其关系的手段而创建的。已经存在一种更好的格式可用于该目的,它就是 UML,并且已被广泛用来对应用程序进行建模以及根据模型生成代码。因此,在 .NET 及其面向对象的编程 (OOP) 概念以及 XSD 的概念之间存在某些(预料的)不匹配现象。当您将 XSD 映射到类时,请记住这一点。
也就是说,可以将 CLR 类型系统视为 XSD 的子集:它支持一些无法映射到常规 OO 概念的功能。因此,如果您只是使用 XSD 来对类进行建模,而不是对文档进行建模,您很可能找不到任何冲突。
在本文的其余部分,我们将讨论类型化数据集方法,还将讨论通过 xsd.exe 工具生成的自定义类如何有助于得到更好的解决方案,以及如何扩展和自定义从 XSD 到类的生成过程的输出。
为了最深入地领会本文的内容,您需要对 CodeDom 有一些基本的了解。
类型化数据集有什么问题?
类型化数据集正在越来越多地用于表示业务实体,也就是说,用于充当应用程序各个层之间的实体数据传送器,甚至充当 Web 服务的输出。与“正常的”数据集不同,类型化数据集很有吸引力,因为您还可以获得对表、行、列等的类型化访问。然而,它们并非没有代价和/或限制:
•实现开销:数据集包含许多可能不为您的实体所需的功能,如更改跟踪、类似于 SQL 的查询、数据视图、大量事件等等。•性能:与 XML 之间的序列化速度不够快。XmlSerializer的性能很容易超过它。•互操作性:对于返回类型化数据集的 Web 服务的非 .NET 客户端而言,可能难以解决。•XML 结构:许多分层的(而且完全有效的)文档及其架构无法扁平化为表模型。获取有关类型化数据集的更多信息。
因此,除非数据集的其他功能普遍对您有用,否则使用类型化数据集进行数据传递可能不是最佳选择。值得庆幸的是,还有另一个可以利用的选择。
XmlSerializer 和自定义类
XmlSerializer改善了 XML 数据处理方法。通过序列化特性,XmlSerializer能够根据对象的 XML 表示形式还原对象,并且能够反序列化到 XML 形式。此外,它还能够以非常有效的方式完成这些工作,因为它可以生成动态编译的基于XmlReader的(因而也是流式的)类,该类专门用于序列化(以及反序列化)具体的类型。所以,它确实非常快捷。
阅读有关 XML 序列化特性的更多内容。
当然,猜测使用哪些特性以便符合某个 XSD 绝对不是一件好玩的事情。为了解决这个问题,.NET SDK 随附了一个可以帮助您完成艰苦工作的实用工具:xsd.exe。它是一个命令行应用程序,能够根据 XSD 文件生成类型化数据集和自定义类。自定义类在生成后具有相应的 XML 序列化特性,因此在进行序列化时,可以保证完全忠实于架构。
阅读 Don Box 对 XSD 以及 CLR 映射和特性的介绍。
迄今为止,一切都很好。我们具有有效且快速的方法将 XML 转换为对象或者将对象转换为 XML,并且我们具有能够为我们生成类的工具。问题在于,我们有时希望得到与所生成的内容稍有不同的内容。例如,xsd.exe 所生成的类无法数据绑定到 Windows 窗体网格,因为它查找属性而不是公共字段来显示。我们可能希望在许多地方添加自己的自定义特性,将数组更改为类型化集合,等等。当然,我们在做这些事情的时候,应保证在序列化时能够与 XSD 兼容。
自定义 XSD 将明显改变所生成的类的形式。如果您只是期望将 PascalCaseIf 变成实际的 XML 标准以便使用 camelCase,那么我建议您三思而后行。MS 的一些即将问世的产品表明它们将要使用 PascalCase 来表示 XML,以便使它们更好地支持 .NET。
如果您需要进行更多的与上述自定义类似的自定义,您的选择是什么?人们几乎普遍认为 xsd.exe 是不可扩展的,并且没有办法对其进行自定义。这是不准确的,因为 .NET XML 团队实际上向我们提供了恰好可供该工具使用的类。您将需要自己动手使用CodeDom以便利用它们,但自定义程度只受到您需要的限制!
您可以在下列文章中阅读有关CodeDom的内容:
Generating and Compiling Source Code Dynamically in Multiple Languages
Generate .NET Code in Any Language Using CodeDOM
代码生成器 篇3
关键词:设计模式,代码生成器,配置管理
随着信息技术的不断发展以及企事业单位对管理信息系统的重视程度以及倚赖程度的加深, 相应软件系统项目的开发也日趋庞大化和复杂化, 软件系统的模块越来越多, 但每个模块的基础部分的实现都很类似, 比如对数据库总数据的操作、翻页功能的实现等等。通常可以结合已有的系统架构和公共模块, 实现根据配置文件或者数据库定义来生成应用模块, 然后在此基础上去进行业务功能的开发。
但这也会面临以下问题: (1) 如果生成的配置文件或模板文件发生变化, 那么相应的文件读取程序也要发生改变; (2) 如果调用逻辑或调用顺序发生变化, 那就要修改相应的调用程序; (3) 如果输出格式发生变化, 就只能重写代码来实现新的输出格式。类似这样的问题还有很多, 这就要求有人专门负责维护这样的应用, 不断更改程序来适应新应用的需要, 但由于生成的调用过程、生成配置、生成模板等功能是混合在一起的, 导致修改比较困难。
1 代码生成器通用框架
为了解决上述问题, 需要一个通用框架, 来把生成应用的核心部分独立出来, 形成公共的应用, 让变化的部分从应用中分离出来, 从而可以根据需要进行配置和扩展来适应各种应用的需要。该框架能够按照模版和配置去生成结果, 支持模版和配置方式的自定义, 支持生成方式的自定义, 以及可以自定义生成的过程。该框架本身并不固定要求配置的格式, 也不要求配置的来源, 因而也不固定获取和解析配置数据的程序, 完全由开发人员来制定配置的方式和格式, 以及如何获取和解析这些配置数据等。
该框架结构主要包括以下几个模块, 其总体模块结构如图1所示。
(1) 分发调度模块。分发调度提供接受用户请求的入口, 然后根据用户请求的内容, 去获取相应的配置数据, 获得户配置的数据过后, 就按照配置的要求来发出命令, 要求按照这些配置数据来完成generate的功能。
在每一个具体命令的实现中, 先动态组合需要完成的generate的功能, 然后就把这些功能交给generate的代理去完成。也就是说分发调度只是负责接收用户的任务, 然后把任务组合好, 最后分配出去, 只是起到一个调度的作用, 本身并不处理用户的请求功能。
(2) 生成代理模块。生成代理是一个介于生成调度和真正生成之间额外的附加层, 目的是能够根据需要切换不同的代理, 比如生成调度根据配置, 需要通过远程来生成, 那么就需要远程代理, 远程访问的方式可能是rmi或webservice等等。
(3) 具体调用模块。这个模块实现一个具体生成功能的调用过程, 通常是把用户配置的参数数据, 按照一定的规则与theme的模板相结合, 从而得到需要生成的结果, 然后把结果输出去的过程。
(4) 模版管理模块。模板管理的功能就是负责获取相应的模板数据, 并对这些模板的数据进行管理, 在外部需要这些模板数据的时候, 可以访问模板管理提供的接口来获取。
(5) 生成输出模块。具体调用运行完成后, 会产生生成的结果, 这些结果如何输出?输出到什么地方?输出成为什么格式等等问题, 都由生成输出来负责。生成输出本身并不固定任何的输出要求, 开发人员完全可以在外部来定义输出的格式, 输出的地方, 以及如何输出等, 只需要在theme的配置中注册新的输出格式即可。
(6) 外部主题模块。外部主题是独立于核心框架之外的, 完全由开发人员根据需要来制定, 通常里面会包含生成所需要的所有原始模板文件, 跟模板文件对应的解析和生成的辅助程序, 生成处理的Action处理程序, 还有theme的配置文件, 一般这几个是必不可少的;其次是根据需要, 由开发人员扩展的功能, 比如:在进行生成处理前后需要额外添加的功能, 自定义的输出类型等等。外部主题决定了按照什么来生成、如何生成以及生成为什么东西的具体信息, 是提供给核心框架使用的重要数据。
(7) 配置管理模块。该模块首先用于获取用户配置的数据, 配置的方式很多, 要求除了框架自身提供的配置方式外, 还要能支持用户自定义的配置方式。其次用于缓存用户配置的数据, 同一份配置数据, 在运行期间会多次使用, 但是获取用户配置数据的动作就只需要一次就可以了, 获取过后, 就缓存下来重复使用。最后是用于对外提供接口, 让其他模块通过这些接口来获取所需要的数据。
2 配置管理模块分析与设计
2.1 配置管理模块的功能边界
(1) 配置管理模块不关心被访问的数据是怎么来的, 它只是按照访问方式去获取这些数据, 当然不同的数据格式有不同的访问方式。
(2) 虽然需要支持自定义配置方式, 但是需要配置的数据是一定的, 只是配置的格式不同, 访问这些配置数据的方式不同, 但最后殊途同归, 都需要配置管理模块提供相应的数据。因此这个需要配置的数据项是要统一起来的。
(3) 配置管理模块不关心获取到的数据是什么, 也不关心这些数据的含义, 更不关心这些数据怎么用, 它只是负责获取这些数据并保存起来。
(4) 配置管理模块不关心谁来使用这些数据, 也不关心外部获取这些数据后如何使用, 它只是负责提供这些数据而已。
2.2 配置管理模块的对外接口
(1) 核心框架运行需要的数据。以xml配置为例, 默认取名为Gen Conf.xml, 在每次使用的时候根据需要修改或配置其内容。
(2) 用户需要生成的模块的配置数据。比如用户想要生成一个模块内的增删改查的源代码, 里面有每个具体功能的配置, 而每个具体功能就是一个源代码文件。这个也需要在每次使用的时候根据需要来配置, 并注册到核心框架运行配置里面去, 每次生成主要配置的就是这类数据。
(3) 外部主题的配置数据。在制作主题的时候就配置好, 里面有这套主题需要的外部数据, 和预定义好的功能配置, 在每次使用的时候一般不需要配置或修改。比如缺省为xml配置, 取名为Theme Conf.xml。
为了让外部获取配置管理模块内的数据, 提供相应的API (应用程序接口) , 也就是Gen Conf Ebi。
2.3 配置管理模块的内部实现
为了更好的理解配置管理模块的内部实现架构, 因此先以一个最简单的实现结构为起点, 采用重构的方式, 逐步把相关的设计模式应用进来, 从简单到复杂, 应用多种软件设计模式以及让多种设计模式协同工作。
(1) 根据对外提供的数据结构定义, 制作出相应的数据模型。 (2) 针对前面定义的API, 提供一个最基本的实现, 只需要满足最基本的功能就可以了, 需要实现读取配置文件的功能, 然后要有缓存配置数据的功能, 最后就是实现API中要求的功能。配置管理模块的结构如图2所示。
3 利用设计模式改进配置管理模块
(1) 加入简单工厂模式。上面的实现虽然向模块外部提供了接口, 可是外部根本不知道模块内部的具体实现, 那么模块外部如何来获取一个实现接口的实现对象呢?简单工厂解决这个问题的思路就是在配置管理模块里面添加一个类Gen Conf Factory, 在这个类里面实现一个方法, 让这个方法来创建一个接口对象并返回然后把这个类提供给客户端, 让客户端通过调用这个类的方法来获取接口对象。
(2) 加入单例模式。如果Gen Conf Ebo被创建多次的话, 那么就会重复获取配置数据, 浪费程序运行时间, 并且每个Gen Conf Ebo的实例都会缓存这些数据, 浪费内存空间。同一个类里面, 既有实现Gen Conf Ebi要求的对外功能, 又有内部实现需要的获取配置数据和缓存数据的功能, 从类的设计上来说, 这个类的职责太不单一了, 应该分离一部分职责出去。此时可以通过使用单例模式添加一个类Conf Manager, 来控制实例的数目。
(3) 加入桥接模式。按照功能要求, 配置数据的来源是多方面, 比如:xml、properties、txt、DB等等, 这也就意味着需要有不同的获取数据的实现来对应这些不同的数据来源。
对于模块外部的应用而言, 并不关心配置数据是如何来的, 只关心需要使用的数据, 而且需要配置管理模块提供所需要的一切数据。这些数据的多少、数据的组合结构是可能发生变化的。这就导致出现了2个纬度的变化, 一个是获取配置数据这边需要不断扩展, 另一个是配置数据所构成的数据模型这边也需要变化和扩展。可以借助于桥接模式把获取配置数据这边, 设计成为桥接模式的实现部分, 而数据模型这边设计成为桥接模式的抽象部分, 而且数据模型这边确实需要使用具体实现部分来获取数据。而Conf Manager就相当于桥接的抽象部分的顶层实现, Gen Conf Ebo就相当于使用基本的抽象部分的扩展部分, 只不过这里采用的是对象组合的方式, 而不是标准桥接模式中的对象继承的方式。
(4) 加入解释器模式。如果xml文件的格式发生了变化, 那么读取配置文件的程序就需要做出相应的变更, 严重的时候, 几乎相当于完全重写程序。那么怎么解决当xml的结构发生改变过后, 能够很方便的获取相应元素、或者是属性的值, 而不用再去修改解析xml的程序呢?要解决通用解析xml的问题, 首先需要先设计一个简单的表达式语言, 在客户端调用解析程序的时候, 传入用这个表达式语言描述的一个表达式, 然后把这个表达式通过解析器的解析, 形成一个抽象的语法树。其次解析完成后, 自动调用解释器来解释抽象语法树, 并执行每个节点所对应的功能, 从而完成通用的xml解析。这样一来, 每次当xml结构发生了更改, 也就是在客户端调用的时候, 传入不同的表达式即可, 整个解析xml过程的代码都不需要再修改了。
(5) 加入备忘录模式。在根据字符串来构建对应的抽象语法树的时候, 有很多字符串前面都是一样的, 这样每次重复创建太浪费时间了, 最好是相同字符串对应的树形对象就不用创建了, 直接分析后面不同的部分, 然后把新的部分添加到已有的这棵树里面。每次在拿到一个字符串过后, 先跟已有的记录进行比较, 找出最大的已经解析过的字符串, 那么这个长度的字符串是不需要再解析的, 只需要把后面没有解析的字符串拿出来进行解析, 然后把解析的对象添加到已经解析好的这个对象后头就可以了。这就需要每次在解析的时候, 每次解析完成, 就向备忘录里面添加一条记录, 而每次进入的时候, 根据最长能匹配的字符串, 从备忘录里面获取到相应的对象, 这就不用解析了。
(6) 加入生成器模式。当解析一个xml文件时, 需要拼接很多很多的字符串, 而这些字符串的拼接过程是十分类似的, 只是最终拼接后的结果不一样, 可以通过加入生成器模式来让这些字符串的拼接过程变得简单和统一。每个字符串都是从根节点开始, 依次拼接到最后需要取值的地方, 因此可以把这个拼接的过程统一起来, 做成生成器, 而调用这些生成器来构建最终产品的客户端, 这个时候就充当了生成器模式中的指导者。
通过以上6种软件设计模式的应用, 最终配置管理模块的功能得到了优化, 其结构示意如图3所示。
参考文献
[1]陈臣, 王斌.研磨设计模式[D].1版.北京:清华大学出版社, 2011.
[2]钟茂生, 王明文.软件设计模式及其应用[D].计算机应用, 2002.
[3]刘海岩, 锁志海.设计模式及其在软件设计中的应用研究[J].西安交通大学学报, 2005 (10) .
[4]赵军.基于模板的代码生成器的研究与实现[J].长春师范学院学报, 2011 (12) .
代码生成器 篇4
高性能并行计算是现代科学研究、工程技术开发和大规模数据处理的关键技术,而并行编译系统是并行计算机系统软件中十分重要的一部分,提高并行编译技术对充分利用并行机资源和提高并行机效率起着十分重要的作用。
并行编译器包括前端处理和后端处理两部分:前端处理主要包括逻辑上的并行识别、计算和数据划分、依赖关系识别;后端处理主要是并行代码的自动生成,代码生成的关键在于如何高效地生成同步通信代码。
并行程序中的通信主要由四部分组成:数据初始分布通信、计算前的数据准备通信、计算过程中的同步通信以及数据收集通信。初始数据分布通信是在进行数据的初始分布过程中引起的通信。由于初始分布中数据划分和计算划分不能做到完全对齐所引起的通信称之为数据准备通信。同步通信是在进行并行计算过程中产生的通信。数据收集通信是所有进程计算结束后,主进程把所有进程的计算结果收集起来得到程序的最终执行结果时所引起的通信。本文重点讨论计算中的同步通信问题。
在文献[1]中对代码生成和通信优化做了介绍,但对具体如何实现没有讨论,本文则提出利用命名的线性不等式系统来表示数组数据空间、循环迭代空间、虚拟处理器空间和物理处理器空间,并建立了它们之间的内联关系,在此基础上给出了同步通信代码的自动生成算法。
1 计算划分
在分布式存储的大型计算机中,循环级的并行性一般是通过对循环嵌套迭代空间进行计算划分并将循环迭代分布到多个进程同时执行来实现的。下面给出与计算划分相关的定义。
定义1 迭代空间 迭代空间I表示一个循环边界是循环索引的线性函数且深度为m的循环嵌套,该空间是一个m维多面体。循环嵌套的每个迭代对应多面体中的一个整数点,即一次计算操作,用索引向量undefined表示。
定义2 处理器空间P 处理器空间P表示一个n维的处理器数组。
定义3 计算划分[4]C 计算划分undefined是满足特定关系的迭代和处理器对undefined的集合,处理器undefined执行迭代undefined当且仅当undefined。其中U是一个扩展的幺模矩阵,undefined是整数向量,B是整数矩阵,undefined是符号向量,undefined。
在计算划分C中,U指示计算划分是对迭代空间的哪些维进行划分以及是正分还是斜分;undefined给出分块的大小;undefined是偏移的大小。计算划分在代码生成的前一遍自动生成。
2 读写依赖关系与LWT树
计算中的同步通信由读写依赖关系和计算划分共同确定,对依赖关系有如下定义。
定义4[5] 嵌套循环L中的语句T的一个实例T(j)和语句S的一个实例S(i),如果存在一个存储单元M满足下述条件,则称语句T的实例T(j)依赖于语句S的实例S(i):
(1) S(i)和T(j)都引用(读或写)M;
(2) 在程序串行执行时,S(i)在T(j)之前执行;
(3)在程序串行执行时,从S(i)执行结束到T(j)开始执行前,没有其他实例对M进行写操作。
一对语句实例可以用4种不同的方式引用相同的存储单元,因此有4种类型的依赖关系:
① 如果S(i)写M而T(j)读M,则T(j)流依赖于S(i);
② 如果S(i)读M而T(j)写M,则T(j)反依赖于S(i);
③ 如果S(i)写M而T(j)也写M,则T(j)输出依赖于S(i);
④ 如果S(i)读M而T(j)也读M,则T(j)输入依赖于S(i)。
在这4种依赖关系中,④不会影响程序的并行化,不会引起通信,②和③的依赖关系是可以消除的,也不会引起通信,只有①需要通信,本文中的同步通信即指由流依赖所引起的通信。
在进行依赖关系分析时,用LWT树来表示数据之间的读写关系,每对读写对对应一棵LWT树。LWT树是一棵二叉树,表示精确的数据流信息,是描述从读操作实例到提供该读操作所读数据的最后一次写操作实例的映射关系。若读、写分别用循环索引undefined和undefined表示,则该函数的定义域是所有满足循环边界限制的undefined的集合。LWT树包括根节点、叶节点和内节点,内节点是对读undefined进一步的限制,叶节点又分为⊥节点和非⊥节点,表示了不同的依赖关系。
LWT[3]树把循环嵌套划分为以其每个叶节点的内容(contexts)ι为元素的集合,undefined。如果一个contextι∈I中迭代所读的值是在循环中产生的,则存在最后写关系undefined,且读迭代undefined所读值在写迭代undefined中产生},其中undefined和undefined是线性函数。
对给定的读迭代,从LWT知道一个写迭代undefined修改了undefined所读的数据且undefined是修改该数据的最后一个写操作,因此可以定义一个写迭代和读迭代之间的函数来表示undefined到undefined之间的关系,记为L,这样就可以通过LWT计算出两次引用之间的数据流依赖向量。
定义5 最后写关系LasarLasar是从读数组访问undefined和读迭代undefined到写数组访问undefined和写迭代undefined的映射,undefined当且仅当undefined;undefined;undefined;undefined且undefined使得undefined;undefined;undefined,其中undefined和undefined是读写访问函数。
由定义5知道,如果写迭代undefined和读迭代undefined都访问同一数组元素undefined之前执行,而且在undefined之间不存在其他迭代修改数组元素undefined则undefined之间存在最后写关系。
通过建立LWT树的方法可以将依赖关系精确到具体数值,这就为各进程之间的通信提供了关键依据。依赖关系分析是在代码生成的前一遍自动生成的。
3计算代码和计算中同步通信代码的自动生成
在并行程序代码自动生成中将涉及到多个多维整数空间,包括数组数据空间、循环迭代空间、虚拟处理器空间和物理处理器空间等。定义1和定义2分别给出了循环迭代空间和处理器空间的定义,下面给出数据空间的定义:
定义6 一个m维的数组A[n0][n1]…[nm-1],li≤ni≤ui,0≤i≤m-1,定义了一个m维的数据空间,该空间每一维的上下界即是数组每一维的上下界li和ui。
用命名的符号系数不等式系统统一表示这些空间,该不等式系统由多个不等式组成,每个不等式表示一种变量之间的关系,每个变量表示的即是空间的某一维。变量的所有可能的整数解的集合用n维离散的笛卡尔空间表示(n是变量数),所有满足该不等式系统的解都与笛卡尔空间中的一个整数点相对应。
计算代码和同步通信代码的自动生成过程分三部分:生成数据的接收和解包代码、生成计算代码、生成数据打包和发送代码。自动代码生成的关键是在程序的什么地方插入何种方式的通信代码?首先判断是否需要进行同步通信,如果LWT树中读的数据不在当前进程则需要通信,且利用计算划分来确定应该与哪个进程进行通信。
定理1 计算划分C满足最后写关系μ的通信集是undefined的集合,其中,undefined。
定理1所处理的是LWT树中的非⊥节点,根据LWT提供的读写依赖关系和读写变量所在
迭代的取值范围以及计算划分来判断两个迭代之间是否需要进行通信,见图1[1]。从图中可以看出,读/写迭代通过计算划分C分布到不同的进程pr和ps,两者之间通过LWT树联系在一起,若undefined则需要通信。
对于每一个物理进程mypid,通过以下算法判断是否需要发送数据给其他进程。
算法输入:计算划分C、依赖关系LWT、处理器空间P、迭代空间I
算法输出:并行同步通信代码和计算代码
算法描述:
(1) 建立虚拟拓扑结构把当前进程的标识mypid转换为多维坐标表示的pids;
(2) 通过计算划分和循环迭代范围得到参与计算的进程范围Pe;
(3) 如果pids∉Pe则不需要通信,否则;
(4) 根据计算划分C得到pids的迭代范围is;
(5) 根据LWT树中非⊥节点提供的读写依赖关系信息得到与is对应的ir;
(6) 依据ir和计算划分C得到ir所在的进程pidr;
(7) 比较pidr和pids是否是同一进程,如果二者相同则不需要同步通信,否则;
(8) 产生同步发送代码:打包数据并发送给pidr。
相应的,进程也需要判断是否需要接收其他进程发送来的数据:
(9) 通过建立的虚拟拓扑结构把当前进程的标识mypid转换为多维坐标表示的pidr;
(10) 通过计算划分和循环迭代范围得到参与计算的进程范围Pe;
(11) 如果pidr∉Pe则不需要通信,否则;
(12) 根据计算划分C得到pidr的迭代范围ir;
(13) 根据LWT树中非⊥节点提供的读写依赖关系信息计算出ir对应的is;
(14) 依据is和计算划分C得到is所在的进程pids;
(15) 比较pidr和pids是否是同一进程,如果二者相同则不需要同步通信,否则;
(16) 产生同步接收代码:接收pids发送来的数据并解包数据。
最后生成计算代码,根据上一遍提供的计算划分C和循环迭代I的边界信息计算出执行计算的进程范围,再依据分块执行的原理把计算分布到各进程执行。
4 实例分析
以下例来说明上面的算法:
例1 for(i=0;i<=N-1;i++)
for(j=i;j<=N-1;j++)
for(k=N-1;k>=i;k--)
a[j][k][i]=a[j][k][i]+a[i][k][j]*a[j][i][k]/a[i][j][k];
该例计算划分C:pid0=-k,pid1=i;迭代空间I:0≤i≤N-1;i≤j≤N-1;i≤k≤N-1,LWT树见图2。从图中可以知道数组引用a[j][k][i]和a[i][j][k]之间存在读写依赖关系,其依赖关系为:ks=-jr;js=ir;is=kr。
设N=4,则由计算划分C和迭代空间I可以计算出虚拟处理器空间P为-3≤pid0≤0,0≤pid1≤3。在生成同步通信代码时,首先判断是否需要进行通信,如果需要通信则产生同步通信代码。以pidr0=-2,pidr1=1为例来说明,根据计算划分C知道ir=1;kr=2;ir≤jr≤3,进而根据LWT树提供的读写依赖关系得到is=2;js=1;-3≤ks≤-1。得到写迭代之后,再根据计算划分C就可以找到该写迭代所在的进程为-3≤pids0≤-1,pids1=2。最后比较pids和pidr是否是同一进程,不是则产生两个进程之间的同步通信代码。此例pidr≠pids,则两个进程之间需要通信,进程pids产生同步发送代码,进程pidr产生同步接收代码。
5 总结与展望
本文主要讨论串行程序并行化中涉及到的计算代码和计算中同步通信代码的自动生成。文中所介绍的算法已在SUIF编译架构上实现,并利用ppopp benchmark程序集进行了验证,实验结果表明该算法能够正确生成计算代码和同步通信代码,但在该算法中未对同步通信的优化进行处理。下一步将主要研究计算中同步通信的优化问题,如多维并行条件下的计算和通信的重叠等。
摘要:简要介绍了并行编译中的计算划分和依赖关系分析,提出如何利用计算划分和依赖关系自动生成并行程序中的计算代码和同步通信代码。
关键词:计算划分,依赖关系,最后写树,同步通信
参考文献
[1]Amarasinghe S P,Lam M S.Communication optimization and CodeGeneration for distributed memory machines.In the Proceedings of TheACMSIGPLAN′93 Conference on Programming Language Design andImplementation,Albuquerque,New Mexico,June,1993:126-138.
[2]Ferner G S.The Paraguin compiler Message-passing code generation u-sing SUIF.In the Proceedings of the IEEE SoutheastCon 2002,Colum-bia,SC,April 5-7,2002:1-6.
[3]Maydan D E,Amarasinghe S P,LamMS.Array data-_flowanalysis andits use in array privatization.In the Proceedings of ACMSIGP-LAN-SI-GACTSymposium on Principles of Programming Languages.Charles-ton,South Carolina,January 10-13,1993:2-15.
[4]Anderson J M,Lam M S.Global Optimizations for Parallelism and Lo-cality on Scalable Parallel Machines.In Proceedings of the SIGPLAN′93 Conference on Program Language Design and Implementation,June1993.
代码生成器 篇5
MathWorks嵌入式应用程序和认证部经理Tom Erkkinen说:“世界各地的工程师都在使用MATLAB和Simulink来设计系统和算法。现在, 有了HDL Coder和HDL Verifier, 他们在开发FPGA和ASIC设计时再也不用手动编写HDL代码, 也不再需要手写HDL测试平台了。”
HDL Coder利用MATLAB功能和Simulink模型生成可移植和可综合的VHDL和Verilog代码, 可用于FPGA编程或ASIC原型开发和设计。因此, 工程师队伍现在可以立即识别出针对硬件实现的最佳算法。Simulink模型和所生成的HDL代码之间的可追溯性同时也支持开发遵循DO-254和其他标准的高完整性应用程序。
代码生成器 篇6
当前,智能终端的普及使得移动应用的需求量不断增长。安卓作为免费、开源的手机平台受到广大开发人员及用户的青睐。安卓应用开发者大多采用传统的方式(由设计至编码),基于集成开发环境(例如Eclipse、Android Studio、Visual Studio等)完成应用的开发工作。在这些工具中,开发者需要为安卓应用的每一项组成部分进行设置或编码,包括应用界面的设计(编写layout文件,或直接通过可视化开发方式进行设计)、核心配置文件的定义(填充Androidmanifest.xml文件)、组成应用的构件的开发(Activity、Service等代码的编写)、构件间跳转关系的实现(Intent代码的编写)等。这些工作可能花费开发者较多的时间。然而,与应用框架相关的部分代码与配置文件具有类似的模式,我们认为这部分的开发工作可以通过复用及定制的方式从而提高应用的开发效率。
针对以上目标,本文提出一套借鉴MDA思想的方法,基于安卓应用的描述来完成安卓代码框架部分的自动生成。MDA指首先建立抽象的、与具体技术无关的模型,随后从抽象模型自动生成应用程序的过程[1]。遵循该过程,本文首先提出一种描述安卓应用的元模型,元模型定义了组成安卓应用的各类元素及其依赖关系。特定安卓应用的模型基于元模型标准,定义了应用的架构,包括名称、属性以及约束关系等。同时,本文方法还提出了一套应用代码模版,其中内嵌了与特定应用相关的可变、可定制的代码部分。在描述应用的模型以及代码模板的基础上本方法涵盖了从元模型所描述元素到代码模版中可变部分的映射过程,支持通过代码生成器自动生成安卓应用的框架,随后开发者可将精力投入在应用业务逻辑的代码编写上。基于本方法,我们实现了安卓应用框架自动生成工具,工具采用GMF[2]图形框架支持用户以可视化的方式设计应用框架,A-pache Freemarker[3]代码生成器负责定制模版,生成应用代码框架。
1 相关工作
近年来,针对基于MDA的代码自动生成技术的研究主要包括UML模型的代码自动生成、模型驱动的安卓界面(Graphical user interface)自动生成、基于DSL(Domain Specific Language)元模型以及采用BPMN模型的代码自动生成。
为了提高GUI的开发效率,Silva等[4,5,6,7,8,9,10]提出了面向安卓界面自动生成的模型。他们为安卓应用的控件、窗口、事件监听器等建立抽象模型,再将模型转换为代码,自动生成安卓应用界面的代码。本文的研究重点在于创建描述安卓应用的模型,自动生成应用的代码框架,而界面自动生成并非本文考虑的重点。本文仅通过配置实现了简单的线性布局。
在基于UML模型驱动的代码自动生成方面,Parada等[11]采用了UML类图和顺序图为安卓应用建立模型,并建立类图与顺序图的对应关系,然后结合两者自动生成安卓应用代码框架。Kraemer等[12,13]采用UML活动图描述安卓应用,将活动图转换为可执行的状态机,再自动生成安卓应用的代码。由于安卓应用的领域特征,UML统一建模语言不能很好地描述安卓应用,因此本文采用了领域特定的元模型定义组成安卓应用的各类元素。
在DSL的元模型的模型驱动技术方面,Lachga等[6,7,8,9,10]借鉴MDA思想,提出了DSL的建模语言,并开发了安卓界面代码自动生成工具。Madari等[8]为应用的GUI和页面跳转做详细设计,建立了应用整体与不同页面的模型,再将页面之间的跳转通过CR模型(UML活动图)联系起来。本文的元模型定义了组成安卓应用的各类元素,如Activity、Service、Broadcast Receiver等,并通过描述应用的模型为安卓应用的框架建模。
其他研究人员采用BPMN建模语言为应用建模,例如SolisMartinez等采用BPMN建模语言来描述业务流程,然后生成跨平台的应用[14,15]。模型通过XML格式定义流程模型,经过一定步骤的配置后,实现了与平台无关的应用(Android、IOS)。但他们并没有对具体的应用开发做出详细阐述,仅提出了平台无关的模型。
此外,一些知名公司开发了图形化工具为开发人员带来便利。例如,谷歌与MIT联合开发的一款安卓快速开发工具App Inventor[16],该工具可以使用户通过基于浏览器的工具开发手机应用。用户可以方便地拖放代码块,通过mushup的方式将它们放在一起,产生一个应用程序。由于是由特定的模块组成的,所以具有一定限制性。IBM公司的Rational Rhapsody[17]提供了用于安卓代码框架生成的Eclipse插件,该工具采用UML类图、顺序图为应用建模。但是,该工具的模型配置比较复杂,与代码开发几乎是一一对应的。相比而言,本文提出了领域特定的元模型,并通过描述应用的模型为安卓应用建模,建模过程更加简单直观。
2 描述安卓应用框架的元模型
2.1 元模型适用范围
由于安卓四大组件包括Activity、Service、Broadcast Receiver和Contentprovider[18],每种组件都具有自己的生命周期,承担不同的职责。对于多数应用开发层的应用而言,都由以上四大组件构成,例如业务信息类应用、社交软件、新闻应用等。本文提出的元模型将安卓组件设计为元模型的元素,可适用于多数安卓应用开发程序。而本文提出的元模型对于诸如框架开发类应用(基于安卓操作平台做定制化开发)、底层开发类(驱动开发、JNI)等尚不适用。具体来说,本文提出的元模型可适用于具有以下特征的应用:
(1)安卓应用包含许多Activity。Activity主要用于显示界面、与用户交互;应用需要实现导航功能,导航指Activity之间的跳转。
(2)安卓应用需启动后台服务。Activity是安卓应用的主线程,为了保证用户交互流畅,Service可以用于完成长时间执行的后台服务,例如下载文件、播放背景音乐等。
(3)安卓应用需要监听全局广播。例如,应用需要监听系统广播,如检测Wi Fi、开机启动、sms消息等。
(4)安卓应用需要实现组件间通信,我们可以通过绑定服务、动态广播实现组件间的通信功能。
2.2 元模型定义
描述安卓应用的元模型定义了描述安卓应用框架的各个元素及其依赖关系,如图1所示。
表1给出了具体的元模型的定义,我们将元模型元素按照类别分为组件类、Widget类、事件监听器类、关联类和权限类。
2.3 元模型依赖关系定义
元模型的依赖关系主要分为以下五个方面:
(1)Transition与Event Listener、Intent依赖关系定义
安卓应用组件之间的转移是由事件驱动并由Intent负责传递消息,因此Transition与Intent、Event Listener具有关联关系。
(2)Component转移依赖关系定义
我们把Component元素之间的依赖关系定义为Transition元素。Activity元素可以跳转到多个其他的Activity;Activity可以启动多个Service;Component元素可以向多个广播发送通知。Transition必须设置好相应广播的action属性,否则广播将接收不到通知。
(3)Activity、Widget、Event Listener依赖关系定义
Activity可以包含多个Widget组件,所以Activity与Widget具有一对多的聚合关系。同样,Widget组件可以触发不同的事件,因此Widget与Event Listener也具有一对多聚合关系。
(4)Activity与Broadcast Receiver依赖关系定义
动态广播是安卓应用开发中实现跨进程通信的常用技术。在Activity中可以动态注册Broadcast Receiver。Activity元素与动态的Broadcast Receiver元素具有一对多聚合关系。
(5)Widget与相应的常用事件监听器依赖关系定义
每种安卓控件都具有与常用事件监听器。
3 基于模版的框架代码生成
3.1 基于Freemarker的代码模板
本文采用Freemarker模板引擎自动生成安卓应用的代码模板。Freemarker遵循MVC(model view controller)模式,是一款轻量级的代码生成引擎。Freemarker模板引擎的语法规则类似于脚本语言,本身也具有一些编程能力,例如条件判断、循环嵌套、集合等概念类似于其他编程语言。例如,${}指令表示可以替换为某个变量的值,if/elseif/else表示条件判断,list指令表示循环遍历对象集合,include指令表示嵌套子模板等。本文按照Freemarker的语法规则定义了源代码、布局、配置模板。其中,源代码模板包含了Activity模板、Service模板、Broadcast Receiver模板。在这些模板中,还可以设置内嵌子模板,例如控件模板、Intent模板、bind Service模板等。图2展示了上述模板和各个模板之间的依赖关系。
本文定义的模板类型非常多,由于篇幅的限制,我们仅展示了其中的一个模板。图3展示了Activity模板,Activity模板主要用于生成Activity类的代码框架,包括引入包、继承Android.app.Activity类、重写Activity的生命周期方法。同时,在on Create方法中设置该Activity的布局文件、注册动态广播。在on Destory方法中,释放绑定的Service,并且取消注册动态广播。图中的高亮区域显示了代码模板的可变部分。Activity模板中可以包含多个widget、动态广播,还可以绑定多个Service。我们将这部分可嵌套的模板定义为子模板,实现了模板之间的包含关系。例如,在on Create方法中调用<#include"/Button.ftl">可以在Activity模板中嵌套Button模板。此外,在事件中还可以嵌套Intent模板从而实现Component元素之间的关联关系。
3.2 基于描述模型的代码模板定制
在前文所提出的元模型与代码模板的基础上,我们按照以下转换过程实现框架代码的自动生成,如图4所示。首先,创建基于元模型标准的描述应用的模型,得到模型的XML描述文件。基于Freemarker的模板规则定义一组代码模板,包括安卓应用的源代码、布局、配置文件、子模板等。接着,将XML描述文件和代码模板作为代码生成器的输入。随后,将描述应用的模型映射到代码模板的可变部分,最后生成应用的代码框架。
其中,应用的源代码是应用程序的主体,根据模型和代码映射规则生成的源代码包括用户定制的Activity类、Service类、Broadcast Receiver类。Android Manifest.xml配置文件可以定义用户所配置的版本,注册Activity、Service、Broadcast Receiver、Permission等。Layout布局文件是安卓应用程序的界面。我们的工具会根据模型和属性配置,自动为Activity生成相应的布局文件。图5展示了一个简单的示例,形象地展示了从特定模型到模板中可变部分的映射过程。
由于模型的XML定义文件是XML DOM树形式的,我们引入了Dom4j API解析该模型的定义文件,遍历读取该定义文件中的各个节点、属性。同时,为其中的节点建立了Java模型与之一一对应。通过遍历该XML文件,可以得到模型所创建的所有节点。根据Node节点的类型分类,将每个Node节点独立作为一个源文件的输出。例如,XML文件中包含Broadcast Receiver元素,将它注入Broadcast Receiver对象,然后生成对应的java源文件。对于类型是Activity的元素,生成它所包含的组件、事件监听器、布局文件以及生命周期等。如果元素的类型是Service,那么根据他的启动类型,判断是否要绑定Service。对于Broadcast Receiver,判断它是否是静态等还是动态的。如果是静态的广播,那么需生成独立的Broadcast Receiver文件,并在配置文件中注册,否则在代码中动态注册。
4 工具实现与实验
基于上述元模型,实现了Android应用框架自动生成工具。本节首先介绍了工具的架构设计。接着,我们使用该工具实现了一个具体的安卓应用“EBook”,并且展示了运行效果。
4.1 Android应用框架自动生成工具
Android应用框架自动生成工具的主要任务是开发一套能为安卓应用创建描述应用的模型的图形编辑器和自动生成应用代码框架的功能。工具架构主要分为五层,分别为Eclipse Platform、Platform Runtime、Tool Framework、工具层和应用层,如图6所示。
最底层的Eclipse Platform层代表Eclipse运行时环境。Eclipse是一个开源的平台,可以使各种功能以插件的形式自由组装的IDE,例如GMF、Draw2D、Android ADT等。Android ADT[19]为开发安卓项目提供了运行时环境。工具框架层提供了图形工具框架、Dom4j XML API、Android SDK所提供的API、代码生成器。本文采用了Apache Freemarker代码生成技术定制代码模板,并且在运行时动态输入数据模型自动生成不同的代码。工具层包括了图形编辑器,该图形编辑器是在GMF的基础上生成的,可以利用该编辑器创建描述安卓应用的模型。工具层还包括我们定义的代码模板、元模型所描述元素到代码模板中可变部分的映射过程、Android工程。我们首先通过EMF[20]创建描述应用的EMF元模型,为了使工具建模更加方便、形象,在元模型的基础上加入了描述流程的节点如Process、Node、Start、End、Edge、Permissions。我们会在模型映射到代码模板的定制过程中,从中提取出符合元模型定义的元素,再将模型转换为最终的代码。然后利用GMF图形框架自动生成描述安卓应用的编辑器。随后,按照Freemarker的模板语法规则定义好安卓应用的代码框架、布局、配置文件。接着,将特定的模型与代码模板的可变部分做映射,完成模型到代码的转换。工具架构图的最上层是应用层,通过该工具,可以快速为安卓应用建模。
4.2 实验案例
EBook是一个简易的电子书应用,提供书籍列表、下载书籍、我的书籍、查看书籍内容的功能。EBook的架构包括手机客户端和服务器端。用户可以在手机客户端下载、翻阅电子书。服务器端则为客户端提供书籍的下载。
我们将使用Android应用框架自动生成工具生成应用的基本框架,再手动编写具体的业务逻辑代码以及服务器代码等。应用包括三个界面Book List Activity、My Book Activity、Book Content Activity。Book List Activity页面提供许多未下载的书籍,通过列表的形式列出,用户可以点击列表中的书进行下载。进入MyBook Activity可以查看已下载的书籍列表。点击已下载的书籍列表可以查看书籍的内容和信息。
4.3 实验
实例应用需将Book List Activity设置为起始界面,在BookList Activity中实现Button、List、Menu、Text View以及动态广播。然后,为Button、List加入点击事件监听器。在点击List时启动一个后台的Down Load Service来完成下载书籍的工作。下载完成之后发送Intent到动态广播,告知用户下载已完成。在点击该Button之后跳转到My Book Activity界面。My Book Activity包括一个Text View和一个List。List列出了所有已下载的书籍,点击List可以进入第三个界面Book Content Activity。此外,我们还为实例应用添加了安卓应用的常用权限存储卡访问权限、震动权限、Internet访问权限。存储卡访问权限用于保存、读取已下载的书籍。Internet访问权限主要用于下载书籍时访问互联网。震动权限用于发送消息给用户时震动提示用户下载完成。
使用Android应用框架自动生成工具生成实例需要创建一个新的Android工程,并在工程目录下创建应用的模型文件。然后拖拽右侧工具栏的模型元素,定义应用的模型节点和关联关系,例如设置组件之间的跳转,以何种方式启动Service,实现发送广播等。随后,在属性视图完成每个节点的属性定义。最后,使用代码自动生成功能将模型转换为代码,从而得到应用的代码框架,如图7所示。
在完成了上述配置后,通过代码自动生成功能可以得到工具自动生成的应用代码框架。我们在该框架的基础上手动添加了业务逻辑代码并且美化了界面。图8显示了EBook的运行效果,图中展示了应用的界面,包括书籍列表、我的书籍、查看书籍内容和版本信息界面。查看书籍内容界面则显示了某一本图书的内容。
5 结语
本文借鉴MDA思想提出了描述应用的元模型。同时,我们定义了代码框架模板并且提出了从元模型所描述元素到代码模板中可变部分的映射方法。基于本文提出的元模型,实现了Android应用框架自动生成工具。该工具可以为安卓应用创建描述应用的模型,生成应用程序的框架,包括源代码、配置文件、布局文件。实验部分通过一个实例演示了Android应用框架自动生成工具的功能。通过实验,体现了Android应用框架自动生成工具可以为开发人员快速构建应用模型,自动生成应用的代码框架。在此框架的基础上继续开发能够有效提高应用开发效率。
代码生成器 篇7
统一建模语言UML (Unified Modeling Language) 的出现和广泛应用大大提升了模型在软件开发中的作用, 以模型为核心的软件开发思想已逐渐为人们所接受, 近年来出现的模型驱动开发方法MDD (Model Driven Development) [1]就是这种开发思想的典型代表。尽管MDD与基于UML的面向对象的开发方法都以模型为核心, 但二者存在着重要的区别。基于UML的面向对象方法强调的是"大而全"的开发思想, 这主要是因为UML是作为一种通用建模语言而设计的[2], 它的功能不仅多而且复杂, 很难为非软件开发人员 (如领域专家) 学习和掌握。整个建模过程领域专家的参与度低, 往往是由软件设计人员独自承担, 且软件设计人员对领域知识不熟悉, 需要花时间学习, 造成不必要的资源浪费。MDD更注重应用领域, 它强调的是"小而专"的开发思想。也就是说, 它不要求像UML这样的通用建模语言提供很多很全的功能, 只要求提供一种面向特定领域的建模语言DSL (Domain Specific Language) [3]能快速有效地解决实际问题即可。由于DSL是面向特定领域的, 其提供的功能仅限于本领域且为领域专家所熟知, 所以简单易学, 整个建模工作可由领域专家独自完成。MDD的另一个开发思想是通过模型自动生成程序代码而无需手工编写, 这样开发人员的主要任务就变成程序代码生成器的设计了。
1、MetaEdit+
通常我们把设计DSL的工具软件称为元建模工具 (meta-modeling tools) , 为设计DSL所建的模型称为元模型 (meta-model) , 而建立元模型和设计DSL的过程称为元建模 (meta-modeling) 。由于领域专家对其所在领域更熟悉, 所以建立元模型、设计DSL以及建立模型的任务应该由领域专家来完成。因此在选择元建模工具时必须保证它简单易学, 这样只要对领域专家稍加培训他们即可掌握, 另外选择的元建模工具必须具有从模型直接生成程序代码的功能。
目前元建模工具有很多, 比较著名的有MetaEdit+、GME、DOME、EMF、GMF等[4], 从易用性和代码生成等因素考虑, 本文选择了MetaEdit+作为元建模工具。
MetaEdit+是一款出现较早、使用最广的商用元建模工具, 是一个基于图形的建模工具。实际上MetaEdit+是一个建模工具集, 它提供了一组建模工具, 如diagram editors, matrix editor, table editor browsers, report and code generation, method tools, API&XML connectivity, object repository等[5]。使用MetaEdit+可以大大缩短软件开发周期, 软件开发效率提高5-10倍[6], 因为它提供了强大代码生成功能, 整个开发过程可不必编写代码[7]。在下面的部分中, 本文以一个从系统功能结构图直接生成应用软件主窗口和菜单系统的例子为例详细介绍如何通过MetaEdit+实现模型驱动下的Java代码自动生成。
2、基于MetaEdit+的元建模
在设计代码生成器之前, 首先要建立领域模型。在UML中通常用类图对静态结构建模, 用顺序图, 状态图对动态行为建模[8], 而MetaEdit+是一种元建模工具, 它并没有事先将图分为类图、顺序图或状态图等, 图的含义是由建模者自己定义。通过MetaEdit+提供的工具建模者可以设计具有自己风格的图形符号和图。
2.1 建立领域元模型
本文中建立领域元模型就是设计绘制系统功能结构图和代码生成所需的各种图形符号和元素。具体地讲, 就是创建MetaEdit+的Property (属性) 、Object (对象) 、Relationship (联系) 、Role (角色) 和Graph (图) 。
2.1.1 创建属性
通过MetaEdit+的Property Tool工具创建每个对象的属性, 创建的属性及说明如下:
Name[Software]:对象的名称
Index[Software]:对象在菜单中的顺序
CallFunction[Software]:叶菜单项调用的功能模块
2.1.2 创建对象
元模型的对象实际上就是模型中的类, 通过Object Tool工具创建。创建的对象有:
MainWindow[Software]:主窗口
Object[Software]:一般对象, 如菜单、菜单项
MainWindow[Software]对象设计界面如图1所示, 其中左图的窗口可以定义对象包含的属性, 右图的窗口用于设计表示对象的图形符号。
2.1.3 创建联系
使用Relationship Tool工具创建对象之间的联系如下:
FromTo[Software]:MainWindow[Software]与Object[Software]之间以及Object[Software]与Object[Software]之间的联系。
2.1.4 创建角色
用Role Tool工具创建角色如下:
From[Software]:连接父对象
To[Software]:连接子对象
2.1.5 创建图
通过Graph Tool工具创建的图为:
FunctionStructure:可用于设计功能结构图的元模型图。图的绑定情况如图2所示:
2.2 建立领域模型
建立领域模型就是用上面所建元模型绘制系统功能结构图, 可通过Diagram Editor工具来设计, 图3给出了设计好的Shopping System的功能结构图。
3、代码生成器设计
模型建好后, 接下来就是设计代码生成器将模型直接转换为Java程序而无需手工编写一行Java语句。也许有的读者会说:"设计代码生成器的工作量可能和设计菜单程序的工作量差不多", 初次设计的确如此, 但它们有质的不同, 因为代码生成器设计好后可用于生成任何系统的菜单程序, 只需绘制出功能结构图即可。这说明生成器的重用性好, 另外, 如果前面设计的功能结构图已做了多处修改, 这时, 只需重新运行代码生成器生成一下代码即可, 而无需修改生成器, 这说明系统易维护性好, 这对那些变化频繁的应用领域特别有意义。
MetaEdit+提供了功能强大的代码生成功能, 包括生成器定义语言MERL和生成器设计器Generator Editor。限于篇幅, 下面只给出主生成器_MainWindow[Software]的设计代码及说明:
为了能生成任意多级菜单 (级数只取决于功能结构图的层次数) , 代码生成器_Menu[Software]采用了递归调用设计方式。
4、结语
我们用MetaEdit+设计的代码生成器可以直接从图形模型自动生成Java代码, 所生成的代码可以在Netbeans6.0和eclipse6.0环境下正常运行。限于篇幅, 本文所述的模型驱动下的代码自动生成方法只介绍了系统主窗口和多级菜单的生成, 虽然如此, 但它不失一般性。也就是说, 用类似的方法我们还可以设计各种功能模块, 如输入对话框的设计[9], 关于功能模块的设计将另文论述。总之, 模型驱动下的代码自动生成方法可以大大提高软件开发速度, 使得软件维护变得更简单更容易, 必将成为未来软件开发的主要方法。
参考文献
[1]Richard S, David S F, John P.The MDA Journal:Model Driv-en Architecture Straight from the Masters[M].New York:MeghanKiffer Press, 2004:135-146.
[2]谢正良, 赵建华, 李宣东等.一种基于J2EE平台的MDA模型转换技术[J].计算机应用研究, 2005 (3) :51-54.
[3]Martin F.Domain Specific Languages[M].Toronto:Addison-Wesley Professional, 2010.
[4]刘辉, 麻志毅, 邯维忠.元建模技术研究进展[J].软件学报, 2008, 19 (6) :1317-1327.
[5]周金根.MetaModelEngine:元模型引擎开发思路[EB/OL].[2010-10-12].http://www.cnblogs.com/zhoujg/archive/2010/07/28/1786155.html
[6]MetaCase.MetaEdit+Domain-Specific Modeling environment[EB/OL].[2010-09-10].http://www.metacase.com/MetaEdit.html.
[7]Juha-Pekka T.MetaEdit+:MetaEdit+:integrated modeling andmetamodeling environment for domain-specific languages[C].//Companion to the 21st ACM SIGPLAN symposium on Object-oriented programming systems, languages, and applications.NewYork:ACM, 2003:690-691.
[8]李思广, 林子禹, 胡峰, 等.基于UML的软件过程建模方法研究[J].计算机工程与应用, 2003, 39 (6) :76-78.
代码生成器 篇8
早期的嵌入式装置由于内存、空间等资源有限, 通常采用汇编语言实现相关程序功能, 之后硬件升级换代, 逐渐转入到C语言开发。在复杂的应用领域, 由于C代码量大, 如果手工编写, 存在维护、理解困难的情况。图形化编程是一种面向对象的软件开发方法, 用各种编程符号块搭建程序模型, 用图形化页面设计应用功能, 并将图形程序转换并编译为目标代码, 随后下载到嵌入式装置运行。与传统的手工编写全部代码的方式相比, 图形化编程采用经过测试验证的模块化功能符号搭建程序功能, 开发效率高, 维护调试 方便 ,直观易懂, 已经在电力系统保护测控装置中进行了应用[1,2,3,4,5]。文献 [1] 以Java语言构建一套图形化编程系统, 编程符号块按照IEC 61850的逻辑节点模型设计, 已在中低压微机保护中研发进行了应用。文献 [2] 论述了图形化平台微机保护系统的结构功能、保护元件库的构成, 并对图形化编程和常规编程进行了比较。文献 [3] 介绍了护测控装置配套软件PCS-Ex-plroe研发版本的设计思路 , 将保护测控功能进行模块化设计 ,并结合IEC61850标准进行逻辑节点建模, 用图形化元件的方式实现可视化配置建模, 是一种免编译的配置集成方法。文献 [4] 介绍了基于IEC61131-3标准的可编程功能在变电站中的实现方案、软件功能模型、执行器软件的设计方法, 并在备自投装置中进行了实现。文献 [5] 介绍了该继电保护软件平台的结构, 包括集成环境、装置信息配置工具、编程开发工具、逻辑编译器等功能。
在图形化编程平台软件中, 代码生成是个关键步骤, 如何将图形化的页面程序转换为不同硬件能运行的目标代码尚未有文献专题阐述。先简要介绍跨平台图形化编程平台软件的模块划分, 重点介绍代码生成的若干技术, 包括图形化编程符号的建模方法、图形化符号的拓扑排序算法, 最后介绍了基于开放API接口和脚本调用的代码生成方法、。该方法可灵活定义符号功能, 可形成适合交直流保护测控、柔性输电控制、电力电子等不同场合使用的高质量C代码。
2 图形化编程工具概要
图形化编程工具由图形编辑模块、编程符号库、代码生成模块、编译链接模块、 调试下载模块组成。软件采用C++开发, 界面和图形库基于QT4.7实现, 支持Windows/Linux环境下运行。软件按照分层结构组织应用程序, 以图形化方式展现程序逻辑。图形化编程软件5个主要模块功能如下:
(1) 图形程序编辑模块 : 按照装置-插件-应用的层次结构管理页面程序, 一个工程管理若干个插件, 一个插件管理若干元件, 一个元件管理若干页面。在页面内实例化编程符号块, 按照逻辑关系, 绘制符号间输入输出连线。图形编辑模块支持符号的复制、粘帖、删除、 移动、缩放、取消、恢复等编辑功能。
(2) 编程符号库 : 各种保护逻辑、运算等编程符号块符号文件, 通过符号编辑器设计编程符号块, 可定义代码行为、数据属性、图形外观等内容。
(3) 代码生成模块 : 该模块解析图形页面 , 形成适合不同应用场景运行的C代码。
(4) 编译链接模块: 该模块通过分析C文件依赖关系, 形成Makefile, 并调用交叉编译器将C程序编译链接成目标文件。
(5) 调试下载模块 : 和装置进行通信 , 下载目标文件。并支持以可视化的方式进行图形程序的调试, 在连接线上显示变量实时运行值。
3 图形化程序代码生成技术
图形化代码生成的主要技术包括: 编程符号块的XML建模方法、图形页面的的拓扑排序算法、代码文本形成方法等。
3.1 编程符号块 XML 建模方法
图形化编程采用功能符号作为编程的最小粒度单元, 编程软件提供了常用的符号库, 当面向不同的装置应用时, 存在一些特殊功能需求的需要扩展新的符号块实现, 为方便应用开发人员能基于符号编辑器开发专有的符号块, 需提供一种简单、通用、可扩展的符号建模方法。已有的一些建模方法, 代码体受制于特定格式, 或目标代码受限于专用的编译器[1,2,3,4]。提出一种容易扩展、方便维护的符号块建模方法 , 基于XML的建模方式 , 将编程符号块分为图形表示和代码实体定义两部分。
编程符号块的图形信息包括: 输入输出管脚信息、包围矩形外框信息、在框内显示的参数值、名字等文本内容, 还包括一些个性化的辅助图形信息, 将这些基本图元组合为一个整体。以逻辑与符号OR2的图形为例, XML图形数据建模如下:
其中input表示输入, 有形参名为in1、in2的两个输入点,output表示输出点 , 有名字为out1的输出点 , x表示左上角横坐标, y表示左上角纵坐标, w表示宽度, h表示高度, na表示变量名字, tv表示字符串, text表示文本, rect表示矩形。
常规符号 块的代码 对应1个C函数、以 及相关私 有变量, 在代码实体文件中定义符号的数据和函数。符号的代码基于CDATA整段文本描述, 依次可划分: 帮助文本段、in-clude文件段、参数声明段、 符号变量定义段、变量初始化设置段、功能函数定义段、 脚本段 (可选), 采用类C语言描述, 在后端处理时主要进行形参-实参变量名替换、参数值替换、 条件编译语句处理。例如以某监视块为例, 其代码行为建模如下:
应用人员只需编写上述格式的文本段, 符号编辑器可以分析该文本, 提取符号输入、输出、参数等接口, 根据变量个数自动形成符号的默认图形, 形成符号的图形和代码XML描述文件。
3.2 页面符号排序算法
拓扑排序: 由某个集合上的一个偏序得到该集合上的一个全序, 这个操作称之为拓扑排序[6]。在图形化代码生成中 ,拓扑排序指分析编程符号间的连接关系, 决定符号的执行调用顺序。全序: 设R是集合X上的偏序, 如果对每个x,y∈X必有x Ry或者y Rx, 则称R是集合X上的全序关系[6]。
搭建完图形程序后, 需要对图形页面进行排序, 图形化程序的符号执行顺序是代码生成的关键步骤。已经介绍的排序算法有: 基于符号的拓扑图转换为AOV网、人工指定顺序[1]。基于AOV网的方法, 是把图形符号之间拓扑连接关系用有向图表示, 在图中用顶点表示活动, 用弧表示活动间优先关系。在AOV网中, 不应该出现有向环, 这意味着某项活动以自己为先决条件, 程序的执行流图存在闭环死锁依赖, 但实际应用中图形程序中往往存在反馈闭环, 这需要在排序时进行破环处理。人工指定顺序存在的问题是当图形化页面数量很多时, 设置维护的工作量很大, 例如在编程中间插入一个符号时, 需要手工调整后续符号的排序值。提出一种图形化图形程序的自动化排序算法, 先按照任务队列中页面调度顺序决定页面之间的执行顺序, 实现粗粒度排序, 然后在单页面内, 按照符号位置坐标和输入输出依赖关系形成符号执行顺序。该算法由3个步骤实现。
(1) 将图形化页面的坐标原点设置为图形页面左上角 , x坐标从左到右递增, y坐标从上到下递增, 使用插入排序算法, 先将符号按照y坐标进行升序排序, 当符号的y坐标相同时, 按照符号的x坐标升序排列。这种从左到右、从上到下的初步排序, 符合图形化图形程序的机器视觉。
(2) 图形化页面程序的闭环检测和破环处理。利用符号之间输入输出点的拓扑连接关系形成符号的前驱链表和后继链表, 通过某符号的后续链表递归遍历, 若可回溯到源符号,则存在闭环。如果存在破环符号, 定义如下处理原则: 弱化约束条件, 如果符号存在一个未知的输入, 可优先输出。
(3) 将经过破环处理后的符号间拓扑连接关系转换成改进的AOV网, 按照符号的拓扑依赖关系形成前驱链表和后继链表。在AOV网中以深度优先的法则选一个没有前驱的顶点并输出; 从图中删除该顶点和所有以它为尾的弧; 迭代处理每个节点, 直到处理完页面上的所有符号。
在图1中, 存在4个跨页面连接输入变量, 输入点都为0, 都满足执行条件 , 根据位置初排结果 , in0、in1、in2、in3可先执行 (实际等效于置后续连接输入点处于已知状态)。A、B、C都有1个输入点处于未知 , 并存在闭 环 , 此时弱化 约束, 降格为有1个输入未知的符号可排序, 结合位置初排结果, 最终编程符号块执行顺序为A、B、C。
3.3 基于 C++API 和 Python 混合调用的代码生成技术
3.1节介绍的编程符号块 , 有Rule可选字段 , 对于常规的逻辑、数学运算符号, 可以为空, 但存在一些编程符号, 其代码不一定能用一个任务函数表示, 对应的代码可能是在分布中系统初始化中、或者有部分设置信息需关联输出到配置文本、IEC 61850建模文件中, 此时需要编程软件具有较强的灵活性, 即代码生成工具能提供一些开放的接口, 可以在符号脚本段中调用, 工具通过解释执行脚本段, 完成相关功能。QT库的QObject类和派生类有property属性和c++slot接口 ,可以在Python解释引擎中注册相关实体对象, 在脚本中调用注册对象的API接口, 脚本执行后, 后端处理器输出文本内容, 从而实现灵活可定制的代码输出功能。
以装置为例, 其类定义如下, 其中inst Name为装置实例名属性, get Board Num()是返回插件个数的接口, get Board Num()、get Inst Name()、set Inst Name() 等属性操作、公共slot函数都可作为API服务接口, 在脚本中使用。
基于C++API和Python混合调用的代码生成的关键技术如下:
1) 定义可编程对象和API函数。可编程对象定义包括 :装置 (app)、插件 (board)、元件 (comp)、页面 (page)、符号(sybl)、代码生成器 (coder), 在脚本执行前 , 往脚本引擎中注册这些实体对象, 即可调用相关API服务接口。这些对象的提供的典型slot接口包括:
1装置app-slot: 装置管理若干插件, 可被脚本访问的slot有获取插件个数、根据名字、 槽号查找插件实例 ; 返回整个工程的路径、文件名等信息。
2插件board-slot: 插件管理若干元件, 提供的slot有获取插件的CPU型号、返回元件个数、基于元件名查找元件实例;在插件范围内查找某个指定的符号。
3元件comp-slot: 元件管理若干页面, 提供的slot有获取元件的结构名、实例名、返回页面个数、基于页面名查找页面实例等。
4页面page-slot: 页面管理若干符号和连接线 , 提供的slot有获取页面名、页面任务等级、 符号个数、基于符号ID或序号查找符号实例等。
5符号sybl-slot: 编程符号开放的接口有: 返回输入、输出、参数个数, 根据名字查找变量; 获取符号的ID、排序序号; 根据输入输出点获取关联的连接线对象等。
6代码生成 器coder-slot: 往各子文 本段落的 添加、前置等。
2) 在符号脚本段中调用注册对象的API函数。以一个设置插件4个等级任务周期的符号为例, 其功能是根据4个参数值, 输出4个系统软件定义的全局变量:
上述文本中, sybl是当前符 号块 , get Set Value是开放的API函数 , 返回参数设置值。
3) 启动脚本引擎执行脚本。脚本引擎基于基于Python-Qt2.0库开发 , 封装了相关执行接口 , 执行1个符号脚本的关键伪代码为:
4) 后端输出代码文本 , 将元件对应的c文件的代码文本划分为COMP_VAR (变量定义)、LOCAL_FUNC (局部函数)、PAGE_NEW ( 页面构造 函数 ) 、PAGE_INIT ( 页面初始 化函数)、PAGE_RUN (页面运行函数)、COMP_NEW (元件构造函数)、COMP _INIT (元件初始化函数)、COMP TASK (元件任务函数) 等段落进行组织, 顺次输出相关内容。
4 结语
基于XML的编程符号块函数建模方法, 将编程符号块函数分为若干段, 用C语言编写其中的代码段, 成员变量定义灵活、易于移植, 使功能函数适合多种CPU运行。一种图形化程序拓扑排序算法, 先以位置视觉进行初步排序, 并自动检测闭环和进行破环处理, 最后按照数据流依赖关系形成运行符号执行调用顺序, 无需人工干预过程, 提高了编辑效率。基于C++API和Python调用的代码生成方法, 能去除对系统软件的接口耦合, 并可在多个的应用场景下使用。本方法形成的代码在多个直流保护控制工程 (例如哈郑直流工程、舟山柔性输电控制工程)、SVC、SVG、SFC等电力电子应用程序开发中进行了成功应用。
摘要:研究了嵌入式装置图形程序代码生成的技术,采用XML描述编程符号块,基于数据流依赖对图形符号进行拓扑排序,通过C++开放接口和Python脚本调用相结合形成代码,最终形成高质量的C代码。介绍的图形化代码生成技术已经在保护控制程装置中进行了批量应用。