一个脚本宿主就是一个能运行其它脚本语言(VB脚本,VBA,Java脚本)并且能将应用程序内部的COM接口暴露给这些脚本的应用程序.微软的IE浏览器,Excel,Word,Access都是脚本宿主.IE用的是VB脚本,而其它的则是用的VBA.
大部分关于如何实现脚本宿主的代码来自于Developer Studio光盘中的"Axscript"范例.该范例是用WIN32 API实现的.这里准备了三个工程来说明用MFC如何实现.
第一步: 一个使你的MFC程序支持VB脚本的例子
第二步: 暴露内部自动化对象到VB脚本引擎
第三步: 添加VB脚本代码来控制由C++代码触发的事件
将三个工程一个个编译运行,看看它们是如何工作的.运行程序后,选择菜单"VBScript->Load script file",会提示你载入一个脚本文件,选择工程目录下的"TestProgram.txt"文件,然后选择"VBScript->Run"菜单来运行脚本.
第一个范例会显示一个消息对话框,每二个则绘制一些矩形,第三个会允许你会鼠标拖动这些矩形.
注意:如果你的工程设置所有的编译结果放在同一目录,切换工程时不要忘记使用"Rebuild All"命令重新编译.
要理解ActiveX脚本引擎是如何工作的请参考MSDN中的帮助,你可以在ActiveX相关帮助中的ActiveX scripting中找到.
第一步:
为了让你的程序运行VB脚本(或其它脚本)你必须通过IActiveScript和IActiveScriptParse接口与脚本引擎打交道.而且你的程序必须向IActiveScriptSite暴露对象,可能同时还要暴露给IActiveScriptSiteWindow.
第一步的范例有一个CMainFrame对象,它同时支持IActiveScript和IActiveScriptParse接口.大部分的IActiveScript方法在该范例中没有实现.要理解如何在MFC中实现OLE接口,请参考相关文档.
第二步:
这一步要用类向导生成两个自动化对象.第一个对象CAXHostDoc由CDocument派生.第二个对象CAXSprite由CCmdTarget派生.CAXHostDoc有一个叫做Animation的对象,它能够管理子图形.CAXSprite是矩形子图形的一个OLE封装.子图形能够添加到Animation对象,能够被删除,移动,改变...,但是Animation对象什么也不做,直到你要求它绘制图像到特定的DC.不要管它到底是如何实现的.
你的程序可以在脚本引擎中加入顶级的条目名称,它将在脚本中有效.这些条目名称应该与你的应用程序中的某些COM对象相对应.通常我们使用"Application"作为顶级对象.在这个示例中,是一个SDI应用,CAXHostApp对象有一个m_pDoc成员保存一个指向文档的指针.在CMainFrame::CreateScriptEngine方法中,"Document"对象名称被加入到脚本引擎.
const TCHAR* szItemName=_T("Document"); USES_CONVERSION; LPCOLESTR lpostrApp = T2COLE(szItemName);
hr=m_pIActiveScript->AddNamedItem(lpostrApp, SCRIPTITEM_ISVISIBLE);
COM对象与添加的条目名称间的关联就是在IActiveScriptSite::GetItemInfo方法中进行的.
STDMETHODIMP CMainFrame::XScriptSite::GetItemInfo
(
LPCOLESTR pstrName,
DWORD dwReturnMask,
IUnknown** ppunkItemOut,
ITypeInfo** pptinfoOut
)
{
//HRESULT hr;
METHOD_PROLOGUE(CMainFrame, ScriptSite)
USES_CONVERSION;
LPCTSTR lpstrApplication=szItemName;
if (dwReturnMask & SCRIPTINFO_ITYPEINFO)
{
if (!pptinfoOut)
return E_INVALIDARG;
*pptinfoOut = NULL;
}
if (dwReturnMask & SCRIPTINFO_IUNKNOWN)
{
if (!ppunkItemOut)
return E_INVALIDARG;
*ppunkItemOut = NULL;
LPTSTR lpszName = OLE2T(pstrName);
//请注意这里
if(!_tcsicmp(lpstrApplication, lpszName))
{
*ppunkItemOut = theApp.m_pDoc->GetIDispatch(TRUE);
return S_OK;
////////////////////////////
}
}
return TYPE_E_ELEMENTNOTFOUND;
}
一旦这种关联被建立,你可以使用文档的方法与属性.如果你的应用中有一些方法返回接口到其它COM对象,你可以使用它们的方法与属性而不需要调用AddNamedItem.它们不是顶层对象,它们是由Document对象获得的(本例中只有一个对象-CAXSprite).
第三步:
源程序下载
Step 3
In this example we make our program able to fire some events which can be handled by the VBScript engine. First of all we will need to provide ITypeInfo information to the script engine. The application object has some members to hold this information (you can use global variables).
ITypeLib* pITypeLib; //library AXHost ITypeInfo* ptinfoClsDoc; //coclass Document ITypeInfo* ptinfoIntDoc; //dispinterface IAXHost ITypeInfo* ptinfoClsSprite; //coclass Sprite ITypeInfo* ptinfoIntSprite; //dispinterface IAXSprite
To make your object fire events you need to create another interface to your object's events. Modify your ODL file.
Example:
[uuid(B6032E41-636A-11d1-8F0B-F54176DCF130)] dispinterface IAXHostEvents { properties: methods: // Events [id(1)] void OnMouseDown([in] short x, [in] short y); [id(2)] void OnMouseMove([in] short x, [in] short y); [id(3)] void OnMouseUp([in] short x, [in] short y); [id(4)] void OnMouseDblClk([in] short x, [in] short y); } // Class information for CAXHostDoc [ uuid(34E25943-6314-11D1-8F0B-F54176DCF130) ] coclass Document { [default] dispinterface IAXHost; [default, source] dispinterface IAXHostEvents; };
In the CAXHostApp::InitInstance() method the type information was loaded
// ##### BEGIN ACTIVEX SCRIPTING SUPPORT ##### const TCHAR* lpstrTLB=_T("AXHost.tlb"); if(S_OK!=LoadTypeInfo(lpstrTLB,0,1,0,0,IID_LIBAXHost,IID_IAXHostClass,IID_IAXHost,FALSE,&pITypeLib,&ptinfoClsDoc,&ptinfoIntDoc)) return FALSE; if(S_OK!=LoadTypeInfo(lpstrTLB,0,1,0,0,IID_LIBAXHost,IID_IAXSpriteClass,IID_IAXSprite,FALSE,&pITypeLib,&ptinfoClsSprite,&ptinfoIntSprite)) return FALSE; // ##### END ACTIVEX SCRIPTING SUPPORT #####
In the MainFrm.module:
The AddNamedItem was called with the SCRIPTITEM_ISSOURCE flag.
pThis->m_pIActiveScript->AddNamedItem(lpszT, SCRIPTITEM_ISVISIBLE|SCRIPTITEM_ISSOURCE);
In the IActiveScriptSite::GetItemInfo ITypeInfo interface to the Document CLASS object has been provided to the scripting engine.
STDMETHODIMP CMainFrame::XScriptSite::GetItemInfo ( LPCOLESTR pstrName, DWORD dwReturnMask, IUnknown** ppunkItemOut, ITypeInfo** pptinfoOut ) { //HRESULT hr; METHOD_PROLOGUE(CMainFrame, ScriptSite) USES_CONVERSION; LPCTSTR lpstrApplication=szItemName; if (dwReturnMask & SCRIPTINFO_ITYPEINFO) { if (!pptinfoOut) return E_INVALIDARG; *pptinfoOut = NULL; //Attention, please LPTSTR lpszName = OLE2T(pstrName); if(!_tcsicmp(lpstrApplication, lpszName)) { *pptinfoOut=theApp.ptinfoClsDoc; return S_OK; } //////////////////// } if (dwReturnMask & SCRIPTINFO_IUNKNOWN) { if (!ppunkItemOut) return E_INVALIDARG; *ppunkItemOut = NULL; //Attention, please LPTSTR lpszName = OLE2T(pstrName); if(!_tcsicmp(lpstrApplication, lpszName)) { *ppunkItemOut = theApp.m_pDoc->GetIDispatch(TRUE); return S_OK; } /////////////////// } return TYPE_E_ELEMENTNOTFOUND; } Also the IActiveScriptSite::RequestTypeLibs method was implemented. //--------------------------------------------------------------------------- // //--------------------------------------------------------------------------- STDMETHODIMP CMainFrame::XScriptSite::RequestTypeLibs(void) { METHOD_PROLOGUE(CMainFrame,ScriptSite); HRESULT hr=pThis->m_pIActiveScript->AddTypeLib(IID_LIBAXHost, 1, 0, 0); return hr; }
If you want your object support events, it must support IConnectionPointContainer interface and IProvideTypeInfo interface. See the article about CConnectionPoint MFC object. See how it was implemented with the CAXHostDoc object.
If you implement all the interfaces correctly, the scripting engine will establish a connection to your object and you will be able to fire events with help of a small function.
void CAXHostDoc::FireEvent(int iEvNum,VARIANTARG* pVar,int nParameters) { const CPtrArray* pConnections = m_xConnPt.GetConnections(); ASSERT(pConnections != NULL); int cConnections = pConnections->GetSize(); IDispatch* pSink; for (int i = 0; i < cConnections; i++) { pSink = (IDispatch*)(pConnections->GetAt(i)); ASSERT(pSink != NULL); InvokeEvent(pSink, iEvNum, pVar, nParameters); } }
The iEvNum must be the same as in the ODL file. To fire the MouseDown event, for instance, i have to use iEvNum to be equal 1. Because [id(1)] void OnMouseDown([in] short x, [in] short y);
I should have made constants like IDEVENT_MOUSEDOWN and use them both in the ODL file and in my C++ code.
Example:
void CAXHostView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default SetCapture(); VARIANTARG var[2]; VariantInit(&var[1]); var[1].vt = VT_I2; var[1].iVal = (short)point.x; VariantInit(&var[0]); var[0].vt = VT_I2; var[0].iVal = (short)point.y; GetDocument()->FireEvent(1,var,2); CView::OnLButtonDown(nFlags, point); }
Observe the sequence of passing parameters to the variant array.
I am not sure that in this small description i have written all the tips to make your program work, but i send you the code which you can investigate. And you should alse see the "Axscript" example which does a little bit more. It provides a way how to make subclassing with VBScript. See the BuildTypeInfo function in the example to see how it has been done there.
If you have any questions, you can send me a message and i will try to answer. But i must confess that i am a novice in COM. COM is not difficult to understand but there are so many different objects and interfaces. And one must know how to use them in one's programs. The same can be said about the C++ language itself.
I also would like to get some examples on this matter if you have them. For example, i don't know how to implement debugging features (they say that Microsoft has already made a debugger fo VBScript), how to interrupt a running script (i think i should create an another thread and use an appropriate function), i tried to implement DUAL interfaces to my objects but the VBScript used just the IDispatch interfaces and i thought that VBScript did not support DUAL interfaces.
If you have any clues concerning this technology, please send them to me. I would like also your opinion whether this feature is interesting, for i can send some more examples. I mean not this sprites but a way of using this technology in business applications. For example, my program use special kinds of objects to work with SQL server databases and Access databases such as "recordsets", "idsets, "caches", "reports", "views" and others.