Запустить функцию 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;
}
};