2023年11月30日发(作者:)

ActiveX Scripting技术介绍

吕思伟 潘爱民

北京大学计算机科学技术研究所

【摘要】本文首先介绍了ActiveX Scripting技术的背景,然后讲述了ActiveX Scripting技术的结

构和实现,并通过实例说明了如何为一个应用程序加上脚本特性的过程。

关键字:ActiveX ScriptingAutomationOLECOM

ActiveX Scripting技术是Microsoft ActiveX技术的一个组成部分,它主要目的是使应用

程序在不被修改的情况下,为各种脚本语言所控制。在软件交互性不断提高的今天,仅仅提供

菜单或工具箱的界面已经不能满足用户的需要了,软件的可定制特性已经成为当今软件的一项

基本特征,尤其对于一些通用的软件更为如此。大家比较熟悉的Microsoft Office软件,比如

Word字处理软件,它不仅提供了界面的任意定制,还提供了方便的Basic语言的可编程特性,

用户可以通过编写BASIC语言实现较为复杂的功能扩充。

Micosoft提供的ActiveX Scripting技术可使软件扩充变得非常简单,软件开发商利用脚本

引擎(Script Engine)支持脚本语言的解释和执行操作,而软件用户可以根据需要编写自己的脚本

代码,交由软件处理,对于用户来说,就好象自己在编写程序控制应用程序,以完成自己所期

望的功能。而应用软件并不需要自己去解释执行用户的脚本代码,只要利用脚本引擎就可以很

方便地实现对用户脚本语言的支持。应用系统也可以利用这种技术来提供二次开发的特性。

虽然脚本引擎提供了脚本语言的解释执行的功能,但要用好ActiveX Scripting技术则需要

对它有一个全面的了解,本文将对ActiveX Scripting技术作一探讨,并给出一个具体例子,以

使读者进一步理解ActiveX Scripting技术。

ActiveX Scripting背景

ActiveX ScriptingMicrosoft Automation技术和Script技术结合的产物,因此,在介绍

ActiveX Scripting之前,首先我们来看看AutomationScript两种技术的发展情况。

(1) Automation技术是Microsoft OLE技术 的一部分,它可使解释性的宏语言(主要是Visual

Basic)能够在不了解应用程序的实现细节的情况下控制Automation对象,随着Visual Basic

软件的发展以及Microsoft Office套件的广泛应用,Automation技术已经成为连接这些应用

或者工具的纽带。而且,更多的应用把这种宏语言作为自己应用扩展的手段,例如Word

Access以及Notes都把BASIC类语言作为其开发语言,并且这些BASIC类语言均支持

Automation对象;另一方面,Internet浏览器也提供了脚本引擎,可用于解释网络页面脚本

语言中的Automation对象。所有这些应用能够得以不断发展,在很大程度上是因为这些

BASIC语言或脚本语言提供了对Automation的支持。

Automation技术以COM(组件对象模型)为基础,所有的Automation对象都实现了标准

IDispatch接口,通过IDispatch接口暴露对象的属性和方法,以便在客户程序中使用这些

属性并调用它所支持的方法。Automation对象的客户程序或者宿主程序通过类型库(Type

Library)获得对象运行时刻的类型信息,并提供事件处理。宏语言解释器或者脚本引擎根据

对象的类型信息,把其中对对象属性和方法的引用解释为对IDispatch接口成员函数Invoke

的调用,从而实现对对象的控制。

(2) Script技术是指脚本语言的技术,目前主要用于Internet浏览器中,它可实现对页面的交互

处理。我们知道,HTML是一种描述性的语言,交互能力很弱,但通过Script技术,可以

编制出一些内容生动、具有极强交互性的网络页面;并且,使用Script技术的另一个好处是,

它能够减轻服务器端计算的负担,把部分计算工作转移到客户端来完成。目前VBScript

JavaScript语言在网络浏览器上应用非常广泛,而且一些主要的浏览器也提供了相应的引擎

用于处理网页中的脚本语言。

通常来说,用于网络浏览器的脚本语言具有以下特点:(1) 它本身是一门解释性语言,

所以语言的语法简单,但执行效率相对较低;(2) 它采用了事件驱动机制,脚本语言主要用

于对交互事件作出响应,脚本语言程序的主体是事件处理过程;(3) 与浏览器内在的对象模

型结构结合紧密,脚本语言单独作为编程语言的价值很小,只有同特定的对象模型结构相

结合后才能够体现出其控制能力。在HTML的脚本程序中用户可以直接使用如Window

PaneDocument等浏览器结构元素,并对其进行控制,产生各种效果。此外脚本语言还可

以对页面上的Java AppletActiveX Control进行操作。能够对宿主应用的对象进行控制正

是脚本语言的真正价值所在。

从上面对两种技术的讨论中不难看出,Automation技术作为对应用程序进行外部控制的成

熟而有力的手段,其设计目标与Script技术有许多共同之处。而Automation技术的基础COM

技术本身是一种语言无关的软件模型,一个很自然的想法是通过COM来统一实现对对象的控

制和对多种脚本语言的无缝支持。这种想法体现在实践上就形成了MicrosoftActive Scripting

技术。

ActiveX Scripting结构

从技术的角度来看,ActiveX Scripting技术实际上是一组COM接口的定义,通过这组接口

建立应用程序和脚本引擎之间的关系。脚本引擎是ActiveX Scripting技术的实现,一个应用系

统如果实现了有关的标准接口,那么它就可以通过脚本引擎提供对用户脚本语言的支持,在介

绍这组接口之前,我们先看看应用系统、脚本引擎和脚本文件三者之间的关系。

应用系统为了支持脚本语言,首先它要实现几个标准接口,然后它把自己的一些被控对象

暴露出来;脚本文件是一个文本文件,文件中包含了一些程序代码;脚本引擎本身是一个COM

2

对象,它负责对脚本文件的解释和执行工作,在必要时通过应用系统的接口与其交互。三者的

关系如图1所示:

创建引擎

应用系统

(宿主程序)

通过脚本控制应用

装入脚本文件

脚本文件

启动引擎、终止引擎

脚本引擎

1 应用系统、脚本引擎和脚本文件三者之间的关系

在图1中,应用系统首先需要创建脚本引擎对象,并设置必要的参数,然后装入脚本文件,

再启动引擎,使引擎进入连接状态(即运行脚本状态),通常我们通过用户显式操作(例如通过菜

单命令或快捷键)完成启动操作;应用系统也可以终止引擎的运行,使其进入无连接状态。在引

擎处于连接状态时,当特定的事件被激发时,脚本文件中的相应的事件控制函数会被执行;在

引擎处于无连接状态时,即使有事件发生,脚本文件中的事件控制函数也不会被执行。

应用系统也需要实现一些接口,分别为:IActiveScriptSiteIActiveScriptSiteWindow(可选)

接口IActiveScriptSite是每一个支持脚本语言的应用系统所必须实现的接口,脚本引擎通过它来

获取其宿主程序的信息,特别是在解释脚本语言中一些名字时更需要用到这些信息,

IActiveScriptSite的接口定义如下:

class IActiveScriptSite : public IUnknown

{

public :

virtual HRESULT GetLCID( LCID *plcid) = 0;

virtual HRESULT GetItemInfo( LPCOLESTR pstrName, DWORD dwReturnMask,

IUnknown *ppiunkItem, ITypeInfo *ppti) = 0;

virtual HRESULT GetDocVersionString(BSTR *pbstrVersion) = 0;

virtual HRESULT OnScriptTerminate(const VARIANT *pvarResult,

const EXCEPINFO *pexcepinfo) = 0;

virtual HRESULT OnStateChange( SCRIPTSTATE ssScriptState) = 0;

virtual HRESULT OnScriptError( IActiveScriptError *pscripterror) = 0;

virtual HRESULT OnEnterScript( void) = 0;

virtual HRESULT OnLeaveScript( void) = 0;

};

在这些成员函数中,GetItemInfo是关键函数,因为脚本引擎管理了一个名字空间,脚本引

