웹, HTML

Unified Browsing with ActiveX Extensions Brings the Internet to Your Desktop

디버그정 2008. 9. 13. 00:11
 

Unified Browsing with ActiveX Extensions Brings the Internet to Your Desktop

Stephen Rauch

Stephen Rauch is a development technical specialist at Reuters. He can be reached on Compuserve at 70313,1455.

I don't know about you, but my head is spinning. I've never seen this much technology introduced in such a short period of time. It's all due to the Internet. New development languages are popping up, Internet browsers have introduced a new navigationmodel,and Microsoft introduced the ActiveXª technology in March 1996.

The vision behind Microsoft's ActiveX is unified browsing—accessing information from the Internet just as you do from your local hard drive. Unified browsing means that you don't have to bring up the Windows¨ Explorer to view your file system and another application like Word to view and edit your documents. You get to use one shell to view and navigate through this jungle of information. In addition, it shouldn't matter where that information resides.

In this article, I will introduce you to some of the ActiveX extensions to the Win32¨ API. These extensions will enable you to write applications, documents, and objects that fit into the ActiveX unified browser context. I'll focus on hyperlinking, which is the navigation method used by Internet browser applications, and a really simple way of downloading data from the Internet with only a Uniform Resource Locator (URL). You don't have to be an OLE expert! Microsoft has hidden all of that OLE stuff under the ActiveX APIs.

URLs and URL Monikers the Hard Way

Before jumping into these new APIs, I want to briefly introduce the foundation that these APIs are based on, URLs and URL monikers. For more information, you may wish to read the asynchronous URL moniker specifications distributed with the ActiveX SDK.

A URL is a string representation of the name and address of an object on the Internet. There are two parts to the syntax of a URL. The first part is the name of a protocol, and the second part is protocol-specific. For a Hypertext Transfer Protocol (HTTP) URL, the protocol is HTTP, which is followed by a colon. The protocol-specific part after that is two slashes, then a host name, an address, and an optional port number. The whole thing comes out like this: http://www.microsoft.com. The nice thing about the URL syntax is that it is extensible. Just about any protocol you define can be encoded as a URL. Examples include File Transfer Protocol (FTP) and Gopher. In addition, the URL syntax is easily read and understood.

To actually do something with a URL, you need to write some protocol-specific code. This is where URL monikers come into the picture. A moniker is just a symbolic name of an object; encapsulated in that name is the mechanism to instantiate the object. The moniker architecture is extensible and supports complete name parsing through the MkParseDisplayName(Ex) API and the IParseDisplayName and IMoniker interfaces. It also supports human-readable names through the IMoniker::GetDisplayName method. When you use monikers, you are actually leveraging an extensible infrastructure that takes care of getting tasks done, such as downloading files, finding and launching code, or encoding/decoding raw data into appropriate formats.

URL monikers provide the framework for building and using URLs. Unlike the synchronous monikers defined in the OLE 2.0 specification, ActiveX URL monikers support asynchronous binding as well. They are implemented asynchronously because URLs frequently refer to resources across low bandwidth, high-latency networks. You don't want your applications or controls to block.

Figure 1 shows the three components involved in using URL monikers: client, system, and transport. You implement the client component in your applications. The system component is provided by the operating system. The third component is transport-specific.

Figure 1 URL Moniker Components

To use URL monikers, you must implement the IBindStatusCallback interface, a callback object used by the URL moniker to provide feedback to the client. It receives progress notification through the IBindStatusCallback::OnProgress member function, data availability notification through IBindStatusCallback::OnDataAvailable, and various other notifications about the status of the binding from the moniker. You can use this information to provide feedback to your users through a progress bar with a cancel button or through progressive rendering of data.

You may optionally implement a format enumerator with the IEnumFormatETC interface. This interface lets you provide protocol-specific information that will affect the bind operation. For example, to support media-type negotiation, you can implement IEnumFormatETC to register a MIME format enumerator. A URL moniker translates these formats into MIME types when binding to HTTP URLs.

Let's take a look at what happens when you use a URL moniker. The first thing you do is create a bind context by calling the ActiveX API CreateAsyncBindCtx. You pass this function a pointer to the IBindStatusCallback interface you implemented, and optionally a pointer to the IEnumFormatETC interface. CreateAsyncBindCtx returns a pointer to IBindCtx, which automatically registers the IBindStatusCallback and the IEnumFormatETC interfaces with the bind context. Once you have a bind context, you can create a URL moniker by calling MkParseDisplayNameEx or CreateURLMoniker. OLE's MkParseDisplayName function, which converts a string into a moniker that identifies the object named by the string, has been extended to support URLs. These two steps are essentially the same steps you use today to create a file or item moniker.

After you create the bind context and the URL moniker, you call BindToStorage or BindToObject on the URL moniker, passing the bind context. When either of these functions are called, the URL moniker figures out if this is an HTTP binding or an FTP binding and instantiates a transport-specific component. How does the URL moniker know which transport protocol to use? It looks at the protocol part of the URL syntax you passed when you created the moniker using MkParseDisplayNameEx or CreateURLMoniker.

Once the transport-specific component is created, the IBinding interface is passed back to you through a callback, IBindStatusCallback::OnStartBinding. With the IBinding interface, you can stop, suspend, and resume the binding operation or change the priority of the download. From this point on, the transport-specific component is driving the bind operation, providing progress and data callback notifications to the client. The bind operation ends when you get your last IBindStatusCallback::OnDataAvailable callback (a parameter lets you know) or when you get the IBindStatusCallback::OnObjectAvailable callback if you are binding to an object.

