Chapter 24

Programming Internet Applications with WinInet


As the Internet becomes more and more integrated with our daily lives, the more important network programming becomes. Microsoft is well aware of the impact the Internet is having on many people's lives. They are also aware that Windows programmers are more anxious than ever to find a good way to create Internet applications. In response, Microsoft created WinInet, a library of functions that makes it easier to create Internet applications. The best news is that MFC 5.0 encapsulates the WinInet library into a number of classes that enable you to connect to the Internet with only a few lines of code. In this chapter, you explore the WinInet classes. Along the way, you learn to access Web pages and to transfer files using the FTP protocol.

Introducing MFC's WinInet Classes

Using MFC's WinInet classes, you can create Internet applications that use high-level function calls, writing Internet connection and communication code in a similar fashion to MFC code you've written previously. The WinInet classes enable your application to take a general approach to Internet programming or, if you like, to dig deeper into the classes to get more control over HTTP, FTP, and gopher sessions. The WinInet classes are listed in Table 24.1 along with their descriptions:

Table 24.1 The MFC WinInet Classes

Class Description
CInternetSession Creates and manages an Internet session. All MFC WinInet applications must create a CInternetSession object before using other WinInet classes.
CInternetConnection Creates and manages an actual Internet connection. This is the base class for the more specific connection classes CFtpConnection, CGopherConnection, and CHttpConnection.
CFtpConnection Creates and manages an FTP connection.
CGopherConnection Creates and manages a gopher connection.
CHttpConnection Creates and manages an HTTP connection.
CInternetFile Enables remote access to files on an Internet server. This is the base class for the more specific WinInet file classes CGopherFile and CHttpFile.
CGopherFile Manages interaction with a file on a gopher server.
CHttpFile Manages interaction with a file on an HTTP server.
CFileFind Enables a program to search for files. This is the base class for the more specific WinInet file search classes CFtpFileFind and CGopherFileFind.
CFtpFileFind Enables a program to search for files on an FTP server.
CGopherFileFind Enables a program to search for files on a gopher server.
CGopherLocator Retrieves a Gopher locator from a gopher server.
CInternetException Manages exceptions generated by the WinInet classes.

In the following sections, you get a closer look at many of the classes listed in Table 24.1 as you create Internet applications with the WinInet classes. First, you create an application that can download and display the HTML code for a Web page. Then, you create an application that can display the contents of an FTP server, as well as download files from that server.

Programming a Web Application

Believe it or not, creating an application that can log onto a Web server and retrieve a Web page is one of the easiest things to do with the WinInet classes. I say that with one major caveat: Although such a simple Web application can download and display a Web page's HTTP document, it is not able to translate the HTTP document into an actual visual Web page. Doing this requires that your application be able to parse an HTTP document and create a display according to the commands that it finds in the HTTP document. That's clearly a major task, one that requires the functionality of a full-fledged Web browser.

Still, connecting to a Web server and downloading its home page as an HTTP document is a good way to get your feet wet with WinInet programming. That's because downloading a Web page is one of the most basic things you can do with the WinInet classes, so it provides a good introduction to MFC Internet programming. To create your first Internet application, complete the steps that follow:

ON THE WEB

http://www.quecorp.com/semfc The complete source code and executable file for the HTTPApp application can be found in the CHAP24\HTTPApp directory at this book's Web site.

1. Create a new AppWizard project workspace called HTTPApp as shown in Figure 24.1.


FIG. 24.1

Create a new project workspace called HTTPApp.

2. Give the new project the following settings in the AppWizard dialog boxes. The New Project Information dialog box should then look like Figure 24.2.

Step 1: Single document

Step 2: Default settings

Step 3: Default settings

Step 4: Turn off all options except 3D Controls

Step 5: Default settings

Step 6: Default settings


FIG. 24.2

Your New Project Information dialog box should look like this.

3. Use the resource editor to create the dialog box shown in Figure 24.3. Give the dialog box the ID IDD_URLDLG, and give the edit control the ID IDC_URL.


FIG. 24.3

Use the dialog box editor to create this dialog box.

4. Press Ctrl+W on your keyboard. The Adding a Class dialog box appears (see Figure 24.4).


FIG. 24.4

Create a class for the new dialog box.

5. Click OK to create a class for the new dialog box. The Create New Class dialog box appears. In the Name box, type CURLDlg (see Figure 24.5), and click the OK button.


FIG. 24.5

Name the new class CURLDlg.

6. In the MFC ClassWizard property sheet, select the Member Variables tab, and create a string member variable called m_url for the IDC_URL edit box, as shown in Figure 24.6. Click OK to close the MFC ClassWizard property sheet.


FIG. 24.6

Create a member variable for the edit box.

7. Use the resource editor to delete the Edit menu from the application's menu bar.

8. Add a Connect menu to the application's menu bar. Give the menu one command called Make Connection with a command ID of ID_CONNECT_MAKECONNECTION, as shown in Figure 24.7.


FIG. 24.7

Create a Connect menu for the application.

9. Use ClassWizard to associate the ID_CONNECT_MAKECONNECTION command with the OnConnectMakeconnection() message response function, as shown in Figure 24.8. Make sure that you have CHTTPAppView selected in the Class Name box before you add the function.


FIG. 24.8

Add the OnConnectMakeconnection() member function.

10. Click the Edit Code button, and then add the lines shown in Listing 24.1 to the new OnConnectMakeconnection() function, right after the TODO: Add your command handler code here comment.

Listing 24.1 lst24_01.cpp Code for the New OnConnectMakeconnection() Function

    CURLDlg dialog(this);
    dialog.m_url = "http://www.mcp.com";
    int result = dialog.DoModal();
    if (result == IDOK)
    {
        CString url = dialog.m_url;
        CInternetSession internetSession;
        try
        {
            CHttpFile* httpFile =
               (CHttpFile*)internetSession.OpenURL(url);
            httpFile->SetReadBufferSize(4096);
            for (int x=0; x<30; ++x)
                httpFile->ReadString(m_webPageLines[x]);
            httpFile->Close();
        }
        catch (CInternetException* pException)
        {
            m_webPageLines[0] = "CInternetException received!";
            pException->ReportError();
        }
        internetSession.Close();
        Invalidate();
    }

11. Still in the HTTPAppView.cpp file, and add the following lines near the top of the file, right after the #endif compiler directive:

#include <afxinet.h>
#include "urldlg.h"

These lines include in your program the header files that declare the WinInet and dialog classes, so that you can access those classes in the program.

12. Add the following lines to the view class's constructor, right after the comment TODO: add construction code here:

    for (int x=0; x<30; ++x)
        m_webPageLines[x] = "";

These lines initialize the array of strings to empty strings. The content of the string array is displayed on the screen by the OnDraw() function. Eventually, the array of strings will hold the first thirty lines of an HTML document.

13. Add the following lines to the class's OnDraw() function, right after the TODO: add draw code for native data here comment:

    for (int x=0; x<30; ++x)
        pDC->TextOut(20, 20*x+20, m_webPageLines[x]);

These lines display the contents of the m_webPageLines[] string array on the screen.

14. Load the HTTPAppView.h header file, and add the following lines to the class's declaration, in the Attributes section, right after the line CHTTPAppDoc* GetDocument():

protected:
    CString m_webPageLines[30];

You've now completed the HTTPApp application. Select Developer Studio's Build, Build command to compile and link the application, Then, select the Build, Execute command to run your new Internet application. When you do, the application's main window appears.

Select the Connect menu's Make Connection command to choose the Web URL to which you want to connect. This brings up the Make Connection dialog box, which has Macmillan Computer Publishing's URL already entered as the default selection (see Figure 24.9). After entering the URL, select the OK button. HTTPApp will then attempt to connect to the URL and will display a maximum of 30 lines of the Web page's HTML document. Figure 24.10 shows HTTPApp connected to Microsoft's home page at www.microsoft.com.


FIG. 24.9

Use the Make Connection dialog box to choose an URL.


FIG. 24.10

This is HTTPApp connected to Microsoft's home page.

Exploring the HTTPApp Application

You're probably surprised at how little programming it takes to connect to a Web site and download an HTML document. In this section, you dispel the mystery by examining the important parts of the HTTPApp application. But before looking at the code, take a look at the steps that you must complete to create a simple Web browser like HTTPApp. Those steps are listedas follows:

1. Create an object of the CInternetSession class.

2. Call the CInternetSession object's OpenURL() member function to connect to the selected URL, which creates an object of the CHttpFile class.

3. Call the CHttpFile object's ReadString() member function to read HTML lines from the remote file.

4. Process the HTTP lines to create the Web page display.

5. Call the CHttpFile object's Close() member function.

6. Call the CInternetSession object's Close() member function.

The following sections will examine how the preceding steps are implemented in the HTTPApp application. Most of the pertinent code is tucked away in the OnConnectMakeconnection() function, which MFC calls when the user selects the Connect, Make Connection command from the application's menu bar.

Creating a CInternetSession Object

This first step couldn't be any easier, thanks to the fact that the CInternetSession class provides default values for all of the constructor's arguments one of the advantages of programming WinInet through MFC. To create your CInternetSession object, just call the constructor like this:

CInternetSession internetSession;

Thanks to the default arguments supplied by the class, the preceding line is all you need to get started. If you want more control over the construction of your CInternetSession object, you can supply arguments for the constructor, whose signature follows:

CInternetSession(LPCTSTR pstrAgent = NULL, DWORD dwContext = 1,
    DWORD dwAccessType = PRE_CONFIG_INTERNET_ACCESS,
    LPCTSTR pstrProxyName = NULL, LPCTSTR pstrProxyBypass = NULL,
    DWORD dwFlags = 0);

If you're interested in more advanced Internet applications, you can look up what these arguments mean in your Visual C++ online documentation. For most applications, though, the default arguments work just fine.

Creating a CHttpFile Object

If you remember the list of steps presented earlier in the section “Exploring the HTTPApp Application,” you know that the next step is to call the CInternetSession object's OpenURL() member function. Doing this, not only connects the program to the given URL, but also supplies your application with an object of the CHttpFile class. You need the CHttpFile object to manipulate the remote file in several ways, not the least of which is to download each line of the HTTP file that the CHttpFile object represents. You call OpenURL() like this:

CHttpFile* httpFile = (CHttpFile*)internetSession.OpenURL(url);

Because OpenURL() actually returns an object of the CInternetFile type, the returned object must be cast to CHttpFile.

OpenURL() is only one of many member functions included in the CInternetSession class. All of the member functions are listed in Table 24.2 along with their descriptions.

Table 24.2 Member Functions of the CInternetSession Class

Function Description
Close() Closes an Internet session
EnableStatusCallback() Enables a callback function, which is used for asynchronous operations
GetContext() Gets the Internet session's context value
GetFtpConnection() Establishes an FTP connection
GetGopherConnection() Establishes a gopher connection
GetHttpConnection() Establishes an HTTP connection
OnStatusCallback() Updates an operation's status
OpenURL() Connects to the given URL
QueryOption() Provides error-checking services
ServiceTypeFromHandle() Gets the service type from an Internet handle
SetOption() Sets an Internet session's options

Notice that all of the calls to the WinInet classes' member functions are enclosed in a try program block. This is because, when connecting to an URL on the Internet, the risk of failure is high, especially when you consider that the user is typing the URL. For all you know, there's not even such an URL available. Another problem is timeouts, which might occur when the requested URL is currently unable to service the connection. Handling the WinInet exceptions, which are represented in MFC by the CInternetException class, is an important part of creating an Internet application with MFC.

Reading the HTML Document

Once you have your HTTP connection established, you can start reading the HTML document. Due to some sort of bug in the MFC WinInet classes, however, you must first call the CHttpFile object's SetReadBufferSize() member function, which HTTPApp does like this:

httpFile->SetReadBufferSize(4096);

In the current version of the WinInet classes, if you fail to call SetReadBufferSize() before reading the file, you might get strange results.

The SetReadBufferSize() problem appears to be fixed in Visual C++ 5.0. It doesn’t hurt to set the buffer, however, so, to be on the safe side, it might be a good idea to leave the call to SetReadBufferSize() in the program.

Now you're ready to read the HTML file. You can do this in several ways, the easiest of which is calling the CHttpFile object's ReadString() member function. HTTPApp calls ReadString() from within a for loop, like this:

for (int x=0; x<30; ++x)
    httpFile->ReadString(m_webPageLines[x]);

The ReadString() function returns a CString object that holds the current text line from the HTML document. In HTTPApp, the program reads thirty lines from the HTML document and stores those lines in a CString array. The view class's OnDraw() function then displays the lines whenever the application's display must be repainted. (To see all of the lines at once, you might have to enlarge the application's window, which doesn't have a scroll bar.) You can, of course, call other member functions to manipulate a CHttpFile object. Those member functions are listed in Table 24.3.

Table 24.3 Member Functions of the CHttpFile Class

Function Description
AddRequestHeaders() Adds request headers to an HTTP request.
Close() Closes the CHttpFile object
GetFileURL() Gets the file's URL
GetObject() Gets an HTTP request verb's target object
GetVerb() Gets a request verb
QueryInfo() Gets response or request headers
QueryInfoStatusCode() Gets an HTTP request's status code
SendRequest() Sends an HTTP request

Because it's derived from CInternetFile, the CHttpFile class also gets a set of member functions from CInternetFile. Those member functions and their descriptions are listed in Table 24.4.

Table 24.4 Member Functions of the CInternetFile Class

Function Description
Abort() Closes the file and ignores all errors
Close() Closes the file
Flush() Flushes the file
Read() Reads bytes from the file
ReadString() Reads a stream of characters from the file
Seek() Repositions the file pointer
SetReadBufferSize() Sets the size of the read buffer
SetWriteBufferSize() Sets the size of the write buffer
Write() Writes bytes to the file
WriteString() Writes a null-terminated string to the file

Processing the HTML Document

If you were writing a full-fledged Web browser, you'd need to take the text lines that the application is reading from the HTTP connection and translate them into an appropriate Web page display. Because HTML is a complex language, doing such a translation can be a major task. In fact, an entire encyclopedia could probably be dedicated to handling HTTP file. HTTPApp takes the easy way out, though, doing nothing more than displaying the HTML lines on the screen.

Closing the File and the Internet Session

When your application is finished with the file object, that object should be closed, like this:

httpFile->Close();

Closing the object frees its resources. The same is true of the CInternetSession object, which HTTPApp closes like this:

internetSession.Close();

Programming an FTP Application

Although most people log onto the Internet through the World Wide Web, FTP servers provide a quick and easy way to download files. For that reason, this introduction to the WinInet classes includes a sample FTP application that, enables you to, not only browse the directory tree of an FTP server, but also to download files from the server.

Because an FTP application must keep track of directories and files, as well as provide an interface with which the user can browse the server, such an application is a bit more complex than the previous Web example. However, it's a heck of a lot easier to program an FTP application using WinInet than it is to build the application from scratch. Complete the steps below to build the FTPApp application.

ON THE WEB

http://www.quecorp.com/semfc The complete source code and executable file for the FTPApp application can be found in the CHAP24\FTPApp directory at this book's Web site.

1. Create a new AppWizard project workspace called FTPApp as shown in Figure 24.11.


FIG. 24.11

Create a new project workspace called FTPApp.

2. Give the new project the following settings in the AppWizard dialog boxes. The New Project Information dialog box should then look like Figure 24.12.

Step 1: Single document

Step 2: Default settings

Step 3: Default settings

Step 4: Turn off all options except 3D Controls

Step 5: Default settings

Step 6: Default settings


FIG. 24.12

Your New Project Information dialog box should look like this.

3. Use the resource editor to create the dialog box shown in Figure 24.13. Give the dialog box the ID IDD_FTPDLG and give the edit control the ID IDC_FTP.


FIG. 24.13

Use the dialog box editor to create this dialog box.

4. Click the ClassWizard button or press Ctrl+W on your keyboard. The Adding a Class dialog box appears (see Figure 24.14).


FIG. 24.14

The Adding a Class dialog box begins the process of creating a class for the new dialog box.

5. Click OK to create a class for the new dialog box. The Create New Class dialog box appears. In the Name box, type CFTPDlg (see Figure 24.15), and click the OK button.


FIG. 24.15

Name the new class CFTPDlg.

6. In the MFC ClassWizard property sheet, select the Member Variables tab, and create a string member variable called m_ftp for the IDC_FTP edit box, as shown in Figure 24.16. Click OK to close the MFC ClassWizard property sheet.


FIG. 24.16

Create a member variable for the edit box.

7. Use the resource editor to delete the Edit menu from the application's menu bar.

8. Add a Connect menu to the application's menu bar. Give the menu one command called Make Connection with a command ID of ID_CONNECT_MAKECONNECTION, as shown in Figure 24.17.


FIG. 24.17

Create a Connect menu for the application.

9. Use ClassWizard to associate the ID_CONNECT_MAKECONNECTION command with the OnConnectMakeconnection() message response function, as shown in Figure 24.18. Make sure that you have CFTPAppView selected in the Class Name box before you add the function.


FIG. 24.18

Add the OnConnectMakeconnection() member function.

10. Click the Edit Code button, and then add the lines shown in Listing 24.2 to the new OnConnectMakeconnection() function, right after the TODO: Add your command handler code here comment.

Listing 24.2 lst24_02.cpp Code for the New OnConnectMakeconnection() Function

    CFTPDlg dialog(this);
    dialog.m_ftp = "ftp.mcp.com";
    int result = dialog.DoModal();
    if (result == IDOK)
    {
        try
        {
            m_ftp = dialog.m_ftp;
            CInternetSession internetSession;
            CFtpConnection* ftpConnection =
                internetSession.GetFtpConnection(m_ftp);
            ftpConnection->GetCurrentDirectory(m_curDirectory);
            CFtpFileFind ftpFileFind(ftpConnection);
            ftpFileFind.FindFile();
            BOOLEAN gotFile;
            int x = 1;
            do
            {
                gotFile = ftpFileFind.FindNextFile();
                m_displayStr[x] = ftpFileFind.GetFileName();
                m_fileLengths[x] = ftpFileFind.GetLength();
                ++x;
            }
            while ((x<20) && (gotFile));
            m_numFiles = x;
            internetSession.Close();
            delete ftpConnection;
        }
        catch (CInternetException* pException)
        {
            pException->ReportError();
        }
        
        Invalidate();
    }

11. Add the following lines near the top of the file, right after the #endif compiler directive:

#include <afxinet.h>
#include "ftpdlg.h"

These lines include the header files that declare the WinInet and dialog classes in your program so that you can access those classes in the program.

12. Add the lines shown in Listing 24.3 to the view class's constructor, right after the comment TODO: add construction code here.

Listing 24.3 lst24_03.cpp Code for the View Class's Constructor

    m_displayStr[0] = ">>>PREV DIR<<<";
    for (int x=1; x<20; ++x)
    {
        m_displayStr[x] = "";
        m_fileLengths[x] = 0;
    }
    m_ftp = "";
    m_curDirectory = "";
    m_numFiles = 0;

13. Add the lines shown in Listing 24.4 to the class's OnDraw() function, right after the TODO: add draw code for native data here comment.

Listing 24.4 lst24_04.cpp Code for the OnDraw() Function

    pDC->TextOut(20, 20, "CURRENT DIRECTORY: " + m_curDirectory);
    pDC->TextOut(20, 40, "File Name");
    pDC->TextOut(200, 40, "File Length");
    pDC->MoveTo(20, 58);
    pDC->LineTo(325, 58);
    for (int x=0; x<m_numFiles; ++x)
    {
        pDC->TextOut(20, 60+x*20, m_displayStr[x]);
        if ((m_displayStr[x] != "") && (x != 0))
        {
            char s[80];
            wsprintf(s, "%d", m_fileLengths[x]);
            pDC->TextOut(200, 60+x*20, s);
        }
    }

14. Use ClassWizard to add the OnLButtonDblClk() message response function to the view class, as shown in Figure 24.19.


FIG. 24.19

The OnLButtonDblClk() function responds to WM_LBUTTONDBLCLK Windows messages.

15. Click ClassWizard's Edit Code button, and add the lines shown in Listing 24.5 to the OnLButtonDblClk() function, right after the TODO: Add your message handler code here and/or call default comment.

Listing 24.5 lst24_05.cppCode for the OnLButtonDblClk() Function

    int index = (point.y - 60) / 20;
    if (index == 0)
        PreviousDirectory();
    else if ((index > 0) && (index < 20))
    {
        if (m_displayStr[index] != "")
        {
            CClientDC clientDC(this);
            clientDC.Rectangle(6, index*20+62, 14, index*20+74);
            try
            {
                CInternetSession internetSession;
                CString dir = 
                    m_curDirectory + '/' + m_displayStr[index];
                CFtpConnection* ftpConnection =
                    internetSession.GetFtpConnection(m_ftp);
                BOOLEAN dirOpen =
                    ftpConnection->SetCurrentDirectory(dir);
                if (dirOpen)
                {
                    m_curDirectory = dir;
                    CFtpFileFind ftpFileFind(ftpConnection);
                    ftpFileFind.FindFile();
                    BOOLEAN gotFile;
                    int x = 1;
                    do
                    {
                        gotFile = ftpFileFind.FindNextFile();
                        m_displayStr[x] = ftpFileFind.GetFileName();
                        m_fileLengths[x] = ftpFileFind.GetLength();
                        ++x;
                    }
                    while ((x<20) && (gotFile));
                    internetSession.Close();
                    delete ftpConnection;
                    m_numFiles = x;
                }
                else
                    MessageBox("Cannot open that directory");
            }
            catch (CInternetException* pException)
            {
                pException->ReportError();
            }
        }
        
        Invalidate();
    }

16. Use ClassWizard to add the OnRButtonDown() message response function to the view class, as shown in Figure 24.20.


