DexterChen

大梦谁先觉?凭生我自知,草堂春睡足,窗外日迟迟

 

在程序里加入对脚本的支持

初次发贴,还请各位多多关照。

有一些软件会把一部分算法以脚本的形式实现,放在程序的外面,当这部分算法需要变更的时候,只需修改脚本即可,从而避免了整个项目的重新编译。虽然使用脚本在效率上会有一定的损失,但如果不是用于大量计算等一些耗时操作的话,还是值得推荐的。下面是用 VC 实现对脚本( VBS JavaScript )支持的主要步骤,以及我对这部分功能的封装。

 

一、首先要实现自己的 IActiveScriptSite 接口。

 

class CAxScriptSite : public IActiveScriptSite

{

protected:

       DWORD m_dwRef; // 用于引用计数

       std::map<_bstr_t, ObjPtr_TypePtr_Pair> m_mapScriptObj; // 用于管理多个顶级对象

       CAxScriptHostWindow m_xScriptSiteWindow; // 一个实现了 IActiveScriptSiteWindow 接口的对象,用于与 UI 交互

 

public:

       ScriptError m_err;

public:

       CAxScriptSite() : m_dwRef(1)

       {

              m_err.m_bIsErr = false;

              m_err.m_bstrDescription = L"";

              m_err.m_bstrSource = L"";

              m_err.m_lChar = 0;

              m_err.m_scCode = 0;

              m_err.m_ulRow = 0;

       }

 

       // IUnknown methods...

       virtual HRESULT __stdcall QueryInterface(REFIID riid, void **ppvObject);

 

       virtual ULONG _stdcall AddRef(void)

       {

              return ++m_dwRef;

       }

 

       virtual ULONG _stdcall Release(void)

       {

              if(--m_dwRef == 0)

                return 0;

 

              return m_dwRef;

       }

 

       // IActiveScriptSite methods...

       virtual HRESULT __stdcall GetItemInfo(LPCOLESTR pstrName, DWORD dwReturnMask, IUnknown **ppunkItem, ITypeInfo **ppti);

 

       virtual HRESULT __stdcall GetLCID(LCID *plcid)

       {

              return S_OK;

       }

 

       virtual HRESULT __stdcall GetDocVersionString(BSTR *pbstrVersion)

       {

              return S_OK;

       }

 

       virtual HRESULT __stdcall OnScriptTerminate(const VARIANT *pvarResult, const EXCEPINFO *pexcepInfo)

       {

              return S_OK;

       }

 

       virtual HRESULT __stdcall OnStateChange(SCRIPTSTATE ssScriptState)

       {

              return S_OK;

       }

 

       virtual HRESULT __stdcall OnScriptError(IActiveScriptError *pse)

       {

              EXCEPINFO ei;

 

              pse->GetExceptionInfo(&ei);

              m_err.m_bstrDescription = ei.bstrDescription;

              m_err.m_bstrSource = ei.bstrSource;

              m_err.m_scCode = ei.scode;

              pse->GetSourcePosition(NULL, &m_err.m_ulRow, &m_err.m_lChar);

             

              m_err.m_bIsErr = true;

 

              return S_OK;

       }

 

       virtual HRESULT __stdcall OnEnterScript(void)

       {

              m_err.m_bstrDescription = L"";

              m_err.m_bIsErr = false;

              m_err.m_lChar = 0;

              m_err.m_ulRow = 0;

 

              return S_OK;

       }

 

       virtual HRESULT __stdcall OnLeaveScript(void)

       {

              return S_OK;

       }

};

 

这里被重写的函数,除了错误处理,大部分只需返回 S_OK 就可以了,具体含义详见 MSDN ,下面来看一下上面未给出实现的两个函数:

 

HRESULT __stdcall CAxScriptSite::QueryInterface(REFIID riid, void **ppvObject)

{

       if(riid == IID_IActiveScriptSiteWindow   )

       {  

              *ppvObject = &m_xScriptSiteWindow;  

              return S_OK;  

       } 

 

       *ppvObject = NULL;  

       return E_NOTIMPL;  

}

这里注意对 IID_IActiveScriptSiteWindow 的判断,如果没有这部分,程序将无法与窗体交互, VBS MsgBox (或 JavaScrip 里的 alert )这样的语句将无法执行,提示没有权限

 

HRESULT __stdcall CAxScriptSite::GetItemInfo(LPCOLESTR pstrName, DWORD dwReturnMask, IUnknown **ppunkItem, ITypeInfo **ppti)

{

       _bstr_t bstrKey((wchar_t*)pstrName);

 

       // Is it expecting an ITypeInfo?

       if(ppti)

       {

              // Default to NULL.

              *ppti = NULL;

 

              // See if asking about ITypeInfo...

              if(dwReturnMask & SCRIPTINFO_ITYPEINFO)

              {

                     //get type infomation according to namespace

                     *ppti = m_mapScriptObj[bstrKey].second;

              }

       }

 

       // Is the engine passing an IUnknown buffer?

       if(ppunkItem)

       {

              // Default to NULL.

              *ppunkItem = NULL;

 

              // Is Script Engine looking for an IUnknown for our object?

              if(dwReturnMask & SCRIPTINFO_IUNKNOWN)

              {

                     //find host object according to namespace

                     IUnknown *pUnkScriptObject = m_mapScriptObj[bstrKey].first;

                     *ppunkItem = pUnkScriptObject;

                     pUnkScriptObject->AddRef();

              }

       }

 

       return S_OK;

}

 

当脚本中出现名字空间时(比如脚本中出现 “MyObject.funXXX” ),会调用该函数使该名字空间与主程序中的组件对象建立关联:其中 pstrName 保存的是这个名字空间的名称(上例中的 ”MyObject” ),整个过程就是将这个名字空间所对应的组件对象和类型信息返回到 *ppunkItem *ppti 中去,这样,脚本就可以与主程序交互了。为了以后能在脚本中使用多个名字空间,这里使用了 std::map

 

二、为了让 ScriptSite 能与 UI 交互,要实现 IActiveScriptSiteWindow 接口,具体原因已经在第一步中讲过了,就是为了能让类似 VBS MsgBox 这样的函数能用,实现起来也很简单:

 

class CAxScriptHostWindow : public IActiveScriptSiteWindow

{

protected:

       DWORD m_dwRef; // 引用计数

 

public:

       HWND m_hWnd; // 可供交互的窗体句柄

 

public:

       CAxScriptHostWindow() : m_dwRef(1), m_hWnd(0)

       {}

 

       // IUnknown methods...

       virtual HRESULT __stdcall QueryInterface(REFIID riid, void **ppvObject)

       {

              if(riid == IID_IActiveScriptSiteWindow)  

                     *ppvObject = this;  

              else   

                     *ppvObject = NULL;

 

              return    S_OK; 

       }

 

       virtual ULONG _stdcall AddRef(void)

       {

              return ++m_dwRef;

       }

 

       virtual ULONG _stdcall Release(void)

       {

              if(--m_dwRef == 0)

                return 0;

 

              return m_dwRef;

       }

      

       virtual HRESULT __stdcall GetWindow(HWND *phwnd)

       {

              *phwnd = m_hWnd;

              return S_OK;

       }

 

       virtual HRESULT __stdcall EnableModeless(BOOL fEnable)

       {

              return S_OK;

       }

};

 

三、 为了使用方便以及复用,我对上述功能进行了封装(脚本宿主)

 

class CAxScriptHost : public CAxScriptSite

{

private:

       bool m_bInit;

       IActiveScript *m_pAS;

       IActiveScriptParse *m_pASP;

      

public:

       CAxScriptHost() :

         m_bInit(false), m_pAS(NULL), m_pASP(NULL)

       {}

 

       ~CAxScriptHost()

       {

              CloseEngine();

       }

 

public:

       //---------------------------------------

       //DESC : 为脚本宿主添加根对象

       //

       //T : COM 对象指针或与之兼容的 COM 指针对象类型

       //

       //bstrAxHostLib : 所在类型库

       //clsidAxHostObj : 类型标识

       //pHostObj : COM 对象指针或与之兼容的 COM 指针对象 ( 必需可以使用 p->QueryInterface() 函数 )

       //bstrAxHostNamespace : 分配的名字空间

       //

       //RETURN :

       //---------------------------------------

       template<class T>

       bool AddHostObject(BSTR bstrAxHostLib, CLSID clsidAxHostObj, T pHostObj, BSTR bstrAxHostNamespace)

