Chapter 27

Internet Programming with the WinInet Classes


CONTENTS

Chapter 13, "Sockets, MAPI, and the Internet," introduced the WinInet classes that you can use to build Internet client applications at a fairly high level. This chapter develops an Internet application that demonstrates a number of these classes. The application also serves a useful function: You can use it to learn more about the Internet presence of a company or organization. You don't need to learn about sockets or handle the details of Internet protocols to do this.

Designing the Internet Query Application

Imagine that you have someone's e-mail address ([email protected], for example) and you'd like to know more about the domain (gregcons.com in this example). Or perhaps you have a great idea for a domain name and want to know if it's taken already. This application, Query, will try connecting to gregcons.com (or greatidea.org, or any other domain name that you specify) in a variety of different ways and will report the result of those attempts to you.

This application will have a simple user interface. The only piece of information that the user will supply is the domain name that is being queried, and there is no need to keep this information in a document. You might want a menu item called _Query that brings up a dialog box in which to specify the name of the site, but a better approach is to use a dialog-based application and incorporate a Query button into the dialog box.

Dialog-based applications, as discussed in the "A Dialog-Based Application" section of Chapter 16, "Choosing an Application Type and Building an Empty Shell," have no document and no menu. The application displays a dialog box at all times; closing the dialog box closes the application. You build the dialog box for this application like any other, with Developer Studio.

To build the shell of this application, choose File, New from within Developer Studio and then choose Project Workspace from the list to bring up AppWizard. Name the application Query, and in Step 1 choose a dialog-based application, as shown in Figure 27.1. Click Next to move to Step 2 of AppWizard.

Figure 27.1 : Choose a dialog-based application for Query.

In Step 2 of AppWizard, request an About box, no context-sensitive help, 3-D controls, no OLE support, and no sockets support. (This application won't be calling socket functions directly.) Give the application a sensible title for the dialog box, and the AppWizard choices should be as summarized in Figure 27.2. Click Next to move to Step 3 of AppWizard.

Figure 27.2 : This application does not need help, OLE, or sockets.

The rest of the AppWizard process should be familiar by now: You want comments, want to link to the MFC libraries as a shared DLL, and don't need to change any of the class names suggested by AppWizard. When the AppWizard process is complete, you are ready to build the heart of the Query application.

Building the Query Dialog Box

AppWizard produces an empty dialog box for you to start with, as shown in Figure 27.3. The following steps will transform this dialog box into the interface for the Query application.

Figure 27.3 : AppWizard generates an empty dialog box for you.

TIP
If working with dialog boxes is still new to you, be sure to read Chapter 17, "Building Menus and Dialogs," especially the "Building the ShowString Dialogs" and "Making the Dialog Work" sections.

  1. Change the caption on the OK button to Query.
  2. Change the caption on the Cancel button to Close.
  3. Delete the TODO static text.
  4. Grab a sizing handle on the right edge of the dialog box and stretch it so that the dialog box is 300 pixels wide, or more.
  5. Move the buttons to the right.
  6. At the top of the dialog box, add an edit box with the resource ID IDC_HOST. Stretch the edit box as wide as possible.
  7. Add a static label next to the dialog box. Set the text to Site name.
  8. Grab a sizing handle along the bottom of the dialog box and stretch it longer, so that the dialog box is 150 pixels high, or more.
  9. Add another edit box and resize it to fill as much as possible of the bottom part of the dialog box.
  10. Give this edit box the resource ID IDC_OUT.
  11. Click the Styles tab on the Properties box and select the Multi-line, Horizontal scroll, Vertical scroll, Border, and Read-only check boxes.

The finished dialog box, and the Style properties of the large edit box, should resemble Figure 27.4.

Figure 27.4 : Build the Query user interface as a single dialog box.

When the user clicks the Query button, this application should somehow query the site. The last step in the building of the interface is to connect the Query button to code with ClassWizard. Follow these steps:

  1. Choose View, Class Wizard to bring up ClassWizard.
  2. There are three possible classes that could catch the command generated by the button click, but CQueryDlg is the logical choice, because the host name will be known by that class. Make sure that CQueryDlg is the class selected in the Class name drop-down list box.
  3. Highlight ID_OK (you did not change the resource ID of the OK button when you changed the caption) in the left list box and BN_CLICKED in the right list box.
  4. Click Add Function to add a function that will be called when the Query button is clicked.
  5. ClassWizard suggests the name OnOK; change it to OnQuery, as shown in Figure 27.5, and then click OK.
    Figure 27.5 : Add a function to handle a click on the Query button, whose ID is still IDOK.

  6. Click the Member Variables tab to connect the edit controls on the dialog box to member variables of the dialog class.
  7. Highlight IDC_HOST and click Add Variable. As shown in Figure 27.6, you will connect this control to a CString member variable of the dialog class called m_host.
    Figure 27.6 : Connect IDC_HOST to CQueryDlg::m_host.

  8. Connect IDC_OUT to m_out, also a CString.

Now all that remains is to write CQueryDlg::OnQuery(), which will use the value in m_host to produce lines of output for m_out.

Querying HTTP Sites

The first kind of connection to try is HTTP, because so many sites have Web pages. The simplest way to make a connection using HTTP is to use the WinInet class CInternetSession and call its OpenURL() function. This will return a file, and you can display the first few lines of the file in m_out. First, add this line at the beginning of QueryDlg.cpp:


#include "afxinet.h"

Because this application will try a number of URLs, add a function to CQueryDlg called TryURL(). It takes a CString parameter called URL and returns void. Right-click on CQueryDlg in the ClassView and choose Add Function to add TryURL() as a protected member function.

The new function, TryURL(), will be called from CQueryDlg::OnQuery() as shown in Listing 27.1.


Listing 27.1  QueryDlg.cpp-CQueryDlg::OnQuery()

void CQueryDlg::OnQuery() 

{

	const CString http = "http://";

	

	UpdateData(TRUE);

	m_out = "";

	UpdateData(FALSE);



	TryURL(http + m_host);



	TryURL(http + "www." + m_host);

}


The call to UpdateData(TRUE) fills m_host with the value that the user typed. The call to UpdateData(FALSE) fills the IDC_OUT read-only edit box with the newly cleared m_out. Then come two calls to TryURL(). If, for example, the user typed microsoft.com, the first call would try http://microsoft.com and the second would try http://www.microsoft.com.

TryURL() is shown in Listing 27.2.


Listing 27.2  QueryDlg.cpp-CQueryDlg::TryURL()

void CQueryDlg::TryURL(CString URL)

{

	CInternetSession session;



	m_out += "Trying " + URL + "\r\n";

	UpdateData(FALSE);



	CInternetFile* file = NULL;

	try

	{

		//We know for sure this is an Internet file,

		//so the cast is safe

		file = (CInternetFile*) session.OpenURL(URL);

	}

	catch (CInternetException* pEx)

	{

		//if anything went wrong, just set file to NULL

		file = NULL;

		pEx->Delete();

	}

	if (file)

	{

		m_out += "Connection established. \r\n";

		CString line;



		file->SetReadBufferSize(4096);

		for (int i=0; i < 20 && file->ReadString(line); i++)

		{

			m_out += line + "\r\n";

		}

		file->Close();

		delete file;

	}

	else

	{

		m_out += "No server found there. \r\n";

	}



	m_out += "-------------------------\r\n";

	UpdateData(FALSE);

}


The remainder of this section presents this code again, a few lines at a time. First, establish an Internet session by constructing an instance of CInternetSession. There are a number of parameters to this constructor, but they all have default values that will be fine for this application. The parameters are the following:

dwAccessType defaults to using the value in the Registry. Obviously, an application that insists on direct Internet access or proxy Internet access is less useful than one that allows users to configure that information. But making users set their Internet access type outside this program may be confusing. To set your default Internet access, double-click the My Computer icon on your desktop, then the Control Panel, and then the Internet tool in the Control Panel. Choose the Connection tab, shown in Figure 27.7, and complete the dialog box as appropriate for your setup:

Figure 27.7 : Set your Internet connection settings once, and all applications can retrieve them from the Registry.

If you want to set up an asynchronous (non-blocking) session, for the reasons discussed in the "Using Windows Sockets" section of Chapter 13, "Sockets, MAPI, and the Internet," your options in dwFlags must include INTERNET_FLAG_ASYNC. In addition, you must call the member function EnableStatusCallback() to set up the callback function. When a request is made through the session-such as the call to OpenURL() that occurs later in TryURL()-and the response will not be immediate, a non-blocking session returns a pseudo error code, ERROR_IO_PENDING. When the response is ready, these sessions automatically invoke the callback function.

For this simple application, there is no need to allow the user to do other work or interact with the user interface while waiting for the session to respond, so the session is constructed as a blocking session and all the other default parameters are also used:


CInternetSession session;

Having constructed the session, TryURL() goes on to add a line to m_out that echoes the URL passed in as a parameter. The "\r\n" characters are return and newline, and they separate the lines added to m_out. UpdateData(FALSE) gets that onto the screen:


m_out += "Trying " + URL + "\r\n";

UpdateData(FALSE);

Next is a call to the session's OpenURL() member function. This function returns a pointer to one of several different file types, since the URL might have been to one of four protocols:

Because CGopherFile and CHttpFile both inherit from CInternetFile, and because you can be sure that TryURL() will not be passed a file:// URL, it is safe to cast the returned pointer to a CInternetFile.

TIP
There is some confusion in Microsoft's online documentation whenever example URLs are shown. A backslash (\) character will never appear in a URL. In any Microsoft example that includes backslashes, use forward slashes (/) instead.

If the URL would not open, file will be NULL or OpenURL()_ will throw an exception. (For background on exceptions, see Chapter 30, "Power-User C++ Features.") While in a normal application it would be a serious error if a URL didn't open, in this application you are making up URLs to see if they work or not, and it's to be expected that some of them won't. As a result, you should catch these exceptions yourself and do something fairly mild, just enough to prevent runtime errors. In this case, it's enough to make sure that file is NULL when an exception is thrown. To delete the exception and prevent memory leaks, call CException::Delete(), which is not mentioned in the online documentation but does exist and safely deletes the exception. The block of code containing the call to OpenURL()is in Listing 27.3.


Listing 27.3  QueryDlg.cpp-CQueryDlg::TryURL()

CInternetFile* file = NULL;

try

{

	 //We know for sure this is an Internet file,

	 //so the cast is safe

	 file = (CInternetFile*) session.OpenURL(URL);

}

catch (CInternetException* pEx)

{

	 //if anything went wrong, just set file to NULL

	 file = NULL;

	 pEx->Delete();

}


If file is not NULL, this routine echoes another line to m_out, and then in a for loop, the routine calls CInternetFile::ReadString() to fill the CString line with the characters in file up to the first \r\n, which are stripped off. This code simply tacks line (and another \r\n) onto m_out. If you would like to see more or less than the first 20 lines of the page, adjust the number in this for loop. When the first few lines have been read, TryURL() closes and deletes the file. That block of code is shown in Listing 27.4.


Listing 27.4  QueryDlg.cpp-CQueryDlg::TryURL()

if (file)

{

	  m_out += "Connection established. \r\n";

	  CString line;



	  file->SetReadBufferSize(4096); //for those without the 4.2a patch

	  for (int i=0; i < 20 && file->ReadString(line); i++)

	  {

		  m_out += line + "\r\n";

	  }

	  file->Close();

	  delete file;

}


NOTE
The call to SetReadBufferSize() is a workaround for a flaw in ReadString(). It's explained in a comment in the source for TryURL()on the CD. If you have installed the 4.2a patch (available free from http://www.microsoft.com) so that your version of Visual C++ is now 4.2a or higher, you do not need this workaround.

If the file could not be opened, a message to that effect is echoed onto m_out:


else

{

	m_out += "No server found there. \r\n";

}

Then whether the file existed or not, a line of dashes is tacked onto m_out to indicate the end of this attempt, and one last call to UpdateData(FALSE) gets the new m_out onto the screen:


	m_out += "-------------------------\r\n";

	UpdateData(FALSE);

}

You can now build and run this application. If you enter microsoft.com in the text box and click Query, you will discover that there are Web pages at both http://microsoft.com and http://www.microsoft.com. Figure 27.8 shows the result of that query.

Figure 27.8 : Query can find Microsoft's Web sites.

If Query doesn't find Web pages at either the domain name you provided or www. plus the domain name, it doesn't mean that the domain doesn't exist or even that the organization that owns the domain name doesn't have a Web page. It does make it less likely, however, that the organization both exists and has a Web page. If you see a stream of HTML, you may be able to read it yourself, but, even if you cannot, you can now connect to the site with a Web browser such as Microsoft's Internet Explorer.

Querying FTP Sites

As part of an investigation of a site name, you should check to see if there is an FTP site, too. Most FTP sites have names like ftp.company.com, though some older sites do not have names of that form. Checking for these sites is not as simple as just calling TryURL() again, because TryURL() assumes that the URL leads to a file, and URLs like ftp.greatidea.org lead to a list of files that cannot simply be opened and read. Rather than making TryURL() even more complicated, add a function to the class called TryFTPSite(CString host). (Right-click on CQueryDlg in the ClassView and choose Add Function to add the function. It can return void.)

TryFTPSite() has to establish a connection within the session, and if the connection is established, it has to get some information that can be added to m_out to show the user that the connection has been made. Getting a list of files is reasonably complex, so since this is just an illustrative application, the simpler task of getting the name of the default FTP directory is the way to go. The code is in Listing 27.5.


