为现有的ATL/WTL应用程序添加宏脚本语言支持





5.00/5 (5投票s)
展示了如何在现有的ATL/WTL应用程序中使用Microsoft Script Hosting
截图

ATLScriptHost应用程序在使用前必须注册其组件。运行“ATLScriptHost /RegServer”来注册它们。
引言
在我之前的文章:《MFCScriptHost》,我曾承诺发布相同类的ATL版本。现在它来了!本文介绍了如何将Active Scripting技术集成到您自己的应用程序中。如果以后有足够多的开发者感兴趣,我可能会提供完整的解决方案,为现有应用程序提供宏编辑器/录制器。再次声明,这将免费提供给您!在ATL应用程序中使用Active Scripting接口
由于我已经提供了一些关于Script Host工作原理的细节,我将直接进入您需要将其集成到应用程序中的细节。希望这里提供的步骤适用于任何ATL/WTL GUI应用程序。另外,本文假定读者对ATL/WTL和COM或ActiveX控件有一定的了解。CodeProject上有许多关于学习COM的文章,您也可以在MSDN上搜索更多。我建议您在将此过程放入现有应用程序之前,先在单独的项目中尝试。这将帮助您熟悉提供的步骤。我相信它们很直接,但对某些人来说可能显得复杂。在尝试将其集成到现有应用程序之前,请先单独尝试。另外,我没有用Visual Studio .NET尝试过此过程,希望它不会太困难。
流程
1. 为项目添加IDL文件
如果您的应用程序不是服务器(即通过ATL/WTL向导中的COM服务器选项创建的),那么这一步是必需的。非服务器应用程序(仅GUI)没有IDL,因为它不需要公开任何接口。现在这对我们来说是个问题,因为稍后我们需要在系统中注册我们的组件。如果您的应用程序已经是自动化服务器,请跳过此步骤。如果不是,请继续!我们将创建一个IDL文件并准备好使用它。首先,您需要创建一个名为您应用程序(YourAppName)的IDL文件,并使用GUIDGen.exe(可在Visual Studio的工具文件夹中找到)为您的类库生成新的GUID。典型的.IDL文件如下所示
// YourAppName.idl : IDL source for YourAppName.exe // // This file will be processed by the MIDL tool to // produce the type library (YourAppName.tlb) and marshalling code. import "oaidl.idl"; import "ocidl.idl"; [ uuid(XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX), version(1.0), helpstring("YourAppName 1.0 Type Library") ] library YourAppNameLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); };现在您有了新的.IDL文件,您会注意到构建项目不会创建任何接口定义文件。要做到这一点,您需要配置您的项目。在“项目设置”下,通过选择.IDL文件,可以在MIDL选项卡下指定文件名。