       {

              HRESULT hr;

              ITypeInfo *pTypeInfo;

              IUnknown *pUnkScriptObject;

 

              // Register your type-library

              ITypeLib *ptLib = 0;

              hr = LoadTypeLib((LPCWSTR)bstrAxHostLib, &ptLib);

              FAIL_RETURN(hr);

 

              // Initialize your IActiveScriptSite implementation with your

              // object's ITypeInfo...

              hr = ptLib->GetTypeInfoOfGuid(clsidAxHostObj, &pTypeInfo);

              ptLib->Release();

              FAIL_RETURN(hr);

             

              //Get interface of the com object

              pHostObj->QueryInterface(IID_IUnknown, (void **)&pUnkScriptObject);

              FAIL_RETURN(hr);

 

              // Add a root-level item to the engine's name space...

              hr = m_pAS->AddNamedItem((LPCWSTR)bstrAxHostNamespace, SCRIPTITEM_ISVISIBLE | SCRIPTITEM_ISSOURCE);

              FAIL_RETURN(hr);

 

              //Add com object and type info to map

              m_mapScriptObj.insert(pair<_bstr_t, ObjPtr_TypePtr_Pair>(bstrAxHostNamespace, ObjPtr_TypePtr_Pair(pUnkScriptObject, pTypeInfo)));

 

              return true;

       }

 

       //---------------------------------------

       //DESC : 设置脚本

       //

       //lpScript : 环境脚本

       //

       //RETURN :

       //---------------------------------------

       bool SetEngineScript(LPCWSTR lpScript);

 

       //---------------------------------------

       //DESC : 脚本引擎初始化

       //

       //lpLanguage : 使用的脚本语言 ProgID

       //hSiteWindow : UI 交互所用窗体句柄

       //

       //RETURN :

       //---------------------------------------

       bool InitEngine(LPCWSTR lpLanguage, const HWND& hSiteWindow);

 

       //---------------------------------------

       //DESC : 关闭脚本引擎

       //

       //RETURN :

       //---------------------------------------

       bool CloseEngine();

 

       //---------------------------------------

       //DESC : 调用脚本中的函数

       //

       //lpFunc : 函数名

       //pParam : 参数 ( 注意参数的顺序是倒置的 )

       //pvarResult : 返回值

       //pEx : 返回执行错误

       //pArgErr : 返回参数错误

       //

       //RETURN :

       //---------------------------------------

       bool ExecuteFunction(LPCWSTR lpFunc, DISPPARAMS *pParam, VARIANT *pvarResult = NULL, EXCEPINFO *pEx = NULL, UINT *pArgErr = NULL);

      

       //---------------------------------------

       //DESC : 指定脚本函数是否存在

       //

       //lpFunc : 函数名

       //

       //RETURN :

       //---------------------------------------

       bool FunctionExist(LPCWSTR lpFunc);

};

 

经过封装以后,要在程序中调用脚本语言,可以按以下步骤完成:

   1 创建一个 CAxScriptHost 对象 , 以及要添加到宿主根名字空间的 COM 对象(比如 CComPtr, CComObject     

   2 初始化宿主对象,设置要使用的语言

   3 将组件对象挂接到根名字空间下

   4 设置脚本(可能会调用主程序中的组件对象)

   5 运行脚本中的方法(可能会调用主程序中的组件对象)

   6 使用结束,关闭引擎,并销毁 COM 对象


   
下面是示例程序的一些截图:

加载脚本后

o_AxScriptHostDemo1.JPG


调用函数
fun

o_AxScriptHostDemo2.JPG

调用名字空间 HostObjectDemo

o_AxScriptHostDemo3.JPG

调用名字空间 HostObject2
o_AxScriptHostDemo4.JPG 


 

posted on 2007-02-13 12:10 DexterChen 阅读(1080) 评论(4)  编辑 收藏 引用 所属分类: C++

评论

# water 2007-02-14 10:04 gm

沙发  回复  更多评论   

# 提个问题 2007-02-14 10:18 gm

对了,作为第一个来到这里的游客,有啥奖励吗?  回复  更多评论   

# re: 在程序里加入对脚本的支持 2007-04-05 16:33 C++程序员

楼主你好,我搜索了很久在网上搜索到你的这篇文章,对我的帮助很大,但是有些地方还是不很明白。能加我的QQ我向你请教一下吗:41086722  回复  更多评论   

# re: 在程序里加入对脚本的支持 2007-11-09 09:56 veryhuai

楼主能不能把代码发一个给我看看?多谢!coolcoffeeboy@163.com  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

统计

常用链接

留言簿(3)

随笔分类

随笔档案

文章分类

搜索

最新评论

阅读排行榜

评论排行榜