热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

用ATLActiveX绘制任意平面函数的曲线

作者:杨老师下载源代码关键词:脚本、ATL、ActiveX、脚本引擎、表达式计算、IActiveScript、IActiveScriptSite一、前言这是非常有挑战性的题目。
作者: 杨老师

下载源代码

关键词
:脚本、ATL、ActiveX、脚本引擎、表达式计算、IActiveScript、IActiveScriptSite

一、前言
  这是非常有挑战性的题目。对于用户输入的任意一个平面函数f(x),绘制出其函数曲线。这里最关键的技术难点就是如何实现计算表达式的值。在《编译原理》和《数据结构》的书中,都有对表达式运算方法的论述。说实在的,在编译型计算机语言中实现对用户输入表达式的运算是非常困难的。需要对表达式进行扫描,去括号,按照运算符的优先级生成2叉树,然后遍历该树生成逆波兰表达式,再然后通过栈的方法进行运算。如果在表达式中再包含有函数的话......描述起来都麻烦,更不要说用程序实现了:-(
  编译型语言不容易实现,那么解释性语言又如何那?有的解释性语言是可以的,但需要一些实现的技巧,而大多数解释性语言光依靠自身功能还是不能完成的。80年代末期,我在 AppleII 的 BASIC 上使用预留程序空间的方式实现了这个功能,能想到这个解决方案,当时还自我陶醉了好多天那 :-)
  最好的,效率最高的解决方案当然是《编译原理》里所描述的方法,但是实现起来的确有一定的难度。上中学的时候,首次接触到计算机和计算机语言,我就立下了“雄心壮志”,将来一定发明一个自己的计算机语言。上大学的时候,我咨询《编译原理》课老师,“学习完成后,能否自己发明计算机语言?”我得到了老师肯定的回答----“别做梦了!”:-( 毕业工作后,我也成为了一名计算机老师,一个偶然的任务,让我重新萌发了我不死的“贼心”。由于实验室的 Z-80 单板机数量有限,试验台又太占地方,结果学生需要5,6个人分一组一起做实验,教学效果太差。于是领导分配给我一个任务:在PC机上作一个Z-80的仿真环境,也就是在PC机上实现一个Z-80的交叉汇编和 DEUBG 调试环境。还好,由于在汇编级别上进行仿真,并不困难,只要经过比较简单的语法分析和词法分析,然后查表给出汇编的二进制机器码,任务就完成了。在此次任务的过程中,积累了一些语法、词法分析的经验,于是,我开始了真正创造计算机语言的工作,并最终完成。语言虽然发明了,功能非常有限,但我主要的“贼心”已然实现,也就没有什么兴趣继续完善它了......不久前,看到 VCKBASE 上一个即将毕业的学生发表的文章和代码,实现了C的编译器。虽然还比较简陋,但比起我们当年,现在的学生水平(至少这个学生)另我刮目相看。
  好了,言归正传,看看今天这个题目的实现方法吧。既然用表达式分析实现起来非常困难,那么换个思路,用我们的C++编译型语言动态地构造出计算表达式的脚本,然后执行脚本,让脚本引擎帮我们去计算就是了。我用ATL写了个ActiveX的控件,下图就是事例程序在“控件测试容器”中的表现。你也可以在其 它环境下去使用它,比如在HTML中。


图一 控件测试容器中运行的函数曲线绘制控件

二、如何执行脚本
  脚本的应用很广泛。HTML中可以嵌入脚本;Internet服务器也可以执行脚本(ASP,JSP...);MS Office提供了功能非常丰富的脚本语言VBA;现在流行的安装程序也使用脚本;XML的解析也可以使用脚本;还有Shell的批处理......在我们的程序中如何实现脚本的调用功能呢?

<2.1> 建立执行脚本的主机
  为了能够执行脚本,你的程序必须要建立并完成IActiveScriptSite 的接口对象。这个接口有8个方法:

HRESULT GetLCID(LCID *plcid)

  脚本引擎在准备执行脚本程序的时候,它首先要调用这个函数来询问脚本所使用的语言环境。你可以简单的返回 E_NOTIMPL,那么引擎就会使用当前系统默认使用的语言。

HRESULT GetItemInfo(LPCOLESTR pstrName,
    DWORD dwReturnMask,
    IUnknown **ppunkItem,
    ITypeInfo **ppTypeInfo)

  脚本引擎执行前调用这个函数,它需要取得两个接口指针:一个是类型库的指针,因为类型库中保存有函数的参数信息(类型库本质上其实就是IDL文件的二进行形式),有了它,引擎才知道如何执行脚本中的函数;另一个指针是IUnknown, 脚本引擎将来会通过它调用 QueryInterface 取得IDispatch指针,然后就可以调用IDispatch::Invoke()执行脚本中的函数了。
  另一个要说明的参数是 pstrName。一个脚本引擎对象可以同时处理多个脚本项目,因此需要通过一个项目名称来区分多个不同的脚本项目。项目名称是通过 IActiveScript::AddNamedItem() 函数来指定的。在GetItemInfo() 函数中,你要通过pstrName这个参数来区分不同的项目,给出相应的IUnknown和ITypeInfo 的指针。

HRESULT GetDocVersionString(BSTR *pbstrVersionString)

  脚本引擎需要通过唯一的一个字符串在适当的时候保存和装入文档的状态,比如在IE中调用记事本编辑HTML源文件。你可以简单的返回 E_NOTIMPL,则脚本引擎默认同步使用文档。

OnScriptTerminate(VARIANT *pvarResult,EXCEPINFO *pexcepinfo)

  脚本引擎执行结束后,在OnStateChange 之前调用这个函数,同时 SCRIPTSTATE_INITIALIZED 已经设置完成。参数pvarResult中传递脚本的执行结果,如果为NULL表示脚本没有执行结果。pexecpinfo为NULL表示脚本执行没有错误,否则你可以从这个结构中取得发生异常的 具体信息。

HRESULT OnStateChange(SCRIPTSTATE ssScriptState)

  脚本引擎在执行脚本过程中,当状态发生改变的时候,调用该函数。更多的状态信息,可以参考IActiveScript::GetScriptState()函数。

HRESULT OnScriptError(IActiveScriptError *pase)
HRESULT OnEnterScript(void)
HRESULT OnLeaveScript(void)

  以上三个函数比较简单,当脚本发生错误,脚本开始执行,脚本执行完毕的时候,调用这些方法来通知你的脚本主机。在错误通知的函数中,你可以根据错误原因做相应的处理。
  另外,IActiveScriptSite接口并不提供窗口功能。如果想让脚本实现与用户的界面交互,那么你还需要实现IActiveScriptSiteWindow的接口。脚本引擎会通过IActiveScriptSite::QueryInterface() 来查询这个接口并使用它。

<2.2> 建立能与脚本交互的自动化对象
  若想让脚本引擎在执行脚本的过程中,与你的程序进行交互,或者说你希望脚本可以调用你扩展的脚本函数。那么你需要建立一个自动化的对象,在IDispatch接口上提供后绑定的方法和属性,然后把这个对象的类型库和IUnknown的接口指针,在IActiveScriptSite::GetItemInfo()的调用中,传递给脚本引擎。

<2.3> 如何使用脚本引擎
  脚本引擎,也是一个COM对象。它提供IActiveScript和IActiveScriptParse接口。目前在Windows平台上,微软提供了Vbscript、JScript 等多个脚本引擎。当然,你也可以自己发明一个脚本语言,然后实现引擎所需要的接口并正确注册类型后,那么在Windows平台上就可以运行你的语言了。想象一下在HTML中可以如下使用你自己的语言,该是多么爽的一件事呀。(只可惜,我发明的语言,目前只有在我自己吃饱了饭后,孤独的自我陶醉而已。)


… …

  脚本引擎 IActiveScript有13个方法,IActiveScriptParse有3个方法。这么多函数中,其实我们只需要调用5个就能满足大多数情况的需求了。具体的函数功能和参数说明,请大家参照MSDN,我就不详细描述了。如下所示是使用引擎的一般步骤:
  1. CoCreateInstance() 建立引擎的COM对象,并得到IActiveScript接口指针。
  2.通过QueryInterface 查询得到 IActiveScriptParse 脚本引擎解析的接口指针。
  3.调用 IActiveScriptParse::InitNew() 初始化脚本引擎的解析对象
  4.调用 IActiveScript::AddNamedItem() 指定本次使用引擎的项目名称。
  5.调用 IActiveScriptParse::ParseScriptText() 提交脚本的文本。
  6.调用 IActiveScript::SetScriptState() 开始执行。
  7.调用 IActiveScript::Close() 关闭引擎,释放接口指针。
  第一个步骤中,要提供脚本引擎的CLSID或ProgID。当前的Windows平台提供了5种引擎:

脚本引擎 ProgID CLSID
Vbscript Vbscript {B54F3741-5B07-11CF-A4B0-00AA004A55E8}
Vbscript encoding Vbscript.Encode {B54F3743-5B07-11cf-A4B0-00AA004A55E8}
JScript JScript {F414C260-6AC0-11CF-B6D1-00AA00BBBB58}
JScript encoding JScript.Encode {F414C262-6AC0-11CF-B6D1-00AA00BBBB58}
XMLScript XML {989D1DC0-B162-11D1-B6EC-D27DDCF9A923}

  第四个步骤,AddNamedItem()的时候,引擎会调用主机IActiveScriptSite::GetItemInfo()的方法,用来取得与脚本交互的自动化组件的类型库和 IUnknown 指针。

三、事例程序的实现
  事例程序是一个用ATL写的ActiveX控件。实现了对用户输入的一个 f(x) 函数,在当前的 ActiveX 的窗口区域中进行函数曲线的绘图功能。由于实现的是一个 ActiveX 控件,它本身就提供了IDispatch的自动化接口,因此这个ActiveX对象,既是一个脚本主机(IActiveScriptSite),又是一个和脚本交互的自动化对象。
  程序的工作原理:当用户输入一个f(x)的函数式后,把这个输入按照属性提交给ActiveX对象,于是 ActiveX 开始工作。它根据目前窗口区域的像素宽度和横轴(X),纵轴(Y)的区间范围,用循环构造并执行Vbscript脚本程序。比如用户输入的函数是sin(x),X的区间范围是[-4,+4],那么在这个区间中共计算200次,每次给出一个 适当的x值,调用脚本计算出y值,然后画点绘制函数曲线。下面这个脚本就是200次调用中第一次调用所动态生成的脚本代码:

    '' 本次调用的序号
    i = 0

    '' 本次调用的计算点,自变量 x 的值
    x = -4.0

    '' 计算出 y=f(x) 的值
    y = sin(x)
 
    '' 把结果传送回与脚本交互的自动化对象(当然,这里就是 ActiveX 对象本身)
    call Result( i, x, y )
  事例程序中,使用的是Vbscript脚本引擎,你可以修改源程序中启动脚本引擎的参数来指定上表中任意一个引擎。当然,我们输入的函数表达式,就要遵照相应的脚本语言的语法了。下表列出了可以在Vbscript中使用的算术运算符号和函数,方便读者使用:
 
+、-、*、/、^、MOD、/ 加、减、乘、除、幂、模、商
Abs() 绝对值
Sgn() 判断正负数
Sqr() 平方根
Int() 舍弃小数,如果输入是负数,则取得小于输入值的最大负数
Fix() 舍弃小数,如果输入是负数,则取得大于输入值的最小负数
Round() 四舍五入
Log() e为底的对数
Exp() e的幂
Sin() 正弦
Cos() 余弦
Tan() 正切
Atn() 反正切
  
在ActiveX中,作为演示,我又扩展了一些方法和属性,你同样可以在函数式中使用:
 
Result(i,x,y) 方法 回传给自动化对象坐标点。不要使用,这个是动态地,自动地添加到脚本的最后一行中的调用。
Pi 只读属性 其实就是3.1415926,比如可以这样使用 sin(x * pi)
log10() 方法 10为底的对数

四、结束语
  本文介绍的重点是在Windows程序中调用脚本的方法。绘制任意的函数曲线,只是脚本调用功能的一个演示。你可以使用脚本引擎实现更多、更有创造性的功能。

好了,到这里,就到这里了,祝大家学习快乐^_^ 


推荐阅读
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • Linux环境变量$PATH的作用及使用方法
    本文介绍了Linux环境变量$PATH的作用及使用方法。$PATH是一个由多个目录组成的变量,用冒号分隔。当执行一个指令时,系统会按照$PATH定义的目录顺序搜索同名的可执行文件,如果有多个同名指令,则先找到的会被执行。通过设置$PATH变量,可以在任何地方执行指令,无需输入绝对路径。 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
  • 本文介绍了一种图片处理应用,通过固定容器来实现缩略图的功能。该方法可以实现等比例缩略、扩容填充和裁剪等操作。详细的实现步骤和代码示例在正文中给出。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • 解决Sharepoint 2013运行状况分析出现的“一个或多个服务器未响应”问题的方法
    本文介绍了解决Sharepoint 2013运行状况分析中出现的“一个或多个服务器未响应”问题的方法。对于有高要求的客户来说,系统检测问题的存在是不可接受的。文章详细描述了解决该问题的步骤,包括删除服务器、处理分布式缓存留下的记录以及使用代码等方法。同时还提供了相关关键词和错误提示信息,以帮助读者更好地理解和解决该问题。 ... [详细]
  • 本文介绍了Cocos2dx学习笔记中的更新函数scheduleUpdate、进度计时器CCProgressTo和滚动视图CCScrollView的用法。详细介绍了scheduleUpdate函数的作用和使用方法,以及schedule函数的区别。同时,还提供了相关的代码示例。 ... [详细]
  • 本文详细介绍了在Centos7上部署安装zabbix5.0的步骤和注意事项,包括准备工作、获取所需的yum源、关闭防火墙和SELINUX等。提供了一步一步的操作指南,帮助读者顺利完成安装过程。 ... [详细]
author-avatar
ougq
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有