IDL文件配置选项卡
如果重新构建项目,您会注意到新生成的“YourAppName.h”和“YourAppName_i.c”文件仅包含有关库的信息,而不包含任何接口。如果您使用不同的名称,请记住在使用此过程时替换它们。然后我们将进行下一步,创建我们的Host对象。
2. 创建Active Scripting Site对象
此步骤展示了如何创建Active Scripting Site对象。现在,如果您尝试使用“插入->新类”命令菜单创建一个新的COM ATL类,您会注意到只有“通用c++类”可用。但我们需要的是COM ATL类!要使其正常工作,首先需要定义一个对象映射,使用ATL对象映射宏。您必须在module声明后插入以下内容。
// This is to be included in "YourAppName.cpp" CAppModule _Module; BEGIN_OBJECT_MAP(ObjectMap) END_OBJECT_MAP()插入这些行后,现在可以创建基于ATL的COM对象。我们将它命名为“CScriptHost”。如果您想使用不同的名称,那随您便!
新的COM ATL对象类
如果构建项目,您可能会遇到错误,因为ATL类向导将对象定义放在了库的外部。修复此问题的简单方法是将整个对象定义移到库内部。您还将遇到一组新的错误,因为向导没有自动为您添加include指令。别担心,我们只需要在正确的位置包含适当的文件。
// Insert in your "YourAppName.cpp" #include "ScriptHost.h" #include "YourAppName_i.c" // Insert this in "ScriptHost.h" #include "YourAppName.h"
就这样,现在没有错误了!如果您仍然有错误,请验证您在前面的步骤中没有遗漏任何内容。现在我们将自定义我们的host,让它真正发挥作用!
3. Active Scripting Host实现
如果您能进行到这一步,那么接下来的步骤应该很简单!您可能遇到的绝大多数复杂性都是由于您的应用程序只是一个简单的GUI。正如大多数高级ATL开发者可能会猜到的,现在我们只需要包含提供的文件(AtlActiveScriptSite.h)并为我们的host对象实现IActiveScriptHostImpl
。我们还将公开我们希望与我们的对象一起使用的函数(方法)和属性。总而言之,最终结果应该如下所示
class CScriptHost : public CComObjectRoot, public CComCoClass<CScriptHost,&CLSID_ScriptHost>, public ISupportErrorInfo, public IConnectionPointContainerImpl<CScriptHost>, public IActiveScriptHostImpl<CScriptHost>, public IDispatchImpl<IScriptHost, &IID_IScriptHost, &LIBID_YOURAPPNAMELib> { public: CScriptHost() {} BEGIN_COM_MAP(CScriptHost) COM_INTERFACE_ENTRY(IScriptHost) COM_INTERFACE_ENTRY(IActiveScriptSite) COM_INTERFACE_ENTRY(IActiveScriptSiteWindow) COM_INTERFACE_ENTRY(ISupportErrorInfo) COM_INTERFACE_ENTRY(IConnectionPointContainer) COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP() // .... other lines // IScriptHost public: STDMETHOD(CreateEngine)(BSTR pstrProgID); STDMETHOD(CreateObject)(/*[in]*/BSTR strProgID, /*[out,retval]*/LPDISPATCH* ppObject); STDMETHOD(AddScriptItem)(/*[in]*/BSTR pstrNamedItem, /*[in]*/LPUNKNOWN lpUnknown); STDMETHOD(AddScriptCode)(/*[in]*/BSTR pstrScriptCode); STDMETHOD(AddScriptlet)(/*[in]*/BSTR pstrDefaultName, /*[in]*/BSTR pstrCode, /*[in]*/BSTR pstrItemName, /*[in]*/BSTR pstrEventName); };我建议您使用ATL向导为您的host对象添加方法(和/或属性)。手动添加这些东西不会为您节省时间,所以继续使用它来节省时间。

为您的host对象添加方法和属性
现在您可以调用host对象的实际实现代码了。例如
STDMETHODIMP CScriptHost::CreateEngine(BSTR pstrProgID) { BOOL bRet = IActiveScriptHostImpl<CScriptHost>::CreateEngine( pstrProgID ); return (bRet? S_OK : E_FAIL); } STDMETHODIMP CScriptHost::CreateObject(BSTR strProgID, LPDISPATCH* ppObject) { LPDISPATCH lpDispatch = IActiveScriptHostImpl <CScriptHost>::CreateObjectHelper( strProgID ); *ppObject = lpDispatch; return ((lpDispatch!=NULL)? S_OK : E_FAIL); }
现在您的对象已经准备好使用了,下一步将展示如何注册它并创建它的实例
4. 准备Script Hosting服务
在我们能够使用任何COM对象之前,您可能知道,它必须在您的系统中注册。更简洁的方法是遵循ATL代码中的当前约定,这意味着我们将为用户提供注册/取消注册应用程序类型库的选项。我们还将创建一个注册表资源。这一步对于GUI应用程序成为服务器是必需的。如果您的应用程序已经是服务器,您可能需要跳过此步骤。
// FindOneOf function LPCTSTR FindOneOf(LPCTSTR p1, LPCTSTR p2) { while (p1 != NULL && *p1 != NULL) { LPCTSTR p = p2; while (p != NULL && *p != NULL) { if (*p1 == *p) return CharNext(p1); p = CharNext(p); } p1 = CharNext(p1); } return NULL; }
// WinMain codes // Modify your WinMain to have these lines hRes = _Module.Init(ObjectMap, hInstance, &LIBID_YOURAPPNAMELib); ATLASSERT(SUCCEEDED(hRes)); TCHAR szTokens[] = _T("-/"); int nRet = 0; BOOL bRun = TRUE; LPCTSTR lpszToken = FindOneOf(lpstrCmdLine, szTokens); while (lpszToken != NULL) { if (lstrcmpi(lpszToken, _T("UnregServer"))==0) { _Module.UpdateRegistryFromResource(IDR_SCRIPTHostServer, FALSE); nRet = _Module.UnregisterServer(TRUE); bRun = FALSE; break; } if (lstrcmpi(lpszToken, _T("RegServer"))==0) { _Module.UpdateRegistryFromResource(IDR_SCRIPTHostServer, TRUE); nRet = _Module.RegisterServer(TRUE); ATLASSERT( SUCCEEDED(nRet) ); bRun = FALSE; break; } lpszToken = FindOneOf(lpszToken, szTokens); } if (bRun) { hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE); int nRet = 0; // BLOCK: Run application { CMainDlg dlgMain; nRet = dlgMain.DoModal(); } _Module.RevokeClassObjects(); }对于单文档/多文档视图应用程序,您需要在调用创建GUI主窗口的
Run
函数之前调用RegisterClassObjects
和RevokeClassObjects
。您还必须创建一个新资源来注册您的应用程序。假设“REGISTRY”资源的名称是IDR_SCRIPTHost_Server
,它应该如下所示(使用GUIDGen创建新GUID)HKCR { NoRemove AppID { ForceRemove {11111111-1111-1111-1111-111111111111} = s 'YourAppName' 'YourAppName.exe' { val AppID = s {11111111-1111-1111-1111-111111111111} } } }使用资源编辑器创建一个新的“REGISTRY”资源,并将其保存为“.rgs”文件。您还必须使用Resource Includes来定义类型库资源。插入以下文本
#ifdef _DEBUG 1 TYPELIB "Debug\\YourAppName.tlb" #else 1 TYPELIB "Release\\YourAppName.tlb" #endif

资源包含对话框
5. 使用Script Hosting服务
现在一切就绪,最后要做的是创建一个host对象的实例。添加您想从脚本语言中公开的任何命名项,您就完成了!典型的初始化代码如下所示。
// Create CComPtr<IScriptHost> m_pScriptHost; HRESULT hr = m_pScriptHost.CoCreateInstance( L"YourAppName.ScriptHost"); if (SUCCEEDED(hr)) { m_pScriptHost->CreateEngine( L"JScript" ); // Adding named-item other than the "ScriptHost" // Web Browser IWebBrowser* pWebBrowser = NULL; GetDlgControl(IDC_WEBBROWSER, __uuidof(IWebBrowser), (void**)&pWebBrowser); m_pScriptHost->AddScriptItem(L"webBrowser", pWebBrowser); pWebBrowser->GoHome(); } else { MessageBox(_T("Class not registered!!!")); }然后,您将通过调用
AddScriptCode
方法来添加宏文本CComBSTR bstrText; GetDlgItemText(IDC_TXT_SCRIPT, bstrText.m_str); if (m_pScriptHost) m_pScriptHost->AddScriptCode( bstrText );就这样,这篇文章看起来可能有点长,但这些步骤基本上就是您完成工作所需要的。如果您的应用程序已经公开了一些接口,那么第1步和第4步会更容易。我整理了一个简单的演示,展示了如何使用它。我很想听听您的意见,因为我计划为现有应用程序(ATL和/或MFC)提供更高级的解决方案,以集成完整的宏支持。祝您使用愉快!
参考文献
查看我以前的MFC应用程序解决方案.下载WTL7