FIG. 24.20

The OnRButtonDown() function responds to the WM_RBUTTONDOWN Windows message.

17. Click ClassWizard's Edit Code button, and add the lines shown in Listing 24.6 to the OnRButtonDown() function, right after the TODO: Add your message handler code here and/or call default comment.

Listing 24.6 lst24_06.cpp Code for the OnRButtonDown() Function

    int index = (point.y - 60) / 20;
    if ((index >= 0) && (index < 20))
    {
        if (m_displayStr[index] != "")
        {
            CClientDC clientDC(this);
            clientDC.Rectangle(6, index*20+62, 14, index*20+74);
            try
            {
                CInternetSession internetSession;
                CString file = m_displayStr[index];
                CFtpConnection* ftpConnection =
                    internetSession.GetFtpConnection(m_ftp);
                BOOLEAN dirOpen =
                    ftpConnection->SetCurrentDirectory
                        (m_curDirectory);
                if (dirOpen)
                {
                    CFtpFileFind ftpFileFind(ftpConnection);
                    BOOLEAN fileFound = ftpFileFind.FindFile(file);
                    if (fileFound)
                    {
                        MessageBox("Click OK to download file");
                        ftpConnection->GetFile(file, file);
                        MessageBox("File retrieved");
                    }
                    else
                        MessageBox("Cannot find the selected file");
                }
                delete ftpConnection;
                internetSession.Close();
            }
            catch (CInternetException* pException)
            {
                pException->ReportError();
            }
        }
    }

18. Add the function shown in Listing 24.7 to the end of the FTPAppView.cpp file.

Listing 24.7 lst24_07.cpp The PreviousDirectory() Function

void CFTPAppView::PreviousDirectory() 
{
    CClientDC clientDC(this);
    clientDC.Rectangle(6, 62, 14, 74);
    CInternetSession internetSession;
    int slashPosition = m_curDirectory.ReverseFind('/');
    CString dir = m_curDirectory.Left(slashPosition);
    try
    {
        CFtpConnection* ftpConnection =
            internetSession.GetFtpConnection(m_ftp);
        BOOLEAN dirOpen = ftpConnection->SetCurrentDirectory(dir);
        if (dirOpen)
        {
            m_curDirectory = dir;
            CFtpFileFind ftpFileFind(ftpConnection);
            ftpFileFind.FindFile();
            BOOLEAN gotFile;
            int x = 1;
            do
            {
                gotFile = ftpFileFind.FindNextFile();
                m_displayStr[x] = ftpFileFind.GetFileName();
                m_fileLengths[x] = ftpFileFind.GetLength();
                ++x;
            }
            while ((x<20) && (gotFile));
            internetSession.Close();
            delete ftpConnection;
            m_numFiles = x;
        }
        else
            MessageBox("Cannot open that directory");
    }
    catch (CInternetException* pException)
    {
        pException->ReportError();
    }
        
    Invalidate();
}

19. Load the FTPAppView.h header file, and then add
the lines shown in Listing 24.8 to the class's declaration, in the Attributes section, right after the line CFTPAppDoc* GetDocument().

Listing 24.8 lst24_08.cpp Data Member Declarations

protected:
    CString m_displayStr[20];
    DWORD m_fileLengths[20];
    CString m_curDirectory;
    CString m_ftp;
    int m_numFiles;

20. Still in the FTPAppView.h header file, add the following line to the class's declaration, in the Implementation section, right after the protected keyword:

void PreviousDirectory();

You've now completed the FTPApp application. Select Developer Studio's Build, Build command to compile and link the application, Then, select the Build, Execute command to run your new Internet application. When you do, the application's main window appears.

Select the Connect menu's Make Connection command to choose the address of the FTP server to which you want to connect. This brings up the Make Connection dialog box, which has Macmillan Computer Publishing's FTP server already entered as the default selection (see Figure 24.21). After entering the address, select the OK button. FTPApp will then attempt to connect to the URL and to display the available directories on the FTP server. Figure 24.22 shows FTPApp connected to Macmillan's server.


FIG. 24.21

The application's default FTP server is ftp.mcp.com.


FIG. 24.22

Here's FTPApp connected to Macmillan's FTP server.

To open a directory on the server, double-click the directory. If the directory is accessible to you (not all directories are), the display will change to reflect the contents of the selected directory. The CURRENT DIRECTORY line of the display shows your current location within the server's directory tree. To move back to the previous directory, double-click the >>>PREV DIR<<< line. When you find a file you want to download, right-click the file. FTPApp will transfer the file to your hard drive.

To avoid dealing with scrollbars, the FTPApp sample program displays only a maximum of twenty directories or files. If you want to be sure you can see an FTP server's entire contents, add scrolling to the application's window and change the directory search code so that it can read more than twenty file names. You'll also have to increase the capacity of the various string arrays that hold file names and sizes.

Exploring the FTPApp Application

Connecting to an FTP server is somewhat similar to connecting to a Web site. That is, there are common steps in both tasks, as you can see in the list that follows, which details what you must do to connect to an FTP server and get the contents of its root directory.

1. Create an object of the CInternetSession class.

2. Call the CInternetSession object's GetFtpConnection() member function to connect to the selected FTP server, which creates an object of the CFtpConnection class.

3. Call the CFtpConnection object's GetCurrentDirectory() member function to items in the server's root directory.

4. Create an CFtpFileFind object, which you'll use the browse the server's directories.

5. Call the CFtpFileFind object's FindFile() to start the file browsing process.

6. Repeatedly call the CFtpFileFind object's FindNextFile() and GetFileName() member functions to retrieve the directories and files stored in the current directory.

7. Delete the CFtpConnection object.

8. Call the CInternetSession object's Close() member function.

The following sections will examine how the preceding steps are implemented in the FTPApp application. In addition, you learn how to download files using the WinInet classes.

Creating a CFtpConnection Object

After creating its CInternetSession object (which you already know how to do), FTPApp makes its FTP connection by calling the CInternetSession object's GetFtpConnection() member function, like this:

CFtpConnection* ftpConnection =
    internetSession.GetFtpConnection(m_ftp);

The GetFtpConnection() method's single argument is a string containing the address of the FTP server (i.e. ftp.mcp.com) to which the program should connect. If the connection is made successfully, the function returns a pointer to a CFtpConnection object, which represents the FTP connection. If the function call fails, the return value is NULL. The previous code line is the easiest way to call GetFtpConnection(), which supplies default values for its five arguments. The function's full signature looks like this:

CFtpConnection* GetFtpConnection(LPCTSTR pstrServer,
    LPCTSTR pstrUserName = NULL, LPCTSTR pstrPassword = NULL,
    INTERNET_PORT nPort = INTERNET_INVALID_PORT_NUMBER,
    BOOL bPassive = FALSE); Throw (CInternetException);

If you're interested in gaining greater control over the creation of the CFtpConnection object, you can supply arguments for those with defaults. For more information, look up the function in your Visual C++ online documentation. Table 24.5 lists other member functions of the CFtpConnection class.

Table 24.5 Member Functions of the CFtpConnection Class

Function Description
Close() Closes the server connection
CreateDirectory() Makes a directory on the server
GetCurrentDirectory() Gets the connection's current directory
GetCurrentDirectoryAsURL() Gets the connection's current directory as a URL
GetFile() Gets a file
OpenFile() Opens a file
PutFile() Stores a file on the FTP server
Remove() Removes a file
RemoveDirectory() Removes a directory
Rename() Renames a file
SetCurrentDirectory() Sets the current directory on the server

Reading the FTP Server's Root Directory

Getting the contents of the FTP server's root directory requires several steps. FTPApp starts by calling the CFtpConnection object's GetCurrentDirectory() member function:

ftpConnection->GetCurrentDirectory(m_curDirectory);

This line returns the current directory into the string given as the function's single argument.

The program also creates a CFtpFileFind object to help it browse the files and directories on the FTP server:

CFtpFileFind ftpFileFind(ftpConnection);

The CFtpFileFind class's constructor takes a pointer to a CFtpConnection object as its argument.

The program can now use the CFtpFileFind object to get the names and sizes of the files on the FTP server. To do this, the program starts the search by calling CFtpFileFind object's FindFile() member function:

ftpFileFind.FindFile();

When you start searching an FTP server's directories, you must be sure to call FindFile() first; otherwise, subsequent calls to functions like FindNextFile() will fail.

Now that the search is started, the program can call the CFtpFileFind object's FindNextFile() member function for each file or directory on the server:

gotFile = ftpFileFind.FindNextFile();

To get the complete contents of the current directory, the program must call FindNextFile() in a loop until the function returns zero, which indicates that the function call failed (in this case, due to there being no next file).

Each time the program calls FindNextFile(), it also calls the CFtpFileFind object's GetFileName() and GetLength() member functions, which return the current file's name and size:

m_displayStr[x] = ftpFileFind.GetFileName();
m_fileLengths[x] = ftpFileFind.GetLength();

The CFtpFileFind class features only one other member function, GetFileURL(), which returns the URL of the current file. CFtpFileFind, however, inherits a set of member functions from its base class, CFileFind.

Browsing the FTP Server

Once the program has the contents of the server's root directory stored, the user can select a directory to browse. The user does this by double-clicking the directory, which causes MFC to call the application's OnLButtonDblClk() message response function. This function first converts the location that the user clicked into an index into the arrays that store the file name strings:

int index = (point.y - 60) / 20;

If index ends up as 0, the user has clicked on the previous directory command:

if (index == 0)
    PreviousDirectory();

Otherwise, if index is less than 20, it's a valid index into the file name array, m_displayStr[]. If the indexed string isn't empty, the user has clicked a directory, so the program can try to open that directory.

The first step in this process is to notify the user that the program has received the command. FTPApp does this by drawing a small rectangle next to the selected directory:

CClientDC clientDC(this);
clientDC.Rectangle(6, index*20+62, 14, index*20+74);

The program then starts an Internet session and builds a string containing the path to the requested directory:

CInternetSession internetSession;
CString dir = m_curDirectory + '/' + m_displayStr[index];

Now, the program can make the FTP connection:

CFtpConnection* ftpConnection =
    internetSession.GetFtpConnection(m_ftp);

With the connection in hand, the program sets the current server directory to the one selected by the user:

BOOLEAN dirOpen =
    ftpConnection->SetCurrentDirectory(dir);

If the directory opens successfully, the program stores the current directory in a member variable, creates a CFtpFileFind object, and starts a file search:

m_curDirectory = dir;
CFtpFileFind ftpFileFind(ftpConnection);
ftpFileFind.FindFile();

Finally, the program stores the new directory's contents, as shown in Listing 24.9.

Listing 24.9 lst24_09.cpp Getting the Contents of the Current Directory

BOOLEAN gotFile;
int x = 1;
do
{
    gotFile = ftpFileFind.FindNextFile();
    m_displayStr[x] = ftpFileFind.GetFileName();
    m_fileLengths[x] = ftpFileFind.GetLength();
    ++x;
}
while ((x<20) && (gotFile));
m_numFiles = x;

The program also, at this point, deletes the CFtpConnection object and closes the Internet connection:

internetSession.Close();
delete ftpConnection;

Downloading Files from an FTP Server

The last thing of interest in FTPApp is the way the program enables the user to download a file from the FTP server. The user indicates that he wants to download a file by right-clicking the file's name in the display. This action generates a WM_RBUTTONDOWN Windows message that causes MFC to call the OnRButtonDown() message response function. In that function, the program uses the mouse-click location to calculate the index of the file name in the file name array, just as the program did in OnLButtonDblClk(). The program then starts an Internet session, sets the server's current directory, and finds the requested file. Again, you learned how to do all of this stuff when you studied the OnLButtonDblClk() member function.

If the server reports that the file exists, the application displays a message box, letting the user know that the program is about to download the file:

MessageBox("Click OK to download file");

The program then downloads the file by calling the CFtpConnection object's GetFile() member function:

ftpConnection->GetFile(file, file);

Here, the function's two arguments are the name of the file to download and the name of the downloaded copy of the file. Believe it or not, this one function call is all that's needed to transfer the file to the user's system. If you want more control over the downloading process, you can supply additional arguments for GetFile(). The function's full signature looks like this:

BOOL GetFile( LPCTSTR pstrRemoteFile, LPCTSTR pstrLocalFile,
    BOOL bFailIfExists = TRUE,
    DWORD dwAttributes = FILE_ATTRIBUTE_NORMAL,
    DWORD dwFlags = FTP_TRANSFER_TYPE_BINARY,
    DWORD dwContext = 1 );

As you can see, the additional parameters all have default values, which is why you don't need to supply them. The default value for the third argument, for example, tells GetFile() that, if the file named in the second argument already exists, the new version should overwrite the old one. If you supply this argument as FALSE, the call to GetFile() will fail if the file already exists. The fourth argument lets you set file attributes for the file; the third argument enables you to choose between a binary or ASCII transfer, and the last argument is a context identifier (which is used with Help files). For more details on these arguments, please refer to your Visual C++ online documentation.

Now that you know a little about WinInet, it's time to move on to Microsoft's newest Internet strategy, the ActiveX technologies. The next several chapters of this book are dedicated to this immense and important topic.