宿

IActiveScriptSite::GetItemInfo函数获取。所以,应用系统通过GetItemInfo成员函数把自己的一

些对象暴露给脚本引擎,以便在脚本文件中引用。

IActiveScriptSiteWindow接口是一个可选的接口,如果在脚本文件中要用到用户接口UI

3

性,则应用系统应该实现IActiveScriptSiteWindow接口,其定义很简单,如下:

class IActiveScriptSiteWindow : public IUnknown

{

public :

virtual HRESULT GetWindow( HWND *phwnd ) = 0;

virtual HRESULT EnableModeless( BOOL fEnable ) = 0;

};

脚本引擎通过GetWindow成员函数获取宿主窗口句柄,作为脚本中弹出窗口的父窗口。

除了应用系统需要实现上面两个接口用作它与脚本引擎之间的通讯之外,脚本引擎也实现

了一组接口用作两者之间的通讯,这组接口包括:IActiveScriptIActiveScriptParse和其它一些

用于调试、状态管理或者错误处理的接口,IActiveScriptIActiveScriptParse是必须实现的接

口,以下是它们的定义:

class IActiveScript : public IUnknown

{

public:

virtual HRESULT SetScriptSite( IActiveScriptSite *pass) = 0;

virtual HRESULT GetScriptSite( REFIID riid, void **ppvObject) = 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 dwMinor, DWORD dwFlags) = 0;

virtual HRESULT GetScriptDispatch( LPCOLESTR pstrItemName,

IDispatch **ppdisp) = 0;

virtual HRESULT GetCurrentScriptThreadID( SCRIPTTHREADID *pstidThread) = 0;

virtual HRESULT GetScriptThreadID( DWORD dwWin32ThreadId,

SCRIPTTHREADID *pstidThread) = 0;

virtual HRESULT GetScriptThreadState( SCRIPTTHREADID stidThread,

SCRIPTTHREADSTATE *pstsState) = 0;

virtual HRESULT InterruptScriptThread( SCRIPTTHREADID stidThread,

const EXCEPINFO *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 pstrCode, LPCOLESTR pstrItemName,

LPCOLESTR pstrSubItemName, LPCOLESTR pstrEventName,

LPCOLESTR pstrDelimiter, DWORD dwSourceContextCookie,

ULONG ulStartingLineNumber, DWORD dwFlags,

BSTR *pbstrName, EXCEPINFO *pexcepinfo) = 0;

virtual HRESULT ParseScriptText( LPCOLESTR pstrCode, LPCOLESTR pstrItemName,

IUnknown *punkContext, LPCOLESTR pstrDelimiter,

DWORD dwSourceContextCookie, ULONG ulStartingLineNumber,

DWORD dwFlags, VARIANT *pvarResult,

EXCEPINFO *pexcepinfo) = 0;

};

4

应用系统通过IActiveScript接口控制脚本引擎的各种行为,也可以获取引擎的各种状态。

通常,应用系统首先调用IActiveScript::SetScriptSite成员函数把自身实现的IActiveScriptSite

口传递给引擎,以后引擎就通过该接口与应用系统通讯。而应用系统也可以通过IActiveScript

的其它成员函数获取或者设置引擎的运行状态。接口IActiveScriptParse用于对脚本代码的操作,

应用系统利用IActiveScriptParse接口装入脚本代码。

在介绍了应用系统和脚本引擎所实现的一些关键接口之后,我们再进一步看看应用系统和

脚本引擎的协作过程,如图2所示:

1. 创建必要的受控对象

2. 创建引擎对象

3. 装入脚本文件

脚本文件

4. 加入名字项

IActiveScriptParse接口

应用系统

IActiveScript接口

脚本引擎

Script

5. 启动引擎,运行脚本

IActiveScriptSite

6. 获取名字项信息

名字项对

应的对象

IDispatch

8. 调用对象的属性和方法

IConnectionPointContainer7. 事件通知

与对象有

关的脚本

2 应用系统与脚本引擎的协作过程

图中给出了8个步骤,下面逐一介绍:

