Introduction
There are numerous examples that demonstrate how to embed Internet Explorer as an OLE/COM object in your own window. But these examples typically use Microsoft Foundation Classes (MFC), .NET, C#, or at least Windows Template Library (WTL) because those frameworks have pre-fabricated "wrappers" to easily give you an "HTML control" to embed in your window. If you're trying to use plain C, without MFC, WTL, .NET, C#, or even any C++ code at all, then there is a dearth of examples and information how to deal with COM objects such as IE's IWebBrowser2. Here is an article and working example in C to specifically show you what you need to do in order to embed IE in your own window.
In fact, I've even wrapped up the example C code (to embed IE in your own window) into a Dynamic Link Library (DLL) so that you can simply call one function to display a web page or some HTML string in a window you create. You won't even need to get your hands dirty with COM (unless you plan to modify the source of the DLL).
Before proceeding with this article, you should first read my series of articles regarding COM in plain C. Part 1 discusses information you'll need to know in order to use COM objects. We will also have to deal with objects that have multiple interfaces, as discussed in Part 4. We'll be using automation datatypes, as discussed in Part 2. Finally, we'll also be dealing with events (callbacks), discussed in Part 5.
Mandatory COM objects we must create
After reading my above series of COM articles, you've got some needed background on writing COM objects in plain C. Let's now examine what we need to host the browser object. You may wish to peruse the source code file Simple.c (in the Simple directory) as you read the following discussion.
First of all, the browser object expects us to provide (at least) 3 of our own COM objects. We need an IOleInPlaceFrame
, IOleClientSite
, and an IOleInPlaceSite
objects. All of these objects (and their VTables, and GUIDs) are already defined for us in Microsoft's include files (shipped with your C interpreter or in the SDK downloadable from Microsoft's web site). So, they each have their own specific pre-defined set of functions in the VTable.
Let's just examine our IOleClientSite
object. It has a VTable which is defined as a IOleClientSiteVtbl
struct. Essentially, it's an array of 9 pointers to functions that we must supply in our program. (ie, We have to write 9 specific functions just for our IOleClientSite object). Of course, the first 3 functions will be the QueryInterface
, AddRef
, and Release
functions for our IOleClientSite object. In Simple.c, I've named those three functions Site_QueryInterface
, Site_AddRef
, and Site_Release
(to avoid any name conflicts with the QueryInterface, AddRef, and Release functions of our other COM objects). In fact, I've named the other 6 functions starting with Site_. They have names like Site_SaveObject, Site_ShowObject,
etc. Our IOleClientSite functions are called by the browser object to interact with our window that contains the browser object. What the specific purpose of each of those functions is, and what arguments are passed to it, you can check for yourself by looking through the documentation on MSDN about the IOleClientSite object.
To create the VTable for our IOleClientSite
object, the easiest thing to do is just declare it as a static global, and initialize it with the pointers to our 9 functions. Here is how we do that in our C source:
static IOleClientSiteVtbl MyIOleClientSiteTable = {Site_QueryInterface,
Site_AddRef,
Site_Release,
Site_SaveObject,
Site_GetMoniker,
Site_GetContainer,
Site_ShowObject,
Site_OnShowWindow,
Site_RequestNewObjectLayout};
Now have a global variable named MyIOleClientSiteTable
which is a properly initialized VTable for our IOleClientSite
object.
In Simple.c, you'll see that we also declare our other objects' VTables as globals. But we do not declare our objects themselves as globals. We are going to add some extra members to these objects for our own private data. For example, instead of a just an ordinary IOleInPlaceFrame
, we're going to define our own _IOleInPlaceFrameEx
which contains an embedded IOleInPlaceFrame
plus an extra HWND
where we can store the handle to our own window. Notice that this extra HWND member is added to the end of the struct, after the IOleInPlaceFrame
. That is very important. The IOleInPlaceFrame
(with its VTable pointer) must come first. And note that our extra data is window-specific. In other words, we'll need a different IOleInPlaceFrame
, IOleClientSite
, and IOleInPlaceSite
struct per each window that has an embedded browser object. For this reason, we'll allocate them when we create a window, instead of declaring them as globals.
The browser object considers our IOleInPlaceFrame
and IOleInPlaceSite
objects to be sub-objects of our IOleClientSite
object. So, when the browser wants/needs a pointer to one of those, it is going to call one of our IOleClientSite
functions to ask us to return a pointer to it. Usually the function it calls is our IOleClientSite
's QueryInterface
function. (But for some objects, such as our IOleInPlaceFrame
object, the browser will request the pointer by calling a different IOleClientSite
function. That's just the way it is). The implication here is that our IOleClientSite
functions will need to have access to our IOleInPlaceFrame
and IOleInPlaceSite
objects so that our IOleClientSite
functions can return pointers to any one (when asked to do that by the browser). For this reason, we're going to define one large object (_IOleClientSiteEx
) which has our 3 objects - IOleClientSite
, IOleInPlaceFrame
and IOleInPlaceSite
- all embedded inside this large struct. That makes it easy to locate any one object from another, simply using pointer arithmetic. The only requirement is that our IOleClientSite
be the first thing inside of this larger object. That way, this larger object can masquerade as an ordinary IOleClientSite.
You can consult the MSDN documentation to learn what the functions in our IOleInPlaceFrame
, IOleClientSite
, and IOleInPlaceSite
VTables are supposed to do, and what is passed to them. In Simple.c, we employ only as much functionality as is needed to display a web page in a window of our own creation.
Extra COM objects we may choose to create
As mentioned, the browser object expects us to supply at least the 3 above objects. But there are other objects we may optionally implement in our program in order to support additional interaction with the browser object. In particular, a IDocHostUIHandler
is very useful. It lets us control certain user interface features, such as being able to replace/disable the pop-up context menu when the user right-clicks on the embedded browser object, or determine whether scroll bars or borders or other such things are rendered, or prevent embedded scripts in the web page from running, or have a new browser window automatically open if the user clicks on any link, etc. Because such an object is so useful, we also implement an IDocHostUIHandler
interface in our example C code. (ie, We have a IDocHostUIHandler
struct, 18 IDocHostUIHandler
functions, and a VTable containing pointers to those 18 functions). We embed this IDocHostUIHandler inside of our large _IOleClientSiteEx
object.
Obtain the browser object
Before we obtain Microsoft's browser object, we must call OleInitialize
once to make sure that the OLE/COM system is initialized for our process. Normally, when using COM, you call CoInitialize
. But the browser needs some extra OLE initialization done (which CoInitialize
does not do). So we call OleInitialize
, which itself calls CoInitialize
for us.
Now we're ready to obtain a browser object. Our function EmbedBrowserObject
is where we obtain a browser object and embed it into a particular window. We need do this only once, so we call EmbedBrowserObject
right when we create the window (and pass the window handle to EmbedBrowserObject
).
First, since we need a separate IOleInPlaceFrame
, IOleClientSite
, IOleInPlaceSite
and IDocHostUIHandler
object for each window (that hosts the browser control), EmbedBrowserObject
allocates these 4 objects (ie, structs). Actually, since we've placed all 4 of these structs inside of our own, larger struct (ie, that we defined as a _IOleClientSiteEx
struct), one call to GlobalAlloc
a _IOleClientSiteEx
struct does it all. After we allocate those 4 COM objects, we have to initialize them (ie, stuff a pointer to each VTable in its respective lpVtbl member). Furthermore, we'll save a pointer to this allocated struct in our window's USERDATA
field. That way, our window procedure (and other functions) can easily retrieve our COM objects from the window handle.
Now, we obtain one of Microsoft's IWebBrowser2
objects (ie, the main object of Internet Explorer) with a call to the operating system function CoCreateInstance
, passing the GUID for the IWebBrowser2 object (defined as the symbol IID_IWebBrowser2
in Microsoft's include files). We also pass the GUID of the DLL in which Microsoft's browser control resides (defined as the symbol CLSID_WebBrowser
).
If all goes well, CoCreateInstance
will return a pointer to a newly created IWebBrowser2 object. The pointer is stored in our variable webBrowser2
.
Next, we need to get the IWebBrowser2 object's IOleObject
sub-object. We get a sub-object by calling the parent object's QueryInterface
function. So we call IWebBrowser2
's QueryInterface to get a pointer to its IOleObject
sub-object (that we store in our variable browserObject
). This sub-object is the one we mostly use to embed IE in our own window, and control the display of web pages. The IOleObject sub-object is not yet embedded. It is merely created. For the remainder of this article, I'll refer to this IOleObject
sub-object as simply the browser object.
Next, we need to call the browser object's SetClientSite
to give it a pointer to our own IOleClientSite
object. The browser object will need to call some of our IOleClientSite
functions to get information from us.
We also call its SetHostNames()
to pass the browser the name of our application (so it can display that in its own message boxes).
So how do we embed the browser object? We need to call the browser object's DoVerb
function to send the browser object a command that tells it to embed itself in our window (OLEIVERB_SHOW
). We also pass our window handle to DoVerb
. While we're inside of this call to DoVerb
, the browser object is going to call some of our IOleClientSite
functions. It will have called several of them before DoVerb
returns.
Sending a OLEIVERB_SHOW
command via DoVerb does not display any web page. (We have another function we can call to do that, after we're finished with EmbedBrowserObject). It merely embeds the browser object in our window so that it is ready to display a web page and have it shown in our window.
At the end of EmbedBrowserObject
, we call the IWebBrowser2
object's Release
function. We don't need this object any more (and if we did, we could call the IOleObject sub-object's QueryInterface
. Remember that a sub-object's QueryInterface
can always be used to locate its parent). But we don't Release the sub-object. We still need that in order to call its functions to display web pages, and do other things. We won't release the sub-object until later in UnEmbedBrowserObject
, when we're finally done using the browser object.
We can call our function DisplayHTMLPage
to display a URL or HTML file on disk. What we do in DisplayHTMLPage
is very similiar to what we do in EmbedBrowserObject
. We use the browser object's QueryInterface()
to grab pointers to other objects associated with it, and use the VTables of those other objects to call their functions in order to display a URL or HTML file on disk. Again, you can consult the MSDN documentation to learn more about the objects we're asking for and their functions we're calling.
Basically, we need to call the IWebBrowser2
's Navigate2
function to display a web page, passing the URL of the page we wish to display. Our URL (ie, web address, such as "http://www.microsoft.com" or an HTM filename on disk such as "c:\myfile.htm") must be passed to the IWebBrowser2
's Navigate2
function as a BSTR
. What's more, our BSTR
needs to be stuffed into a VARIANT
struct, and that VARIANT struct is then passed to Navigate2
.
Navigate2 will fetch the contents of the page (from wherever it resides) and display it in the browser object embedded in our window.
Display an HTML formatted buffer
What if we have a buffer (in memory) that already contains the HTML page we wish to display? In this case, we can get the brower object to display it, but this involves a few extra steps.
First, we need to create an empty page, which we can do by calling Navigate2
and passing it a URL of about:blank. This is a special URL that the IE engine recognizes as a blank page.
Next, we get the browser's IHTMLDocument2
object, and call its write
function to tell it to write the contents of our buffer to this empty web page. We must format our buffer as a BSTR
, and also wrap it in a standard COM struct known as a "safe array". COM provides some functions we can call to allocate a safe array (and also free it when we're done with it).
Our function DisplayHTMLStr
accomplishes this.
Display a page from a CHM file
The browser object can display a page from a compiled help (.CHM) file by using the special URL protocol its:. Just call DisplayHTMLPage
like so:
DisplayHTMLPage(hwnd, "its:MyChmFile.chm::mywebpage.htm");
Resize the browser display area
If the user resizes our window containing the browser object, the object will not automatically resize its display area. We need to specifically call some browser functions if we wish to enlarge/shrink the display area. We call put_Width
and put_Height
, passing the desired width and height, respectively.
Our function ResizeBrowser
accomplishes this. Normally, this is called when we process the WM_SIZE
message for our window.
Forward, back, and other actions
In fact, you can create several browser objects if desired, for example, if you wanted several windows - each hosting its own browser object so that each window could display its own web page. Simple.c creates two windows that each host a browser object. (So we call EmbedBrowserObject
once for each window). In one window, we call DisplayHTMLPage
to display Microsoft's web page. In another window, we call DisplayHTMLStr
to display some HTML string in memory.
Indeed, after we've embedded a browser object, we can call DisplayHTMLPage
or DisplayHTMLStr
repeatedly to change what is being displayed.
The web browser automatically keeps a "history" of the URLs we have displayed. We can cause the browser to go back to displaying a previously-viewed page by calling the browser's GoBack
function. This would be akin to clicking on IE's "Back" button. In fact, there are several actions that correspond to IE buttons, such as Refresh, Forward, Search, etc that we can invoke. Our function DoPageAction
serves as a generic interface to several of these browser functions. (Although Simple.c doesn't utilize this, you could add Back, Forward, Refresh, Search, etc, buttons to the example code, and utilize DoPageAction).
Free the browser object
When we're finally done with the browser object, we need to Release
it to free any resources it used. We do that in UnEmbedBrowserObject
. This needs to be done only once, so we do it right when the window is being destroyed. And we need to call OleUninitialize
before our program exits.
The cwebpage.dll
The Simple directory contains a complete C example with everything in one source file. Study this to familiarize yourself with the technique of using the browser object in your own window. It demonstrates how to display either an HTML file on the web or disk, or an HTML string in memory, and creates 2 windows to do such.
The Browser directory also contains a complete C example. It demonstrates how to add "Back", "Forward", "Home", and "Stop" buttons. It creates a child window (inside of the main window) into which the browser object is embedded.
The Events directory also contains a complete C example. It demonstrates how to implement your own special link to display a web page with links to other HTML strings (in memory). You could use this technique to define other specialized types of "links" that can send messages to your window when the user clicks upon the link.
The DLL directory contains a DLL that has the functions EmbedBrowserObject, UnEmbedBrowserObject, DisplayHTMLPage, DisplayHTMLStr
, and DoPageAction
in it. The DLL also contains all of the IStorage, IOleInPlaceFrame, IOleClientSite, IOleInPlaceSite
, and IDocHostUIHandler
VTables and their functions. The DLL also calls OleInitialize
and OleUninitialize
on your behalf. So to use this DLL, you don't need to put any OLE/COM coding in your C program at all. It's all in the DLL instead. And there is a small example called Example.c that uses the DLL. It's just Simple.c with all the OLE/COM stuff ripped out of it and replaced with calls to use the DLL. The DLL functions have been modified slightly to support both UNICODE or ansi. I use the function IsWindowUnicode
to discover whether the application window (hosting the browser object) is UNICODE or not.
The DLL also has a few new functions to support events, which will be discussed below.
Events
An HTML page is typically composed of numerous elements, such as various tags like a FONT tag, links, forms, etc. Each element may have various "actions" or "events" associated with it. For example, a link generates an event when the user moves the mouse pointer over it. It generates another event when the user moves the mouse pointer off of it. And there are other events it may generate.
An application may ask the browser to provide feedback when a particular event occurs with a particular element. In order for us to get feedback about an element, the HTML page must be written to give that element an ID (ie, a string name). For example, let's assume that our page has a FONT
element on it. Let's arbitrarily give this FONT element an ID of testfont. Here is how the HTML page's FONT
element may look:
<FONT id=testfont color=red> This is some red text. </FONT>
Each event itself has a unique string name. For example, when the mouse is moved over the above FONT
element (ie, the mouse pointer is moved over the red text), the event which occurs is a mouseover
event. When the mouse is moved off of the FONT
element, the event which occurs is a mouseout
event.
For every element on the web page, the web browser has an IHTMLElement
object for it. To get feedback on an element, we first must get its IHTMLElement
object. In the DLL directory's Dll.c is a function called GetWebElement
which shows how to get an IHTMLElement
object for a particular element. GetWebElement
is passed the window containing the browser object, and the ID (name) of the desired element. To get the IHTMLElement
, we have to run through several other browser objects. We have to get the browser's IHTMLDocument2
, and then get the IHTMLElementCollection
(for the desired element) from that, and then get the element's IDispatch
, and finally get the element's IHTMLElement
object from that IDispatch
. Whew!
Once we have an element's IHTMLElement
, we can then "attach" to the element to receive feedback on its events. As you've learned from my article about COM events, we need to provide the browser with an IDispatch
object we create. The browser will call our IDispatch
's Invoke
function when an event occurs. We must give our event IDispatch to the browser, which we do by obtaining the browser's IHTMLWindow3
object and calling its attachEvent
function, passing our IDispatch
.
Then, to tell the browser to call our IDispatch
's Invoke whenever a FONT
element's "mouseover" event occurs, we call that FONT
IHTMLElement
's put_onmouseover
function, passing a pointer to our IDispatch
. (Actually, we need to wrap our IDispatch pointer in a VARIANT
). To get feedback on that FONT element's "mouseout" event, we call that FONT
IHTMLElement
's put_onmouseout
function, passing a pointer to our IDispatch
.
Different types of elements may have different events, and so some elements, such as a FORM, have additional sub-objects we can get via its IHTMLElement
's QueryInterface
. For example, if we had a FORM element's IHTMLElement
, we could call its QueryInterface
to get its IHTMLFormElement
. Then, we could call the IHTMLFormElement
's
put_onsubmit
function to attach to its submit event (ie, when the user submits the FORM
data). Consult MSDN docs to determine which web page elements have which sub-objects (ie, which elements generate which events).
Of course, we want to isolate all of the COM stuff in our cwebpage.dll, so what we're going to do is provide a function that will create an IDispatch on behalf of the application. That function is CreateWebEvtHandler
. The IDispatch's
functions will be inside of cwebpage.dll, so the application will not need to create any of its own COM objects. To abstact this, we'll make the application assign an ID number of its choice to each element it wants feedback upon. For example, the app may decide to assign the ID number 1 to the FONT element. Then when our DLL IDispatch Invoke gets feedback on that FONT element's mouseover event, for example, we will pass a custom message to the app's window. The custom message will include the ID number of the element, and the string name of the event (ie, "mouseover").
In the directory HTMLEvents is an example application, and an example web page. The web page has several elements on it, including a FORM
, and a FONT
element. The application receives feedback on some events for both elements.
An application can also receive feedback for events associated with the web page itself (such as the user double-clicking on a blank area of the page), or the browser's scroll bars, etc. The example receives feedback on some of those non-element events too.
There are lots more events an app could get feedback upon. Consult MSDN docs, and experiment.
History
- Initial release upon December 1, 2002.
- Update upon December 6, 2002. Added IDocHostUIHandler interface, and the function DoPageAction. Applied a fix to UnEmbedBrowserObject() and DisplayHTMLStr(). Revamped the comments in the code to be more explicit and clear. Added the Browser.c and Events.c examples.
- Update upon May 15, 2006. Added the ability to receive "events" from the web page, such as knowing when the user clicks upon some item on the page, or submits a form, etc. Incorporated both UNICODE and ANSI support into the one DLL.
- Update upon August 1, 2006. Rewrote the article to reference my series on COM in plain C, while expounding more upon browser specific matters. Also updated the download link on this page to contain the latest code base.