Embed an HTML control in your own window using plain C
2015-07-18 16:30
633 查看
Embed an HTML control in your own window using plain C
JeffGlatt, 3 Aug 2006 CPOL
713.8K
12.6K
240
4.95 (143 votes) |
|
as creating your own script engine.
Download source - 90.26 KB
Mandatory
COM objects we must create
Extra
COM objects we may choose to create
Obtain
the browser object
Display
a web page
Display
an HTML formatted buffer
Display
a page from a CHM file
Resize
the browser display area
Forward,
back, and other actions
Free
the browser object
The cwebpage.dll
Events
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 frameworkshave 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
1discusses 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
IOleInPlaceSiteobjects. 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
IOleClientSiteobject. It has a VTable which is defined as a
IOleClientSiteVtblstruct.
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 IOleClientSiteobject). Of course, the first 3 functions will be the
QueryInterface,
AddRef,
and
Releasefunctions for our IOleClientSiteobject. 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 IOleClientSitefunctions 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 IOleClientSiteobject.
To create the VTable for our
IOleClientSiteobject, 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:
Hide Copy Code
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
MyIOleClientSiteTablewhich is a properly initialized VTable for our
IOleClientSiteobject.
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
_IOleInPlaceFrameExwhich
contains an embedded
IOleInPlaceFrameplus an extra
HWNDwhere
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
IOleInPlaceSitestruct 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
IOleInPlaceFrameand
IOleInPlaceSiteobjects
to be sub-objects of our
IOleClientSiteobject. So, when the browser wants/needs a pointer to one of those, it is going
to call one of our
IOleClientSitefunctions to ask us to return a pointer to it. Usually the function it calls is
our
IOleClientSite's
QueryInterfacefunction.
(But for some objects, such as our
IOleInPlaceFrameobject, the browser will request the pointer by calling a different
IOleClientSitefunction.
That's just the way it is). The implication here is that our
IOleClientSitefunctions will need to have access to
our
IOleInPlaceFrameand
IOleInPlaceSiteobjects
so that our
IOleClientSitefunctions 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,
IOleInPlaceFrameand
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
IOleClientSitebe
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
IOleInPlaceSiteVTables 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 interactionwith the browser object. In particular, a
IDocHostUIHandleris 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
IDocHostUIHandlerinterface
in our example C code. (ie, We have a
IDocHostUIHandlerstruct, 18
IDocHostUIHandlerfunctions,
and a VTable containing pointers to those 18 functions). We embed this IDocHostUIHandlerinside of our large
_IOleClientSiteExobject.
Obtain the browser object
Before we obtain Microsoft's browser object, we must call OleInitializeonce 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
CoInitializedoes not do). So we call
OleInitialize,
which itself calls
CoInitializefor us.
Now we're ready to obtain a browser object. Our function
EmbedBrowserObjectis where we obtain a browser object
and embed it into a particular window. We need do this only once, so we call
EmbedBrowserObjectright when we create
the window (and pass the window handle to
EmbedBrowserObject).
First, since we need a separate
IOleInPlaceFrame,
IOleClientSite,
IOleInPlaceSiteand
IDocHostUIHandlerobject
for each window (that hosts the browser control),
EmbedBrowserObjectallocates 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
_IOleClientSiteExstruct),
one call to
GlobalAlloca _
IOleClientSiteExstruct
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
USERDATAfield.
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
IWebBrowser2objects (ie, the main object of Internet Explorer) with a call to
the operating system function
CoCreateInstance, passing the GUID for the IWebBrowser2object (defined as the symbol
IID_IWebBrowser2in
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,
CoCreateInstancewill return a pointer to a newly created IWebBrowser2object. The pointer is
stored in our variable
webBrowser2.
Next, we need to get the IWebBrowser2object's
IOleObjectsub-object. We get a sub-object by calling the parent
object's
QueryInterfacefunction. So we call
IWebBrowser2's
QueryInterfaceto get a pointer to its
IOleObjectsub-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 IOleObjectsub-object is not yet embedded. It is merely created. For the remainder of this article, I'll refer to this
IOleObjectsub-object
as simply the browser object.
Next, we need to call the browser object's
SetClientSiteto give it a pointer to our own
IOleClientSiteobject.
The browser object will need to call some of our
IOleClientSitefunctions 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
DoVerbfunction 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
IOleClientSitefunctions.
It will have called several of them before
DoVerbreturns.
Sending a
OLEIVERB_SHOWcommand via DoVerbdoes 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
IWebBrowser2object's
Releasefunction.
We don't need this object any more (and if we did, we could call the IOleObjectsub-object's
QueryInterface. Remember
that a sub-object's
QueryInterfacecan 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.
Display a web page
We can call our function DisplayHTMLPageto display a URL or HTML file on disk. What we do in
DisplayHTMLPageis
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
Navigate2function
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
Navigate2function
as a
BSTR. What's more, our
BSTRneeds
to be stuffed into a
VARIANTstruct, and that VARIANTstruct 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
Navigate2and passing it a URL ofabout:blank.
This is a special URL that the IE engine recognizes as a blank page.
Next, we get the browser's
IHTMLDocument2object, and call its
writefunction
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
DisplayHTMLStraccomplishes 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 DisplayHTMLPagelike
so:
Hide Copy Code
// Display the page named "mywebpage.htm" from our .CHM file // named MyChmFile.chm 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 callput_Widthand
put_Height,
passing the desired width and height, respectively.
Our function
ResizeBrowseraccomplishes this. Normally, this is called when we process the
WM_SIZEmessage
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
EmbedBrowserObjectonce for each window). In one window, we call
DisplayHTMLPageto
display Microsoft's web page. In another window, we call
DisplayHTMLStrto display some HTML string in memory.
Indeed, after we've embedded a browser object, we can call
DisplayHTMLPageor
DisplayHTMLStrrepeatedly
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
GoBackfunction.
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
DoPageActionserves
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 Releaseit 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
OleUninitializebefore 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 demonstrateshow 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
DoPageActionin it. The DLL also contains all of
the
IStorage, IOleInPlaceFrame, IOleClientSite, IOleInPlaceSite, and
IDocHostUIHandlerVTables
and their functions. The DLL also calls
OleInitializeand
OleUninitializeon
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
IsWindowUnicodeto
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 FONTtag, 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 mousepointer 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
FONTelement on it. Let's arbitrarily give this FONTelement an ID of testfont.
Here is how the HTML page's
FONTelement may look:
Hide Copy Code
<FONTid=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
FONTelement (ie,
the mouse pointer is moved over the red text), the event which occurs is a
mouseoverevent. When the mouse is moved
off of the
FONTelement, the event which occurs is a
mouseoutevent.
For every element on the web page, the web browser has an
IHTMLElementobject for it. To get feedback on an element,
we first must get its
IHTMLElementobject. In the DLL directory's Dll.c is
a function called
GetWebElementwhich shows how to get an
IHTMLElementobject
for a particular element.
GetWebElementis 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
IHTMLElementobject
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
IDispatchobject we create.
The browser will call our
IDispatch's
Invokefunction
when an event occurs. We must give our event IDispatchto the browser, which we do by obtaining the browser's
IHTMLWindow3object
and calling its
attachEventfunction, passing our
IDispatch.
Then, to tell the browser to call our
IDispatch's Invoke whenever a
FONTelement's
"mouseover" event occurs, we call that
FONT
IHTMLElement's
put_onmouseoverfunction,
passing a pointer to our
IDispatch. (Actually, we need to wrap our IDispatchpointer in a
VARIANT).
To get feedback on that FONTelement's "mouseout" event, we call that
FONT
IHTMLElement's
put_onmouseoutfunction,
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 FORMelement's
IHTMLElement, we could call its
QueryInterfaceto
get its
IHTMLFormElement. Then, we could call the
IHTMLFormElement's
put_onsubmitfunction
to attach to its submit event (ie, when the user submits the
FORMdata). 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 IDispatchon behalf of the application. That function is
CreateWebEvtHandler.
The
IDispatch'sfunctions 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 FONTelement. Then when our DLL IDispatchInvoke gets feedback
on that FONTelement's mouseoverevent, 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
FONTelement. 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.
使用纯C在自己的窗口中嵌入HTML控件
2010-04-09 11:56 1568人阅读 评论(0) 收藏 举报htmlc浏览器webbrowserdll磁盘
标 题: 【翻译】过年了,献上小礼 - 使用纯C在自己的窗口中嵌入HTML控件
作 者: xiep
时 间: 2010-01-22,13:42:29
链 接: http://bbs.pediy.com/showthread.php?t=105702
前段时间因为项目需要,所以翻译了这篇,希望对某些人有用。由于时间仓促,错误之处在所难免,请不吝指出。
另:browser lib为我改写的一个静态库,test为测试程序。
使用纯C在自己的窗口中嵌入HTML控件
原文链接:http://www.codeproject.com/KB/COM/cwebpage.aspx
翻译:xiep
简介
必须被创建的COM对象
可以选择创建的COM对象
获取浏览器对象
显示一个网页
显示一个HTML格式的缓冲区
显示一个CHM页面
调整浏览器的显示区域
后退,返回,和其他动作
释放浏览器对象
cwebpage.dll
事件
简介
将Internet Explorer当作OLE/COM对象中嵌入自己的窗口的例子很多。但这些例子通常使用MFC,.NET,C#,至少使用了Windows模板库(WTL),因为这些框架都被已经将Internet Explorer预先包装为一个易用的HTML控件。如果你想使用纯C,而不是MFC,WTL,.NET,C#,甚至是根本不使用任何C++代码,那么像使用IE的IWebBrowser2控件的例子就很少了。本文就是关于使用纯C在自己的窗口中嵌入IE浏览器的详细描述,并且附带可以运行的例子。
事实上,我已经使用C代码将IWebBrowser2包装成一个动态链接库,因此你可以在你所创建的窗口中简单的调用其中的某个函数显示一个网页或者HTML文本,而不需要了解关于COM的任何内容,除非你想修改该动态链接库的源码。
在继续阅读本文之前,你应当阅读我关于《纯C中的COM》系列文章。其中第一部分是使用COM对象的基础知识;第四部分是关于处理含有多接口的COM对象的;第二部分关于自动数据类型的使用;以及第五部分关于处理事件(回调函数)的内容。
必须被创建的COM对象
只要阅读了以上提到的文章,你就已经具备了在纯C中编写COM对象的基础知识。下面我们看看使用浏览器对象都需要做些什么。你可以边读本文边查看Simple.c(在Simple目录)。
首先浏览器对象希望我们提供至少3个COM对象,即一个IOleInPlaceFrame对象,一个IOleClientSite对象,以及一个IOleInPlaceSite对象。这些对象以及它们的虚函数表和GUID都已经在SDK的头文件中被定义。因此它们都有自己的预定义的虚函数集合。
我们来看看IOleClientSite对象吧。它的虚函数表被定义为IOleClientSiteVtbl结构。IOleClientSiteVtbl实际上是一个由9个函数指针组成的数组,这些函数必须在程序中被提供。当然前3个函数是QueryInterface,AddRef和Release。为了避免和其他COM对象的这些函数产生冲突,在Simple.c中我将它们分别命名为Site_QueryInterface,Site_AddRef和Site_Release。事实上我将其他的6个函数也以Site_前缀命名,如Site_SaveObject,Site_ShowObject等。IOleClientSite对象被浏览器对象调用以便和我们的窗口交互。每个函数的具体目的依据传递给它们的参数而定,你可以参考MSDN关于IOleClientSite对象的描述。
为IOleClientSite对象创建虚表,最简单的方式就是将虚表定义为一个全局的静态变量,并且使用我们定义的9个函数的指针初始化该虚表。使用的C代码如下:
static IOleClientSiteVtbl MyIOleClientSiteTable = { Site_QueryInterface,
Site_AddRef,
Site_Release,
Site_SaveObject,
Site_GetMoniker,
Site_GetContainer,
Site_ShowObject,
Site_OnShowWindow,
Site_RequestNewObjectLayout
};
现在这个名叫MyIOleClientSiteTable的全局变量就是一个被正确初始化的虚表。
在Simple.c中,你也可以看到我为其他对象定义的虚表。但是我们并没有将对象本身定义为全局的。我们将为这些对象增加一些另外的域以便存放私有数据。例如,我们定义_IOleInPlaceFrameEx结构,它包含内嵌的IOleInPlaceFrame和一个用来存放我们的窗口句柄的HWND。应当注意的是这里的HWND成员被放置在结构的最后面,也就是在IOleInPlaceFrame之后,这点非常重要。IOleInPlaceFrame(带有虚表)必须被放在最前面,并且这里的额外数据是和窗口相关的,也就是说每一个窗口都嵌有自己的浏览器对象,因此我们在创建窗口的时候动态为IOleInPlaceFrame分配内存,而不是将它们定义为全局变量。
浏览器对象将IOleInPlaceFrame和IOleInPlaceSite对象当做IOleClientSite的子对象。因此当浏览器需要它们中一个的指针,它必须通过调用IOleClientSite对象虚函数获取,通常这个函数是QueryInterface(但是对于某些对象,如IOleInPlaceFrame,浏览器对象通过调用IOleClientSite的另一个函数获取。)。言外之意是,IOleClientSite的虚函数需要存取IOleInPlaceFrame和IOleInPlaceSite对象,从而向浏览器返回它们的指针。因此我们定义了另一个对象_IOleClientSiteEx,它包含了IOleClientSite,IOleInPlaceFrame和IOleInPlaceSite3个对象。这样使得从一个对象定位另一个对象非常容易。唯一的要求是IOleClientSite必须放在最前面,这样就可以将整个对象当做一个IOleClientSite对象。
您可以查阅MSDN文档,以了解IOleInPlaceFrame,IOleClientSite和IOleInPlaceSite的各虚函数的功能和参数。在Simple.c中,我仅仅实现了在窗口中显示一个网页。
可以选择创建的COM对象
上面提到浏览器对象期望我们至少提供3个对象。但是还存在其他对象,我们可以选择性的创建,从而可以与浏览器对象进行更丰富的交互。IDocHostUIHandler是一个特别有用的对象。它可以让我们控制某些用户界面特性,如替换或禁用右键弹出式菜单,或者确定是否有滚动条或边界以及其他此类事件,或者防止嵌入式脚本运行,用户点击链接在新窗口中打开等。这个对象如此有用,因此我们在源码中还定义了一个IDocHostUIHandler结构,包含18个IDocHostUIHandler函数,以及一个包含这些函数的虚表,同样的我也将该结构放在IOleClientSiteEx结构内部。
获取浏览器对象
在我们获得微软的浏览器对象之前,我们必须调用OleInitialize一次,以确保与OLE/COM系统在我们的进程中初始化。通常,创建COM对象使用CoInitialize函数。但这里的浏览器对象需要做一些额外的OLE初始化工作(CoInitialize不会初始化这些数据),因此我们调用OleInitialize,该函数在内部会自动调用CoInitialize函数。
现在我们准备取得浏览器对象。函数EmbedBrowserObject取得浏览器对象,并嵌入到特定的窗口。我们必须且只调用该函数一次,所以我们在窗口创建时调用该函数(并将窗口句柄作为参数传入)。
首先,因为每个嵌入浏览器的窗口都需要一个单独的IOleInPlaceFrame,IOleClientSite,IOleInPlaceSite和IDocHostUIHandler对象,因此EmbedBrowserObject函数用来为这四个对象分配内存。而我们将这4个对象都包含于_IOleClientSiteEx结构内部,因此只需要调用GlobalAlloc函数分配一个_IOleClientSiteEx结构就可以了。为这4个对象分配内存后,我们需要初始化它们(它们的虚表)。此外,我们还应当将_IOleClientSiteEx的内存地址保存在窗口的用户数据域(USERDATA),这样窗口例程和其他函数可以通过窗口句柄很容易的获取这些对象。
现在我们可以通过调用API CoCreateInstance获得微软的IWebBrowser2对象(也就是Internet Explorer对象),这需要将IWebBrowser2对象的GUID(在SDK的头文件被定义为IID_IWebBrowser2)作为参数,我们还需要传递提供该浏览器对象的动态链接库的GUID(在SDK的头文件被定义为CLSID_WebBrowser)作为参数。
如果一切顺利,CoCreateInstance将返回一个新创建的IWebBrowser2对象指针,这个指针存储在webBrowser2变量里。
接下来,我们需要得到IWebBrowser2对象的IOleObject子对象。我们通过调用父对象的QueryInterface函数得到子对象。因此我们调用IWebBrowser2的QueryInterface函数获得其IOleObject子对象指针(我们将它存储在变量browserObject里)。我们大多使用这一子对象嵌入IE浏览器到我们自己的窗口,显示和控制的网页。此时该IOleObject子对象只是被建立,而没有被嵌入。以下部分我将把IOleObject子对象简单的称作浏览器对象。
下一步,我们需要调用浏览器对象的SetClientSite函数,并传递IOleClientSite对象指针作为参数。浏览器对象将需要调用IOleClientSite的一些函数获取信息。
我们还需要调用它的SetHostNames函数,并传递我们的应用程序名作为参数(这样才能在消息框中显示)。
那么我们如何嵌入浏览器对象呢?我们需要调用浏览器对象的DoVerb函数给浏览器对象发送一个命令,告诉它扎根在我们的窗口( OLEIVERB_SHOW),当然还需要传递窗口句柄作为参数。当我们调用DoVerb,浏览器对象将在DoVerb返回返回之前调用IOleClientSite的一些函数。
通过DoVerb发送OLEIVERB_SHOW命令并不显示任何网页(这需要在调用EmbedBrowserObject之后,调用另一个函数)。它只是嵌入浏览器对象到我们的窗口,准备现实一个网页,并显示在我们的窗口。
在EmbedBrowserObject的最后,我们调用IWebBrowser2的Release函数,我们不再需要这个对象(如果该对象被释放,我们可以调用IOleObject的QueryInterface函数,子对象的QueryInterface用于定位其母对象)。但是我们并不释放该子对象,我们仍然需要调用它的函数,来显示网页和做其他事情。直到UnEmbedBrowserObject中,我们不再使用浏览器对象时才释放该子对象。
显示一个网页
我们可以通过DisplayHTMLPage来显示一个URL或者磁盘上的HTML文件。在DisplayHTMLPage中所做的事情和UnEmbedBrowserObject非常类似。我们使用浏览器对象的QueryInterface函数来得到其他对象的指针,并调用它们的虚函数以显示URL或磁盘上的HTML文件。你可以再次查看MSDN,了解这些对象和它们的函数。
我们最终调用IWebBrowser2的Navigate2来显示一个网页,传递一个URL作为参数(如http://www.microsot.com),类型为BSTR,或者是磁盘上的一个HTML文件(如C:/myfile.htm)。该BSTR参数必须被塞进一个VARIANT结构,也就是说最终传递的是VARIANT结构指针。
Navigate2将撷取网页内容,并显示在浏览器对象被嵌入的窗口。
显示一个HTML格式的缓冲区
如果我们拥有的一个缓冲区包含需要显示的HTML页面,怎么来显示这个页面呢?仍然可以使用浏览器对象来显示,但是需要一些多余的步骤。
首先,我们需要创建一个空页面,也就是传递URL about:blank参数到Navigate2,IE浏览器引擎将这个URL作为空页面看待。
接下来,我们获取浏览器的IHTMLDocument2对象,并调用它的write函数让浏览器显示我们缓冲区的内容到这个空页面。我们必须将这个缓冲区格式化为BSTR,并且包装到一个叫做“safe array”标准的COM结构。COM提供了一些函数用于分配safe array,当不再使用时应当将它释放掉。
函数DisplayHTMLStr完成这个功能。
显示一个CHM页面
浏览器对象也可以从.CHM中显示一个页面,这需要通过its:协议,代码如下:
//显示MyChmFile.chm文件中的mywebpage.htm页面
DispHTMLPage(hwnd, “its:MyChmFile.chm::mywebpage.htm”);
调整浏览器的显示区域
如果包含浏览器对象的窗口尺寸被改变,浏览器对象将不会自动更新显示区域。我们需要调用浏览器对象的put_Width和put_Height函数来缩放显示区域。
函数ResizeBrowser完成这个功能,它将在处理WM_SIZE窗口消息时被调用。
后退,返回,和其他动作
事实上,如果需要你可以创建若干个浏览器对象,例如创建多个窗口,每个窗口都嵌入了自己的浏览器对象来显示自己的网页。Simple.c创建了两个窗口,并各自嵌入了一个浏览器对象。(因此我们为每个窗口都调用了EmbedBrowserObject各一次)。我们在其中的一个窗口显示微软的主页,而另一个页面显示一个HTML文件。
当浏览器对象被嵌入后,可以使用DisplayHTMLPage或DisplayHTMLStr重复改变现实的页面。
浏览器自动保存了已经被显示的页面URL历史。我们可以使用浏览器对象的GoBack使浏览器回到显示的上一个页面,这将类似于点击IE浏览器的“返回”按钮,实际上还存在其他的动作对应于IE浏览器的按钮,如刷新,转寄,搜索等,我们可以援引。函数DoPageAction用于实现这些功能,虽然Simple.c没有利用这一点,但是你可以增加返回,前进,刷新,搜索等,按钮的例子代码,并利用DoPageAction ) 。
释放浏览器对象
最终我们将需要调用Release函数来释放浏览器对象使用的所有资源,我们在UnEmbedBrowserObject函数中实现这个功能。该函数应当而且只在窗口销毁时被调用一次。同时我们需要在退出程序之前调用OleUninitialize。
cwebpage.dll
Simple目录包含一个完整的C示例代码。可以通过学习这段源码熟悉如何在窗口中使用浏览器对象。它演示了如何显示网页或者磁盘上的HTML文件,或内存中的一个HTML字符串,并创建两个窗口来分别显示。
Browser目录也包含有一个完整的C示例代码。它演示了如何增加“返回”,“前进”,“主页”,和“停止”按钮。它是通过创建子窗口嵌入浏览器对象。
Events目录也包含有一个完整的C示例代码。它演示了如何实现自己的特殊链接,显示链接到其他的HTML字符串(在内存中)的网页 。您可以使用此技术来定义其他特殊类型的“链接”, 当用户点击的链接可以将消息发送到你的窗口。
DLL目录包含一个动态链接库,它包含EmbedBrowserObject,UnEmbedBrowserObject, DisplayHTMLPage,DisplayHTMLStr和DoPageAction函数。DLL文件还包含所有的IStorage , IOleInPlaceFrame,IOleClientSite,IOleInPlaceSite和IDocHostUIHandler虚表和虚函数。DLL文件自动为你调用OleInitialize和OleUninitialize函数,因此使用该DLL你将不在需要调用添加OLE/COM代码到C程序。这些代码都被放在DLL文件中了。目录下还有一个example.c用于测试该动态链接库,它没有包含任何OLE/COM代码,仅仅调用了DLL。动态链接库稍被修改以便支持Unicode和ANSI,我使用函数IsWIndowUnicode来确定窗口是否使用了Unicode。
该动态链接库也包含了一些支持事件的函数,下面详细讨论这个。
事件
HTML页面通常是由许多元素,如各种标签如字体标记,链接,表格等组成的。每个元素可能有与此相关的不同的动作或事件。例如,链接生成一个事件当用户移动鼠标指针到该链接上。而当用户移动鼠标指针离开它时产生另一个事件。还有其他的活动有可能产生。
当某个元素产生特定的事件时,应用程序可能要求浏览器给与反馈。为了得到某个元素的反馈,HTML页面必须给与该元素一个ID(即一个字符串名字)。例如,网页上有一个字体元素,ID为testfont,HTML源码类似于下面的样子:
<FONTid=testfont color=red>This is some red text.</FONT>
每个事件都有一个唯一的字符串名。例如,当鼠标移到上面字体元素(即,将鼠标指针移动到红色文字上),发生的是一个鼠标悬停事件。当鼠标离开字体元素时,事件发生的是一个鼠标离开(mouseout)事件。
对于页面上的每个元素,都对应浏览器的一个IHMTLElement对象。为了获的某个元素的反馈必须获得它对应的IHMTLElement对象。DLL目录中的Dll.c中有一个名为GetWebElement的函数,用户获取特定元素的IHTMLElement对象。GetWebElement是通过传递的包含浏览器对象窗口的句柄,和所需元素的编号(名称)。要获得IHTMLElement,我们要经过几个其他浏览器对象,我们必须先获得浏览器的IHTMLDocument2对象,和所需元素的IHTMLElementCollection对象,在获取元素的IDispatch接口,并最终从IHTMLElement对象的IDispatch获得元素的IHTMLElement对象。噢!
一旦我们有一个元素的IHTMLElement,我们就可以附加到该元素从而得到反馈。如上所述,我们需要给浏览器提供一个IDispatch对象。事件发生时,浏览器将调用IDispatch的Invoke函数。我们必须获取浏览器的IHTMLWindow3对象,然后IDispatch为参数调用它的attachEvent函数,为浏览器提供IDispatch。
为了告诉浏览器字体元素的“鼠标悬停”事件发生时,调用我们的IDispatch的Invoke函数,我们需要调用该字体元素IHTMLElement对象的put_onmouseover函数,并传递我们的IDispatch对象指针(实际上我们需要将该指针包装为一个VARIANT变量)。同样的对于“鼠标离开”事件,调用put_onmouseout函数。
不同类型的元素可能会有不同的事件,因此一些元素,如表格,有更多的子对象我们可以通过其IHTMLElement的QueryInterface函数获得。例如,如果我们有一个FORM元素的IHTMLElement,我们可以调用它的QueryInterface获得它的IHTMLFormElement。然后,我们调用IHTMLFormElement的put_onsubmit函数附加到其提交的事件(例如当用户提交表单数据时)。查看的MSDN以确定哪些网页元素含有子对象(即哪些元素产生哪些事件)。
当然,我们希望所有的关于COM的东西都被封装在cwebpage.dll中,我们需要做的是提供一个创建代表应用程序本身的IDispatch的函数。该函数就是CreateWebEvtHandler。IDispatch的函数位于cwebpage.dll内部 ,因此应用程序不需要创建任何COM对象。应用程序为为需要反馈的元素制定一个ID。例如,应用程序可能决定指派字体元素的编号为1。然后,当DLL IDispatchInvoke获取字体元素的鼠标悬停事件,例如,我们给应用程序窗口发送一个自定义消息。自定义的信息将包括元素的ID和时间的字符串名称(即“mouseover”)。
目录HTMLEvents包含一个例子程序和一个示例网页。该网页上有几个元素,包括一个表格,和一个字体元素。应用程序能够收到这两个元素某些事件的反馈。
应用程序也能获得页面本身产生的事件(如用户双击的空白区域的页面),或浏览器的滚动条等。例子也能收到一些非页面元素产生的事件。
应用程序还能得到更多事件反馈。请查看MSDN并实验。
相关文章推荐
- 论坛:Html代码生成器>>FCKeditor的使用
- 关于HTML DOM的一些问题
- 将HTML特殊转义为实体字符的两种实现方式
- 浏览器加载和渲染html的顺序
- C#实现将HTML转换成纯文本的方法
- 【北大青鸟】--HTML 表单中get和post请求的区别
- Html 标签的嵌套规则
- Html 标签的嵌套规则
- <!DOCTYPE HTML>有什么作用?
- innerHTML、outerHTML和innerText是哪个对象的属性?请举例说明他们的含
- 运行html代码
- html教程
- html格式规范化—<option>内容空格问题
- html 学习
- Html——footer的使用
- IE8 中"HTML Parsing Error:Unable to modify the parent container element before the child element is c
- html
- HTML导入本地图片
- Html学习笔记2
- HTML实体符号代码速查表