banuj
banuj

Reputation: 3230

Run JavaScript function from C++ without MFC

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

Answers (1)

Captain Obvlious
Captain Obvlious

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

Related Questions