Even through this was a brief description of how to create and use URL monikers, it sounds like a lot of work. If you want something simpler, you may want to use the APIs Microsoft created for hyperlinking and downloading data based on this infrastructure.

URLs the Easy Way: Hyperlinking

You see an ad for a cool-looking upcoming movie and decide to check out the Web site with info on it. You enter a string in your favorite World Wide Web browser and presto, you're viewing an HTML document. You continue to navigate your way through the Internet, clicking on some underlined text displayed in an HTML document or some cool graphics, and before you know it, you've eaten up six hours. You have to go to work in three hours and your connect-time charges are pricier than a Pentagon toilet seat.

Hyperlinks permit you to explore vast amounts of information without caring about the location of the server or the name of the file you want to view. All you need to do is enter a URL or point-and-click on some underlined text displayed in an HTML document and your browser is off to a new location displaying something new. World Wide Web browser applications have other navigational features as well. Let's face it, as you browse the Internet, you can easily forget where you came from. That's why browsers provide a history of places you've been, Go Forward and Go Back buttons, and a favorites list.

Microsoft extended the hyperlinking metaphor by introducing ActiveX hyperlinks. Like other OLE-based integration technologies, ActiveX hyperlinks allow you to integrate them seamlessly with other applications that support hyperlinks. A few different forms of navigation include:

  • From a standalone application to another standalone application, in the absence of a browser.
  • From a standalone application to an ActiveX document object or HTML document in a browser.
  • From one ActiveX document object in a browser to another ActiveX document object in the same browser, analogous to navigating from one HTML document to another.
  • From an ActiveX document object in a browser to an ActiveX document object in a Microsoft Office Binder-like application if the hyperlink target is embedded in an Office Binder document.
  • From one location in an object/document to another location in the same object/document.

The ActiveX hyperlink functions and interfaces support both ActiveX document and non-ActiveX document applications. You can also create hyperlinks to jump from standalone, non-OLE applications to HTML documents and back again. Depending on the complexity of the hyperlinking you need to include in your documents and applications, you have two choices: the ActiveX Simple Hyperlink Navigation APIs or the ActiveX Hyperlinking interfaces.

Simple Hyperlinking Interfaces, and a Sample

The Simple Hyperlink Navigation APIs (see Figure 2) are ActiveX extensions to the Win32 API. They encapsulate the more complex full hyperlinking interfaces, which I will discuss later. If you can say "Here is where I want to go, now go there" without needing to know any OLE interfaces and objects, you want to use these APIs.

Figure 2 Simple Hyperlink Navigation APIs

Function

Description

HlinkSimpleNavigateToString

Executes a hyperlink jump to a new document or object represented as a string

HlinkSimpleNavigateToMoniker

Executes a hyperlink jump to a new document or object represented as a moniker

HlinkGoBack

Executes a hyperlink jump backwards within the navigation stack

HlinkGoForward

Executes a hyperlink jump forwards within the navigation stack


For example, if you're a control writer and if you want your control to hyperlink to one HTML document while embedded in another HTML document, you could use these APIs to get the job done. Another use would be in an image map object; when users click on areas in the image map, you can link them to sites represented by the areas of the image map. These APIs will be extended in the future to provide a simple method of accessing other hyperlink navigation metaphors such as histories and favorites.

Let's see these APIs in action. I modified the Bindable Scribble sample application (BINDSCRB) distributed with Microsoft Visual C++ 4.1 so that it displays a context menu. A context menu is nothing more than a popup menu displayed when Windows sends a WM_CONTEXTMENU message to your application. I chose the BINDSCRB application because calling the Simple Hyperlink Navigation APIs from within an application whose documents are displayed in-frame results in different behavior than if you call the APIs from a standalone application. The BINDSCRB document is an ActiveX document object; its documents can be hosted within any hyperlink frame application (see Figure 3), including Microsoft¨ Internet Explorer 3.0 (IE 3.0).

Figure 3 BINDSCRB sample application.

The Simple Hyperlink Navigation APIs are defined in the URLHLINK.H header file from the ActiveX SDK beta available at http://www.microsoft.com/intdev/. You must also link with the URLMON.LIB and URLHLINK.LIB static libraries. These are static libraries for now; this functionality will be folded into URLMON.DLL in the next beta release.

To add the context menu support to the BINDSCRB application, I added a popup menu to SCRIBBLE.RC.


 IDR_CONTEXTMENU MENU DISCARDABLE 
BEGIN
    POPUP ""
    BEGIN 
        MENUITEM "Go &Back",      ID_GOBACK
        MENUITEM "Go &Forward",   ID_GOFORWARD
        MENUITEM SEPARATOR
        MENUITEM "&Microsoft Home page", ID_MSHOMEPAGE
    END
END

Selecting of the Go Back and Go Forward menu items causes BINDSCRB to jump backwards and forwards within the browser's history list. Selecting "Microsoft Home page" will hyperlink to a Scribble HTML document residing on the Internet (actually http://www.microsoft.com/default.htm).

I created a message handler using ClassWizard for the WM_CONTEXTMENU window message in the CScribView class (see Figure 4). This message is received by the BINDSCRB application when the user right-clicks anywhere in its view.

Figure 4 WM_CONTEXTMENU Message Handler


 void CScribView::OnContextMenu(CWnd* pWnd, CPoint point) 
{
    CRect rect;
    GetClientRect (&rect);
    ClientToScreen (&rect);

    if (rect.PtInRect (point))
    {
        CMenu menu;
        menu.LoadMenu (IDR_CONTEXTMENU);
        CMenu* pContextMenu = menu.GetSubMenu (0);

        // If the Scribble application is not hosted in a frame that
        // is capable of hosting ActiveX Document Objects, disable the
        // "Go Back" and "Go Forward" menu items.  The Simple
        // Hyperlink Navigation APIs related to Go Back and Go Forward
        // only work in-frame.
        CScribDoc* pScribDocument = GetDocument ();
        if (!pScribDocument -> IsDocObject())
        {
            pContextMenu -> EnableMenuItem(ID_GOBACK, MF_GRAYED);
            pContextMenu -> EnableMenuItem(ID_GOFORWARD, MF_GRAYED);
        }

        // Display the context menu.
        pContextMenu -> TrackPopupMenu (TPM_LEFTALIGN | TPM_LEFTBUTTON |
                                        TPM_RIGHTBUTTON, point.x, 
                                        point.y, this);
    }
}

I call the CScribDoc member function IsDocObject to determine if the view is hosted in a container that supports the IOleDocumentSite interface. Containers that support this interface are generally hyperlink-aware and will host ActiveX doc objects. IE 3.0 and Microsoft Office Binder are examples. If the view is not hosted in such a container, I disable the Go Back and Go Forward menu items. HlinkGoBack and HlinkGoForward may be called only if the object calling the APIs is hosted within a hyperlink-aware container.

To handle the individual context menu items, I added three message handlers to SCRIBDOC.CPP (see Figure 5). CScribDoc::OnGoBack and CScribDoc::OnGoForward are invoked when the user selects the Go Back and Go Forward menu items, and CScribDoc::OnMSHomePage is invoked when the user selects "Microsoft Home page."

Figure 5 Context Menu Item Message Handlers


 ////////////////////////////////////////////////////////////////////
//
//    OnGoBack: Context menu handler for "Go Back".  Calls the
//              Simple Hyperlink Navigation API HlinkGoBack to go
//              back in the Hyperlink history stack.
//
////////////////////////////////////////////////////////////////////
void CScribDoc::OnGoBack()
{
  IUnknown* pUnk = NULL;

  // Obtain the IUnknown pointer to the document.
  HRESULT hr = this -> ExternalQueryInterface ((void*)&IID_IUnknown,
                                               LPVOID *)&pUnk);

  if (hr == S_OK)
    // Go Back in the hyperlink history stack
    HlinkGoBack (pUnk);
}

///////////////////////////////////////////////////////////////////
//    OnGoForward:  Context menu handler for "Go Back".  Calls
//                  the Simple Hyperlink Navigation API HlinkGoForward
//                  to go forward in the Hyperlink history stack.
//
///////////////////////////////////////////////////////////////////
void CScribDoc::OnGoForward()
{
  IUnknown* pUnk = NULL;

  // Obtain the IUnknown pointer to the document.
  HRESULT hr = this -> ExternalQueryInterface ((void*)&IID_IUnknown,
                                               LPVOID *)&pUnk);

  if (hr == S_OK)
    // Go Forward in the hyperlink history stack
    hr = HlinkGoForward (pUnk);
}

///////////////////////////////////////////////////////////////////
//
//    OnMSHomePage:  Context menu handler for "Microsoft Home Page".
//                   Calls the Simple Hyperlink Hyperlink Navigation
//                   API HlinkSimpleNavigateToString to display the
//                   HTML page for Microsoft's Web site
//
///////////////////////////////////////////////////////////////////
void CScribDoc::OnMSHomePage()
{
  HRESULT hr;
  IUnknown* pUnk = NULL;

  if (this -> IsDocObject())
    // Obtain the IUnknown pointer to the document.
    hr = this -> ExternalQueryInterface ((void*)&IID_IUnknown,
                                         LPVOID *)&pUnk);

  HlinkSimpleNavigateToString(L"http://www.microsoft.com/default.htm",
                              NULL, NULL, pUnk, 0, NULL, NULL, 0);
}

When calling HlinkGoBack and HlinkGoForward, you must pass the IUnknown pointer of the document or object initiating the hyperlink. For an aggregated COM object, this must be punkOuter. This information is used to traverse the ActiveX hyperlinking interfaces to determine where the object resides in the history list. Once its position has been established, the hyperlinking architecture navigates from that position in the list backward or forward as appropriate.

You can declare where you want to go using the HlinkSimpleNavigateToString or HlinkSimpleNavigateToMoniker APIs. If you have a string that identifies the hyperlink target, such as http://www.microsoft.com, you will use HlinkSimpleNavigateToString. Pass the string as the first argument; it will be automatically resolved into a moniker by the API for underlying binding operations by calling MkParseDisplayNameEx internally. If you have a moniker that identifies the hyperlink target, you will use HlinkSimpleNavigateToMoniker. The moniker passed can be of several different types: a URL moniker, a file moniker, or an item moniker. For both of these API functions you can also pass NULL as the first argument, in which case the navigation is considered within the same document or object.

The remaining arguments to these two APIs are the same.


 STDAPI HlinkSimpleNavigateToString 
           (LPCWSTR szTarget,LPCWSTR szLocation,
            LPCWSTR szTargetFrameName, 
            IUnknown *pUnk,IBindCtx *pbc,     
            IBindStatusCallback *pbsc, 
            DWORD grfHLNF, DWORD dwReserved);

STDAPI HlinkSimpleNavigateToMoniker 
           (IMoniker *pmkTarget, LPCWSTR szLocation,     
            LPCWSTR szTargetFrameName, IUnknown *pUnk,  
            IBindCtx *pbc, IBindStatusCallback *pbsc,
            DWORD grfHLNF, DWORD dwReserved);

szLocation is an optional string representing the location within the hyperlink target for the new hyperlink. For an example of this, bring up an HTML document that uses hyperlinks for a table of contents. When you select a table-of-contents hyperlink, you jump to a different location within the same HTML document. szTargetFrameName is an optional string that describes the target frame for the hyperlink when navigating within a document container that supports the IHlinkFrame interface. The pUnk argument is a pointer to IUnknown, as described earlier. It is used by the hyperlink architecture as a reference to the document or object in the history list. If pUnk is NULL, it is assumed that the hyperlink originates from an OLE-unaware application, in which case the APIs will call ShellExecute to open the szTarget or pmkTarget document or object in a newly created instance of the browser.

In my CScribDoc::OnMSHomePage implementation (see Figure 5), I call the CScribDoc class's member function IsDocObject to determine if the view is hosted in a container that supports the IOleDocumentSite interface. If it is not, I leave pUnk set to its initialized state, NULL. When the user is running the BINDSCRB application standalone and selects the Microsoft Home page menu item, the hyperlinking architecture creates a new instance of IE 3.0 (see Figure 6). If the BINDSCRB document is hosted in a hyperlink-aware container, I set pUnk equal to the IUnknown pointer of CScribDoc, whose base class is CDocObjectServerDoc. When the user selects the Microsoft Home page menu item, a new instance of IE 3.0 is not created. Instead, the browser containing the BINDSCRB document hyperlinks and displays the requested target, in this case http://www.microsoft.com/default.htm.

Figure 6 Hyperlinking from BINDSCRB

The grfHLNF argument is a value from the HLNF enumeration defined in the header file HLINK.H. Values from the HLNF enumeration indicate how hyperlink navigation is to proceed. For example, if you set grfHLNF equal to HLNF_OPEN_INNEWWINDOW, the hyperlinking architecture will create an instance of IE 3.0 to display the navigation target. You can see HLNF_OPEN_INNEWWINDOW in practice today if you right-click on a hyperlink displayed in an HTML document by IE 3.0 and select Open In New Window. The values in the HLNF enumeration also convey context information about the navigation from each of the objects participating in the navigation protocol to the other objects. The pbc argument is a pointer to a bind context to use for any moniker binding performed during the navigation. The bind context is an object that stores information about a particular binding operation. You can pass the pointer to the function if you wanted to add additional arguments on the IBindCtx such as a format enumerator. Finally, the pbsc argument is a pointer to an IBindStatusCallback interface. You would pass a pointer to this interface if you are interested in progress notification, cancellation, pausing, or any low-level binding information.

To make things even simpler, because many of the arguments that you pass to HlinkSimpleNavigateToString or HlinkSimpleNavigateToMoniker will probably be NULL, Microsoft also provides two macro equivalents, HlinkNavigateString and HlinkNavigateMoniker. HlinkNavigateString has two arguments, a pointer to IUnknown and the string identifying the hyperlink target. HlinkNavigateMoniker also has two arguments, a pointer to IUnknown and a pointer to a moniker that identifies the hyperlink target.

Based on what I have covered so far, you should be able to add very simple hyperlink navigation functionality to your existing applications, documents, and objects. The Simple Hyperlink Navigation APIs hide all of that OLE interface stuff, making your entry into ActiveX hyperlinking quick and painless. But what if you wanted to do more advanced hyperlinking? What is actually going on underneath these Simple Hyperlink Navigation APIs?

Advanced Hyperlinking Interfaces

You may want to incorporate more advanced features of hyperlinking into your applications. Authoring tools and browsers, for example, will need to implement a number of the ActiveX hyperlinking interfaces to host document objects. Five OLE interfaces make up the hyperlink interfaces: IHlink, IHlinkTarget, IHlinkFrame, IHlinkSite, and IBrowseContext (see Figure 7).

Figure 7 Hyperlink OLE Interfaces

The ActiveX hyperlinking architecture centers around the Hlink object. Hlink is a standard system object that exposes an IHlink interface and encapsulates all of the information needed to hyperlink. This includes a target moniker (which points to where someone wants to go), a string that describes the location within the target, and some additional arguments, including a friendly name. In addition, the ability to navigate on behalf of the container is encapsulated in this object. This is accomplished through its IHlink::Navigate method. The Simple Hyperlinking Navigation APIs that I discussed earlier create an Hlink object from the arguments passed to the functions. They use this object to drive the navigation.

The Hlink object can be created through several APIs documented in HLINK.H: HlinkCreateFromMoniker, HlinkCreateFromString, HlinkCreateFromData, and HlinkQueryCreateFromData. The Hlink object can also be persisted because it implements an IPersistStream interface. Since you can create an Hlink object from a data object, you can drag, drop, and paste it to the clipboard. The most important item encapsulated in the Hlink object is the moniker that points to a hyperlink target. A hyperlink target is the document to which you are navigating: an ActiveX doc object hosted in frame, a standalone application, an OLE object embedded in another document, or anything that a moniker can point to.

If you are implementing a hyperlink target, you have the option of implementing and exposing an IHlinkTarget interface. If you decide not to, users can still hyperlink to your object, but functionality will be limited. Generally, objects that support the IHlinkTarget interface are given information about navigating within a document. After the object has been instantiated through the IHlinkTarget interface, the hyperlinking framework can tell the object "here is the location within the document I want you to hyperlink to." An example is hyperlinking to a Microsoft Excel spreadsheet and navigating to a specific cell within the spreadsheet. Another use of the IHlinkTarget interface is to pass a pointer to the standard system browse context object to the hyperlink target. The browse context object implements an IBrowseContext interface and knows the order in which documents were visited. All jumps are recorded with this context, and this context chains them together in a navigation stack, so it knows where to go in response to Go Back or Go Forward. In IE 3.0, the drop-down combo box under the toolbar displays the history list. Given the pointer to the browse context object's interface, your application can enumerate and display the navigation stack.

Now that I have defined a target, I have to define the source of a hyperlink. The source is the hyperlink container—the object from which you are linking. The hyperlink container is responsible for the visual representation of the hyperlink, generally underlined text or a bitmap. You can represent a hyperlink any way you want—it could be a toolbar button or a menu—but you should try to be consistent with common methods of representation. (Microsoft is working on producing UI guidelines for representing hyperlinks.) An example of a hyperlink container is an HTML document. Generally, an HTML document displays underlined text that a user can select, which results in a hyperlink to another HTML document. What about selecting some underlined text within an HTML document and hyperlinking to a position in the same HTML document? In this case, not only is the HTML document the hyperlink container, it's also a hyperlink target (see Figure 7). When I modified the BINDSCRB application to call HlinkSimpleNavigateToString when the user selects the Microsoft Home page, my application became a hyperlink container.

You don't have to implement any OLE interfaces to become a hyperlink container, but if you implement an IHlinkSite interface, your hyperlink container will have access to additional information and your hyperlinking can become more efficient. A hyperlink container implementing an IHlinkSite interface will have access to all of the information contained within the IHlink object. In addition, when a hyperlink container calls a hyperlink function to jump to a hyperlink target, the container will be through the IHlinkSite::OnNavigationComplete member function notified when the hyperlink has been completed. To make navigating even simpler, if your document supports a base URL and the IHlinkSite interface, your users can use relative URLs. When a user wants to hyperlink to a relative URL, the hyperlinking architecture will call back into the IHlinkSite interface to obtain the base URL to resolve where it has to go.

The last interface in the hyperlinking architecture is IHlinkFrame. The container that hosts a document and implements the IHlinkFrame interface is called a hyperlink frame. An example of a hyperlink frame is IE 3.0 (see Figure 7). Through this interface, the hyperlink frame is notified when a hyperlink container has navigated to a new hyperlink target. From this information, the hyperlink frame displays the new document. In addition, the hyperlink frame generally contains the user interface that displays the navigation stack and buttons or menu items to move backwards or forwards in the navigation stack.

URLs the Easy Way: Data Downloading

The latest addition to the Win32 ActiveX extensions API is the URL Open Stream (UOS) functions (see Figure 8). These functions were made public in the ActiveX SDK beta posted on Microsoft's Internet site in June.

Figure 8 URL Open Stream Functions

Function

Description

URLOpenStream

Creates a push-type stream object from a URL

URLOpenBlockingStream

Creates a blocking-type stream object from a URL

URLDownloadToFile

Downloads bits from the Internet and saves them to a file

URLOpenPullStream

Creates a pull-type stream object from a URL

URLOpenHttpStream

Advanced function for doing more sophisticated HTTP and FTP downloads, such as performing an HTTP POST


