This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


June 1998

Microsoft Systems Journal Homepage

How to Design Reusable HTTP Components by Exploiting WinInet and COM

Download Jun98WinInetCode.exe (257KB)

Aaron Skonnard works as a Project Technical Lead for Axiom Technologies, specializing in MFC-based business applications, COM and ActiveX technologies, and Internet application development. He can be reached at aarons@axtech.com. Portions of this article will appear in an upcoming book to be published by Addison-Wesley.

Every time I use the Windows® Internet API (WinInet) in my applications, it seems like I'm writing the same lines of code over and over again. And, every time I write a new WinInet-based function, it looks just like the last one I wrote with a few minor modifications. After a thorough investigation, I've decided that this is mostly due to the general nature of Internet application development. Typically, all transactions that use a given Internet protocol are going to follow the same high-level logic. Of course, certain aspects of an Internet transaction will always need to be customized for a given task.
      For example, suppose you want to create an application that uses HTTP to communicate via TCP/IP with different servers across the Internet. The natural approach would be to write WinInet-based Win32® functions to handle data transmission. Most overhead-conscious developers would look for ways to make the functions reusable and generic. Object-oriented developers might even develop their own C++ classes to encapsulate the desired transmission functionality. Regardless of the transmission implementation, every task is going to require different HTTP methods, headers, input data formats, host names, port numbers, and parsing routines to handle the various HTTP response formats. To add another layer of complexity, some requests may require Secure Sockets Layer (SSL) and some form of authentication, while other requests may simply transmit clear text.
      While you can find ways to solve this design problem using standard Win32 functions or C++ classes, isn't there a better approach that will allow more reusable and extensible WinInet code? If you haven't already guessed, the solution I'm hinting at is the COM architecture. Whether you're in the business of selling shrinkwrapped products, client-customized applications, or software components, finding better ways to write reusable code will make everyone around you (including yourself) happier.
      Most developers find that simple, concise examples help put concepts into practice. The COM-based approach to developing WinInet code presented here will help you get one step closer to using COM in your everyday work. As you'll see, COM is the perfect solution for writing reusable WinInet components that are easily extensible and reusable. Breaking the functionality described in the HTTP scenario into multiple COM objects allows you to reuse the more generic components and rewrite the task-specific components on a case-per-case basis. This approach allows you to leverage well-written and tested components while minimizing the amount of code required for a new WinInet-related task. Furthermore, the COM approach offers dynamic application extensibility by simply adding new components to the user's system at runtime.
      In this article, I'll present a case study that illustrates how to write reusable WinInet HTTP COM components. First, I'll give you a brief WinInet primer and describe how you can begin using WinInet in your applications. Then, I'll cover the typical client/server application model and dive into HTTP details. With that foundation in place, I'll show you how to implement two ATL-based COM components: IHttpRequest and IQuoteProvider. Finally, I'll show you how to tie it all together in a sample application called Stock Watcher. Stock Watcher downloads stock quotes from different Internet stock quote providers and displays the M to the user. If you get nothing else from this article, you'll at least end up with a nifty application for monitoring your portfolio.

WinInet Primer
      WinInet is a high-level interface to the TCP/IP-based HTTP, FTP, and Gopher Internet protocols. WinInet gives you the ability to add HTTP, FTP, or Gopher functionality to your application without having to understand or keep up with the changing protocol standards. This transparency is one of the key benefits of WinInet. As standards evolve, WinInet will also evolve while keeping your interface to the protocol the same. Without a high-level interface like Winsock or WinInet, you would have to write code to handle the responsibilities of all the TCP/IP protocol layers. Figure 1 illustrates how the TCP/IP Internet protocols are layered. Each layer performs specific tasks and plays an important role in the Overall functionality of TCP/IP.

Figure 1  TCP/IP Protocol Layers
Figure 1  TCP/IP Protocol Layers


      The application protocol layer consists of protocols like HTTP, FTP, and Gopher, but includes many others like POP, SMTP, NNTP, Telnet, and Finger. the Se are the protocols you'll deal with most often in your applications. The tools you'll use most often for developing at the application protocol layer are Winsock and WinInet. While Winsock requires you to write code for the application-level protocols, WinInet encapsulates and hides many of the application-level protocol details.
      The application protocol is built on top of the transport protocol. TCP and UDP are the most common transport layer protocols. TCP is a reliable, connection-oriented protocol; UDP is an unreliable, connectionless protocol. With TCP, there is a one-to-one connection between machines capable of full-duplex communication. UDP, on the Other hand, is capable of broadcasting messages to multiple hosts. Unlike TCP, UDP sends packets out, but doesn't care if they ever get there. While UDP has its advantages (especially in games like Quake), TCP is more commonly used by application-level protocols.
      The next layer is the Internet Protocol (IP). IP only performs one basic task: it finds a path of communication. For example, when you send email to a buddy, IP finds a way for the data to get there. IP isn't concerned with transmission errors; error control is the transport protocol's responsibility. Like UDP, IP is a connectionless protocol; it simply defines how the packet will reach its destination. While on smaller networks this may seem trivial, on the Internet it's much more complex considering the number of hosts a given packet may route through.
      The lowest layer consists of physical protocols such as Ethernet. This layer handles the protocols for dealing with physical media. Since the Se protocols are medium-specific, there is no way to standardize at this level. Understanding that there are specific protocols at this level is more than sufficient. With the high-level tools available today, you'll rarely find the need to work at the physical layer.

Figure 2  HTTP Client/Server Model
Figure 2  HTTP Client/Server Model


      Figure 2 illustrates how this protocol layer model works in the case of HTTP. The user initiates an HTTP request, which is processed by the underlying HTTP interpreter on the client machine. All data, including the request, is sent to the server via TCP/IP. After the server's HTTP interpreter processes the request, it sends a response, which must be processed by the client's HTTP interpreter. I'll be talking about the part of this process that involves the client-side HTTP interpreter.
      As you can see, Internet application development would be quite a complex task without the higher-level interfaces. But how do you know whether to use WinInet or Winsock? If you're planning to use HTTP, FTP, or Gopher in your applications, WinInet is definitely the best choice. If you need to access any of the Other application-level Internet protocols, Winsock is your only choice.

Getting Started With WinInet
      The general WinInet functionality is contained in WinInet.dll. If you've installed Microsoft® Internet Explorer 3.0 or greater, you should be able to find it in your Windows system directory—Internet Explorer is built on top of WinInet. With Internet Explorer part of the Windows operating system, WinInet automatically follows suit.
      To start using WinInet in your applications, you only need to do two things. First, include WinInet.h anywhere that you plan to call a WinInet function. Second, link against WinInet.lib. Assuming that you're using Microsoft Visual C++® 5.0, you can find WinInet.h in the DevStudio\VC\include directory and WinInet.lib in the DevStudio\VC\lib directory. If you're using another development environment or a previous version of Microsoft Visual C++, you'll need to download and install the Microsoft Internet Client SDK (also known as the Internet Explorer 4.0 Author's Toolkit).
  Figure 3  MFC WinInet Classes
  Figure 3  MFC WinInet Classes

      There are two main sources of WinInet documentation. You can find complete WinInet API documentation integrated with the Visual C++ help files (version 4.2 or greater). The most complete and up-to-date sample code and reference material is provided in the Internet Client SDK online documentation.
      WinInet was originally developed for programmers using Win32, so it closely resembles the standard Win32 API. All Internet APIs use a new Internet handle type called HINTERNET. You can find a complete HINTERNET handle hierarchy in the Internet Client SDK documentation.
      Microsoft has also provided a set of MFC class wrappers that encapsulate the WinInet API functionality (see Figure 3). The WinInet MFC classes provide default parameters, exception handling, and buffered I/O. Furthermore, if you're an MFC developer, they'll fit into your overall application design. If you plan on using the MFC classes, you'll need to include afxinet.h instead of WinInet.h.

The HTTP Protocol
      HTTP is probably the most widely used application-level Internet protocol. When you navigate to a Web site, the browser uses HTTP to communicate with the Web server and download all the files (text, images, sounds, ActiveX® controls, and so on) associated with the particular URL. While HTTP is mostly used to transmit HTML files over the Internet, it can be used to transmit any data format.
      HTTP defines the format of a client request and a server response. A basic HTTP transaction consists of the following four steps:

  1. Establish a connection (TCP/IP).
  2. Client sends a request to the server.
  3. Server sends a response to the client.
  4. The connection is closed (TCP/IP).

      Most HTTP servers listen for requests on the well-known HTTP port 80. While you'll normally use port 80 to communicate via HTTP, other ports can be specified to establish the connection.
      Unlike the binary TCP and IP protocols, the HTTP request and response protocols are ASCII-based and much easier to work with. For example, an HTTP request consists of a method, a URI (Uniform Resource Identifier), the protocol version, a list of headers, a blank line, and an entity body (meaning data):

 Method URI ProtocolVersion 
 Headers 
 CRLF (blank line)
 Entity-Body
      The CRLF (a carriage-return line-feed sequence) is very important in the request format. The CRLF tells the HTTP server where the entity body begins. Leaving out the CRLF character will cause big headaches. Here is an example of a typical HTTP request:

 POST /cgi-bin/basketball-scores HTTP/1.0
 Accept: text/plain, text/html
 Accept: image/gif, image/jpeg
 Accept-Encoding: x-compress; x-zip
 Accept-Language: en
 User-Agent: MSIE/4.0 
 If-Modified-Since: Mon, 10 Jan 1998
    13:22:34 GMT
 Pragma: no-cache
 Content-Type: text/plain
 Content-Length: 43

 Team1=Utah Jazz&Team2=Portland Trailblazers
      The HTTP response format is much like the request format. Everything but the first line of the response header is exactly the same. Here is the format of an HTTP response:

 HTTP-Version Status-Code Reason-Phrase
 Headers
 CRLF (blank line)
 Entity-Body
The first line in the response format is referred to as the response status line. This line contains important information about the status of the request. The entity body contains the data returned by the server. Here is an HTTP response returned by the previous request:

 HTTP/1.0 200 OK
 Server: Microsoft-IIS/2.0
 Date: Mon, 3 Jan 1998 13:22:34 GMT
 Content-Type: text/html
 Last-Modified: Mon, 10 Jan 1998 13:22:34 GMT
 Content-Length: 179
     
 <html>
 <head><title>Basketball Scores</title></head>
 <body>
 Utah Jazz vs. Chicago Bulls: 101-99<br>
 Portland Trailblazers vs. Phoenix Suns: 113-88<br>
 <br>
 <b>GO JAZZ!</b>
 </body>
 </html>
      I've told you more than you possibly need to know about HTTP. WinInet lets you forget about most of the HTTP implementation details so you can focus on the Overall design and functionality of your app. Plus, as I walk through the development of some the WinInet-based COM components in the sample application, you'll see how easy it is to reuse and leverage well-written WinInet code.

The Stock Watcher App
      Stock Watcher illustrates many interesting WinInet concepts. In particular, Stock Watcher uses the WinInet MFC classes CInternetSession, CHttpConnection, and CHttpFile to implement the HTTP transaction functionality. It also shows how to derive your own class from CInternetSession to override the CInternetSession::OnStatusCallback method.
      In addition to the WinInet concepts, Stock Watcher also demonstrates the generation of powerful and reusable COM components using ATL. The Stock Watcher components implement the COM interfaces IUnknown, IConnectionPointContainer, and IConnectionPoint, and the application-specific interfaces IQuoteProvider and IHttpRequest. Stock Watcher also illustrates using COM component categories.
      The Stock Watcher application is quite simple. It's an MFC-based SDI application with a single view. The view consists of a single CListCtrl that displays the user-defined stock portfolio (see Figure 4). This sample allows the user to add stock symbols to their portfolio, save their portfolio to a file, and refresh their portfolio data by connecting to an Internet stock quote provider and downloading the current stock information (price, change, open, volume, and so on).

Figure 4  Stock Watcher Application
Figure 4 Stock Watcher Application

      The Stock Watcher code and executable are available from the link at the top of this article. I wrote Stock Watcher using Microsoft Visual C++ 5.0 and some of the new common control features provided by the Internet Explorer 3.x version of the Windows common control library. If you compile the workspace, all the COM components should be registered for you properly. If you want to run the executable without compiling the project, run the SW.BAT file first to register the components.
      If you open Stock Watcher.dsw in Developer Studio®, you should see the following three projects: Stock Watcher, HttpObjectServer, and QuoteProviders. Stock Watcher is an MFC AppWizard-generated application that implements the user interface. The HttpObjectServer and QuoteProviders projects are both ATL COM AppWizard-generated projects that service the IHttpRequest and IQuoteProvider interfaces.
       Figure 5 provides a high-level view of the Stock Watcher design model. When the user initiates an HTTP request, the MFC application creates instances of the CHttpRequest and CQuoteProvider components. The application establishes an event sink between the CQuoteProvider component and its own CQuoteProviderEventSink component. At this point, the application passes the IQuoteProvider interface pointer to the CHttpRequest component, which subsequently interfaces with it directly. As the CHttpRequest component needs quote-provider specific information for the request, it simply queries the IQuoteProvider interface. Finally, when CQuote Provider is finished parsing the HTTP result, it fires an event notifying the application.
Figure 5  Stock Watcher Design Model
Figure 5 Stock Watcher Design Model

      As far as memory management is concerned, the MFC application only needs to track the IHttpRequest interface pointers. After the application passes the IQuoteProvider interface pointer to the CHttpRequest component, the CHttpRequest component calls IUnknown::AddRef, which is followed by the MFC application calling IUnknown::Release. Thus, the CQuoteProvider component will remain in memory until the CHttpRequest destructor releases the IQuoteProvider interface.
      Now let's look at each of the interfaces and components in more detail.

The Quote Provider Component
      First, let's examine the QuoteProviders project and components. To begin, I created a new ATL COM AppWizard project called QuoteProviders and added it to the blank Stock Watcher workspace. The ATL COM AppWizard currently consists of only one step that allows you to specify the COM server type, whether you want to allow merging of proxy/stub code, and whether you want to support MFC. To keep things simple, I made QuoteProviders an in-process COM server (DLL) and added support for MFC.
      Once you press Finish, the wizard generates the files for an ATL project without any initial objects. The wizard generates the main DLL entry points needed for a COM server, including DllCanUnloadNow, DllGetClassObject, DllRegisterServer, and DllUnregisterServer. the Se functions are implemented in QuoteProviders.cpp and are exported in QuoteProviders.def. The wizard also generates QuoteProviders.idl and QuoteProviders.rgs. The .idl file contains the interface and component descriptions, while the .rgs file contains information about how the component will be set up in the registry. I'll look at the Se files a little closer once they contain more interesting interface information. After creating the ATL project, you add new interfaces and components through the ATL Object Wizard.

To MFC or Not to MFC
      Complicated module state issues arise when using MFC in both a COM server and its client application. the Se issues result from being able to dynamically link the MFC DLL to both regular DLLs and an application executable that loads the DLL. (COM in-process servers, by the way, are implemented as regular DLLs.)
      The problem with this configuration is how MFC manages its global data. MFC global data consists of the current CWinApp and CWinThread pointers, the resource handle, and the temporary and permanent window maps. (The window maps correlate the MFC window classes, like CWnd, with their corresponding HWNDs.) Each process using a Win32 DLL gets its own copy of the DLL's data. The problem is with the AFXDLL model, which assumes that there is only one CWinApp object and one set of handle maps in the process. Based on that assumption, the Se items could be tracked in the MFC DLL itself.
      However, now it's possible to have more than one CWinApp object in a process. The application executable owns a CWinApp object, and any dynamically linked regular DLL also owns a CWinApp object. Thus, a call to AfxGetApp in the DLL should return a pointer to the DLL's CWinApp object instead of the CWinApp object belonging to the executable. To maintain the correct MFC module state, a mechanism is required to switch the module state as module boundaries are crossed.
      The AFX_MANAGE_STATE macro implements this module state switching. You need to make sure that all DLL entry points explicitly set the module state by calling AFX_MANAGE_STATE. Message handlers, however, are taken care of for you automatically by MFC. Since I specified in the QuoteProviders project that I want to support MFC, every wizard-generated entry point is going to contain the following line of code automatically:


 AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
After the AFX_MANAGE_STATE macro goes out of scope, the previous module state will be reset. While the ATL wizards take care of most of the details, you must manage this manually for any entry points not generated by ATL. This is especially true for connection points and their corresponding event sink handlers. If you think this gyration is more than you care to deal with, you can simply avoid using MFC in your COM server.