Listing 27.5  QueryDlg.cpp-CQueryDlg::TryFTPSite()

void CQueryDlg::TryFTPSite(CString host)

{

	CInternetSession session;



	m_out += "Trying FTP site " + host + "\r\n";

	UpdateData(FALSE);



	CFtpConnection* connection = NULL;

	try

	{

		connection = session.GetFtpConnection(host);

	}

	catch (CInternetException* pEx)

	{

		//if anything went wrong, just set connection to NULL

		connection = NULL;

		pEx->Delete();

	}

	if (connection)

	{

		m_out += "Connection established. \r\n";

		CString line;



		connection->GetCurrentDirectory(line);

		m_out += "default directory is " + line + "\r\n";



		connection->Close();

		delete connection;

	}

	else

	{

		m_out += "No server found there. \r\n";

	}



	m_out += "-------------------------\r\n";

	UpdateData(FALSE);

}


This code is very much like TryURL(), except that instead of opening a file with session.OpenURL(), it opens an FTP connection with session.GetFtpConnection(). Again, exceptions are caught and essentially ignored, with the routine just making sure that the connection pointer won't be used. The call to GetCurrentDirectory() returns the directory on the remote site that sessions start in. The rest of the routine is just like TryURL().

Add two lines at the end of OnQuery( ) to call this new function:


TryFTPSite(m_host);

TryFTPSite("ftp." + m_host);

Build the application and try it: Figure 27.9 shows Query reporting no FTP site at microsoft.com and finding one at ftp.microsoft.com.

Figure 27.9 : Query finds one Microsoft FTP site.

If Query hasn't found Web pages or FTP sites, it is getting less likely that this domain exists at all, or has any Internet services other than e-mail, but there are a few more investigative tricks available.

Querying Gopher Sites

