我不建议使用IWebBrowser2
来获取脚本主机的接口。创建浏览器对象具有在后台启动 Internet Explorer 实例的令人讨厌的副作用,至少在 Windows 7 上是这样。它还会引入几个可能与您尝试完成的对象冲突的命名对象(文档、窗口等) .
相反,我建议您使用Active Scripting Interfaces。使用它们需要一些额外的代码,但它更加灵活并且给你更多的控制。具体来说,您可以设置脚本控制站点IWebBrowser2
,而不必担心冲突或创建使用这些控件时需要的转发适配器。尽管它需要一些额外的代码,但实现起来并不难。
第一步是获取GUID
您要使用的特定语言的 。这可以是 Javascript、VBScript、Python 或为 Windows Scripting Host 设计的任何脚本引擎。您可以GUID
自己提供,也可以使用如下所示的 COM 应用程序 ID 通过名称从系统中获取它。
GUID languageId;
CLSIDFromProgID(L"JavaScript" , &languageId);
下一步是通过CoCreateInstance
使用上面的 GUID 作为类 ID 调用来创建脚本引擎的实例。
CoCreateInstance(
languageId,
0,
CLSCTX_INPROC_SERVER,
IID_IActiveScript,
reinterpret_cast<void**>(&scriptEngine_));
这将返回一个指向IActiveScript对象的指针,该对象是脚本引擎的主要接口。
下一步是设置脚本站点。这是您提供的IActiveScriptSite的实现。这是脚本引擎用来获取某些信息并调度某些事件(例如脚本状态的更改)的接口。
scriptEngine_->SetScriptSite(siteObjectPointer);
此时,您拥有开始使用脚本引擎调用脚本函数并允许脚本访问本机 C++ 对象所需的所有对象。要将对象添加window
到document
根“命名空间”中,请调用IActiveScript::AddNamedItem或IActiveScript::AddTypeLib。
scriptEngine_->AddNamedItem(
"objectname",
SCRIPTITEM_ISVISIBLE | SCRIPTITEM_NOCODE);
您要向脚本公开的对象必须实现IDispatch
或IDispatchEx
。您实现哪个接口取决于对象的要求,但如果可能,请使用 IDispatch,因为它需要的代码更少。
现在您已经初始化了脚本引擎,您可以开始向其中添加脚本了。您需要调用QueryInterface
脚本引擎来获取指向IActiveScriptParse
接口的指针,初始化解析器,然后添加脚本。您只需要初始化解析器一次。
scriptEngine_->QueryInterface(
IID_IActiveScriptParse,
reinterpret_cast<void **>(&parser));
// Initialize the parser
parser->InitNew();
parser->ParseScriptText("script source" ....);
现在您已经初始化了引擎并添加了一个或两个脚本,您可以通过将引擎状态更改为SCRIPTSTATE_CONNECTED
.
scriptEngine_->SetScriptState(SCRIPTSTATE_CONNECTED);
这些是将脚本引擎嵌入应用程序的基本步骤。下面的示例是一个完整的框架,它实现了脚本站点和一个向脚本公开功能的对象。它还包括稍微修改过的代码版本,用于调用您在问题中引用的脚本函数。它并不完美,但它可以工作,并且有望让您更接近您想要完成的目标。
BasicScriptHost.h
#ifndef BASICSCRIPTHOST_H_
#define BASICSCRIPTHOST_H_
#include <string>
#include <vector>
#include <activscp.h>
#include <comdef.h>
#include <atlbase.h>
class BasicScriptObject;
class BasicScriptHost : public IActiveScriptSite
{
public:
typedef IActiveScriptSite Interface;
// Constructor to
explicit BasicScriptHost(const GUID& languageId);
BasicScriptHost(
const GUID& languageId,
const std::wstring& objectName,
CComPtr<IDispatch> object);
virtual ~BasicScriptHost();
// Implementation of IUnknown
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,void ** object);
virtual ULONG STDMETHODCALLTYPE AddRef ();
virtual ULONG STDMETHODCALLTYPE Release();
// Implementation of IActiveScriptSite
virtual HRESULT STDMETHODCALLTYPE GetLCID(DWORD *lcid);
virtual HRESULT STDMETHODCALLTYPE GetDocVersionString(BSTR* ver);
virtual HRESULT STDMETHODCALLTYPE OnScriptTerminate(const VARIANT *,const EXCEPINFO *);
virtual HRESULT STDMETHODCALLTYPE OnStateChange(SCRIPTSTATE state);
virtual HRESULT STDMETHODCALLTYPE OnEnterScript();
virtual HRESULT STDMETHODCALLTYPE OnLeaveScript();
virtual HRESULT STDMETHODCALLTYPE GetItemInfo(
const WCHAR* name,
DWORD req,
IUnknown** obj,
ITypeInfo** type);
virtual HRESULT STDMETHODCALLTYPE OnScriptError(IActiveScriptError *err);
// Our implementation
virtual HRESULT Initialize();
virtual HRESULT Parse(const std::wstring& script);
virtual HRESULT Run();
virtual HRESULT Terminate();
virtual _variant_t CallFunction(
const std::wstring& strFunc,
const std::vector<std::wstring>& paramArray);
private:
ULONG refCount_;
protected:
CComPtr<IActiveScript> scriptEngine_;
CComPtr<IDispatch> application_;
};
#endif // BASICSCRIPTHOST_H_
BasicScriptHost.cpp
#include "BasicScriptHost.h"
#include "BasicScriptObject.h"
#include <stdexcept>
#include <atlbase.h>
BasicScriptHost::BasicScriptHost(
const GUID& languageId,
const std::wstring& objectName,
CComPtr<IDispatch> object)
: refCount_(1)
{
CComPtr<IActiveScript> engine;
HRESULT hr = CoCreateInstance(
languageId,
0,
CLSCTX_INPROC_SERVER,
IID_IActiveScript,
reinterpret_cast<void**>(&engine));
if(FAILED(hr) || engine == nullptr) {
throw std::runtime_error("Unable to create active script object");
}
hr = engine->SetScriptSite(this);
if(FAILED(hr)) {
throw std::runtime_error("Unable to set scripting site");
}
hr = engine->AddNamedItem(
objectName.c_str(),
SCRIPTITEM_ISVISIBLE | SCRIPTITEM_NOCODE);
if(FAILED(hr)) {
throw std::runtime_error("Unable to set application object");
}
// Done, set the application object and engine
application_ = object;
scriptEngine_ = engine;
}
BasicScriptHost::BasicScriptHost(const GUID& languageId)
: refCount_(1)
{
CComPtr<IActiveScript> engine;
HRESULT hr = CoCreateInstance(
languageId,
0,
CLSCTX_INPROC_SERVER,
IID_IActiveScript,
reinterpret_cast<void**>(&engine));
if(FAILED(hr) || engine == nullptr) {
throw std::runtime_error("Unable to create active script object");
}
hr = engine->SetScriptSite(this);
if(FAILED(hr))
{
throw std::runtime_error("Unable to set scripting site");
}
// Done, set the engine
scriptEngine_ = engine;
}
BasicScriptHost::~BasicScriptHost()
{
}
HRESULT BasicScriptHost::QueryInterface(REFIID riid,void ** object)
{
if(riid == IID_IActiveScriptSite) {
*object = this;
}
if(riid == IID_IDispatch) {
*object = reinterpret_cast<IDispatch*>(this);
}
else {
*object = nullptr;
}
if(*object != nullptr) {
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
ULONG BasicScriptHost::AddRef()
{
return ::InterlockedIncrement(&refCount_);
}
ULONG BasicScriptHost::Release()
{
ULONG oldCount = refCount_;
ULONG newCount = ::InterlockedDecrement(&refCount_);
if(0 == newCount) {
delete this;
}
return oldCount;
}
HRESULT BasicScriptHost::GetLCID(DWORD *lcid)
{
*lcid = LOCALE_USER_DEFAULT;
return S_OK;
}
HRESULT BasicScriptHost::GetDocVersionString(BSTR* ver)
{
*ver = nullptr;
return S_OK;
}
HRESULT BasicScriptHost::OnScriptTerminate(const VARIANT *,const EXCEPINFO *)
{
return S_OK;
}
HRESULT BasicScriptHost::OnStateChange(SCRIPTSTATE state)
{
return S_OK;
}
HRESULT BasicScriptHost::OnEnterScript()
{
return S_OK;
}
HRESULT BasicScriptHost::OnLeaveScript()
{
return S_OK;
}
HRESULT BasicScriptHost::GetItemInfo(
const WCHAR* /*name*/,
DWORD req,
IUnknown** obj,
ITypeInfo** type)
{
if(req & SCRIPTINFO_IUNKNOWN && obj != nullptr) {
*obj = application_;
if(*obj != nullptr)
{
(*obj)->AddRef();
}
}
if(req & SCRIPTINFO_ITYPEINFO && type != nullptr) {
*type = nullptr;
}
return S_OK;
}
HRESULT BasicScriptHost::Initialize()
{
CComPtr<IActiveScriptParse> parse;
HRESULT hr = scriptEngine_->QueryInterface(
IID_IActiveScriptParse,
reinterpret_cast<void **>(&parse));
if(FAILED(hr) || parse == nullptr) {
throw std::runtime_error("Unable to get pointer to script parsing interface");
}
// Sets state to SCRIPTSTATE_INITIALIZED
hr = parse->InitNew();
return hr;
}
HRESULT BasicScriptHost::Run()
{
// Sets state to SCRIPTSTATE_CONNECTED
HRESULT hr = scriptEngine_->SetScriptState(SCRIPTSTATE_CONNECTED);
return hr;
}
HRESULT BasicScriptHost::Terminate()
{
HRESULT hr = scriptEngine_->SetScriptState(SCRIPTSTATE_DISCONNECTED);
if(SUCCEEDED(hr)) {
hr = scriptEngine_->Close();
}
return hr;
}
HRESULT BasicScriptHost::Parse(const std::wstring& source)
{
CComPtr<IActiveScriptParse> parser;
HRESULT hr = scriptEngine_->QueryInterface(
IID_IActiveScriptParse,
reinterpret_cast<void **>(&parser));
if(FAILED(hr) || parser == nullptr) {
throw std::runtime_error("Unable to get pointer to script parsing interface");
}
hr = parser->ParseScriptText(
source.c_str(),
nullptr,
nullptr,
nullptr,
0,
0,
0,
nullptr,
nullptr);
return hr;
}
#include <iostream>
HRESULT BasicScriptHost::OnScriptError(IActiveScriptError *err)
{
EXCEPINFO e;
err->GetExceptionInfo(&e);
std::wcout << L"Script error: ";
std::wcout << (e.bstrDescription == nullptr ? L"unknown" : e.bstrDescription);
std::wcout << std::endl;
std::wcout << std::hex << L"scode = " << e.scode << L" wcode = " << e.wCode << std::endl;
return S_OK;
}
_variant_t BasicScriptHost::CallFunction(
const std::wstring& strFunc,
const std::vector<std::wstring>& paramArray)
{
CComPtr<IDispatch> scriptDispatch;
scriptEngine_->GetScriptDispatch(nullptr, &scriptDispatch);
//Find dispid for given function in the object
CComBSTR bstrMember(strFunc.c_str());
DISPID dispid = 0;
HRESULT hr = scriptDispatch->GetIDsOfNames(
IID_NULL,
&bstrMember,
1,
LOCALE_SYSTEM_DEFAULT,
&dispid);
if(FAILED(hr))
{
throw std::runtime_error("Unable to get id of function");
}
// Putting parameters
DISPPARAMS dispparams = {0};
const int arraySize = paramArray.size();
dispparams.cArgs = arraySize;
dispparams.rgvarg = new VARIANT[dispparams.cArgs];
dispparams.cNamedArgs = 0;
for( int i = 0; i < arraySize; i++)
{
// FIXME - leak
_bstr_t bstr = paramArray[arraySize - 1 - i].c_str(); // back reading
dispparams.rgvarg[i].bstrVal = bstr.Detach();
dispparams.rgvarg[i].vt = VT_BSTR;
}
//Call JavaScript function
EXCEPINFO excepInfo = {0};
_variant_t vaResult;
UINT nArgErr = (UINT)-1; // initialize to invalid arg
hr = scriptDispatch->Invoke(
dispid,
IID_NULL,
0,
DISPATCH_METHOD,
&dispparams,
&vaResult,
&excepInfo,
&nArgErr);
delete [] dispparams.rgvarg;
if(FAILED(hr))
{
throw std::runtime_error("Unable to get invoke function");
}
return vaResult;
}
BasicScriptObject.h
#ifndef BASICSCRIPTOBJECT_H_
#define BASICSCRIPTOBJECT_H_
#include "stdafx.h"
#include <string>
#include <comdef.h>
class BasicScriptObject : public IDispatch
{
public:
BasicScriptObject();
virtual ~BasicScriptObject();
// IUnknown implementation
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,void ** object);
ULONG STDMETHODCALLTYPE AddRef ();
ULONG STDMETHODCALLTYPE Release();
// IDispatchimplementation
HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *count);
HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT, LCID, ITypeInfo** typeInfo);
HRESULT STDMETHODCALLTYPE GetIDsOfNames(
REFIID riid,
LPOLESTR* nameList,
UINT nameCount,
LCID lcid,
DISPID* idList);
HRESULT STDMETHODCALLTYPE Invoke(
DISPID id,
REFIID riid,
LCID lcid,
WORD flags,
DISPPARAMS* args,
VARIANT* ret,
EXCEPINFO* excp,
UINT* err);
private:
static const std::wstring functionName;
ULONG refCount_;
};
#endif // BASICSCRIPTOBJECT_H_
BasicScriptObject.cpp
#include "BasicScriptObject.h"
const std::wstring BasicScriptObject::functionName(L"alert");
BasicScriptObject::BasicScriptObject() : refCount_(1)
{}
BasicScriptObject::~BasicScriptObject()
{}
HRESULT BasicScriptObject::QueryInterface(REFIID riid,void ** object)
{
if(riid == IID_IDispatch) {
*object = static_cast<IDispatch*>(this);
}
else {
*object = nullptr;
}
if(*object != nullptr) {
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
ULONG BasicScriptObject::AddRef ()
{
return ::InterlockedIncrement(&refCount_);
}
ULONG BasicScriptObject::Release()
{
ULONG oldCount = refCount_;
ULONG newCount = ::InterlockedDecrement(&refCount_);
if(0 == newCount) {
delete this;
}
return oldCount;
}
HRESULT BasicScriptObject::GetTypeInfoCount(UINT *count)
{
*count = 0;
return S_OK;
}
HRESULT BasicScriptObject::GetTypeInfo(UINT, LCID, ITypeInfo** typeInfo)
{
*typeInfo = nullptr;
return S_OK;
}
// This is where we register procs (or vars)
HRESULT BasicScriptObject::GetIDsOfNames(
REFIID riid,
LPOLESTR* nameList,
UINT nameCount,
LCID lcid,
DISPID* idList)
{
for(UINT i = 0; i < nameCount; i++) {
if(0 == functionName.compare(nameList[i])) {
idList[i] = 1;
}
else {
return E_FAIL;
}
}
return S_OK;
}
// And this is where they are called from script
HRESULT BasicScriptObject::Invoke(
DISPID id,
REFIID riid,
LCID lcid,
WORD flags,
DISPPARAMS* args,
VARIANT* ret,
EXCEPINFO* excp,
UINT* err)
{
// We only have one function so no need to a lot of logic here. Just validate
// the call signature!
if(id != 1) {
return DISP_E_MEMBERNOTFOUND;
}
if(args->cArgs != 1) {
return DISP_E_BADPARAMCOUNT;
}
if(args->rgvarg->vt != VT_BSTR) {
return DISP_E_TYPEMISMATCH;
}
MessageBox(NULL, args->rgvarg->bstrVal, L"Script Alert", MB_OK);
return S_OK;
}
main.cpp
#include "BasicScriptHost.h"
#include "BasicScriptObject.h"
#include <iostream>
int main()
{
HRESULT hr;
hr = CoInitialize(nullptr);
if(FAILED(hr))
{
throw std::runtime_error("Unable to initialize COM subsystem");
}
try
{
// Initialize the application object
GUID javascriptId;
HRESULT hr = CLSIDFromProgID(L"JavaScript" , &javascriptId);
if(FAILED(hr))
{
throw std::runtime_error("Unable to acquire javascript engine GUID");
}
// Create our object that exposes functionality to the script
CComPtr<BasicScriptObject> appObject;
appObject.Attach(new BasicScriptObject());
CComPtr<IDispatch> appObjectDispatch;
hr = appObject.QueryInterface<IDispatch>(&appObjectDispatch);
if(FAILED(hr))
{
throw std::runtime_error("Unable to acquire host dispatch");
}
// Create the script site
CComPtr<BasicScriptHost> host;
host.Attach(new BasicScriptHost(javascriptId, L"window", appObjectDispatch));
// Do stuff!
hr = host->Initialize();
if(SUCCEEDED(hr))
{
wchar_t* source =
L"function ProcessData(msg) { window.alert(msg); }"
L"window.alert('cplusplus.com SUCKS!');"
;
hr = host->Parse(source);
}
if(SUCCEEDED(hr))
{
hr = host->Run();
}
if(SUCCEEDED(hr))
{
std::vector<std::wstring> args;
args.push_back(L"use cppreference.com instead!");
host->CallFunction(L"ProcessData", args);
}
if(SUCCEEDED(hr))
{
hr = host->Terminate();
}
}
catch(std::exception& e)
{
std::cout << e.what() << std::endl;
}
};