The UOS functions are the easiest and most powerful way to download data from the Internet into your applications. These functions combine the familiarity of C programming with the power of COM. In fact, that's exactly what's underneath these functions. They use the services of URL monikers and WinInet, which is the ActiveX API set that lets you get information from the Internet into your application without using a browser (see Nancy Nicolaisen's article on page 69 of this issue for more—Ed). This means you get all of the caching and thread-synchronization features of these services every time you call the UOS functions. If you're calling these functions from an ActiveX container, the UOS functions will handle all of the binding operations. If you're not calling them from an ActiveX container, you won't be shortchanged; the UOS functions work equally well inside the ActiveX framework or within a standalone application.

Before jumping into the UOS functions let's cover a few basics—using these functions requires the knowledge and use of the IStream and IBindStatusCallback interfaces.

The IStream interface has been around for quite some time. This interface supports reading and writing data to stream objects using methods similar to the MS-DOS¨ FAT file functions. For example, each stream object has its own access rights and a seek pointer. The main difference between a stream object and an MS-DOS file is that streams are not opened with a file handle. Instead, you use the IStream interface pointer. The methods defined for this interface present your object data as a contiguous sequence of bytes that you can read or write. How is the IStream interface related to the UOS functions? When calling the UOS functions, the data you request will be returned to you in a STGMEDIUM structure (a generalized global memory handle used for data transfer operations). The union member pstm specifies an IStream instance; you use the methods of the IStream interface to read the data.

IBindStatusCallback (see Figure 9) is a new ActiveX interface for asynchronous monikers. Since URL monikers are an implementation of asynchronous monikers and the UOS functions use the services of URL monikers, you will have to implement this interface when using a few of the UOS functions. Figure 10 is a decision table that describes when to implement an IBindStatusCallback interface.

Figure 9 IBindStatusCallback Interface


 interface IBindStatusCallback: IUnknown
    {
    HRESULT GetBindInfo([out] DWORD* pgrfBINDF, 
                        [in, out] BINDINFO* pbindinfo);
    HRESULT OnStartBinding([in] DWORD dwReserved, [in] IBinding* pbinding);
    HRESULT GetPriority([out] LONG* pnPriority);
    HRESULT OnProgress([in] ULONG ulProgress, [in] ULONG ulProgressMax, 
                       [in] ULONG ulStatusCode, [in] LPCWSTR szStatusText);
    HRESULT OnDataAvailable([in] DWORD grfBSC, [in] DWORD dwSize, [in]
                            FORMATETC* pformatetc, [in] STGMEDIUM*   pstgmed);
    HRESULT OnObjectAvailable( [in] REFIID riid, [in] IUnknown *punk);
    HRESULT OnLowResource([in] DWORD dwReserved);
    HRESULT OnStopBinding([in] HRESULT hrStatus, [in] LPCWSTR szStatusText);
    };

Figure 10 IBindStatusCallback Implementation

Function

Implementation

URLOpenStream

Mandatory

URLOpenBlockingStream

Optional

URLDownloadToFile

Optional

URLOpenPullStream

Mandatory

URLOpenHttpStream

Optional


To make things easy, you only have to implement the IBindStatusCallback::OnDataAvailable member function for functions that require an IBindStatusCallback interface. URLOpenStream and URLOpenPullStream call IBindStatusCallback::OnDataAvailable every time data arrives from the Internet. To abort the download, return E_ABORT from the OnDataAvailable call. Since the rest on the member functions are optional, you can simply return NOERROR or E_NOTIMPL.

If the implementation of the IBindStatusCallback interface is optional, you only have to implement the IBindStatusCallback::OnProgress member function. URLOpenBlockingStream and URLDownloadToFile call IBindStatusCallback::OnProgress on some connection activity. By implementing IBindStatusCallback::OnProgress, you can implement other progress-monitoring functionality like the progress bar in the IE 3.0 status bar. In addition, you can cancel the download operation by returning E_ABORT from the IBindStatusCallback::
OnProgress call. Once again, you can return NOERROR or E_NOTIMPL for the other member functions.

In all cases, IBindStatusCallback::GetBindInfo is never invoked for clients using the UOS functions. This is because the bind information is determined according to the UOS function being called. Let's take a look at each of the functions in more detail.

The URLOpenStream function creates a push-type stream object from a URL.


 URLOpenStream (LPUNKNOWN pCaller, LPCWSTR szURL, 
               DWORD dwResv, 
               LPBINDSTATUSCALLBACK lpfnCB);

In a push-type data model, the URL moniker drives the bind operation and continuously notifies the client through the IBindStatusCallback::OnDataAvailable member function whenever data is available. Data is downloaded as fast as possible in this model.

The first argument to this function, pCaller, is a pointer to the controlling IUnknown of the ActiveX component. If the caller is not an ActiveX component, this value may be set to NULL. szURL is the string representation of the URL that will be converted into a stream object by the function, such as http://www.acmewidgets.com/giant_hair_dryer.gif. dwResv is reserved for future use, and lpfnCB is a pointer to the caller's IBindStatusCallback interface.

When a callback is invoked, the IBindStatusCallback::OnDataAvailable member function is called. A typical implementation of IBindStatusCallback::OnDataAvailable as used by the URLOpenStream function is shown in Figure 11. If the pstm member of the STGMEDIUM structure is not NULL, you can read from the stream the amount of data specified in the dwSize argument passed to the IBindStatusCallback::OnDataAvailable call. When the OnDataAvailable argument grfBSCF indicates BINDF_LASTDATANOTIFICATION, data will no longer be downloaded.

Figure 11 PushCBindStatusCallback::OnDataAvailable


 HRESULT CBindStatusCallback::OnDataAvailable 
(DWORD grfBSCF, DWORD dwSize, FORMATETC* pfmtetc, STGMEDIUM* pstgmed)
{
.
.
.
    if (dwSize < sizeof(BITMAPINFOHEADER) )
        return (NOERROR);  // not enough has been read yet

    // if we did not get the header information, read it now
    if (!g_bGotInfoHeader)
    {
        if (pstgmed->pstm != NULL)
        {
            DWORD dwRead;
            HRESULT hr = pstgmed->pstm->Read (&bmih, sizeof(bmih), &dwRead);

            if (SUCCEEDED(hr))
            {
                g_bGotInfoHeader = TRUE;
                return(hr);
            }
        }
    }
.
.
.
}

The URLOpenBlockingStream function creates a blocking-type stream object from a URL.


 URLOpenBlockingStream (LPUNKNOWN pCaller,
                       LPCWSTR szURL, 
                       LPSTREAM *ppStream, 
                       DWORD dwResv,
                       LPBINDSTATUSCALLBACK lpfnCB);

In this model, data is downloaded from the Internet on demand by a call to IStream::Read. When calling IStream::Read, your application or object will block until enough data has arrived. When you're reading the data, the URLOpenBlockingStream function will always try to obtain the bits from the local cache. If the data is not in the local cache, the function will try to put the downloaded bits into the cache so your application won't have to get the data from the Internet. This scheme provides a quick and efficient method of getting your data.

The pointer to the IStream interface, ppStream, is created and returned to you when you call the URLOpenBlockingStream function. As soon as you have a valid IStream pointer, you can begin to read from the stream object.


 
IStream * pStream;
URLOpenStream (NULL, L"http://www.msn.com/", &pStream,
               0, 0);

char buffer[0x100];

DWORD dwGot;
HRESULT hr = NOERROR;

do {
    hr = pStream->Read (buffer, sizeof(buffer),&dwGot);

    //.. do something with contents of buffer ...
    } while (SUCCEEDED(hr));
.
.
.

The URLDownloadToFile function downloads the bits from the Internet and saves them to a file that you specify in the szFileName argument.


 URLDownloadToFile(LPUNKNOWN pCaller, LPCWSTR szURL,
                  LPCTSTR szFileName, DWORD dwResv,
                  LPBINDSTATUSCALLBACK lpfnCB);

If you decide to implement the IBindStatusCallback::OnProgress member function, you will get notified of the download progress. I show you how this works later.

The URLOpenPullStream function creates a pull-type stream object from a URL.


 URLOpenPullStream (LPUNKNOWN pCaller, LPCWSTR szURL,
                   DWORD dwResv, 
                   LPBINDSTATUSCALLBACK lpfnCB);

In a data pull-type model, the client drives the operation. You can control how much data you want to pull from the server and when. This is handy when you have a list box full of data to fill or a document containing several pages worth of data. Initially, you retrieve what you need to fill the display. When the user scrolls down in the list box or wants to display a new page in the document, your application or control can read more data. The data is downloaded on demand by calling the IStream::Read member function. If enough data is not available locally to satisfy the requests, the IStream::Read member function will not block. Instead, IStream::Read immediately returns E_PENDING and the URLOpenPullStream function requests the next packet of data from the Internet server. Figure 12 shows a typical implementation of OnDataAvailable as it is used by the URLOpenPullStream function.

Figure 12 PullCBindStatusCallback::OnDataAvailable


 HRESULT CBindStatusCallback::OnDataAvailable (DWORD grfBSCF, DWORD dwSize,
                                              FORMATETC* pfmtetc, 
                                              STGMEDIUM* pstgmed)
{
    HRESULT hr = NOERROR;
    DWORD dwAmountToRead = dwSize - m_readSoFar;
    BYTE *buffer = new BYTE [dwAmountToRead];

    while (TRUE)
    {
        DWORD dwRead;

        hr = pstgmed->pstrm->Read (buffer, dwAmountToRead, &dwRead);

        if (hr == E_PENDING)
        {
            // we'll get notified again when more data comes
            return (NOERROR);
        }

        if (SUCCEEDED(hr))
        {
            // ok, process bits .... and keep looping
        }
        else
        {
            // we have an error...
            return(hr);
        }
    }
}

URLOpenHttpStream is a catch-all function for doing more sophisticated HTTP and FTP downloads, such as performing an HTTP POST.


 URLOpenHttpStream (LPUOSHTTPINFO *lphttpInfo);

By filling in a UOSHTTPINFO structure, you can explicitly tell the function the functionality you expect and how you want the bits delivered from the Internet. The structure comes from the UOS specification (see Figure 13).

Figure 13 Members of the UOS Structure

Member

Description

ULONG ulSize

Size of this structure.

LPUNKNOWN punkCaller

Pointer to the controlling IUnknown of the calling ActiveX component (if the caller is an ActiveX component). If the caller is not an ActiveX component, this value may be set to NULL. Otherwise, the caller is a COM object that is contained in another component (such as an ActiveX control in the context of an HTML page). The argument represents the outermost IUnknown of the calling component. The function will attempt the download in the context of the ActiveX client framework and allow the caller's container to receive callbacks on the progress of the download.

LPCTSTR szURL

URL to be downloaded.

LPCTSTR szVerb

GET, PUT, POST, or a custom verb understood by the server. If NULL, GET is assumed.

LPCTSTR szHeaders

HTTP headers to use during connection to server. Can be NULL.

LPBYTE szPostData

Additional data to send after the headers. Typically this will be a POST data arguments. Can be NULL.

ULONG ulPostDataLen

Size of szPostData. Must not be 0 if szPostData is not NULL.

ULONG fURLEncode

Flags with either the UOS_URLENCODPOSTDATA, UOS_URLENCODEURL, or can be NULL. The two flags can be combined by a bitwise OR.

ULONG ulMode

Can be one of UOSM_PUSH, UOSM_PULL, UOS_BLOCK, or UOS_FILE. Each of these maps to one of the functions above and makes this function's programming model fit the corresponding function.

LPCTSTR szFileName

Name of the local file to write URL data to if ulMode is UOS_FILE. Otherwise, must be NULL.

LPSTREAM *ppStream

Pointer to IStream pointer if ulMode is UOS_BLOCK. Otherwise, must be NULL.

LPBINDSTATUSCALLBACK lpbscb

Pointer to IBindStatusCallback interface. This interface and the caller's programming model responsibility depend on the ulMode flag above. The function will behave exactly like the corresponding UOS functions above, depending on the flag settings.


URL Open Stream Sample Application

The following sample application illustrates how the URLDownloadToFile function downloads the bits of a file, represented as a URL, from the Internet to a file created on your local machine. I call the sample application "UOS AVI Downloader" (see Figure 14) because it downloads an AVI file from the Internet and plays the AVI file within the Media Architects Video Play OCX control distributed with Visual C++ 4.1. You will need this OCX control to run this application.

Figure 14 UOS AVI Downloader

The UOS AVI Downloader is a dialog-based MFC application. It contains an edit control where you enter the URL of the AVI file you want to download, a Retrieve button that you select to download the file, and the Media Architects Video Play OCX control. If you want to build the application, you will need Visual C++ 4.1 and the URLMON.LIB, URLHLINK.LIB, WININET.LIB, and UUID3.LIB libraries from the ActiveX Beta SDK. (The names of these libraries may be different when the final ActiveX SDK is released.)

All of the work is done in the BN_CLICKED message handler for the Retrieve button (see Figure 15). In the message handler, I create an IBindStatusCallback object, which I implemented in DOWNLOAD.CPP. I want to know when the download is complete, so I implement the IBindStatusCallback::OnProgress method. In this method I set a private data member m_gotFile to TRUE when the ulStatusCode argument is equal to BINDSTATUS_ENDDOWNLOADDATA. The asynchronous moniker specification lists all of the status codes returned to the ulStatusCode argument. By receiving different status codes during the download process, you can give your user feedback on the progress of the download.

Figure 15 AVIDNLDR Retrieve Button Message Handler


 ///////////////////////////////////////////////////////////////////
// CAVIDownloaderDlg::OnBtnRetrieve - Message handler for the Retrieve
// command button.  Downloads the .AVI file from the Internet and runs // it in the Media Architects Video Play OCX Control.
//
///////////////////////////////////////////////////////////////////
void CAVIDownloaderDlg::OnBtnRetrieve() 
{
    // This application does not do any checking to insure that the
    // user has entered a valid URL.  This exercise is left to the
    // reader.

    // Create a IBindStatusCallback object 
    ptrURLDownloadToFileCallback pURLDownloadToFileCallback;

    if (!pURLDownloadToFileCallback)
    {
        AfxMessageBox (_T("Unable to create IBindStatusCallback object."));
        return;
    }

    // Disable the "Retrieve" button    
    m_btnRetrieve.EnableWindow(FALSE);
    BeginWaitCursor();

    // Create a temporary file to hold the the .AVI file.  This demo
    // does not manage files.
    LPTSTR lpTempFileName = new TCHAR[MAX_PATH];
    LPTSTR lpPathName= new TCHAR[MAX_PATH];

    // Retrieve the path of the directory designated for temporary
    // files.
    ::GetTempPath(MAX_PATH,lpPathName);

    // Create a name for a temporary file.
    if (0 != GetTempFileName (lpPathName, _T("AVI"), 0,
           lpTempFileName))
    {
        // Swap out the .TMP extension with .AVI
        CString szFileName = lpTempFileName;
        szFileName = szFileName.Left ((szFileName.GetLength() - 3)) +
              _T("AVI");

        // Call the UOS function to do the file download
        CString szURL;
        m_URLString.GetWindowText (szURL);
        HRESULT hr = URLDownloadToFile (NULL, szURL, szFileName, 0,
              pURLDownloadToFileCallback);

        if ((hr == S_OK) && (pURLDownloadToFileCallback -> got_File ()
               == TRUE))
        {
            // Setup the Media Architects Video Play OCX Control
            // and play the .AVI file that was downloaded.
            m_VideoPlay.SetFilename (szFileName);
            m_VideoPlay.SetLoop (TRUE);

           // Play the entire video
           VARIANT varEntireVideo;
           varEntireVideo.vt = VT_ERROR;
           m_VideoPlay.Play (varEntireVideo, varEntireVideo);
        }
    }

    delete [] lpTempFileName;
    delete [] lpPathName;

    pURLDownloadToFileCallback -> Release();

    m_btnRetrieve.EnableWindow(TRUE);
    EndWaitCursor();
}

After the IBindStatusCallback object has been created, I create a temporary filename to hold the downloaded AVI file in the directory designated for temporary files. GetTempFileName creates the file with a TMP extension. I change the extension to AVI before calling URLDownloadToFile because the Media Architects Video Play OCX control will only play files with the AVI extension. I then call URLDownloadToFile passing NULL in the first argument (because I am not calling the function from an ActiveX component), the URL entered in the edit control, the temporary AVI file where I want the bits placed, and a pointer to my IBindStatusCallback object. The URLDownloadToFile function blocks until the download operation is completed. On a separate thread, the function will be calling my IBindStatusCallback::OnProgress member function to provide the download status.

When the URLDownloadToFile function completes, I check its return value and call the CBindStatusCallback member function got_File (which I created), to determine if the application actually received the file. If all goes well, I set a few properties and call some methods implemented by the Media Architects Video Play OCX control and the AVI file is displayed and playing in the UOS AVI Downloader application. In Figure 14, I downloaded the CUP.AVI file from http://www.microsoft.com/ie/avi. This cup is used in Microsoft's Volcano Coffee HTML demo page.

Conclusion

If you're writing ActiveX documents (see "The Visual Programmer Puts ActiveX Document Objects Through Their Paces" by Joshua Trupin, MSJ June 1996), you will get the integrated navigation user interface because IE 3.0 is capable of hosting ActiveX documents in-frame. Since your ActiveX documents are in-frame, you will get the in-frame hyperlinking navigation for free. If you are not writing ActiveX documents or you want to add hyperlinking navigation to your applications or OLE controls that you have developed today, the easiest way is to use the Simple Hyperlink Navigation APIs.

Just as it's important to navigate to documents, it's also important to access data as it moves to servers with Internet access. Using the URL Open Stream Functions you can retrieve data from these servers quickly and easily. You also have a large choice in the type of data retrieval model, data-push model, data-pull, and blocking model.

This article is reproduced from Microsoft Systems Journal. Copyright © 1995 by Miller Freeman, Inc. All rights are reserved. No part of this article may be reproduced in any fashion (except in brief quotations used in critical articles and reviews) without the prior consent of Miller Freeman.

To contact Miller Freeman regarding subscription information, call (800) 666-1084 in the U.S., or (303) 447-9330 in all other countries. For other inquiries, call (415) 358-9500.