您的位置:首页 > 其它

为应用程序添加脚本访问功能

2012-11-16 18:01 148 查看
首先,要有一个可以通过代码访问的应用程序,两种方式,一种是程序提供应用接口,要么是API,要么是COM接口,另一种,能够获得程序全部的源代码。

以上条件具备后,接下来,比较专业一点的方式,建立一个脚本编辑器程序,一般用一个单文档的应用就行,View类里放一个RichEditCtrl或者第三方的脚本控件,如Scintilla等,主要是为了编辑脚本方便以及提供一个美观的界面,让脚本程序员用着舒服。建立完编辑程序之后,在其中加入编译及运行操作按钮,如果能够加上调试按钮并实现的话,最好不过了。

这些架子搭完后,就要选择脚本引擎,一般我们不需要自己去写引擎,有几种优秀而且容易获得的引擎可供选择。比如微软的Active Scripting,谷歌开发的V8引擎等。V8引擎用于javascript脚本,Active Scripting分为VBScript和JScript,如果仅做javascript脚本,那么选择V8,如果要做VB脚本的话,那就选择微软。

这里选择Active Scripting。为建立好的脚本编辑器程序添加一个脚本操作器,这个脚本操作器就是一个Atl简单对象,可以实现加载脚本、运行脚本、停止脚本三种操作。这三种操作都是自己定义的,然后别的程序可以调用的,而且调用起来很方便。其中运行脚本这个操作就要使用到Active Scripting技术了。要实现运行脚本功能,得在脚本操作器的要继承的类中添加CObject类、IActiveScriptSite接口和IActiveScriptSiteWindow接口。这三项是最基本的,如果要实现调试功能的话,还要加一些其它的接口。添加CObject类是因为脚本操作器的MapNamedItems要设置为类本身,所以操作器要成为CObject的子类,ManNamedItems是一个CMapStringToOb类,它的项必须要设置为CObject类。后面的两个接口都是AS最基本的接口。IActiveScriptSite接口的GetItemInfo方法是要实现的,OnScriptError事件是可选的,为了调试方便,最好是实现,其它方法则不用实现,直接返回S_OK或E_NOTIMPL就行。在实现GetItemInfo时,需要返回SCRIPTINFO_ITYPEINFO类型的信息时,可以使用IDispatch的方法:this->GetTypeInfo(0,
NULL, ppti);需要返回SCRIPTINFO_IUNKNOWN类型的信息时,则用IDispatch本身:*ppiunkItem = (IDispatch*)this;然后还要实现IActiveScriptSiteWindow接口的GetWindow方法,把hWnd直接赋为NULL就行,然后返回S_OK,其它方法直接返回S_OK。

这些都做完后,可以为脚本操作器添加一些应用程序的接口,比如你要读取变量,就添加一个ReadTag的方法,这些都能直接写到脚本里。

真正要实现脚本操作器的三个方法了:加载脚本、运行脚本和停止脚本。加载脚本好说,建立一个字符串成员变量,在方法中传入一个文本文件名的参数,然后把这个文本文件内容,也就是脚本赋值给这个变量。运行脚本,在脚本操作器类中建立IActiveScript和IActiveScriptParse变量,这是脚本操作器是最重要的两个变量。用下面这条语句建立IActiveScript指针变量:hr = CoCreateInstance(CLSID_VBScript, NULL, CLSCTX_INPROC_SERVER,
IID_IActiveScript, (void **)&Axs);然后用下面这条语句查询出IActiveScriptParse指针变量:hr = Axs->QueryInterface(IID_IActiveScriptParse, (void **)&Axsp);之后就是下面几条语句:

hr = Axs->SetScriptSite(this); // 设置脚本操作范围

hr = Axsp->InitNew();// 初始化

mapNamedItems[_T("App")] = this;// 设置命名项,脚本中可以直接使用

// 将命名项添加到脚本操作范围中

hr = Axs->AddNamedItem(L"App", SCRIPTITEM_ISVISIBLE | SCRIPTITEM_ISSOURCE | SCRIPTITEM_GLOBALMEMBERS);

这时,已经做好运行前的准备工作,下面要真正往引擎中加载脚本了:

CString scriptText; // 脚本字符串

EXCEPINFO ei; // 异常信息

HRESULT hr;

scriptText = Script;

BSTR ParseMe = scriptText.AllocSysString(); // 将脚本字符串转化为BSTR

Axsp->ParseScriptText(ParseMe, L"App", NULL, NULL, 0, 0, 0L, NULL, &ei); // 先过一遍

// 最后,启动脚本

hr = Axs->SetScriptState(SCRIPTSTATE_CONNECTED);

运行脚本到这里就可以了,如果中间发生了故障,则要在OnScriptError中写上:Axs->Close();以及你要把故障信息输出到什么地方。如要不写这些,有可能脚本操作器无法正常结束,导致整个应用程序无法结束。

那么结束脚本,无非就是把Axs和Axsp清理一下:Axsp->Release();Axs->Release(),要注意顺序。

脚本操作器写完了,如何调用呢?用Atl建立的脚本操作器,我们命名为CScriptOper,调用时可以这样:

CComObject<CScriptOper> *oper= NULL;

CComObject<CScriptOper>::CreateInstance(&oper);

engine->AddRef(); // 这条不写的话,程序不能退出,很奇怪

BSTR file = _com_util::ConvertStringToBSTR("C:\\test.vb");

oper->LoadScript(file);

oper->Run();

oper->Stop();

SysFreeString(file);

oper->Release();

oper=NULL;

这几行调用都不用怎么写注释,看得很明白。最后要注意的是内存泄漏,值得一提的是BSTR变量是最容易泄漏的,要确保所有的BSTR都要用SysFreeString函数来释放一下。

下面写一个可以读数的脚本:

dim startDate

dim startTime

dim endDate

dim endTime

dim data

startDate = DateSerial(2012, 11, 14)

startTime = TimeSerial(10, 44, 00)

endDate = DateSerial(2012, 11, 14)

endTime = TimeSerial(10, 45, 00)

data = App.ReadTag ("Tag1", startDate, startTime, endDate, endTime)

dim dataString

dim dataLength

dataLength = UBound(data, 1)-LBound(data, 1) + 1 '获得数据的长度

dataString = CStr(dataLength)

App.ShowMessage dataString

dataString = CStr(data(0))

App.ShowMessage dataString

dataString = CStr(data(1))

App.ShowMessage dataString

这段脚本也很明白,这里面最有意思的地方就是data这个变量最后成了数组,因为VBS里面所有的变量都是VARIANT类型的,当然数组也是一样的,如果ReadTag没有读上来数的话,那么UBound这些函数就会发生运行时错误。VBS中的数组和COM里面的安全数组是一致的,关于安全数组,则是另外一个值得讨论的话题了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: