| |||||||||||
IntroductionThere 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, and more generally, show you how to interact with OLE/COM objects and create your own objects in plain C. The latter is useful for other tasks such as creating your own script engine, using COM and ActiveX components, and embedding other OLE objects, in plain C. 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 OLE/COM (unless you plan to modify the source of the DLL). With standard Win32 controls such as a Static, Edit, Listbox, Combobox, etc, you obtain a handle to the control (ie, an Not so with an OLE/COM object. You don't pass messages back and forth. Instead, the COM object gives you some pointers to certain functions that you can call to manipulate the object. For example, the IWebBrowser2 object will give you a pointer to a function you can call to cause the browser to load and display a web page in one of your windows. And if the COM object needs to notify you of something or pass data to you, then you will be required to write certain functions in your program, and provide (to the COM object) pointers to those functions so the object can call those functions when needed. In other words, you need to create your own COM object(s) inside your program. Most of the real hassle in C will involve these embedded COM objects that you may need to provide so some other object can call your functions to fully interact with your program.
In conclusion, you call functions in the COM object to manipulate it, and it calls functions in your program to notify you of things or pass you data or interact with your program in some way. This scheme is analogous to calling functions in a DLL, but as if the DLL is also able to call functions inside your C program -- sort of like with a "callback". But unlike with a DLL, you don't use An OLE/COM object and its VTableSo at its simplest, a COM object itself is really just a C structure that contains pointers to functions that anyone may call. These pointers to functions must be the first thing inside of the structure. There can be other data elements in the structure later on, but the pointers must be first. This is a very important thing to note (because we'll be creating our own COM objects inside our C example, and you'll need to understand how to declare and set up such a COM "structure"). Actually, the first thing inside of the object will be a pointer to another structure that actually contains the pointers to the functions. In essence, this second structure is just an array of pointers to various functions. We refer to this array as a "VTable". Also, the first 3 pointers in the VTable must be to 3 specific functions. We'll call them HRESULT STDMETHODCALLTYPE QueryInterface(IUnknown FAR* This, REFIID riid, LPVOID FAR* ppvObj); HRESULT STDMETHODCALLTYPE AddRef(IUnknown FAR* This); HRESULT STDMETHODCALLTYPE Release(IUnknown FAR* This);Right now, let's not worry about the details of what these functions do, what those args are, and what they return. The first thing inside of a COM object must be a pointer to a VTable which contains pointers to at least those 3 functions, and the pointers must be named QueryInterface, AddRef, and Release. (These 3 are referred to as the IUnknown interface). And QueryInterface must be defined as a pointer to the QueryInterface function we defined above. AddRef must be defined as a pointer to the AddRef function. And Release must be defined as a pointer to the Release function. So here is the most simple example of the structure for a OLE/COM object which I'll just call " /* This is the VTable for MYOBJECT. It must start out with at least the * following 3 pointers. */ struct MYOBJECT_VTBL { (HRESULT STDMETHODCALLTYPE *QueryInterface)(IUnknown FAR* This, REFIID riid, LPVOID FAR* ppvObj); (HRESULT STDMETHODCALLTYPE *AddRef)(IUnknown FAR* This); (HRESULT STDMETHODCALLTYPE *Release)(IUnknown FAR* This); /* There would be other pointers here if this object had more functions. */ } /* This is MYOBJECT structure */ struct MYOBJECT { /* The first thing in the object <U>must</U> be a pointer to its VTable! */ struct MYOBJECT_VTBL *lpVtbl; /* The Object may have other embedded objects here, or some private data. * But such extra stuff must be <U>after</U> the above VTable pointer. */ }As you can see, a COM object always starts with a pointer to its VTable. And the first 3 pointers in the VTable will always be named QueryInterface ,
AddRef , and
Release . What additional functions may be in its VTable, and what the name of their pointers are, depends upon what type of object it is. For example, the browser object will undoubtably have different functions than some object that plays music. But all COM objects begin with a pointer to their VTable, and the first 3 VTable pointers are to the object's
QueryInterface ,
AddRef , and
Release functions. That is the law. Obey it.
Of course, when you create your own COM Object, you'll put the 3 "IUnknown" functions inside your program. For example, maybe you'll have 3 functions named HRESULT STDMETHODCALLTYPE MyQueryInterface(IUnknown FAR* This, REFIID riid, LPVOID FAR* ppvObj) { return(S_OK); } HRESULT STDMETHODCALLTYPE MyAddRef(IUnknown FAR* This) { return(1); } HRESULT STDMETHODCALLTYPE MyRelease(IUnknown FAR* This) { return(1); }And of course, you need to initialize your COM object to store pointers to those functions in its VTable. Here's an example of us declaring a MYOBJECT struct named Example with its VTable named ExampleTable, and initializing it: int main() { struct MYOBJECT Example; struct MYOBJECT_VTBL ExampleTable; ExampleTable.QueryInterface = MyQueryInterface; ExampleTable.AddRef = MyAddRef; ExampleTable.Release = MyRelease; Example.lpVtbl = &ExampleTable; }We have now created a COM object ( ie, Example is that object), fully initialized with a VTable containing pointers to its functions. Now, all we need do is pass a pointer to this struct to some operating system function, and then some other object (like the browser object) will be able to call MyQueryInterface(), MyAddRef(), and MyRelease() within our C executable. That's not so bad, right? QueryInterface(), AddRef(), and Release()Well, there is more to know. Let's take a look at the definitions of those 3 functions. You'll notice that the first arg to each is a pointer to an IUnknown struct. That's the generic template. When we define a specific COM object, we need to redefine this arg. So for our HRESULT STDMETHODCALLTYPE MyQueryInterface(MYOBJECT FAR* This, REFIID riid, LPVOID FAR* ppvObj); HRESULT STDMETHODCALLTYPE MyAddRef(MYOBJECT FAR* This); HRESULT STDMETHODCALLTYPE MyRelease(MYOBJECT FAR* This);And our MYOBJECT_VTBL needs to be as follows: struct MYOBJECT_VTBL {
(HRESULT STDMETHODCALLTYPE *QueryInterface)(MYOBJECT FAR* This, REFIID riid, LPVOID FAR* ppvObj);
(HRESULT STDMETHODCALLTYPE *AddRef)(MYOBJECT FAR* This);
(HRESULT STDMETHODCALLTYPE *Release)(MYOBJECT FAR* This);
}
You may ask,
"Are you telling me that the first arg passed to each of my 3 functions is going to be a pointer to some MYOBJECT struct?". Yes indeed. For example, when we give the browser object a pointer to our
Example MYOBJECT, and the browser object uses that struct's VTable to call MyQueryInterface(), then the first arg passed will be that pointer to
Example. In this way, we know which exact struct was used to call MyQueryInterface(). Furthermore, we could add data fields to the end of the struct in order to store per-instance data for the struct. So we never need reference any global data within our functions, and can make them fully re-entrant -- able to be used with plenty of MYOBJECT structs, should we need more than one.
"Wait a minute! This is starting to look suspiciously like C++! It looks like the invisible 'this' pointer of a C++ class!", you scream. Damn right. That's exactly what COM is built upon. So, you're effectively creating C++ classes in your C code (but without some of the other baggage/bloat of C++).
In conclusion, when one of your functions is called by somebody, the very first arg is always a pointer to whatever object ( ie, structure) was used to obtain your VTable. This mimics the behavior of a C++ class, and a COM Object is analogous to that. After you obtain a pointer to some COM object (ie, structure), such as the browser object, you're going to do the same thing when you call the object's functions. You'll find a pointer to some desired function somewhere within that object's VTable. And when you call the function, the first arg you pass will always be the pointer to that COM object. Generic Datatypes (ie, BSTR, VARIANT)You may be thinking that things are a little complex, but not too bad. Well, there's another wrinkle. Most OLE/COM objects are designed to be called by a program written in most any language. To that end, the object tries to abstract datatypes. What do I mean by this? Take a string in ANSI C. A C string is a series of 8-bit bytes ending with a 0 byte. But that isn't how strings are stored in Pascal. A Pascal string starts with a byte that tells how many more bytes follow. In other words, a Pascal string starts with a length byte, and then the rest of the bytes. There is no terminating 0 byte. And what about UNICODE versus ANSI? With UNICODE, every character in the string is actually 2 bytes (ie, a short). So in order to support any language (as well as extensions in each language such as UNICODE), most COM objects instead employ generic datatypes that accommodate most every language. For example, if a COM object is passed a string, then the string will often take the form of a BSTR. What is a BSTR? Well, it is sort of a UNICODE Pascal string. Every character is 2 bytes, and it starts with an unsigned short that tells how many more shorts follow. This accomodates the "string" datatype of most every language/extension. But it also means that, sometimes you'll need to reformat your C strings to a BSTR when you want to pass a string to some COM object's functions. Fortunately, there is an operating system function called SysAllocString to help do that. And there are other "generic datatypes" too, such as a generic structure that holds a numeric (for example, In fact, some COM objects' functions can operate upon a variety of datatypes, so they employ another structure called a
In conclusion, when dealing with objects like the browser object, some of its functions may require you to convert/stuff your data into one of these generic datatypes (structs), and then also perhaps wrap that in a VARIANT struct. Your IStorage/IOleInPlaceFrame/IOleClientSite/IOleInPlaceSite objectsNow that you've got some background on COM objects, let's 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) 4 objects. We need an Let's just examine the So, to create the VTable for my IStorageVtbl MyIStorageTable = {Storage_QueryInterface, Storage_AddRef, Storage_Release, Storage_CreateStream, Storage_OpenStream, Storage_CreateStorage, Storage_OpenStorage, Storage_CopyTo, Storage_MoveElementTo, Storage_Commit, Storage_Revert, Storage_EnumElements, Storage_DestroyElement, Storage_RenameElement, Storage_SetElementTimes, Storage_SetClass, Storage_SetStateBits, Storage_Stat};So we now have a global variable named MyIStorageTable which is a properly initialized VTable for our
IStorage object.
Next, we need to create our IStorage MyIStorage = { &MyIStorageTable };So we now have a global variable named MyIStorage which is a properly initialized
IStorage object. It is ready to be passed to some operating system function that will give it to the browser object so it can call any of the above 18 functions. Of course, you'll find those 18 functions in Simple.c too. (But mostly, they do nothing because these functions aren't actually utilized by the browser object. Nevertheless, we really do have to provide at least some stubs just in case some wiseguy takes our IStorage object and tries to call our functions).
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 fields to some of those other objects for our own private data. For example, instead of a just an ordinary You can consult your MSDN documentation to learn what the functions in your As mentioned, the browser object expects us to supply at least the 4 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 The browser objectAfter we've set up those above objects, we're ready to obtain a browser object. We can do that with a call to the operating system function Our function
The first 2 args we pass to If all goes well, So how do we embed the browser object? We need to call one of the browser object's functions. No problem. We just got a pointer to its object, and we know that the first field in the object (ie, lpVtbl) is a pointer to its VTable. So we just grab the pointer to the desired function (which is DoVerb), and use it to call that function. In fact, we call several browser object functions that way. We call The browser object (ie, struct) has another object called an And so we ask the browser object to return a pointer to its
In conclusion, to get a pointer to a "large object" such as a browser object, you usually call some Ole or Com function in the Windows operating system. To get other objects that are somehow associateed with that "large" object, you will call the large object's QueryInterface function to ask it to return a pointer to the particular type of object you want. This is also true of your own COM objects in your program. Your So what we do in There is one more thing to note about what we do in 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. In fact, Simple.c creates two windows that each host a browser object. (So we call Indeed, after we've embedded a browser object, we can call When we're finally done with the browser object, we need to Using the codeThe 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 HistoryInitial 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. Jeff Glatt |
Embed an HTML control in your own window using plain C
最新推荐文章于 2024-07-25 11:26:35 发布