您的位置:首页 > Web前端 > HTML

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

Jeff
Glatt, 3 Aug 2006 CPOL

713.8K

12.6K

240





4.95 (143 votes)
Rate this:vote
1vote
2vote
3vote
4vote
5
Specifically shows how to embed a browser OLE object in your own window, and more generally demonstrates how to manipulate and create COM/OLE objects, in plain C (ie, no MFC, WTL, ATL, .NET, C#, nor even C++). The latter is applicable to many other uses, such
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 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
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
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 IOleClientSiteobject). Of course, the first 3 functions will be the
QueryInterface
,
AddRef
,
and
Release
functions 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
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:

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
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 IDocHostUIHandlerinside 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 IWebBrowser2object (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 IWebBrowser2object. The pointer is
stored in our variable
webBrowser2
.

Next, we need to get the IWebBrowser2object's
IOleObject
sub-object. We get a sub-object by calling the parent
object's
QueryInterface
function. So we call
IWebBrowser2
's
QueryInterfaceto 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 IOleObjectsub-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 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
IWebBrowser2
object's
Release
function.
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
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.


Display a web page

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 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
Navigate2
and 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
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:

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 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 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 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 FONTelement an ID of testfont.
Here is how the HTML page's
FONT
element 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
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 IDispatchto 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 IDispatchpointer in a
VARIANT
).
To get feedback on that FONTelement'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 FORMelement'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 IDispatchon 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 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
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.


使用纯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并实验。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: