程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 用ATL ActiveX繪制任意平面函數的曲線

用ATL ActiveX繪制任意平面函數的曲線

編輯:關於VC++

一、前言

這是非常有挑戰性的題目。對於用戶輸入的任意一個平面函數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中可以如下使用你自己的語言,該是多麼爽的一件事呀。(只可惜,我發明的語言,目前只有在我自己吃飽了飯後,孤獨的自我陶醉而已。)

<HTML>
<Script language="YouScript">
… … // 你自己發明的腳本語言程序
</Script>
… …
</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程序中調用腳本的方法。繪制任意的函數曲線,只是腳本調用功能的一個演示。你可以使用腳本引擎實現更多、更有創造性的功能。

好了,到這裡,就到這裡了,祝大家學習快樂^_^

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved