2

我正在尝试在没有任何 MFC 或 GUI 的情况下在 C++ 中运行 JavaScript 函数。

我试图创建一个 webbrowser 指针,然后从中获取文档。

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”。你知道如何做到这一点,或者我做错了什么?

你还知道另一种在没有 MFC 和 GUI 的情况下运行 JS 函数的方法吗?

谢谢,

PS 使用 MFC,我可以使用http://www.codeproject.com/Articles/2352/JavaScript-call-from-C运行 JS 函数

4

1 回答 1

8

我不建议使用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++ 对象所需的所有对象。要将对象添加windowdocument根“命名空间”中,请调用IActiveScript::AddNamedItemIActiveScript::AddTypeLib

scriptEngine_->AddNamedItem(
    "objectname",
    SCRIPTITEM_ISVISIBLE | SCRIPTITEM_NOCODE);

您要向脚本公开的对象必须实现IDispatchIDispatchEx。您实现哪个接口取决于对象的要求,但如果可能,请使用 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;
    }
}; 
于 2013-05-31T15:17:19.633 回答