Reputation: 3230
I'm trying to run a JavaScript function in C++ without any MFC or GUI.
I tried to create a webbrowser pointer and after to get the document from it.
CoCreateInstance(CLSID_WebBrowser, NULL, CLSCTX_SERVER,
IID_IWebBrowser2, (void**)&pBrowser2);
if (pBrowser2)
{
VARIANT vEmpty;
VariantInit(&vEmpty);
BSTR bstrURL = SysAllocString(L"file://D:/file.html");
HRESULT hr = pBrowser2->Navigate(bstrURL, &vEmpty, &vEmpty, &vEmpty, &vEmpty);
if (SUCCEEDED(hr))
{
IDispatch *pDisp=NULL;
hr=pBrowser2->get_Document(&pDisp); <- This is NULL
hr=pDisp->QueryInterface(IID_IHTMLDocument,(void**)&pDoc);
}
}
Basically, I need a pointer to a IHtmlDocument2 structure, because on this pointer I can make 'get_script' and 'invoke'. Do you know how to achieve this, or what I'm doing wrong?
Also do you know another method to run a JS function without MFC and GUI?
Thank you,
P.S Using MFC, I'm able to run JS functions using http://www.codeproject.com/Articles/2352/JavaScript-call-from-C
Upvotes: 2
Views: 3472
Reputation: 20063
I do not recommend using IWebBrowser2
to acquire an interface to a scripting host. Creating a browser object has the nasty side effect of launching an instance of Internet Explorer in the background, at least on Windows 7. It also pulls in several named objects (document, window, etc) that may conflict with what you are trying to accomplish.
Instead I suggest you use the Active Scripting Interfaces. Using them requires a bit of extra code but it is far more flexible and gives you a lot more control. Specifically you can set the scripting control site without having to worry about conflicts or creating a forwarding adapter that would be required if you used the IWebBrowser2
controls. Even though it requires a bit of extra code, it's not difficult to implement.
The first step is to acquire the GUID
for the particular language you want to use. This can be Javascript, VBScript, Python or any scripting engine designed for Windows Scripting Host. You can either supply the GUID
yourself or acquire it from the system by name using a COM Application ID like below.
GUID languageId;
CLSIDFromProgID(L"JavaScript" , &languageId);
The next step is to create an instance of the scripting engine by calling CoCreateInstance
using the GUID from above as the class id.
CoCreateInstance(
languageId,
0,
CLSCTX_INPROC_SERVER,
IID_IActiveScript,
reinterpret_cast<void**>(&scriptEngine_));
This will return a pointer to an IActiveScript object that is the primary interface to the scripting engine.
The next step is to set the scripting site. This is an implementation of IActiveScriptSite that you provide. This is the interface the scripting engine uses to acquire certain information and dispatch certain events such as changes in the scripting state.
scriptEngine_->SetScriptSite(siteObjectPointer);
At this point you have all the necessary objects to begin using the scripting engine to call scripting functions and allow the script to access native C++ objects. To add objects such as window
or document
to the root "namespace" you call IActiveScript::AddNamedItem or IActiveScript::AddTypeLib.
scriptEngine_->AddNamedItem(
"objectname",
SCRIPTITEM_ISVISIBLE | SCRIPTITEM_NOCODE);
Objects that you want to expose to the scripts must implement either IDispatch
or IDispatchEx
. Which interface you implement depends on the requirements of the object but if possible use IDispatch as it requires less code.
Now that you have initialized the scripting engine you can start adding scripts to it. You need to call QueryInterface
on the scripting engine to acquire a pointer to the IActiveScriptParse
interface, initialize the parser and then add scripts. You only need to initialize the parser once.
scriptEngine_->QueryInterface(
IID_IActiveScriptParse,
reinterpret_cast<void **>(&parser));
// Initialize the parser
parser->InitNew();
parser->ParseScriptText("script source" ....);
Now that you have initialized the engine and added a script or two you start execution by changing the engine state to SCRIPTSTATE_CONNECTED
.
scriptEngine_->SetScriptState(SCRIPTSTATE_CONNECTED);
Those are the basic steps to embed the scripting engine into your application. The example below is a fully working skeleton that implements the scripting site and an object that exposes functionality to the script. It also includes a slightly modified version of the code to call script functions that you referenced in your question. It's not perfect but it works and should hopefully get you closer to what you are trying to accomplish.
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;
}
};
Upvotes: 8