As was the case with FTP, TryURL() won't work for querying a Gopher site like gopher. company.com, because this returns a list of file names rather than a single file. The solution is to write a function called TryGopherSite() that is almost identical to TryFTPSite(), except that it opens a CGopherConnection, and instead of a single line describing the default directory, it echoes a single line describing the Gopher locator associated with the site. Add TryGopherSite to CQueryDlg by right-clicking on the class name in ClassView and choosing Add Function, as you did for TryFTPSite(). The code for TryGopherSite() is in Listing 27.6.


Listing 27.6  QueryDlg.cpp-CQueryDlg::TryGopherSite()

void CQueryDlg::TryGopherSite(CString host)

{

	CInternetSession session;



	m_out += "Trying Gopher site " + host + "\r\n";

	UpdateData(FALSE);



	CGopherConnection* connection = NULL;

	try

	{

		connection = session.GetGopherConnection(host);

	}

	catch (CInternetException* pEx)

	{

		//if anything went wrong, just set connection to NULL

		connection = NULL;

		pEx->Delete();

	}

	if (connection)

	{

		m_out += "Connection established. \r\n";

		CString line;



		CGopherLocator locator = connection->CreateLocator(NULL, NULL,

                                       ›;GOPHER_TYPE_DIRECTORY);

		line = locator;

		m_out += "first locator is " + line + "\r\n";



		connection->Close();

		delete connection;

	}

	else

	{

		m_out += "No server found there. \r\n";

	}



	m_out += "-------------------------\r\n";

	UpdateData(FALSE);

}


The call to CreateLocator() takes three parameters. The first is the file name, which may include wild cards. NULL means any file. The second parameter is a selector that can be NULL. The third is one of the following types:

GOPHER_TYPE_TEXT_FILE
GOPHER_TYPE_DIRECTORY
GOPHER_TYPE_CSO
GOPHER_TYPE_ERROR
GOPHER_TYPE_MAC_BINHEX
GOPHER_TYPE_DOS_ARCHIVE
GOPHER_TYPE_UNIX_UUENCODED
GOPHER_TYPE_INDEX_SERVER
GOPHER_TYPE_TELNET
GOPHER_TYPE_BINARY
GOPHER_TYPE_REDUNDANT
GOPHER_TYPE_TN3270
GOPHER_TYPE_GIF
GOPHER_TYPE_IMAGE
GOPHER_TYPE_BITMAP
GOPHER_TYPE_MOVIE
GOPHER_TYPE_SOUND
GOPHER_TYPE_HTML
GOPHER_TYPE_PDF
GOPHER_TYPE_CALENDAR
GOPHER_TYPE_INLINE
GOPHER_TYPE_UNKNOWN
GOPHER_TYPE_ASK
GOPHER_TYPE_GOPHER_PLUS

Normally, you don't build locators for files or directories, but instead you ask the server for them. The locator that will be returned from this call to CreateLocator() describes the locator associated with the site you are investigating.

Add a pair of lines at the end of OnQuery() that call this new TryGopherSite() function:


TryGopherSite(m_host);

TryGopherSite("gopher." + m_host);

Build and run the program again. You may find the delay until results start to appear a little disconcerting. You could correct this by using asynchronous sockets, or threading, but for a simple demonstration application like this, just wait patiently until the results appear. Figure 27.10 shows that Query has found two Gopher sites for harvard.edu. In both cases, the locator describes the site itself. This is enough to prove that there is a Gopher site at harvard.edu, which is all that Query is supposed to do.

Figure 27.10 : Query finds two Harvard Gopher sites.

TIP
Gopher is an older protocol that has been almost entirely supplemented by the World Wide Web. As a general rule, if a site has a Gopher presence, it's been on the Internet since before the World Wide Web existed (1989) or at least before the huge upsurge in popularity began (1992). What's more, the site was probably large enough in the early 1990s to have an administrator who would set up the Gopher menus and text.

Using Gopher to Send a Finger Query

There is another protocol that can give you information about a site. It's one of the oldest protocols on the Internet, and it's called Finger. You can Finger a single user or an entire site, and, though many sites have disabled Finger, many more will provide you with useful information in response to a Finger request.

