- 浏览: 151203 次
- 性别:
- 来自: 北京
文章分类
最新评论
Embed an HTML control in your own window using plain C
Embed an HTML control in your own window using plain C
By Jeff Glatt, 3 Aug 2006
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 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.
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 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:
// 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 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'sput_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.
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
About the Author
相关推荐
标题提到的"Embed an HTML control in your own window using plain C"恰好指向了这一主题。描述中提到的学习COM的好资源,暗示了我们可能会使用到微软的ActiveX技术,其中HTML Control(也称为WebBrowser控件)是...
HTML5中的`<embed>`标签用于在网页中嵌入外部内容,例如各种类型的插件。要使用`<embed>`标签嵌入内容,必须通过其`src`属性来指定要嵌入的资源的URL地址,而且该URL地址必须包含具体的文件扩展名。这一点在尝试播放...
### 如何通过I2C接口控制FPGA 在电子工程领域,现场可编程门阵列(FPGA)作为可编程逻辑器件的一种,在多种应用中扮演着核心角色。本文旨在探讨如何利用I2C(Inter-Integrated Circuit)接口来实现对FPGA的有效控制...
Set up the wx main frame by adding your own Frame class to the application Create two stage widgets by using a three step process Set up an event handler, customize, receive and handle events by ...
在HTML中,`<embed>`标签是一个非常重要的元素,它允许我们嵌入外部资源,如音频、视频、Flash动画等,使得网页内容更加丰富多彩和动态。 `<embed>`标签的使用方式如下: ```html <embed src="资源URL" width="宽度...
<embed src="path_to_your_media_file" width="320" height="240"> ``` 这里的`src`属性指定了要加载的媒体文件的URL,`width`和`height`属性定义了媒体的显示尺寸。 ### 二、embed的属性 1. **src**:必需属性,...
例如:`<embed src="your.mid" autostart=true loop=2>`、`<embed src="your.mid" autostart=true loop=true>` 和 `<embed src="your.mid" autostart=true loop=false>` 3. 面板显示:`hidden` 属性规定控制面板...
HTML-embed 代码详解 HTML-embed 代码是 HTML 中的一种标签,用于插入多媒体文件,如音频、视频等。下面是 HTML-embed 代码的详细解释: 基本语法 embed src=url 其中,src 是音频或视频文件的路径,可以是相对...
7. To use LoginControl in your own project, do the following: 8. In your ASP.NET Web project, right-click on References folder, select "Add Reference...", "Browse..." to add JoeWoods.LoginControl.dll...
标题“Embed Java VM in Executables using Java Native Interface (JNI)”指向一个实践教程或项目,其目标是将Java虚拟机(Java Virtual Machine, JVM)嵌入到可执行文件中。这通常是出于减少依赖性、提高启动速度...
HTML中的`<embed>`标签是一种用于嵌入外部应用程序或资源的通用标签,它可以用来播放音频、视频、PDF、Flash动画等。在本案例中,我们关注的是如何使用`<embed>`标签来播放FLV(Flash Video)格式的视频。FLV是Adobe...
【embed标签】 embed标签是HTML中用于嵌入外部资源的元素,主要应用于网页中插入音频、视频等多媒体内容。由于HTML5的兴起,现在更多使用video和audio标签来处理多媒体,但embed仍然在某些场景下被使用,尤其是在...
<embed src="media_url" width="width_value" height="height_value" controls="control_type" ...> ``` #### 三、embed与object标签的区别 - **兼容性**:`embed`标签主要针对非IE浏览器设计,而`object`标签适用...
(43KB)<END><br>26,holder.zip HOLDER contains the Internet Explorer web browser control using CHtmlView in an MFC application. (67KB)<END><br>27,langload.zip LANGLOAD shows how to use the ...
It is a program to embed and extract data in and an image using key.
和大家分享,一本关于嵌入式c的书
Using LUA in your program does require some thinking ahead... You have to choose what kind of functions you allow in the scripts. For example: a function savegame would be logic and usefull and so ...
import "yourproject/resources" func main() { htmlContent := resources.IndexHtml // 使用htmlContent进行进一步处理... } ``` 4. 编译和运行:现在,当编译你的Go程序时,资源已经被内联到可执行文件中...
detecting specific sequences within the incoming data stream, ...) and embed them in your own test code. Docklight Scripting is also network-enabled. Instead of using a serial COM port, Docklight ...
在Golang生态系统中,`embed`包是一个内置于语言的标准库,自Go 1.16版本开始引入,用于将静态内容(如HTML、CSS、JavaScript、图片等)嵌入到Go程序中,使得这些资源可以随程序一起编译,无需在运行时额外管理文件...