伯乐共勉

讨论。NET专区
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

ActiveX Scripting[转]

Posted on 2009-09-03 10:21  伯乐共勉  阅读(569)  评论(0编辑  收藏  举报
ActiveX Scripting技术(一)
    吕思伟 潘爱民
    ActiveX Scripting技术是Microsoft ActiveX技术的一个组成部
分,它的主要目的是使应用程序在不被修改的情况下,为各种脚本语言
所控制。在软件交互性不断提高的今天,仅仅提供菜单或工具箱的界
面已经不能满足用户的需要了,软件的可定制特性已经成为当今软件
的一项基本特征,尤其对于一些通用的软件更为如此。大家比较熟悉
的Microso ft Office,比如Word字处理软件,它不仅提供了界面的任
意定制,还提供了方便的BASIC语言的可编程特性,用户可以通过编写B
ASIC语言实现较为复杂的功能扩充。
    Microsoft提供的ActiveX Scripting技术可使软件扩充变得非常
简单,软件开发商利用脚本引擎(Script Engine)支持脚本语言的解释
和执行操作,而软件用户可以根据需要编写自己的脚本代码,交由软件
处理。对于用户来说,就好像自己在编写程序控制应用程序,以完成自
己所期望的功能。而应用软件并不需要自己去解释执行用户的脚本代
码,只要利用脚本引擎就可以很方便地实现对用户脚本语言的支持。
应用系统也可以利用这种技术来提供二次开发的特性。
    虽然脚本引擎提供了脚本语言的解释执行的功能,但要用好Activ
eX Scripting技术则需要对它有一个全面的了解。本系列讲座将对Ac
tiveX Scripting技术作一探讨,并给出一个具体例子,以使读者进一
步理解ActiveX Scripting技术。
    一、ActiveX Scripting背景
    ActiveX Scripting是Microsoft的Automation技术和Script技术
结合的产物。因此,在介绍ActiveX Scripting之前,首先我们来看看A
utomation和Script两种技术的发展情况。
    1. Automation技术是Microsoft OLE技术的一部分,它可使解释
性的宏语言(主要是Visual Basic)能够在不了解应用程序的实现细节
的情况下控制Automation对象。随着V isual Basic软件的发展以及M
icrosoft Office套件的广泛应用,Automation技术已经成为连接这些
应用或者工具的纽带。而且,更多的应用把这种宏语言作为自己应用
扩展的手段,例如Word、Access以及Notes都把BASIC类语言作为其开
发语言,并且这些BASIC类语言均支持Automation对象;另一方面,Inte
rnet浏览器也提供了脚本引擎,可用于解释网络页面脚本语言中的Aut
omation对象。所有这些应用能够得以不断发展,在很大程度上是因为
这些BASIC语言或脚本语言提供了对Automation的支持。
    Automation技术以COM(组件对象模型)为基础,所有的Automation
对象都实现了标准的IDispatch接口,通过IDispatch接口暴露对象的
属性和方法,以便在客户程序中使用这些属性并调用它所支持的方法
。Automation对象的客户程序或者宿主程序通过类型库(T ypeLibrar
y)获得对象运行时刻的类型信息,并提供事件处理。宏语言解释器或
者脚本引擎根据对象的类型信息,把其中对对象属性和方法的引用解
释为对IDispatch接口成员函数Invoke的调用,从而实现对对象的控制
。
    2. Script技术是指脚本语言的技术,目前主要用于Internet浏览
器中,它可实现对页面的交互处理。我们知道,HTML是一种描述性的语
言,交互能力很弱,但通过Script技术, 可以编制出一些内容生动、具
有极强交互性的网络页面;并且使用Script技术的另一个好处是,它能
够减轻服务器端计算的负担,把部分计算工作转移到客户端来完成。
目前VBSc ript和JavaScript语言在网络浏览器上应用非常广泛,而且
一些主要的浏览器也提供了相应的引擎用于处理网页中的脚本语言。
    通常来说,用于网络浏览器的脚本语言具有以下特点:(1)它本身
是一门解释性语言, 所以语言的语法简单,但执行效率相对较低;(2)
它采用了事件驱动机制,脚本语言主要用于对交互事件作出响应,脚本
语言程序的主体是事件处理过程;(3)与浏览器内在的对象模型结构结
合紧密,脚本语言单独作为编程语言的价值很小,只有同特定的对象模
型结构相结合后才能够体现出其控制能力。在HTML的脚本程序中用户
可以直接使用如Window、Pa ne、Document等浏览器结构元素,并对其
进行控制,产生各种效果。此外脚本语言还可以对页面上的Java Appl
et和ActiveX Control进行操作。能够对宿主应用的对象进行控制正
是脚本语言的真正价值所在。
    从上面对两种技术的讨论中不难看出,Automation技术作为对应
用程序进行外部控制的成熟而有力的手段,其设计目标与Script技术
有许多共同之处。而Automation技术的基础COM技术本身是一种语言
无关的软件模型,一个很自然的想法是通过COM来统一实现对对象的控
制和对多种脚本语言的无缝支持。这种想法体现在实践上就形成了Mi
crosoft的A ctiveX Scripting技术。
    二、ActiveX Scripting结构
    从技术的角度来看,ActiveX Scripting技术实际上是一组COM接
口的定义,通过这组接口建立应用程序和脚本引擎之间的关系。脚本
引擎是ActiveX Scripting技术的实现, 一个应用系统如果实现了有
关的标准接口,那么它就可以通过脚本引擎提供对用户脚本语言的支
持。在介绍这组接口之前,我们先看看应用系统、脚本引擎和脚本文
件三者之间的关系。
    应用系统为了支持脚本语言,首先它要实现几个标准接口,然后它
把自己的一些被控对象暴露出来;脚本文件是一个文本文件,文件中包
含了一些程序代码;脚本引擎本身是一个COM对象,它负责对脚本文件
的解释和执行工作,在必要时通过应用系统的接口与其交互。三者的
关系如图1所示。
图1 应用系统、脚本引擎和脚本文件三者之间的关系
    在图1中,应用系统首先需要创建脚本引擎对象,并设置必要的参
数,然后装入脚本文件,再启动引擎,使引擎进入连接状态(即运行脚本
状态),通常我们通过用户显式操作(例如通过菜单命令或快捷键)完成
启动操作;应用系统也可以终止引擎的运行,使其进入无连接状态。在
引擎处于连接状态时,当特定的事件被激发时,脚本文件中的相应的事
件控制函数会被执行;在引擎处于无连接状态时,即使有事件发生,脚
本文件中的事件控制函数也不会被执行。
    应用系统也需要实现一些接口,分别为:IActiveScriptSite和IAc
tiveScriptSiteWi ndow(可选)。接口IActiveScriptSite是每一个支
持脚本语言的应用系统所必须实现的接口,脚本引擎通过它来获取其
宿主程序的信息,特别是在解释脚本语言中一些名字时更需要用到这
些信息,IActiveScriptSite的接口定义如下:
    class IActiveScriptSite : public IUnknown
    {
    public :
    virtual HRESULT GetLCID( LCID *plcid) = 0;
    virtual HRESULT GetItemInfo( LPCOLESTR pstrName, DWORD d
wReturnMask, IUn known *ppiunkItem, ITypeInfo *ppti) = 0;
    virtual HRESULT GetDocVersionString(BSTR *pbstrVersion) 
= 0;
    virtual HRESULT OnScriptTerminate(const VARIANT *pvarRes
ult, const EXCEP INFO *pexcepinfo) = 0;
    virtual HRESULT OnStateChange( SCRIPTSTATE ssScriptState
) = 0;
    virtual HRESULT OnScriptError( IActiveScriptError *pscri
pterror) = 0;
    virtual HRESULT OnEnterScript( void) = 0;
    virtual HRESULT OnLeaveScript( void) = 0;
    };
    在这些成员函数中,GetItemInfo是关键函数,因为脚本引擎管理
了一个名字空间,脚本引擎在解释执行过程中,如果需要某个名字的信
息,则通过宿主程序的IActiveScriptS ite::GetItemInfo函数获取。
所以,应用系统通过GetItemInfo成员函数把自己的一些对象暴露给脚
本引擎,以便在脚本文件中引用。
    IActiveScriptSiteWindow接口是一个可选的接口,如果在脚本文
件中要用到用户接口UI特性,则应用系统应该实现IActiveScriptSite
Window接口,其定义很简单,如下:
    class IActiveScriptSiteWindow : public IUnknown
    {
    public :
    virtual HRESULT GetWindow( HWND *phwnd ) = 0;
    virtual HRESULT EnableModeless( BOOL fEnable ) = 0;
    };
    脚本引擎通过GetWindow成员函数获取宿主窗口句柄,作为脚本中
弹出窗口的父窗口。
    除了应用系统需要实现上面两个接口用作它与脚本引擎之间的通
讯之外,脚本引擎也实现了一组接口用作两者之间的通讯,这组接口包
括:IActiveScript、IActiveScriptPa rse和其它一些用于调试、状
态管理或者错误处理的接口,IActiveScript和IActiveScri ptParse
是必须实现的接口,以下是它们的定义:
    class IActiveScript : public IUnknown
    {
    public:
    virtual HRESULT SetScriptSite( IActiveScriptSite *pass) 
= 0;
    virtual HRESULT GetScriptSite( REFIID riid, void **ppvOb
ject) = 0;
    virtual HRESULT SetScriptState( SCRIPTSTATE ss) = 0;
    virtual HRESULT GetScriptState( SCRIPTSTATE *pssState) =
 0;
    virtual HRESULT Close( void) = 0;
    virtual HRESULT AddNamedItem( LPCOLESTR pstrName, DWORD 
dwFlags) = 0;
    virtual HRESULT AddTypeLib( REFGUID rguidTypeLib, DWORD 
dwMajor, DWORD d wMinor, DWORD dwFlags) = 0;
    virtual HRESULT GetScriptDispatch( LPCOLESTR pstrItemNam
e, IDispatch**pp disp) = 0;
    virtual HRESULT GetCurrentScriptThreadID( SCRIPTTHREADID
 *pstidThread) = 0;
    virtual HRESULT GetScriptThreadID( DWORD dwWin32ThreadId
,SCRIPTTHREADID* pstidThread) = 0;
    virtual HRESULT GetScriptThreadState( SCRIPTTHREADID sti
dThread, SCRIPTT HREADSTATE *pstsState) = 0;
    virtual HRESULT InterruptScriptThread( SCRIPTTHREADID st
idThread,const E XCEPINFO *pexcepinfo, DWORD dwFlags) = 0;
    virtual HRESULT Clone( IActiveScript **ppscript) = 0;
    };
    class IActiveScriptParse : public IUnknown
    {
    public:
    virtual HRESULT InitNew( void) = 0;
    virtual HRESULT AddScriptlet( LPCOLESTR pstrDefaultName,
LPCOLESTR pstrCo de,LPCOLESTR pstrItemName, LPCOLESTR pstrSu
bItemName, LPCOLESTR pstrEventNam e, LPCOLESTR pstrDelimiter
, DWORD dwSourceContextCookie, ULONG ulStartingLin eNumber, 
DWORD dwFlags, BSTR *pbstrName, EXCEPINFO *pexcepinfo) = 0;
    virtual HRESULT ParseScriptText( LPCOLESTR pstrCode, LPC
OLESTR pstrItemN ame,IUnknown *punkContext, LPCOLESTR pstrDe
limiter,DWORD dwSourceContextCook ie, ULONG ulStartingLineNu
mber,DWORD dwFlags, VARIANT *pvarResult,EXCEPINFO *pexcepinf
o) = 0;
    };
    应用系统通过IActiveScript接口控制脚本引擎的各种行为,也可
以获取引擎的各种状态。通常,应用系统首先调用IActiveScript::Se
tScriptSite成员函数把自身实现的I ActiveScriptSite接口传递给
引擎,以后引擎就通过该接口与应用系统通讯。而应用系统也可以通
过IActiveScript的其它成员函数获取或者设置引擎的运行状态。接
口IActive ScriptParse用于对脚本代码的操作,应用系统利用IActiv
eScriptParse接口装入脚本代码。

 

ActiveX Scripting技术(二)
    吕思伟 潘爱民
    在介绍了应用系统和脚本引擎所实现的一些关键接口之后,我们
再进一步看看应用系统和脚本引擎的协作过程,如图2所示。
    图中给出了8个步骤,下面逐一介绍。
图2 应用系统与脚本引擎的协作过程
    (1)创建必要的受控对象,这些受控对象是指将要在脚本文件中引
用到的Automation 对象,通常是应用系统的文档对象,也可以是某些A
ctiveX控制;
    (2)创建引擎对象,不同的脚本语言使用不同的引擎对象,通常我
们使用VBScript引擎或者JavaScript引擎,创建得到的接口指针是应
用系统控制引擎的惟一途径;
    (3)装入脚本文件,调用引擎的IActiveScriptParse接口的ParseS
criptText成员函数把脚本代码装入到引擎中,注意ParseScriptText
成员函数只接收UNICODE字符串,如果程序中用到了ANSI字符串,则需
要进行转换才能装入到引擎中;
    (4)加入名字项,凡是应用系统中要暴露给脚本文件的所有对象都
需要加入到引擎的名字空间中,可以通过调用IActiveScript接口的Ad
dNamedItem成员函数来完成;
    (5)启动引擎,以便运行脚本,直接调用IActiveScript::SetScrip
tState成员函数使其进入连接状态(运行状态)即可;
    (6)引擎在执行脚本时,首先处理其名字空间中的名字项,调用应
用系统IActiveScri ptSite接口的GetItemInfo成员函数获取每一个
名字所对应的受控对象的信息,主要是CO M接口;如果在脚本中有事件
控制函数的话,则还要获取受控对象的类型信息;
    (7)在脚本执行过程中,当特定的事件发生时,引擎中的事件控制
函数就要被调用;
    (8)在脚本执行过程中,有可能会调用到受控对象的属性和方法,
则引擎会通过它所获取的对象接口调用IDispatch::Invoke成员函数;
    如果应用系统希望终止引擎的执行过程,可以调用IActiveScript
::SetScriptState 成员函数使其进入非运行状态即可。
    以上的步骤基本上反映了应用系统和引擎之间的协作过程。在实
际使用过程中,可以根据情况的不同灵活应用。
    三、ActiveX Scripting实现
    因为Microsoft或者其他的软件厂商已经为我们提供了脚本引擎(
如果机器上安装了Internet Explorer,则VBScript和JavaScript的引
擎就已经被安装在机器上了),所以我们只需要在应用系统中直接使用
就可以。为了使应用系统更好地支持这种脚本特性,我们对实现脚本
特性过程中的一些要点作一分析。
    首先我们定义一个通用的类CScriptHost,它实现了两个接口IAct
iveScriptSite和I ActiveScriptSiteWindow,类的定义如下:
    class CScriptHost : public IActiveScriptSite , public IA
ctiveScriptSiteW indow
    {
    public:
    CScriptHost(LPUNKNOWN lpUnkCtrl, LPCOLESTR
    pNamedItem, HWND hWnd);
    virtual ~CScriptHost();
    HRESULT CreateScriptEngine();
    HRESULT ParseFile(const char*pszFileName,LPCOLESTR pstrI
temName);
    public:
    //IUnknown members
    STDMETHOD(QueryInterface)(REFIID riid,void** ppvObj);
    STDMETHOD_(unsigned long,AddRef)(void);
    STDMETHOD_(unsigned long,Release)();
    //IActiveScriptSite members
    STDMETHOD(GetLCID)(LCID *plcid) ;
    STDMETHOD(GetItemInfo)(LPCOLESTR pstrName,DWORDdwReturnM
ask,IUnknown **p piunkItem,ITypeInfo * *ppti) ;
    STDMETHOD(GetDocVersionString)(BSTR *pbstrVersion) ;
    STDMETHOD(OnScriptTerminate)(const VARIANT
    *pvarResult,const EXCEPINFO *pexcepinfo) ;
    STDMETHOD(OnStateChange)(SCRIPTSTATE ssScriptState) ;
    STDMETHOD(OnScriptError)(IActiveScriptError *pscripterro
r) ;
    STDMETHOD(OnEnterScript)(void) ;
    STDMETHOD(OnLeaveScript)(void) ;
    //IActiveScriptSiteWindow members
    STDMETHOD(GetWindow)(HWND *phwnd) ;
    STDMETHOD(EnableModeless)(BOOL fEnable);
    public:
    IActiveScript* m_ps;
    IActiveScriptParse* m_psp;
    private:
    UINT m_cref;
    CLSID m_clsidEngine;
    LPUNKNOWN m_lpUnkCtrl;
    LPOLESTR m_pNamedItem;
    HWND m_Wnd;
    };
    类CScriptHost通过多继承的方法实现了两个接口,并负责脚本引
擎的创建和维护工作。其数据成员m_ps和m_psp用于保存引擎的IActi
veScript和IActiveScriptParse接口指针;数据成员m_clsidEngine记
录了引擎的类ID;m_Wnd记录了应用系统提供给引擎的主窗口;m_lpUnk
Ctrl记录了应用系统的惟一的一个受控对象,m_pNamedItem记录了受
控对象的名字。在CScriptHost类的构造函数中初始设置m_lpUnkCtrl
、m_pNamedItem和m_Wnd成员变量。构造函数和析构函数代码如下:
    CScriptHost::CScriptHost(LPUNKNOWN lpUnkCtrl,LPCOLESTR p
NamedItem , HWND  hWnd)
    : m_ps(NULL),m_psp(NULL),m_cref(0)
    {
    m_lpUnkCtrl = lpUnkCtrl;
    m_pNamedItem = pNamedItem;
    m_Wnd = hWnd;
    // the clsid of VBScript Engine
    static CLSID const clsid = {0xb54f3741, 0x5b07, 0x11cf,{
0xa4, 0xb0, 0x0,  0xaa, 0x0, 0x4a, 0x55, 0xe8}};
    // Default to VBScript
    m_clsidEngine = clsid;
    }
    CScriptHost::~CScriptHost()
    {
    if(m_psp)
    m_psp- Release();
    // we must first close the script engine
    if(m_ps)
    {
    m_ps- Close();
    m_ps- Release();
    }
    }
    在构造函数中,我们指定使用缺省的VBScript脚本引擎对象,并设
置其相应的CLSID。在析构函数中我们不能释放所有引擎的接口指针,
因为这会导致脚本引擎对象释放这个指针时出错。在析构函数中调用
IActiveScript::Close关闭与脚本引擎的连接。
    成员函数CreateScriptEngine是由应用系统调用的函数,代码如
下:
    HRESULT CScriptHost::CreateScriptEngine()
    {
    HRESULT hr = S_OK;
    hr = ::CoCreateInstance(m_clsidEngine,NULL,CLSCTX_INPROC
_SERVER,IID_IAct iveScript,(void**)&m_ps);
    if (SUCCEEDED(hr))
    {
    // QI the IActiveScriptParse pointerhr = m_ps- QueryInte
rface(IID_IActiv eScriptParse,(void**)&m_psp);
    if (FAILED(hr) )
    {
    m_ps- Release();
    return hr;
    }
    // set the script site
    hr = m_ps- SetScriptSite(this);
    if ( FAILED( hr ) )
    return hr;
    m_ps- SetScriptState(SCRIPTSTATE_INITIALIZED);
    // initiate the script engine
    hr = m_psp- InitNew();
    if (FAILED(hr))
    return hr;
    hr = m_ps- AddNamedItem(m_pNamedItem,SCRIPTITEM_ISVISIBL
E|SCRIPTITEM_ISS OURCE);
    }
    return hr;
    }
    CreateScriptEngine函数首先创建引擎对象,然后把引擎对象的I
ActiveScript和IA ctiveScriptParse接口指针分别赋给数据成员m_p
s和m_psp,最后把m_pNamedItem名字加入到引擎的名字空间中。
    成员函数ParseFile可以把脚本文件装入到引擎中,代码如下:
    HRESULT CScriptHost::ParseFile(const char * pszFileName,
LPCOLESTR pstrIt emName)
    {
    HRESULT hr = S_OK;
    struct _stat stat;
    size_t cch;
    EXCEPINFO ei;
    FILE *pfile;
    if(_stat(pszFileName,&stat))
    return E_FAIL;
    cch = stat.st_size;
    char* pszAlloc = new char;
    pszAlloc = '\0';// this is vitally important
    if (pszAlloc==NULL)
    return E_OUTOFMEMORY;
    memset(pszAlloc,0,cch);
    //get the script text into a memory block
    pfile = fopen(pszFileName,"rb");
    if (!pfile)
    {
    hr = E_FAIL;
    return hr;
    }
    fread(pszAlloc,cch,1,pfile);
    fclose(pfile);
    LPOLESTR pwszCode;
    int CharCount = MultiByteToWideChar(CP_ACP,0,pszAlloc,-1
,NULL,0);
    pwszCode = new WCHAR;
    MultiByteToWideChar(CP_ACP,0,pszAlloc,-1,pwszCode,CharCo
unt);
    size_t t = wcslen(pwszCode);
    hr = m_psp- ParseScriptText(pwszCode, pstrItemName,NULL,
NULL, 0,0,0L,NUL L,&ei);
    delete pwszCode;
    delete pszAlloc;
    return hr;
    }
    ParseFile函数首先确定脚本文件的长度,然后打开文件并装入到
内存中,最后把内存中脚本代码通过引擎的IActiveScriptParse接口
成员函数ParseScriptText成员函数装入到引擎中。需要注意的是,我
们这里调用MuitiByteToWide函数完成了代码从ANSI到UNIC ODE码的
转换。
ActiveX Scripting技术(三)

    ActiveX Scripting技术(三)(接上期)然后我们看看类CScriptHo
st中接口IActiveS criptSite的成员函数GetItemInfo的实现,因为引
擎调用GetItemInfo函数获取其名字空间中名字项的信息,所以我们要
在此函数中把应用系统的对象暴露给引擎和脚本,代码如下:
    STDMETHODIMP CScriptHost::GetItemInfo(LPCOLESTR pstrName
,DWORD dwReturnM ask,IUnknown **ppiunkItem, ITypeInfo **ppti
)
    {
    HRESULT hr = S_OK;
    // initialize the sent-in pointers
    if(dwReturnMask & SCRIPTINFO_ITYPEINFO)
    {
    if(ppti == NULL)
    return E_INVALIDARG;
    *ppti = NULL;
    }
    if(dwReturnMask & SCRIPTINFO_IUNKNOWN)
    {
    if(ppiunkItem == NULL)
    return E_INVALIDARG;
    *ppiunkItem = NULL;
    }
    if(!_wcsicmp(m_pNamedItem, pstrName))
    {
    if(dwReturnMask & SCRIPTINFO_IUNKNOWN)
    {
    // give out the object's IUnknown pointer
    *ppiunkItem = m_lpUnkCtrl;
    static_cast(*ppiunkItem)- AddRef();
    }
    if(dwReturnMask & SCRIPTINFO_ITYPEINFO)
    {
    IProvideClassInfo* pClsInfo = NULL;
    hr = m_lpUnkCtrl- QueryInterface(IID_IProvideClassInfo, 
(void**)&pClsInf o);
    if(pClsInfo != NULL)
    {
    hr = pClsInfo- GetClassInfo(ppti);
    pClsInfo- Release();
    }
    }
    }
    return hr;
    }
    函数GetItemInfo首先对输出参数ppiunkItem和ppti进行有效性
检查,然后判断是否输入的名字与应用支持的受控对象的名字一致,如
果一致的话,则根据参数dwReturnMask 所指示的标志,把对象的IUnkn
own接口或者对象的类型信息通过输出参数传递给引擎,供引擎解释执
行脚本代码使用。
    我们再看类CScriptHost中接口IActiveScriptSiteWindow的成员
函数GetWindow的实现。函数比较简单,只是把应用系统的窗口句柄通
过输出参数传递给引擎,代码如下:
    HRESULT CScriptHost::GetWindow(HWND *phwnd)
    {
    if (m_Wnd != NULL) {
    *phwnd = m_Wnd;
    return S_OK;
    } else
    return E_FAIL;
    }
    类CScriptHost的其他成员函数都比较简单,有的接口成员函数可
以不实现,仅仅返回S_OK或者E_NOTIMPL即可,其代码不再一一列举。
    CScriptHost提供了应用系统为支持脚本代码运行所做的基本工
作,CScriptHost为引擎提供了应用系统的必要信息。CScriptHost类
是一个通用的类,如果应用系统只有一个Automation对象暴露给脚本
代码,则可以用CScriptHost类快速实现对脚本代码的支持。如果应用
系统有多个Automation对象要暴露给脚本代码,则需要对上面介绍的C
ScriptHo st类作些修改,使其支持多个名字项的处理。
    四、ActiveX Scripting实例
    在这一节,我们通过一个实例来说明如何利用上节提供的CScript
Host类为应用程序加上脚本特性。例程序很简单,只是一个基于对话
框的应用,但对话框中有一个日历控制,这是Microsoft提供的ActiveX
控制,它本身也是一个Automation对象,我们将在脚本代码中对该日历
对象进行控制,并且在脚本代码中响应日历控制的一些事件。图3是例
程序的运行界面图。
图3 例程序运行界面图
    创建例程序的过程并不复杂,利用Microsoft Visual C++ 5.0(或
6.0)提供的AppWiz ard和ClassWizard可以很快创建工程,并添加各项
功能,下面是其操作过程。
    (1)首先我们创建一个MFC工程,因为例程序比较简单,所以我们选
择了基于对话框的应用类型。工程名为Script,对话框类名为CScript
Dlg。
    (2)然后我们在对话框资源模板中添加日历控制,打开对话框模板
,用右键单击,从菜单中选择"Insert ActiveX Control"命令,选择Cal
endar Control,然后调整大小合适即可,并设置控制的ID为IDC_CONTR
OL1。
    (3)在对话框模板中添加两个按钮"Load Script"和"Run Script"
放在适当的位置上。
    (4)把上一节完成的文件ScriptHost.h和ScriptHost.cpp加入到
工程中。
    (5)在类CScriptDlg中加入数据成员m_pScHost,其类型为CScript
Host *。
    (6)用ClassWizard生成按钮"Load Script"的消息控制函数,编写
代码如下。
    void CScriptDlg::OnLoadscript()
    {
    CFileDialog dlg(TRUE, "*.txt","*.txt",OFN_HIDEREADONLY |
    OFN_OVERWRITEPROMPT,"Text files (*.txt)");
    if(dlg.DoModal()==IDOK)
    {
    CString strPath;
    strPath = dlg.GetPathName();
    if (strPath.IsEmpty())
    return;
    if (m_pScHost != NULL)
    m_pScHost-m_ps-Close();
    CWnd *pCalander = GetDlgItem(IDC_CONTROL1);
    m_pUnknownCtrl = pCalander- GetControlUnknown();
    m_pScHost = new CScriptHost(m_pUnknownCtrl, L"control", 
m_hWnd);
    HRESULT hr = m_pScHost-CreateScriptEngine();
    hr = m_pScHost- ParseFile(strPath,L"control");
    GetDlgItem(IDC_RUNSCRIPT)-EnableWindow(TRUE);
    GetDlgItem(IDC_RUNSCRIPT)- SetWindowText("Run Script");
    return;
    }
    return;
    }
    在消息控制函数OnLoadscript中,首先打开标准文件对话框,待用
户选中脚本文件后,取到文件名,放到变量strPath中,如果原先已经存
在引擎对象,则先关闭引擎对象。
    然后通过CWnd::GetControlUnknown函数取出日历控制的IUnknow
n接口指针。完成了这些准备工作后,再构造一个CScriptHost对象,把
控制的IUnknown接口指针、控制名以及对话框的窗口句柄传到CScrip
tHost对象中,然后调用其CreateScriptEngine成员函数创建脚本引擎
对象,创建完成后,再调用ParseFile成员函数装入脚本文件。装入脚
本之后, 设置"Run Script"按钮使其接收运行脚本的命令。注意,在O
nLoadscript函数返回后,脚本引擎已经创建完成,脚本文件也已经装
入到引擎中,但这时脚本代码并没有被运行。
    (7)用ClassWizard生成按钮"Run Script"的消息控制函数,编写
代码如下。
    void CScriptDlg::OnRunscript()
    {
    if (m_pScHost != NULL) {
    SCRIPTSTATE ss;
    if (FAILED(m_pScHost- m_ps- GetScriptState(&ss)))
    return;
    if (ss == SCRIPTSTATE_CONNECTED) {m_pScHost- m_ps-SetScr
iptState(SCRIPTS TATE_DISCONNECTED);
    GetDlgItem(IDC_RUNSCRIPT)-SetWindowText("Run Script");
    } else {
    m_pScHost- m_ps- SetScriptState(SCRIPTSTATE_CONNECTED);
    GetDlgItem(IDC_RUNSCRIPT)- SetWindowText("Stop Script");
    }
    }
    }
    OnRunscript函数比较简单,它调用引擎的IActiveScript接口的G
etScriptState成员函数获取当前引擎的状态,如果当前引擎中脚本正
在运行,则调用SetScriptState成员函数使引擎停止运行,引擎进入非
运行状态,并设置"Run Script"按钮的标题变为"Run Scr ipt";如果
当前引擎中脚本不在运行,则调用SetScriptState成员函数使引擎进
入运行状态,并设置"Run Script"按钮的标题变为"Stop Script"。
    (8)编译并连接例程序。
    至此我们已经完成了例程序的创建工作,接下来我们再写一个脚
本文件用来测试例程序是否能正确运行脚本文件。脚本文件中的代码
分两部分,一部分是全局的执行代码,当引擎首次被启动时,这部分代
码就开始运行;另一部分是事件响应函数,当日历控制产生事件时,脚
本代码中的事件响应函数就会被执行。为了测试例程序的正确性,我
们使用了下面的脚本代码。
    ' Golobal code
    MSGBOX("Global!")
    '---------------------------------------------
    Sub control_DblClick()
    control.Nextyear
    MSGBOX("You have double-clicked!")
    End Sub
    '----------------------------------------------
    上述脚本代码并不进行实际的操作,只是弹出一个消息框表明脚
本代码获得了控制。
    因为我们在例程序的CScriptDlg::OnLoadscript函数中指定了应
用受控对象名字为"control",所以在脚本代码中的control_DblClick
函数即指响应"control"对象的"DblC lick"事件,在函数control_Dbl
Click中,调用了control对象方法Nextyear使当前日历后翻一年。并
弹出消息框以示脚本代码被执行了。
    程序执行过程中,对事件响应后的情况如图4所示。图4 例程序响
应双击事件后的运行结果
    如果我们单击"Stop Script"按钮停止脚本的执行,则再双击日历
控制,脚本代码中的事件响应函数不会被执行;如果用户再单击"Run S
cript"按钮,则事件响应函数会再次被执行。如果用户希望执行其他
的脚本文件,则可以单击"Load Script"按钮,重新装入脚本文件。从
这里我们可以看出,应用程序对脚本引擎的控制是非常灵活的。读者
可以试一试。
    五、结束语
    Active Scripting技术是近几年发展起来的新技术,它对于软件
的性能扩展有重要的意义。从本文以上几节的介绍可以看出,在应用
系统中提供脚本语言的支持并不困难,甚至非常简单,因此,这种技术
有着广泛的发展前景,而且我们也已经看到越来越多的应用系统提供
了脚本语言的支持。
    本文旨在对脚本技术作一个基本的介绍,希望文中所讲述的内容
能帮助读者在工作中用好这种技术。(全文完)