在介绍了应用系统和脚本引擎所实现的一些关键接口之后,我们再进一步看看应用系统和脚本引擎的协作过程,如图2所示。 图中给出了8个步骤,下面逐一介绍。图2 应用系统与脚本引擎的协作过程 (1)创建必要的受控对象,这些受控对象是指将要在脚本文件中引用到的Automation 对象,通常是应用系统的文档对象,也可以是某些ActiveX控制; (2)创建引擎对象,不同的脚本语言使用不同的引擎对象,通常我们使用VBScript引擎或者JavaScript引擎,创建得到的接口指针是应用系统控制引擎的惟一途径; (3)装入脚本文件,调用引擎的IActiveScriptParse接口的ParseScriptText成员函数把脚本代码装入到引擎中,注意ParseScriptText成员函数只接收UNICODE字符串,如果程序中用到了ANSI字符串,则需要进行转换才能装入到引擎中; (4)加入名字项,凡是应用系统中要暴露给脚本文件的所有对象都需要加入到引擎的名字空间中,可以通过调用IActiveScript接口的AddNamedItem成员函数来完成; (5)启动引擎,以便运行脚本,直接调用IActiveScript::SetScriptState成员函数使其进入连接状态(运行状态)即可; (6)引擎在执行脚本时,首先处理其名字空间中的名字项,调用应用系统IActiveScri ptSite接口的GetItemInfo成员函数获取每一个名字所对应的受控对象的信息,主要是CO M接口;如果在脚本中有事件控制函数的话,则还要获取受控对象的类型信息; (7)在脚本执行过程中,当特定的事件发生时,引擎中的事件控制函数就要被调用; (8)在脚本执行过程中,有可能会调用到受控对象的属性和方法,则引擎会通过它所获取的对象接口调用IDispatch::Invoke成员函数; 如果应用系统希望终止引擎的执行过程,可以调用IActiveScript::SetScriptState 成员函数使其进入非运行状态即可。 以上的步骤基本上反映了应用系统和引擎之间的协作过程。在实际使用过程中,可以根据情况的不同灵活应用。 三、ActiveX Scripting实现 因为Microsoft或者其他的软件厂商已经为我们提供了脚本引擎(如果机器上安装了Internet Explorer,则VBScript和JavaScript的引擎就已经被安装在机器上了),所以我们只需要在应用系统中直接使用就可以。为了使应用系统更好地支持这种脚本特性,我们对实现脚本特性过程中的一些要点作一分析。 首先我们定义一个通用的类CScriptHost,它实现了两个接口IActiveScriptSite和I ActiveScriptSiteWindow,类的定义如下: class CScriptHost : public IActiveScriptSite , public IActiveScriptSiteW indow { 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,DWORDdwReturnMask,IUnknown **p piunkItem,ITypeInfo * *ppti) ; STDMETHOD(GetDocVersionString)(BSTR *pbstrVersion) ; STDMETHOD(OnScriptTerminate)(const VARIANT *pvarResult,const EXCEPINFO *pexcepinfo) ; STDMETHOD(OnStateChange)(SCRIPTSTATE ssScriptState) ; STDMETHOD(OnScriptError)(IActiveScriptError *pscripterror) ; 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用于保存引擎的IActiveScript和IActiveScriptParse接口指针;数据成员m_clsidEngine记录了引擎的类ID;m_Wnd记录了应用系统提供给引擎的主窗口;m_lpUnkCtrl记录了应用系统的惟一的一个受控对象,m_pNamedItem记录了受控对象的名字。在CScriptHost类的构造函数中初始设置m_lpUnkCtrl、m_pNamedItem和m_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_IAct iveScript,(void**)&m_ps); if (SUCCEEDED(hr)) { // QI the IActiveScriptParse pointerhr = m_ps- QueryInterface(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_ISVISIBLE|SCRIPTITEM_ISS OURCE); } return hr; } CreateScriptEngine函数首先创建引擎对象,然后把引擎对象的IActiveScript和IA ctiveScriptParse接口指针分别赋给数据成员m_ps和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,CharCount); 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码的转换。
