爲應用程序添加腳本訪問功能

        首先,要有一個可以通過代碼訪問的應用程序,兩種方式,一種是程序提供應用接口,要麼是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裏面的安全數組是一致的,關於安全數組,則是另外一個值得討論的話題了。

發佈了56 篇原創文章 · 獲贊 7 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章