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码的
转换。
在介绍了应用系统和脚本引擎所实现的一些关键接口之后,我们
再进一步看看应用系统和脚本引擎的协作过程,如图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码的
转换。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了