1、如何在 vc 程序中嵌入脚本语言今天很多大型程序中都能够见到内嵌脚本进行二次开发的功能,例如 ms word,excel,visual studio 等。一直以来我都希望能在自己的程序中加入同样的功能,经过前一段时间的研究,终于有所心得与大家分享。在研究过程中,我查找了 发现一篇比较有价值的文章( ,其中描述了在 vc 程序中加入脚本语言的过程。其原理如下1. 首先使用 CoCreateInstance()创建某种脚本语言(javascript ,vbscript)的引擎,获得某种语言的脚本引擎的接口 IActiveScript。2. 实现回调站点接口 IActiveScriptSite 通
2、过 IActiveScript-SetScriptSite()交由脚本引擎回调,在 site 中可以取得引擎的状态信息,并提供用户的自定义变量的自动化对象。3. 通过 IActiveScript-QueryInterface()取得 IActiveScriptParse 接口,IActiveScriptParse 用于解释执行用户的脚本代码。幸运的是这一系列接口和操作已经被文章的作者封装成 CActiveScriptHost 类,只需要掌握CreateEngine() (创建脚本引擎) ,AddScriptCode() (加入用户脚本代码) ,AddScriptItem() (加入用户自定义变
3、量)四个常用的方法即可。下面描述如何在自己 mfc 程序中使用上述类嵌入脚本和自定义脚本对象的过程,步骤如下。1. 首先将文章所附例子工程中ActiveScriptHost.cpp,ActiveScriptHost.h ,Host_Proxy.cpp,Host_Proxy.h ,MFCScriptHost.odl 拷贝到当前工程中。2. 在当前工程的 xxx.rc 中加入以下内容,即将类型库加入到程序资源文件中#ifdef _DEBUG1 TYPELIB “DebugMFCScriptHost.tlb“#else1 TYPELIB “ReleaseMFCScriptHost.tlb“#endi
4、f3. 在需要使用的类成员中加入 CHost_Proxy m_ScriptProxy;成员,在 OnCreate 或OnInitDialog 中加入m_ScriptProxy.CreateEngine( L“JavaScript“ );/创建脚本引擎m_ScriptProxy.AddScriptItem(L“test“,m_ptestObject-GetUnknown();/加入名称为 test的 IDispatch 对象4. com 对象的生成有两种方案,一种是使用 MFC 方式生成,即对象从 CmdTarget 继承,并选中 automation 的 radio button(如图表 1)
5、 ,并通过 Class Wizard 中的自动化标签加入方法和属性(如图表 2) 。这种对象的缺点是无法自定义事件源供脚本程序接收。 。图表 1图表 25. 另一种是使用 ATL 产生 com 对象,这种方式可以生成带事件功能的对象,此外功能灵活、方便。我更倾向于 ATL 方式生成 com 对象。在 Classes 点击右键选择 New ATL Object(如图表 3) ,出现 ATL Object Wizard(如图表 4) 。选中 SimpleObject, 出现ATL Object Wizard 属性对话框(如图表 5) ,按要求填入 short name(即组件名称) ,如果欲支持组
6、件的事件功能一定选中 Support Connection Points(如图表 6) ,点击确定后 vc 会自动生成代码。图表 3图表 4图表 5图表 66. 在 vc 生成的 InitATL()后,一定要手工加入_Module.RegisterTypeLib() 用来注册组件的类型库。这是因为 TL 的 IDispatch 实现会将自身调用委托给相应组件的类型库接口ITypeInfo 去执行(如图表 7) 。图表 77. 如欲实现组件的事件功能还需在相应对象点击 Implement Connection Point 菜单项(如图表 8) ,选择实现事件源接口(如图表 9) ,点击确定后系统
7、会生成发送出发事件的委托类,并添加相应代码(如图表 10) 。图表 8图表 9图表 108. 因为 com 对象发出的事件需要在脚本环境下使用,脚本环境需要通过对象的IProvideClassInfo2 接口获得默认事件源(即 source default dispinterface) ,所以 com 对象还需实现 IProvideClassInfo2 接口(如图表 11) ,加入红框内内容即可。图表 119. 脚本内调用的 code sample 例子如下,假设对象 test 有方法 hello,有事件 OnRun()javaScript 例子test.hello() /调用 test 对象
8、的 hello 方法function test:OnRun()/test 对象事件 OnRun()的回调函数vbScript 例子test.hello 调用 test 对象的 hello 方法Sub test_OnRun() test 对象事件 OnRun()的回调函数end SubJscript 与 VbScript 详细用法见msdn/platform SDK documentation/Tools and Scripting/Scripting10. 如果使用 mfc 方式生成的 com 对象,用以下代码将对象作为脚本变量加入到脚本环境中m_ScriptProxy.AddScriptIt
9、em(L“testctrl“,m_ctrl.GetIDispatch(FALSE);11. 如果使用 ATL 方式生成的 com 对象,用以下代码将对象作为脚本变量加入到脚本环境中CComObject:CreateInstance( /因为多步骤构造需要调用 finalContructm_ScriptProxy.AddScriptItem(L“event“,m_peventObject-GetUnknown();12. 脚本代码的接入方法,strScriptText 为代码脚本CString strScriptText;m_ctlScriptText.GetWindowText( strScr
10、iptText );if (strScriptText.GetLength()0)BSTR bstrText = strScriptText.AllocSysString();m_ScriptProxy.AddScriptCode(bstrText);SysFreeString(bstrText);下面简要介绍将一个 MFC 的 CButton 变为脚本中可用组件的方法用上述步骤建立 CAtlButton,CAtlRectclass ATL_NO_VTABLE CAtlButton : public CComObjectRootEx,public CComCoClass,public ICon
11、nectionPointContainerImpl,public IDispatchImpl/ IAtlButtonpublic:STDMETHOD(Move)(/*in*/IAtlRect* rect);/移动 buttonSTDMETHOD(get_Rect)(/*out, retval*/ IAtlRect* *pVal);/得到 button size;class ATL_NO_VTABLE CAtlRect : public CComObjectRootEx,public CComCoClass,public IDispatchImplpublic:CRect m_rc;public
12、:STDMETHOD(get_Bottom)(/*out, retval*/ long *pVal);STDMETHOD(put_Bottom)(/*in*/ long newVal);STDMETHOD(get_Right)(/*out, retval*/ long *pVal);STDMETHOD(put_Right)(/*in*/ long newVal);STDMETHOD(get_Left)(/*out, retval*/ long *pVal);STDMETHOD(put_Left)(/*in*/ long newVal);STDMETHOD(get_Top)(/*out, ret
13、val*/ long *pVal);STDMETHOD(put_Top)(/*in*/ long newVal);class CButtonWithAtl : public CButton,public CComObjectGlobalpublic:STDMETHOD(get_Rect)(/*out, retval*/ IAtlRect* *pVal);STDMETHOD(Move)(/*in*/IAtlRect* rect);CComObjectGlobal 表示 com 对象全局生存不需要引用计数,CComObject 表示在堆上创建对象,通过引用计数控制生命周期。STDMETHODIMP
14、 CButtonWithAtl:get_Rect(IAtlRect *pVal)CRect rc;this-GetWindowRect(rc);this-GetParent()-ScreenToClient(rc);CComObject* pRect;CComObject:CreateInstance(pRect-m_rc = rc;pRect-QueryInterface(pVal);return S_OK;STDMETHODIMP CButtonWithAtl:Move(IAtlRect *rect)CRect rc;CComPtr ptr(rect);ptr-get_Left(ptr-get_Top(ptr-get_Right(ptr-get_Bottom(