Using the IE kernel to develop client products, the interaction between the system and the front-end pages is often a great convenience for development and maintenance. But the interaction between the operating system and the front end is more complex. Specifically, the interaction between the scripting language and the compiled language. In the IE kernel, HTML and CSS are incompatible, but the IE programming interface is exactly the same, thanks to the structured design and implementation of Microsoft's COM components. So interacting with IE, you have to first say the Com,com full name Component Object Model (Component object).
The basic idea of COM is simple, all component modules provide a most basic interface, IUnkown, it has three methods, AddRef and release to implement the reference count, QueryInterface implementation of the interface ID to query the additional interface, All interfaces are derived from IUnkown. Based on IE core development, there is an interface is the most critical, IDispatch (for details to the IDispatch interface-GetIDsOfNames and Invoke). The IDispatch interface is the core of COM automation. In fact, IDispatch this interface itself is very simple, there are only 4 methods: The most critical two methods invoke and GetIDsOfNames. Communication between the scripting language and the compiled language is done through the IDispatch interface. Here's a look at the prototype of this critical approach:
Idispatch:public iunkown{ //... HRESULT Invoke (DispID dispidmember,refiid riid, LCID lcid,word wflags,dispparams far* pdispparams,variant far* PVarResul T, Excepinfo far* pexcepinfo, unsigned int far* puargerr); //...}
The meaning of each parameter of this method is described in detail on MSDN, as I understand it, as a component that provides a universal interface to the outside, which enables two useful functions:
1. Gets and sets the component's property variables. Dispatch_propertyget and dispatch_propertyput corresponding to wflags
2. Call any of the supported methods with any parameters. corresponding to the Dispatch_method of wflags
In the object-oriented perspective, with these two functions, any component that implements the interface is abstracted into a universal object implemented by the same interface, and can be used to get the Set property and the calling method through the specified string name. If you have any experience with scripting, developers will find that the script is exactly the case. C + + Writing code is also accessed by the name of the object, but compiled into binary code after the concept of No name, only the offset and address. In the script, the code is written by name, and the execution is done by name. The biggest difference between scripts and native languages is that all the properties and methods of a script object are dynamic and can be modified at execution time. See here, it is easy to think of the implementation of the IDispatch Component object has the characteristics of the script, C + + objects are scripted! This means that you can use the original C + + write all the properties and methods of the class through invoke to execute, in the script can be directly accessed! Equivalent to adding a native extension to the script. One of the most important features of the IDispatch interface is this, which is what Microsoft usually says about dual interfaces. With this in view, it is easier to interact with the JS script next.
Since it is the IE kernel in the JS and C + + call each other, we first simple to understand the next IE kernel programming needs a few common interfaces. Say more is not good understanding, first look at the picture.
IWebBrowser2, Ihtmlwindow2,ihtmldocument2 These three common interfaces are derived from IDispatch. IWebBrowser2 interface mainly provides browser functions such as open URL, forward and backward functions. IHTMLWindow2 is mainly to provide the Window object opened in the interface Operation Browser, IHTMLDocument2 to obtain information about the document, and to review and modify the HTML elements and text in the document, including getting the JS object.
IHTMLWindow2 corresponds to a view of a window, IHTMLDocument2 is a IHTMLWindow2 rendered document, corresponding to the DOM tree structure. In JS there are two Global Objects window and document, respectively, corresponding to IHTMlWindow2 and IHTMLDocument2.
To learn more about these, see the following information:
IWebBrowser2 Interface https://msdn.microsoft.com/en-us/library/aa752127%28VS.85%29.aspx
IHTMLWindow2 Interface https://msdn.microsoft.com/zh-cn/library/aa741505
IHTMLDocument2 Interface https://msdn.microsoft.com/zh-cn/library/aa752574
To complete the C + + and JS interaction, can be decomposed into two tasks, one is the C + + call JS code, and the second is JS call C + + code, which is actually all scripts and natvie interaction of two basic tasks. This article mainly according to own understanding from the design development angle to explain why to do so.
C + + Call JS
Each JS execution code has its own execution environment, in IE can be seen as IHTMLWindow2.
According to the above, we obtain the Global object document first, and get the Ihtmldocument2,ihtmldocument2 interface get_ from document into the IDispatch interface. The script method obtains the IDispatch interface of the JS code in the HTML document, and we use the IDispatch interface to manipulate the JS code in the HTML document as a COM object.
Ccomptr<idispatch> GetScript () {ccomptr<iwebbrowser2> spwebbrowser; HRESULT HRESULT = Querycontrol (Iid_iwebbrowser2, (void**) &spwebbrowser); if (SUCCEEDED (HResult)) {ccomptr< idispatch> Spdocdisp;hresult = spwebbrowser->get_document (&spdocdisp), if (SUCCEEDED (HResult)) {CCOMPTR <IHTMLDocument2> Spdocdisp2;hresult = Spdocdisp->queryinterface (Iid_ihtmldocument2, (void**) & SPDOCDISP2); if (SUCCEEDED (HResult)) {ccomptr<idispatch> Spscript;hresult = Spdocdisp2->get_script (& Spscript); if (SUCCEEDED (HResult)) {return spscript;}}}}
There are two ways to execute JS, one is to call IHTMLWindow2 's Execscript method directly.
HRESULT ExecScript (BSTR Code, BSTR language, VARIANT *pvarret);
code example:
Wstring Strjavascript; CComVariant Pvarret; CComBSTR Bstrjavascript (Strjavascript.c_str ()); CComBSTR Bstrscripttype (_t ("JavaScript")); Ccomqiptr<ihtmlwindow2> SpWindow2 (SPSCRIPTDISP); Spwindow2->execscript (Bstrjavascript, BstrScriptType, &pvarret);
To understand this code is not difficult, we first understand the next ccomqiptr, using the IDispatch interface to invoke the various methods of COM object, set and get the properties of COM object, let COM object callback us, are implemented with the IDispatch invoke method. An invoke is going to do so many functions, which is of course troublesome. Fortunately, the ccomdispatchdriver (i.e. ccomqiptr<idispatch>) in the ATL Smart pointer class encapsulates the IDispatch interface, which is very convenient to use! First get the IHTMLWindow2 interface smart pointer, directly to the JS code environment IDispatch pointer assignment to it. Note, however, that this is a string of BSTR, which can be allocated using SysAllocString.
The second scenario is also the use of the IHTMLDocument Get_script () method. It can get a IDispatch pointer, this IDispatch is the JS in IHTMLDocument. Using the IDispatch described earlier, you can invoke any JS function through it. For example, to execute a functional function in JS.
Ccomptr<ihtmldocument2> Spdocdisp2;spdocdisp2->get_script (&spscript); OLECHAR * Names= L "function ";D Ispid dispid=0;//to get the Accept dispatch identifier DispID First, call GetIDsOfNames to get Spscript->getidsofnames (iid_null,&names,1, Locale_system_default, &dispid);//Invoke the JS method via Invoke (Invoke) method Spscript->invoke (Dispid,,iid_null, Locale_system_ Default,dispatch_method,null,null,null,null);
Here the function is a global function in JS. Here you can see that the invoke does not take the string name directly, but instead uses another method getdispofnames to do a mapping to get the DispID to accept the dispatch identifier. The script interface obtained by ihtmldocument corresponds to the JS Global environment of the page, from which we can get any global variable, function, and thus the member variable or method of the object.
The second option is to use the invoke call to access the JS variable and the calling function in C + +. The difference between this and the first scheme is obvious, one is in C + + Write JS code, a bit like their own in the parsing of the execution JS, and the former more simple, and then complex JS call sequence, a string all done.
A basic question to do with C + + and scripting interactions is how the data types in the script correspond to the types of data in C + +. As we all know, JS has many types, Boolean, number, String, Object, Array, function and so on. Write here, in a word, in all the basic languages there are strings and numbers of the two basic data types (c + + only for the end of the character array), object-oriented language will also have a composite data type such as objects. In the invoke invocation parameter, Varaint represents the basic data type in C, and the number in JS is converted to VT_I4 or VT_R4 or VT_R8. The string is converted to a VT_BSTR type BSTR (which is the string type used in the Microsoft COM Standard), and all other composite types include an object array function that corresponds to a IDispatch pointer to Vt_dispatch in C. With the IDispatch pointer, you can access the object's properties arbitrarily in the previous method, or you can initiate a function call and get the return value. Understanding these, you can do C and JS interaction, they are all through the IDispatch invoke call to complete. CComDispatchDriver GetIDsOfNames and invoke are further encapsulated, requiring less parameters that are convenient to invoke.
INVOKE0//method to invoke 0 parameters
Invoke1//method to invoke 1 parameters
Invoke2//method to invoke 2 parameters
Invoken//methods to invoke multiple parameters
Having said so much, it is estimated that some people can see foggy. Below is a direct example:
Let's write an HTML that contains such a section of JS code:
<script type= "Text/javascript" > function Add (value1, value2) { return value1 + value2; } </script>
Then we use WebBrowser to load this HTML, in the VC to call this function named Add JS function:
Don't forget that # include <MsHTML.h> //m_webbrowser is a WebBrowser ActiveX control object. ccomqiptr<ihtmldocument2> Spdoc = M_webbrowser.get_document (); CComDispatchDriver Spscript; Spdoc->get_script (&spscript); CComVariant var1 = ten, var2 = Varret; Spscript.invoke2 (L "Add", &var1, &var2, &varret);
The function of Spscript.invoke2 is to call the functions named Add in the JS function, pass in two parameters, and receive the return value with Varret. After the Invoke2 call succeeds, Varret gets the return value of 30.
However, you can only accept one return value at a time. What if you want to accept more than one return value at a time?
We can let JS return an array or object in JS.
When the JS function return an array or an object, the Varret on the VC side will accept a IDispatch interface representing the object. We still use CComDispatchDriver to manage this IDispatch. There are four methods of CComDispatchDriver:
GetProperty
Getpropertybyname
PutProperty
Putpropertybyname
To remove the data we want from this array or object.
Practice is the only criterion for testing truth, let's write a JS function again:
<script type= "Text/javascript" > function Add (value1, value2) { var array = new Array (); Array[0] = value1; ARRAY[1] = value2; ARRAY[2] = value1 + value2; return array; }
Then write this in VC:
ccomqiptr<ihtmldocument2> Spdoc = M_webbrowser.get_document (); CComDispatchDriver Spscript; Spdoc->get_script (&spscript); CComVariant var1 = ten, var2 = Varret; Spscript.invoke2 (L "Add", &var1, &var2, &varret); CComDispatchDriver Sparray = varret.pdispval; Gets the number of elements in the array, this length in JS is the property of the array object CComVariant Vararraylen; Sparray.getpropertybyname (L "Length", &vararraylen); Gets the value of the 0,1,2 element in the array: ccomvariant varvalue[3]; Sparray.getpropertybyname (L "0", &varvalue[0]); Sparray.getpropertybyname (L "1", &varvalue[1]); Sparray.getpropertybyname (L "2", &varvalue[2]);
As you can see, 10,20,30, the values returned by these three JS functions are already lying in our varvalue[3].
Of course, if you do not know the array object JS returned there are several elements, we can get its Length property on the VC side, and then in a loop to take out each value in the array.
If our JS function returns an object containing multiple property values, how can the VC receive it here?
Let's write a JS function again:
<script type= "Text/javascript" > function Add (value1, value2) { var data = new Object (); Data.result = value1 + value2; Data.str = "hello,world!"; return data; } </script>
Then in the VC we receive:
ccomqiptr<ihtmldocument2> Spdoc = M_webbrowser.get_document (); CComDispatchDriver Spscript;
Spdoc->get_script (&spscript); CComVariant var1 = ten, var2 = Varret; Spscript.invoke2 (L "Add", &var1, &var2, &varret); CComDispatchDriver spData = varret.pdispval; CComVariant varValue1, varValue2; Spdata.getpropertybyname (L "result", &varvalue1); Spdata.getpropertybyname (L "str", &varvalue2);
We take the two properties of the object from JS, and result and STR, respectively, are a single shaped data and a string. Here JS code is written by ourselves, on the VC side of course know beforehand that this JS function returns the object has the result and str these two attributes. What if the JS code is not written by us, or if its properties are not predetermined in advance? The answer is to use the IDispatchEx interface to enumerate information about this object (method name, property name).
The example of C + + Invoke JS ends here.
JS calling C + + code
According to the use of IDispatch, we can infer how to do this, customize a C + + class, implement a IDispatch interface, its pointer through a certain JS call as the return value to JS, then the JS code to hold the object, You can use it just as you would with a normal JS object. The problem is, at first JS nothing, how to directly transferred to C + + to return to C + + objects? IE has already considered this problem, it has a built-in IDispatch object for each IWebbrowser2 instance (top level), which can be set up in C + + after the browser control instance is created, and is accessed using window.external in JS. This means that each JS environment has a built-in global object external, and its corresponding C + + IDispatch can be specified by the programmer. Let's talk about how to set up this object instance.
In Windows you want to host an active control, if you write it yourself with the SDK. There is an interface called IDocHostUIHandler, it has a method getexternaldisp to the host query a IDispatch object, directly corresponds to the external script object in JS. IDocHostUIHandler also has a useful way of showcontextmenu, and when it comes to the show menu, the method is called back, and the application can customize the menu. MFC can also be very convenient host an IE control, but its class library is too large, thanks to Microsoft again out of ATL, provides a lightweight way to let you achieve the same effect. Paste the code snippet directly below.
Class Cwebbrowser:public caxhostwindow{private:ccomptr<iwebbrowser2> M_pwebbrowser;//Save the created browser Control instance Begin_ Msg_map (CWebBrowser) message_handler (wm_create,oncreate) chain_msg_map (Caxhostwindow) END_MSG_MAP () LRESULT OnCreate (UINT umsg, WPARAM WPARAM, LPARAM LPARAM, bool&/*bhandled*/) {//Create WebBrowser objectlpolestr pname=null; Stringfromclsid (Clsid_webbrowser,&pname); ccomptr<idispatch>disp; Ccomptr<iunknown> P;_internalqueryinterface (IID_IDispatch, (void**) &disp);//Create Webbrowsercreatecontrolex (PNAME,M_HWND,NULL,&P,DIID_DWEBBROWSEREVENTS2,DISP); CoTaskMemFree (pName);//Query IWebBrowser2 interface for controlling HRESULT Hret = Querycontrol (Iid_iwebbrowser2, void**) &this->m_ Pwebbrowser); Return M_pwebbrowser? S_ok:-1;}}
CWebBrowser is the user's own host window, in its oncreate to create a COM object, a browser window is out, this code is not very concise? Caxhostwindow do a lot of things for us, including IDocHostUIHandler is also implemented, so we derive from it naturally have a lot of control of IE control capabilities, of course, are done through the COM interface. If you have custom requirements later, you can rewrite the virtual function of the parent class to achieve the purpose. Caxhostwindow also encapsulates a method Setexternaldispatch, where everything can be temporarily over, you can implement IDispatch in CWebBrowser and can be implemented by a single class, Then the IDispatch interface can be set in. Interested in studying the host control process of the children's shoes can see Caxhostwindow code implementation, all in a header file.
Suppose your external provides a function to create object functions Newmyobject, in JS
var newobject=window.external.newmyobject (); Build a C + + object from external to JS hold
alert (newobject.name); Accessing the properties of this object
Alert (Newobject.getvalue ())//method to invoke the object
So what you need to do is focus on invoke. In external's IDispatch invoke implementation
STDMETHODIMP Cwebbrowserdisp::invoke (DispID dispidmember, refiid riid, lcid lcid, WORD wflags, dispparams* pDispParams, variant* Pvarresult, excepinfo* pexcepinfo, unsigned int* puargerr) { HRESULT nret = S_OK; if (Wflags&dispatch_method)//belongs to the method call {// Newmyobject assigned ID, String name Mapping if (dispidmember== dispid_newmyobject) { idispatch* pmyobject=null;//Create a C + + object and get its IDispatch interface Createmyobject (&pmyobject);p varresult->vt=vt_dispatch; pvarresult->pdispval=pmyobject; Passed as return value to JS} } return 0;}
This code is also very concise. It can be seen that in order to export C + + objects to JS, then the object must be implemented IDispatch interface, only need to use this interface as the return value of Invoke JS can be passed. It has a reference count and does not have to worry about the memory release problem, which will naturally be destroyed at some point when the JS garbage collection is triggered. Next, MyObject has the properties and methods that can be called by JS, then the IDispatch of its own invoke implementation to care.
Another is that in the WebBrowser control, JS calls the C + + method. If you're familiar with WebBrowser controls, it's easier to use them here. The Invoke interface implementation is basically similar to the top, the only difference is how to make JS call to the local C + + code. The function Window.external.newMyObject () is created in the JS code. When the page renders, it triggers the browser's GetExternal event, in the browser, through the message filter, when the message is wn_getexternal, through the IDispatch interface, get the JS code to call the class.
IDispatch **ppdispatch = (idispatch**) WParam;
*ppdispatch = &m_superCall;
To sum up, in IE and C + + and JS interaction, IDispatch played a very important role, understand it you can arbitrary C + + and JS mixed programming. COM interfaces are not easy to understand and know how to use them, but it is difficult to understand their internal mechanisms. In fact, in the previous process, IDispatch was created by its own code, which is completely unrelated to the system. From the syntax of C + +, it inherits a virtual base class, implements all of its methods, and is a reference count. So, we can use very simple C + + code to write their own IDispatch, do not have to ignore so many of the COM features. The JS execution environment is always in the main thread, so you need to know that the method of your object is always called on the main thread. A simple implementation code is given below:
#include "StdAfx.h" #include "SQSuperCall.h" cjscallc::cjscallc (void) {M_mapfunction[text ("functest")] = Dispid_ Functest;} CJSCALLC::~CJSCALLC (void) {}hresult stdmethodcalltype cjscallc::getidsofnames (/* [in] */__rpc__in REFIID riid,/* [size _is][in] */__rpc__in_ecount_full (cnames) lpolestr *rgsznames,/* [range][in] */UINT cnames,/* [in] */LCID lcid,/* [Size_ Is][out] */__rpc__out_ecount_full (cnames) DispID *rgdispid) {HRESULT hr = noerror;for (UINT nIndex = 0; NIndex < cnames ; ++nindex) {wstring strfuntion = rgsznames[nindex];map<wstring, Int>::iterator iter = M_mapfunction.find ( Strfuntion); if (M_mapfunction.end ()! = iter) {Rgdispid[nindex] = Iter->second;} ELSE{HR = Resultfromscode (disp_e_unknownname); Rgdispid[nindex] = Dispid_unknown;}} return HR;} /* [Local] */HRESULT Stdmethodcalltype cjscallc::invoke (/* [in] */DISPID dispidmember,/* [in] */REFIID riid,/* [in] */ LCID lcid,/* [in] */WORD wflags,/* [out][in] */Dispparams *pdispparams,/* [out] */VARIANT *pvarresult,/* [Out] */Excepinfo *pexcepinfo,/* [out] */UINT *puargerr) {if (Dispidmember = = dispid_functest) {int paramscount = P Dispparams->cargs;if (Paramscount < 2) return S_FALSE; variantarg* Cmdvar = (variantarg*) (&pdispparams->rgvarg[paramscount-1]); if (! ( CMDVAR->VT = = VT_I4 | | CMDVAR->VT = = VT_BSTR)) return S_FALSE; int nCmdID = cmdvar->intval; Cmdvar = (variantarg*) (&pdispparams->rgvarg[paramscount-2]); if (cmdvar->vt! = VT_BSTR) return S_FALSE; CString Csinfos = cmdvar->bstrval; Wstring Strinfos (Csinfos);} return S_OK;} HRESULT stdmethodcalltype cjscallc::queryinterface (/* [in] */REFIID riid,/* [iid_is][out] */__rpc__deref_out void **pp Vobject) {//*ppvobject = null;if (riid = = IID_IUnknown) {*ppvobject = static_cast<iunknown*> (this);} else if (riid = = IID_IDispatch) {*ppvobject = static_cast<idispatch*> (this);} Else{return E_nointerface;} return S_OK;}
Reference Documentation:
VC and JavaScript Interaction (a) ———— how to achieve
Browser programming of the two IE control and JS Interactive Chapter
CEF3 Developer series outside the--ie JS and C + + interaction