The CProviderYahoo Component
      Since both the IHttpRequest interface and the MFC application depend on the IQuoteProvider interface, it makes sense to start there. I let the ATL Object Wizard take care of most of the implementation details for me. Then, I'll manually tweak the interface and code to make it fit my exact needs.
      The first step is to initiate the ATL Object Wizard by selecting Insert | New ATL Object. The ATL Object Wizard offers many different types of ATL components (Objects, Controls, Miscellaneous, and Data Access). Plus, for each component type, it offers many different functional choices. For this project, the Simple Object will do just fine.
      The wizard's only step allows you to specify the names for the C++ class and the COM interface it implements. If you're following along at home, I chose a C++ class called CProviderYahoo that implements the IProviderYahoo interface. In this example, CProviderYahoo will provide information for connecting to the Yahoo! quote server and parsing the files returned by the quote request. Notice that I also changed the interface type from dual to custom. As a general rule of thumb, if you ever plan on using a component from a scripting language, you should stick with a dual interface. If you know that the component will only be used in the C++ arena, custom interfaces are much more flexible. I also checked the Support Connection Points option. This just adds a few extra lines of code to your class definition file, which I'll look at shortly. After pressing OK, the wizard performs its magic to generate the files and code required by this new component.
      At this point, the CProviderYahoo class and the IProviderYahoo interface should show up in the Developer Studio class view. First, take a look at the wizard-generated IProviderYahoo interface defined in the QuoteProviders.idl (see Figure 6). If you're unfamiliar with the interface descriptor language, refer to the Visual C++ online documentation.
      As I add methods to interfaces and even new interfaces, the ATL wizards will continue to modify this file. Each time you compile the project, Visual C++ uses the MIDL compiler to process QuoteProviders.idl. The MIDL compiler generates the type library (QuoteProviders.tlb) and various other helper files that will be used by this and other projects. Figure 7 describes the MIDL-generated files in more detail.
      Now, take a look at the CProviderYahoo definition file (see Figure 8). CProviderYahoo inherits from some of the standard ATL base template classes, such as CComObjectRootEx and CComCoClass. the Se templates take care of the class factory and IUnknown implementations for CProviderYahoo. Since I chose to support connection points in the ATL Object Wizard, CProviderYahoo also inherits from IConnectionPointContainerImpl. (I'll show you the rest of the connection point code when I implement the event interface.) Finally, CProviderYahoo also inherits from the IProviderYahoo interface that you saw earlier. As with the IDL file, when I add methods to interfaces and even new interfaces, the ATL wizards will continue to modify this file.

Adding Interface Methods
      At this point, I'm ready to start adding methods to the IProviderYahoo interface. I began by right clicking on IProviderYahoo in class view and selecting Add Method. The dialog in Figure 9 will be displayed. Adding a method to the interface in this manner will add the method definition to QuoteProviders.idl and a CProviderYahoo method implementation stub to ProviderYahoo.cpp.

Figure 9 Add Method Dialog
Figure 9 Add Method Dialog

      Before wildly adding methods to the IProviderYahoo interface, let's take a step back and look once again at the purpose of this module. The CHttpRequest component is going to use the quote provider components to get information about a given quote server and to parse the data returned from the same quote server. Obviously, every quote server is going to have different host names and potentially different HTTP port numbers. Furthermore, different quote servers will have different server-side script names and will require different input parameters. Additionally, since I don't control the server-side scripts, I must take special care in parsing the results from the different stock quote providers.
      I want to make the IProviderYahoo component intelligent enough to tell the IHttpRequest component everything it needs to know about connecting to the Yahoo! server and parsing the data it returns. Figure 10 describes the methods that are part of the IProviderYahoo interface. Once again, refer to ProviderYahoo.cpp for more details on how the Se methods are implemented.

Didn't I Mean IQuoteProvider?
      If you look back at Figure 5, you might be wondering why the IProviderYahoo interface isn't anywhere in the application design. Furthermore, the design model shows that the CProviderYahoo component implements the IQuoteProvider interface. So what's going on? Did I mean to implement the IQuoteProvider interface?
      Yes. The goal of this application design is to allow runtime extensibility by simply plugging in additional quote provider components that implement the same interface. It would make more sense to have the standard quote provider interface called something like IQuoteProvider instead of IProviderYahoo.
      However, since the ATL wizards kindly generate IDL code, it makes sense to implement the first component using the wizards and then generalize the resulting IDL interface description. (Trust me—this approach beats writing the IDL code by hand.) In other words, the IProviderYahoo interface found in QuoteProviders.idl needs to be renamed to IQuoteProvider. You can do this by simply replacing all occurrences of IProviderYahoo with IQuoteProvider in the QuoteProviders.idl file. The next time you compile the project, QuoteProviders.h will contain the new IQuoteProvider interface declarations.

Connection Points
      The ProviderYahoo component needs a mechanism for notifying its clients when it finishes parsing the HTTP response data. COM provides the IConnectionPoint and IConnectionPointContainer interfaces for supporting events. If you've ever tried implementing a connection point by hand, you'll appreciate the shortcuts offered by ATL.
      Remember, when I created the ProviderYahoo component, I selected the Support Connection Points option. This option adds a few extra lines of code to the ATL object class declaration. For example, if you take a look at ProviderYahoo.h (see Figure 8), you'll notice that CProviderYahoo inherits from (among other things) IConnectionPointContainerImpl.


 CProviderYahoo : public  
     IConnectionPointContainerImpl<CProviderYahoo>,
Additionally, the wizard adds the COM_INTERFACE_ ENTRY_IMPL for IConnectionPointContainer to the CProviderYahoo COM map.

 BEGIN_COM_MAP(CProviderYahoo)
     COM_INTERFACE_ENTRY(IQuoteProvider)
     COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
 END_COM_MAP()
And finally, the wizard adds a connection point map without any initial entries.

 BEGIN_CONNECTION_POINT_MAP(
    CProviderYahoo)
 END_CONNECTION_POINT_MAP()
This wizard-generated code fully implements the IConnectionPointContainer interface. Now, I simply need to implement my own connection point interface.

IQuoteProviderEvent
      The connection point that I implemented for CProviderYahoo is called IQuoteProviderEvent. IQuoteProviderEvent consists of one method called UpdateSymbol, which passes the information gathered from the Internet back to the client. To implement a connection point using ATL, the first step is to describe the interface in QuoteProviders.idl. To do so, you'll need to generate a new GUID using guidgen.exe (found in the \Devstudio\VC\bin directory). Then you can add the new interface to the interface section of the IDL file. Here is what IQuoteProviderEvent looks like:


 [
 uuid(BFD86BC0-A004-11d1-9912-004033D06B6E),
 helpstring("IQuoteProviderEvent Interface"),
 pointer_default(unique)
 ]
 interface IQuoteProviderEvent : IUnknown 
 {
 import "oaidl.idl";
 HRESULT UpdateSymbol(LPCTSTR lpszSymbol,
                      LPCTSTR lpszPrice,
                      LPCTSTR lpszChange,
                      LPCTSTR lpszOpen,
                      LPCTSTR lpszVolume);
 };
      At this point, you must recompile the QuoteProviders project to generate a new type library containing the IQuoteProviderEvent interface information. Once you've generated a new type library, you're ready to use the ATL Proxy Generator component.
      You can access the ATL Proxy Generator through the Components and Controls Gallery (Project | Add To Project | Components and Controls). After double clicking on the Developer Studio Components directory, select the ATL Proxy Generator component and press Insert. After confirming, the ATL Proxy Generator dialog appears. Next, enter the path to the QuoteProvider type library (QuoteProvider.tlb); both the IQuoteProvider and IQuoteProviderEvent interfaces should appear on the left. Finally, select the IQuoteProviderEvent interface and make sure the Proxy Type is set to Connection Point before pressing Insert.
      The ATL Proxy Generator creates a new file named CPQuoteProviders.h (see Figure 8). This contains the declaration of CProxyIQuoteProviderEvent, which implements the IConnectionPoint interface and is capable of calling UpdateSymbol on all clients of this connection point through its Fire_UpdateSymbol method.
      To add this functionality to the CProviderYahoo component, CProviderYahoo also needs to inherit from the CProxyIQuoteProviderEvent template, so I added the following line of code to the list of CProviderYahoo base classes:

 public CProxyIQuoteProviderEvent<CProviderYahoo>
Finally, I added a connection point entry for the IQuoteProviderEvent interface to the CProviderYahoo connection point map:

 BEGIN_CONNECTION_POINT_MAP(CProviderYahoo)
     CONNECTION_POINT_ENTRY(IID_IQuoteProviderEvent)
 END_CONNECTION_POINT_MAP()
      Figure 8 illustrates what the CProviderYahoo class definition looks like with connection point support and the methods I added above. This approach to adding connection point functionality to your ATL COM components greatly reduces the amount of code that you must write.

The Quote Provider Component Category
      The goal of this design is to allow additional IQuoteProvider components to be added at runtime. The Stock Watcher application needs to detect the Se new components and utilize the M. This whole concept is similar to the flexible snap-in architecture introduced by Steve Zimmerman in the July 1997 issue of Microsoft Systems Journal.
      A simple way for the Stock Watcher application to detect new components is through COM component categories. A component category is simply a registry convention that tells you if a component implements certain interfaces required by the component category.
      To take advantage of component categories, you need to use the Component Category Manager system component. The Component Category Manager implements two interfaces: ICatRegister and ICatInformation. As their names suggest, the Se interfaces are used for registering component categories, registering components with component categories, and retrieving information on a component category.
      To create the Quote Provider component category, I generated a new GUID and called it CATID_QuoteProviders:


 // {32014981-9CD4-11d1-9912-004033D06B6E}
 DEFINE_GUID(CATID_QuoteProviders, 
             0x32014981, 0x9cd4, 0x11d1, 0x99, 
             0x12, 0x0, 0x40, 0x33, 0xd0, 0x6b, 0x6e);
Then, in QuoteProviders.cpp, I added some component category helper functions to make it easier to create the component category and register/unregister components with the category (see Figure 8). To register the QuoteProvider component category and the CProviderYahoo component, I added the Se lines of code to DllRegisterServer:

 hr = CreateComponentCategory(
   CATID_QuoteProviders, 
   L"Stock Watcher Quote Providers"); 
 if (FAILED(hr))
   return hr;
 hr =  	  RegisterCLSIDInCategory(CLSID_ProviderYahoo, 
                           CATID_QuoteProviders);
 if (FAILED(hr))        
   return hr;
To unregister the CProviderYahoo component, I added the following lines to DllUnregisterServer:

 hr=UnRegisterCLSIDInCategory(CLSID_ProviderYahoo, 
                              CATID_QuoteProviders);
 if (FAILED(hr))        
     return hr;
      The Stock Watcher application will use the Component Category Manager to query the CATID_QuoteProviders component category for all available Quote Provider components. After Stock Watcher has been installed, additional quote provider components may be added to the user's system by simply registering the M with the CATID_QuoteProviders component category.

The HTTP Component
      The HTTP component is only responsible for the HTTP transaction functionality. The HTTP transaction functionality consists primarily of sending and receiving data. To make the HTTP component abstract and reusable, the underlying HTTP request intelligence is built into the IQuoteProvider interface.
      I created a separate project for the HTTP component called HttpObjectServer. It contains a single COM server for the CHttpRequest component. The CHttpRequest component implements the IHttpRequest interface. I created the ATL project and objects in the same manner as described for the QuoteProviders project.
      The IHttpRequest interface is defined in HttpObjectServer.idl and the class definition is found in HttpRequest.h (see Figure 11). IHttpRequest consists of only the two methods GetProviderInterface and ProcessRequest. GetProviderInterface returns the IQuoteProvider interface currently being used by the component. ProcessRequest encapsulates the entire HTTP transaction process. The following is the implementation of the ProcessRequest.


 STDMETHODIMP CHttpRequest::ProcessRequest(
                            IUnknown* pQuoteProvider, 
                             long lMainHwnd)
 {
     AFX_MANAGE_STATE(AfxGetStaticModuleState())
     m_pQuoteProvider =
       static_cast<IQuoteProvider*>(pQuoteProvider);
     m_pQuoteProvider->AddRef();
     m_hwndMain = reinterpret_cast<HWND>(lMainHwnd);
     AfxBeginThread(HttpWorkerThread, this);
     return S_OK;
 }
      After storing the IQuoteProvider pointer in m_pQuoteProvider, ProcessRequest must call IUnknown::AddRef on the interface pointer. ProcessRequest launches a worker thread, HttpWorkerThread, that takes care of the WinInet business from here. Since MFC objects are not thread safe, calls that manipulate the main application's MFC objects must be synchronized through window messages.

HttpWorkerThread
      The first step in the worker thread is to create the CInternetSession object. To override the OnStatusCallback method, I declared an instance of CMyInternetSession, which is derived from CInternetSession (see Figure 11). Then you must call CInternetSession::EnableStatusCall-back to enable the status callback method. WinInet calls the OnStatusCallback method as the status of CInternetSession changes.
      Without going into details, HttpWorkerThread calls the following WinInet methods:


     CInternetSession::GetHttpConnection 
         // creates a CHttpConnection
     CHttpConnection::OpenRequest // creates a CHttpFile
     CHttpFile::SendRequest // transmits data
     CHttpFile::Read // reads the HTTP response
The HttpWorkerThread gathers all of the information it needs to make the Se WinInet calls by calling the appropriate IQuoteProvider methods. For example, CInternetSession::GetHttpConnection requires a host name and port number. Thus, before calling this method, the HttpWorkerThread calls IQuoteProvider::GetHost and IQuoteProvider:: GetPort to retrieve the provider-specific information. Furthermore, after HttpWorkerThread finishes receiving the HTTP response, it allows each Quote Provider to handle its responses differently by calling IQuoteProvider::ParseResult.
      The CHttpRequest object is generic enough to support all types of Internet stock quote providers. As additional IQuoteProvider components are implemented and released, the CHttpRequest component will be able to transmit data to and from the new providers without modifying a single line of code.
      

Stock Watcher Implementation Details
      While there isn't enough space to cover the details of the Stock Watcher application, there are a few aspects that I want to discuss briefly, including the CQuoteProviderDlg, CQuoteProviderEventSink, and Refresh All functionality.
       Figure 12 illustrates the use of CQuoteProviderDlg. Currently, I've only implemented and registered a single stock quote provider (CProviderYahoo). CQuoteProviderDlg, however, will enumerate all of the components that belong to the CATID_QuoteProviders component category and allow the user to select one of the M in the quote provider combobox.

Figure 12 Quote Providers
Figure 12 Quote Providers

      After the user selects a stock provider and presses OK, CQuoteProviderDlg saves the CLSID of the component to an application-specific registry key named ProviderCLSID. From that point on, the application will use the selected quote provider component.
      For Stock Watcher to receive events fired by the IQuoteProvider components, it must implement a sink object and establish a connection with the IQuoteProvider connection point. I called the sink object used by the application CQuoteProviderEventSink. The following is the CQuoteProviderEventSink class declaration:

 class CQuoteProviderEventSink : 
     public CComObjectRoot,
     public IQuoteProviderEvent
 {
 public:
     CStockWatcherDoc* m_pDoc;
     CQuoteProviderEventSink() {}
 
 BEGIN_COM_MAP(CQuoteProviderEventSink)
     COM_INTERFACE_ENTRY(IQuoteProviderEvent)
 END_COM_MAP()
 
 // IHttpEvent Methods
     STDMETHOD(UpdateSymbol)(LPCTSTR lpszSymbol, 
                             LPCTSTR lpszPrice, 
                             LPCTSTR lpszChange,        
                             LPCTSTR lpszOpen, 
                             LPCTSTR lpszVolume);
 };
      CStockWatcherDoc creates an instance of this object in its constructor (see Figure 13). Then, each time it creates an IHttpRequest component, it calls AtlAdvise to establish the connection point with IQuoteProvider. When CProviderYahoo::ParseResult calls Fire_UpdateSymbol, the CQuoteProviderEventSink::UpdateSymbol method will be called.
      The Stock Watcher application allows the user to decide when to update the stock quote information by choosing Refresh All on the Symbol menu. The CStockWatcherDoc:: OnRefreshAllSymbols handler creates an instance of the currently selected quote provider component along with an IHttpRequest component. Then it passes the stock symbol information to the quote provider component using the IQuoteProvider::InitializeData method. Finally, after establishing the connection point with IQuoteProvider, it calls IHttpRequest::ProcessRequest (see Figure 13), which encapsulates the entire HTTP transaction functionality.

Conclusion
      I've shown you how to use COM to write reusable WinInet components. While WinInet is already a high-level interface that encapsulates a great deal of Internet protocol code, why not make your code even more reusable through COM? Furthermore, with the easy-to-use wizards and templates provided by ATL, it makes sense to leverage this powerful architecture.
      The Stock Watcher application illustrates how a simple IHttpRequest interface can encapsulate the entire HTTP transaction functionality. Furthermore, it demonstrates how new quote provider components (IQuoteProvider) can be added to the user's system at runtime.
      I discussed only one quote provider component, CProviderYahoo, which uses the Yahoo! Finance stock quote server. The sample application includes two other quote provider components: CProviderFastQuote and CProviderDatek, which interface with the Quote.com and Datek stock quote servers (see Figure 12). The Datek stock quote server even requires user authentication. the Se two components show how easy it is to implement additional quote provider components. You might want to try implementing your own custom quote provider component and registering it with the component category.
      Feel free to reuse the components provided with the Stock Watcher application. While the name IQuoteProvider is appropriate for this application, I probably should have used a more generic name like IHttpRequestIntelligence or IHttpRequestInformation to illustrate that this interface can be used for all types of HTTP requests.
      Hopefully, I've helped you appreciate the power of COM/ATL and how well it fits into the WinInet design model. Since everything tied to the Internet changes so quickly, it makes even more sense to use the extensible COM architecture. As things change, COM allows you to simply update the affected component instead of the entire application. Incidentally, if either of the quote providers in the sample application happen to change their HTML formats before this article is published, you may have to tweak their corresponding ParseResult routines. Enjoy!

From the June 1998 issue of Microsoft Systems Journal.