(1) 创建必要的受控对象,这些受控对象是指将要在脚本文件中引用到的Automation对象,

通常是应用系统的文档对象,也可以是某些ActiveX控制;

(2) 创建引擎对象,不同的脚本语言使用不同的引擎对象,通常我们使用VBScript引擎或

JavaScript引擎,创建得到的接口指针是应用系统控制引擎的唯一途径;

(3) 装入脚本文件,调用引擎的IActiveScriptParse接口的ParseScriptText成员函数把脚本

代码装入到引擎中,注意ParseScriptText成员函数只接收UNICODE字符串,如果程序

中用到了ANSI字符串,则需要进行转换才能装入到引擎中;

(4) 加入名字项,凡是应用系统中要暴露给脚本文件的所有对象都需要加入到引擎的名字

空间中,可以通过调用IActiveScript接口的AddNamedItem成员函数来完成;

5

(5) 启动引擎,以便运行脚本,直接调用IActiveScript::SetScriptState成员函数使其进入连

接状态(运行状态)即可;

(6) 引擎在执行脚本时,首先处理其名字空间中的名字项,调用应用系统IActiveScriptSite

接口的GetItemInfo成员函数获取每一个名字所对应的受控对象的信息,主要是COM

接口;如果在脚本中有事件控制函数的话,则还要获取受控对象的类型信息;

(7) 在脚本执行过程中,当特定的事件发生时,引擎中的事件控制函数就要被调用;

(8) 在脚本执行过程中,有可能会调用到受控对象的属性和方法,则引擎会通过它所获取

的对象接口调用IDispatch::Invoke成员函数;

(9) 如果应用系统希望终止引擎的执行过程,可以调用IActiveScript::SetScriptState成员函

数使其进入非运行状态即可。

以上的步骤基本上反映了应用系统和引擎之间的协作过程。在实际使用过程中,可以根据

情况的不同灵活应用。

ActiveX Scripting实现

因为Microsoft或者其它的软件厂商已经为我们提供了脚本引擎(如果机器上安装了Internet

Explorer,则VBScriptJavaScript的引擎就已经被安装在机器上了),所以我们只需要在应用

系统中直接使用就可以。为了使应用系统更好地支持这种脚本特性,我们对实现脚本特性过程

中的一些要点作一分析。

CScriptHostIActiveScriptSite

IActiveScriptSiteWindow,类的定义如下:

class CScriptHost : public IActiveScriptSite , public IActiveScriptSiteWindow

{

public:

CScriptHost(LPUNKNOWN lpUnkCtrl, LPCOLESTR pNamedItem, HWND hWnd);

virtual ~CScriptHost();

HRESULT CreateScriptEngine();

HRESULT ParseFile(const char* pszFileName,LPCOLESTR pstrItemName);

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,DWORD dwReturnMask,

IUnknown * *ppiunkItem,ITypeInfo * *ppti) ;

STDMETHOD(GetDocVersionString)(BSTR *pbstrVersion) ;

STDMETHOD(OnScriptTerminate)(const VARIANT *pvarResult,const EXCEPINFO *pexcepinfo) ;

STDMETHOD(OnStateChange)(SCRIPTSTATE ssScriptState) ;

STDMETHOD(OnScriptError)(IActiveScriptError *pscripterror) ;

6

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_psm_psp用于保存引擎的IActiveScriptIActiveScriptParse接口指针;数据成

m_clsidEngine记录了引擎的类IDm_Wnd记录了应用系统提供给引擎的主窗口;m_lpUnkCtrl

记录了应用系统的唯一的一个受控对象,m_pNamedItem记录了受控对象的名字。CScriptHost

类的构造函数中初始设置m_lpUnkCtrlm_pNamedItemm_Wnd成员变量。构造函数和析构

函数代码如下:

CScriptHost::CScriptHost(LPUNKNOWN lpUnkCtrl, LPCOLESTR pNamedItem , 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_IActiveScript,(void**)&m_ps);

if ( SUCCEEDED( hr ) )

{

// QI the IActiveScriptParse pointer

hr = m_ps->QueryInterface(IID_IActiveScriptParse, (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_ISVISIBLE | SCRIPTITEM_ISSOURCE);

}

return hr;

}

CreateScriptEngineIActiveScript

IActiveScriptParse接口指针分别赋给数据成员m_psm_psp最后把m_pNamedItem名字加入

到引擎的名字空间中。

成员函数ParseFile可以把脚本文件装入到引擎中,代码如下:

HRESULT CScriptHost::ParseFile(const char * pszFileName, LPCOLESTR pstrItemName)

{

HRESULT hr = S_OK;

struct _stat stat;

size_t cch;

EXCEPINFO ei;

FILE *pfile;

8

if(_stat(pszFileName,&stat))

return E_FAIL;

cch = _size;

char* pszAlloc = new char[cch + 1];

pszAlloc[cch] = '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[CharCount];

MultiByteToWideChar(CP_ACP,0,pszAlloc,-1,pwszCode,CharCount);

size_t t = wcslen(pwszCode);

hr = m_psp->ParseScriptText(pwszCode, pstrItemName,NULL, NULL, 0,0,0L,NULL,&ei);

delete []pwszCode;

delete []pszAlloc;

return hr;

}

ParseFile函数首先确定脚本文件的长度,然后打开文件并装入到内存中,最后把内存中脚

本代码通过引擎的IActiveScriptParse接口成员函数ParseScriptText成员函数装入到引擎中,需

要注意的是,我们这里调用MuitiByteToWide函数完成了代码从ANSI UNICODE码的转换。

然后我们看看类CScriptHost中接口IActiveScriptSite的成员函数GetItemInfo的实现,因为

引擎调用GetItemInfo函数获取其名字空间中名字项的信息,所以我们要在此函数中把应用系统

的对象暴露给引擎和脚本,代码如下:

STDMETHODIMP CScriptHost::GetItemInfo(LPCOLESTR pstrName,DWORD dwReturnMask,

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**)&pClsInfo);

if(pClsInfo != NULL)

{

hr = pClsInfo->GetClassInfo(ppti);

pClsInfo->Release();

}

}

}

return hr;

}

函数GetItemInfo首先对输出参数ppiunkItemppti进行有效性检查,然后判断是否输入的

名字与应用支持的受控对象的名字一致,如果一致的话,则根据参数dwReturnMask所指示的

标志,把对象的IUnknown接口或者对象的类型信息通过输出参数传递给引擎,供引擎解释执

行脚本代码使用。

我们再看类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

10

象暴露给脚本代码,则可以用CScriptHost类快速实现对脚本代码的支持。如果应用系统有多个

Automation对象要暴露给脚本代码,则需要对上面介绍的CScriptHost类作些修改,使其支持多

个名字项的处理。

ActiveX Scripting实例

在这一节,我们通过一个实例来说明如何利用上节提供的CScriptHost类为应用程序加上脚

本特性。例程序很简单,只是一个基于对话框的应用,但对话框中有一个日历控制,这是Microsoft

提供的ActiveX控制,它本身也是一个Automation对象,我们将在脚本代码中对该日历对象进

行控制,并且在脚本代码中响应日历控制的一些事件。图3是例程序的运行界面图。

3 例程序运行界面图

创建例程序的过程并不复杂,利用Microsoft Visual C++ 5.0(6.0)提供的AppWizard

ClassWizard可以很快创建工程,并添加各项功能,下面是其操作过程:

(1) 首先我们创建一个MFC工程,因为例程序比较简单,所以我们选择了基于对话框的应

用类型。工程名为Script,对话框类名为CScriptDlg

(2) 然后我们在对话框资源模板中添加日历控制,打开对话框模板,用右键单击,从菜单

中选择“Insert ActiveX Control”命令,选择Calendar Control,然后调整大小合适即可,并设置

控制的IDIDC_CONTROL1

(3) 在对话框模板中添加两个按钮“Load Script”和“Run Script”放在适当的位置上。

(4) 把上一节完成的文件ScriptHost.h加入到工程中。

(5) 在类CScriptDlg中加入数据成员m_pScHost,其类型为CScriptHost *

11

(6) ClassWizard生成按钮“Load Script”的消息控制函数,编写代码如下:

void CScriptDlg::OnLoadscript()

{

CFileDialog dlg(TRUE, "*.txt","*.txt",

OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,

"Text files (*.txt)");

if(l()==IDOK)

{

CString strPath;

strPath = hName();

if (y())

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函数取出日历控制的IUnknown接口指针。完成了这些准备工作后,

再构造一个CScriptHost对象,把控制的IUnknown接口指针、控制名以及对话框的窗口句柄传

CScriptHost对象中,然后调用其CreateScriptEngine成员函数创建脚本引擎对象,创建完成

后,再调用ParseFile成员函数装入脚本文件。装入脚本之后,设置“Run Script”按钮使其接收

运行脚本的命令。注意,在OnLoadscript函数返回后,脚本引擎已经创建完成,脚本文件也已

经装入到引擎中,但这时脚本代码并没有被运行。

(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->SetScriptState(SCRIPTSTATE_DISCONNECTED);

GetDlgItem(IDC_RUNSCRIPT)->SetWindowText("Run Script");

} else {

m_pScHost->m_ps->SetScriptState(SCRIPTSTATE_CONNECTED);

GetDlgItem(IDC_RUNSCRIPT)->SetWindowText("Stop Script");

}

}

}

OnRunscript函数比较简单,它调用引擎的IActiveScript接口的GetScriptState成员函数获取

当前引擎的状态,如果当前引擎中脚本正在运行,则调用SetScriptState成员函数使引擎停止运

行,引擎进入非运行状态,并设置“Run Script”按钮的标题变为“Run Script;如果当前引擎

中脚本不在运行,则调用SetScriptState成员函数使引擎进入运行状态,并设置“Run Script”按

钮的标题变为“Stop Script

(8) 编译并连接例程序。

至此我们已经完成了例程序的创建工作,接下来我们再写一个脚本文件用来测试例程序是

否能正确运行脚本文件。脚本文件中的代码分两部分,一部分是全局的执行代码,当引擎首次

被启动时,这部分代码就开始运行;另一部分是事件响应函数,当日历控制产生事件时,脚本

代码中的事件响应函数就会被执行。为了测试例程序的正确性,我们使用了下面的脚本代码:

' Golobal code

MSGBOX("Global!")

'---------------------------------------------

Sub control_DblClick()

ar

MSGBOX("You have double-clicked!")

End Sub

'----------------------------------------------

上述脚本代码并不进行实际的操作,只是弹出一个消息框表明脚本代码获得了控制。因为

我们在例程序的CScriptDlg::OnLoadscript函数中指定了应用受控对象名字为control所以在

脚本代码中的control_DblClick函数即指响应“control”对象的“DblClick”事件,在函数

control_DblClick中,调用了control对象方法Nextyear使当前日历后翻一年。并弹出消息框以

示脚本代码被执行了。

程序执行过程中,对事件响应后的情况如图4所示。

13

4 例程序响应双击事件后的运行结果

如果我们单击“Stop Script”按钮停止脚本的执行,则再双击日历控制,脚本代码中的事件

响应函数不会被执行;如果用户再单击“Run Script”按钮,则事件响应函数会再次被执行。如

果用户希望执行其它的脚本文件,则可以单击“Load Script”按钮,重新装入脚本文件。从这

里我们可以看出,应用程序对脚本引擎的控制是非常灵活的。读者可以试一试。

结束语

Active Scripting技术是近几年发展起来的新技术,它对于软件的性能扩展有重要的意义。

从本文以上几节的介绍可以看出,在应用系统中提供脚本语言的支持并不困难,甚至非常简单,

因此,这种技术有着广泛的发展前景,而且我们也已经看到越来越多的应用系统提供了脚本语

言的支持。

本文旨在对脚本技术作一个基本的介绍,希望文中所讲述的内容能帮助读者在工作中用好

这种技术。

14