Запустить функцию JavaScript из C++ без MFC

Я пытаюсь запустить функцию JavaScript в C++ без MFC или GUI.

Я попытался создать указатель веб-браузера, а затем получить из него документ.

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);

   }

}

По сути, мне нужен указатель на структуру IHtmlDocument2, потому что по этому указателю я могу сделать 'get_script' и 'invoke'. Ты знаешь как этого добиться или что я делаю не так?

Также знаете ли вы другой метод для запуска функции JS без MFC и GUI?

Спасибо,

PS Используя MFC, я могу запускать функции JS, используя http://www.codeproject.com/Articles/2352/JavaScript-call-from-C

1 ответ

Решение

Я не рекомендую использовать IWebBrowser2 получить интерфейс к хосту сценариев. Создание объекта браузера имеет неприятный побочный эффект - запуск экземпляра Internet Explorer в фоновом режиме, по крайней мере, в Windows 7. Он также использует несколько именованных объектов (документ, окно и т. Д.), Которые могут конфликтовать с тем, что вы пытаетесь выполнить.,

Вместо этого я предлагаю вам использовать Active Scripting Interfaces. Для их использования требуется немного дополнительного кода, но он гораздо более гибок и дает вам гораздо больше контроля. В частности, вы можете установить сайт управления сценариями, не беспокоясь о конфликтах или создании адаптера переадресации, который потребуется, если вы используете IWebBrowser2 управления. Хотя это требует немного дополнительного кода, это не сложно реализовать.

Первым шагом является приобретение GUID для конкретного языка, который вы хотите использовать. Это может быть Javascript, VBScript, Python или любой скрипт-движок, разработанный для Windows Scripting Host. Вы можете либо поставить GUID самостоятельно или приобретите его из системы по имени, используя идентификатор приложения COM, как показано ниже.

GUID languageId;

CLSIDFromProgID(L"JavaScript" , &languageId);

Следующим шагом является создание экземпляра механизма сценариев путем вызова CoCreateInstance используя GUID сверху как идентификатор класса.

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;
    }
}; 
Другие вопросы по тегам