There is no MFC class or API function with the word finger in its name, but that doesn't mean you can't use the classes already presented. This section relies on a trick-and on knowledge of the Finger and Gopher protocols. While the WinInet classes are a boon to new Internet programmers who don't quite know how the Internet works, they also have a lot to offer to old-timers who know what's going on under the hood.

As discussed in the "Using Windows Sockets" section of Chapter 13, "Sockets, MAPI, and the Internet," all Internet transactions involve both a host and a port. Well-known services use standard port numbers. For example, when you call CInternetSession::OpenURL() with a URL that starts http://, the code behind the scenes connects to port 80 on the remote host. When you call GetFtpConnection(), the connection is made to port 21 on the remote host. Gopher uses port 70. If you look at Figure 27.10, you will see that the locator that describes the gopher.harvard.edu site includes a mention of port 70.

The Gopher documentation makes this clear: If you build a locator with a host name, port 70, Gopher type 0 (GOPHER_TYPE_TEXT_FILE is defined to be 0), and a string with a file name, any Gopher client simply sends the string, whether it's a file name or not, to port 70. The Gopher server listening on that port responds by sending the file.

Now, Finger is a simple protocol, too. If you send a string to port 79 on a remote host, the Finger server that is listening there will react to the string by sending a Finger reply. If the string is only \r\n, the usual reply is a list of all the users on the host and some other information about them, such as their real names. (Many sites consider this an invasion of privacy or a security risk, and they disable Finger. But many other sites deliberately make this same information available on their Web pages.)

Putting this all together, if you build a Gopher locator using port 79-rather than the default 70-and an empty file name, you can do a Finger query using the MFC WinInet classes. First, add another function to CQueryDlg called TryFinger(), which takes a CString host and returns void. The code for this function is very much like TryGopherSite(), except that the connection is made to port 79:


connection = session.GetGopherConnection(host,NULL,NULL,79);

Once the connection is made, a text file locator is created:


CGopherLocator locator = connection->CreateLocator(NULL, NULL, ›;GOPHER_TYPE_TEXT_FILE);

This time, rather than simply casting the locator into a CString, use it to open a file:


CGopherFile* file = connection->OpenFile(locator);

Then echo the first 20 lines of this file, just as TryURL() echoed the first 20 lines of the file returned by a Web server. The code to do this is in Listing 27.7.


Listing 27.7  QueryDlg.cpp-CQueryDlg::TryFinger() Excerpt

if (file)

{

	CString line;



	file->SetReadBufferSize(4096);

	for (int i=0; i < 20 && file->ReadString(line); i++)

	{

		m_out += line + "\r\n";

	}

	file->Close();

	delete file;

}


Putting it all together, TryFinger() is shown in Listing 27.8.


Listing 27.8  QueryDlg.cpp-CQueryDlg::TryFinger()

void CQueryDlg::TryFinger(CString host)

{

	CInternetSession session;



	m_out += "Trying to Finger " + host + "\r\n";

	UpdateData(FALSE);



	CGopherConnection* connection = NULL;



	try

	{

		connection = session.GetGopherConnection(host,NULL,NULL,79);

	}

	catch (CInternetException* pEx)

	{

		//if anything went wrong, just set connection to NULL

		connection = NULL;

		pEx->Delete();

	}

	if (connection)

	{

		m_out += "Connection established. \r\n";



		CGopherLocator locator = connection->CreateLocator(NULL, NULL,    

                                ›;GOPHER_TYPE_TEXT_FILE);



		CGopherFile* file = connection->OpenFile(locator);

		if (file)

		{

			CString line;



			file->SetReadBufferSize(4096);

			for (int i=0; i < 20 && file->ReadString(line); i++)

			{

				m_out += line + "\r\n";

			}

			file->Close();

			delete file;

		}



		connection->Close();

		delete connection;

	}

	else

	{

		m_out += "No server found there. \r\n";

	}



	m_out += "-------------------------\r\n";

	UpdateData(FALSE);



}


Add a line at the end of OnQuery() that calls this new function:


TryFinger(m_host);

Now build and run the application. Figure 27.11 shows the result of a query on the site whitehouse.gov, scrolled down to the Finger section.

Figure 27.11 : Query gets e-mail addresses from the White House Finger server.

Using Gopher to Send a Whois Query

One last protocol provides information about sites. It, too, is an old protocol not supported directly by the WinInet classes. It is called Whois, and it's a service offered by only a few servers on the whole Internet. The servers that offer this service are maintained by the organizations that register domain names. For example, domain names that end in .com are registered through an organization called InterNIC, which runs a Whois server called rs.internic.net (the rs stands for Registration Services). Like Finger, Whois responds to a string sent on its own port; the Whois port is 43. Unlike Finger, you don't send an empty string in the locator; you send the name of the host that you want to look up. You connect to rs.internic.net every time. (Dedicated Whois servers offer users a chance to change this, but, in practice, no one ever does.)

So add a function called TryWhois(); as usual, it takes a CString host and returns void. The code is in Listing 27.9.


Listing 27.9  QueryDlg.cpp-CQueryDlg::TryWhois()

void CQueryDlg::TryWhois(CString host)

{

	CInternetSession session;



	m_out += "Trying Whois for " + host + "\r\n";

	UpdateData(FALSE);



	CGopherConnection* connection = NULL;

	try

	{

	        connection = session.GetGopherConnection("rs.internic.net",

                                ›;NULL,NULL,43);

	}

	catch (CInternetException* pEx)

	{

		//if anything went wrong, just set connection to NULL

		connection = NULL;

		pEx->Delete();

	}

	if (connection)

	{

		m_out += "Connection established. \r\n";

		CGopherLocator locator = connection->CreateLocator(NULL, host,                         

                                ›;GOPHER_TYPE_TEXT_FILE);

		CGopherFile* file = connection->OpenFile(locator);

		if (file)

		{

			CString line;

			file->SetReadBufferSize(4096);

			for (int i=0; i < 20 && file->ReadString(line); i++)

			{

				m_out += line + "\r\n";

			}

			file->Close();

			delete file;

		}

		connection->Close();

		delete connection;

	}

	else

	{

		m_out += "No server found there. \r\n";

	}

	m_out += "-------------------------\r\n";

	UpdateData(FALSE);

}


Add a line at the end of OnQuery() to call it:


TryWhois(m_host);

Build and run the application one last time. Figure 27.12 shows the Whois part of the report for mcp.com-this is the domain for Macmillan Computer Publishing, Que's parent company.

Figure 27.12 : Query gets real-life addresses and names from the InterNIC Whois server.

Future Work

The Query application built in this chapter does a lot, but it could do so much more. There are e-mail and news protocols that could be reached by stretching the WinInet classes a little more and using them to connect to the standard ports for these other services. You could also connect to some well-known Web search engines and submit queries by forming URLs according to the pattern used by those engines. In this way, you can automate the sort of poking around on the Internet that most of us do when we're curious about a domain name or an organization.

If you'd like to learn more about Internet protocols, port numbers, and what's happening when a client connects to a server, you can find out a lot by reading Que's Building Internet Applications with Visual C++. The book was written for Visual C++ 2.0, and though all the applications in the book compile and run under later versions of MFC, they would be much shorter and easier to write now. Still, the insight into the way the protocols work is valuable.

And the WinInet classes can do much more than you've seen here, too. Query doesn't use them to retrieve real files over the Internet. There are two WinInet sample applications included with Visual C++ 4.2 that do a fine job of showing how to retrieve files:

There are lots more Microsoft announcements coming over the next few months, as well. Keep an eye on its Web site, www.microsoft.com, for libraries and software development kits that will make Internet software development even easier and faster.

From Here…

This chapter introduced you to the WinInet classes as one way to write Internet programs. Some related chapters that may interest you include: