Developer.com Click here to support our advertisers
Click here to support our advertisers
SOFTWARE
FOR SALE
BOOKS
FOR SALE
SEARCH CENTRAL
* JOB BANK
* CLASSIFIED ADS
* DIRECTORIES
* REFERENCE
Online Library Reports
* TRAINING CENTER
* JOURNAL
* NEWS CENTRAL
* DOWNLOADS
* DISCUSSIONS
* CALENDAR
* ABOUT US
----- Journal:

Get the weekly email highlights from the most popular online Journal for developers!
Current issue -----
developer.com
developerdirect.com
htmlgoodies.com
javagoodies.com
jars.com
intranetjournal.com
javascripts.com

REFERENCE

All Categories : ActiveX

Chapter 6

Internet Control Pack


CONTENTS

ActiveX's Internet Control Pack provides the developer with a handy, almost full-featured set of drop-in OCXs for using most of the popular Internet protocols.

In this chapter, we'll begin by examining the essential properties of the TCP/IP family of Internet protocols, to better understand important TCP/IP application-layer protocols such as FTP, NNTP, SMTP, and Telnet, to name a few. We'll see how these high-level protocols make use of two important transport-layer protocols-the Transmission Control Protocol (TCP) and the User Datagram Protocol (UDP)-and the network-layer Internet Protocol (IP). The chapter also describes each OCX control in its own section, offering example programs developed in Visual C++ and Visual Basic.

Internet Protocols and Standards

The abstract Internet is a vast, interconnected "network of networks." It includes campus LANs, corporate LANs and WANs, and more, all hooked together with regional mid- to high-speed pipes and very high-speed national and international backbone pipes. The Internet networking protocols are collectively known by two of the best-known examples, Transmission Control Protocol/Internet Protocol, or TCP/IP. This family of protocols is based on open standards, comprising published Requests for Comment (RFCs) and Internet Experiment Notes (IENs). It is organized into four layers, as described in Table 6.1. (These layers are defined in TCP/IP Illustrated, vol. I, by W. Richard Stevens, Addison-Wesley Press, 1994. See "Further Reading" at the end of this chapter.)

Table 6.1 Protocol Layers
LayerProtocols
Application layer (highest)Telnet, FTP, SMTP, NNTP, and others
Transport layerTCP, UDP, and others
Network layerIP and others
Link layer (lowest)Device drivers and interface cards

Layers Upon Layers: The TCP/IP Suite of Services

The notion of layers is a convenient way to think about Internet Services. Consider an example from the highest layer, the FTP application protocol. FTP makes use of services provided by the TCP protocol, which is responsible for dividing data passed by the FTP application into discrete data items (datagrams) and routing them to the IP protocol below it. IP, in turn, is responsible for routing the datagrams from one router to the next on the Internet. Thinking of it another way, many of the application-layer protocols are concerned with end-to-end transmission (two hosts "talking" in a given application protocol, such as FTP or SMTP). In contrast, the network layer (IP) is a hop-to-hop protocol, simply concerned with the very next destination of the packet it is sending.

The IP protocol is called "unreliable" because it does not guarantee that any given packet will get through. The overhead of such a guarantee is borne by the transport-layer protocol, TCP, which sends acknowledgments from the recipient to the sender as packets arrive, and manages a timer so that when certain packets do not arrive, a request for a resend is sent. Furthermore, TCP numbers the packets and reassembles them into proper order at the recipient. This order, however, may be affected by IP because it routes packets in different ways.

Sometimes application layer protocols (such as Telnet) request a handshake end-to-end from the transport layer TCP routines, but the TCP cannot establish an end-to-end link with the requested domain. In that case, error messages such as "Host unreachable" are sent around the Internet by the Internet Control Message Protocol, ICMP, a lower (network layer) protocol that's in the same layer as IP.

In general, anytime an application cares about proper reassembly of (multiple) packets as they make their way from sender to recipient, the TCP service should be used. TCP automatically chops up a long stream of bytes into datagram entities, transparent to the application.

TCP is a full-duplex protocol; this means data can flow from host A to host B, from host B to host A, or in both directions simultaneously. The protocol establishes datagram sequence numbers and acknowledgment mechanisms at both hosts after the initial connection is made. Because it offers bi-directional transfer and needs to handshake at both ends to set up shop, TCP is a connection-oriented protocol. And, since the connection sets up a byte stream, the two hosts are simply sending ASCII data to each other, with TCP deciding how application writes will be divided up into datagrams. The details of separate occurrences of an application write are lost at the other end, since the recipient sees only IP packets properly reassembled by the TCP protocol.

On the other hand, if an application is writing a discrete burst of data (not a long stream) and this burst fits into a datagram, then the overhead of TCP can be avoided by using User Datagram Protocol, UDP. The datagram in turn must be shorter than the Maximum Transmission Unit, or MTU, of the network. (For more information, see the Stevens book referenced earlier and in "For Further Reading" at the end of this chapter.)

UDP provides the simple service of passing datagrams from an application's writes to the IP network layer but does not attempt to correct IP's inherent unreliability. UDP has neither the timer mechanism nor the packet-receipt acknowledgment mechanism of its weightier cousin, TCP. Nor is UDP connection oriented, since there is no handshaking between hosts before UDP sends a datagram to IP to be routed.

If it uses the UDP service, an application must accept the risk that some of the datagrams will not reach their destination. Some applications nevertheless are naturally suited to the UDP protocol. Consider a common Internet task: the resolution of a domain name (such as edgar.stern.nyu.edu) into its IP number, 128.122.197.196. A Domain Name Server (DNS) must be consulted to translate the domain name into the IP number, but the query itself is very small and can easily fit into one datagram. Using UDP to query DNS costs less network overhead, because if there's no answer from DNS within a few seconds, the datagram query can be resent with the assumption that the first query got misplaced somewhere along the way. Choosing UDP incurs the risk that an application can write datagrams faster than the network can handle, or that an application writes a larger datagram than the other host's read buffer size.

The moral of the story is that choosing the transport layer is critical when designing an application that will make use of the Inter-networking protocols. The developer must be concerned not only with parsing expected responses but also handling exceptional network conditions-such as severe congestion leading to time-outs; or crashes of the server or intermediate router, leading to "Host Unreachable" errors.

From the developer's perspective, functions of the bottom three layers are usually carried out at the operating system ("kernel") level and are abstracted away from the application. So it is with the OCX Internet Controls: If the application makes use of TCP, the byte stream will be chopped up into datagrams and sent with error-correction facilities built into the TCP/IP services.

Protocols and Ports

Let's assume for the moment that a standard Internet mail application is started, using SMTP protocol. As noted above, application-layer protocols are end-to-end and the client must specify a server host name. The standard mail port is 25, so the fully qualified server is the host name concatenated with the port number. SMTP makes use of the TCP services, so once the connection is established, TCP establishes a byte stream end-to-end. This ensures the integrity of the mail connection. (Recall that missing packets are resent, and out-of-order packets are resequenced.)

Many of the Internet client/server protocols permit interactive clear-text commands; some of the newer ones are based on C program structures and require encoded (binary) interaction. From the client side, a connection is usually opened to the server on a well-established port. The Internet Assigned Numbers Authority, IANA, coordinates the assignment of unique port numbers to the Internet protocols. Table 6.2 gives a subset of the standard ports from a UNIX host (as found in the ASCII UNIX system file /etc/services).

Table 6.2 Common Ports on a UNIX Host for TCP/IP and UDP Connections
Service
Port
echo
7/tcp
echo
7/udp
discard
9/tcp
discard
9/udp
systat
11/tcp
daytime
13/tcp
daytime
13/udp
netstat
15/tcp
chargen
19/tcp
chargen
19/udp
ftp-data
20/tcp
ftp
21/tcp
telnet
23/tcp
smtp
25/tcp
time
37/tcp
time
37/udp
name
42/udp
whois
43/tcp
domain
53/udp
domain
53/tcp
gopher
70/tcp
hostnames
101/tcp
pop-3
110/tcp
finger
79/tcp
nntp
119/tcp
talk
517/udp
route
520/udp

Note that the ports in Table 6.2 are not required but are only examples of what is considered a standard configuration. However, if the system administrator elects to map services that the IANA does not certify as standard, would-be clients will have a very hard task starting an application protocol session with the oddball server.

With many of the well-established ports, it is possible to Telnet to the port, issue commands to the server, and verify its response codes to input as defined by the protocol's specifications. Some ports will respond to a "telnet [port #]" command with some sort of output, but many expect input data to be properly formatted and will reject or ignore the connection request.

For example, it is possible to Telnet to the mail port and type SMTP commands interactively to communicate directly with the mail server. These commands are in readable ASCII and are not encoded. In fact, this has been a traditional way for attackers to try to break into a site-by Telnetting to the mail port and issuing commands to trick the mail server into allowing access to the machine. Similarly, the HTTP Web protocol is also based on TCP. The HTTP server listens on port 80 for a Web request and will respond with clear text to commands typed into a Telnet session at port 80.

Interactive vs. Noninteractive Protocols

The NNTP, SMTP, POP3, and FTP controls use protocols that are "interactive." That is, a client establishes a connection to the host, which remains open until some event occurs (for example, the user issues a Quit command) to break the connection.

Two of the OCXs, the HTML and HTTP controls, use an application-layer protocol that is not typically interactive: HTTP. The client issues a request and receives a response, and that is usually the end of the transaction. The HTTP protocol was designed with this property of "statelessness." It is lightweight and aims to avoid persistent connections in its native state. The result is that the client request to the server and the server's response take, on average, only a few hundred milliseconds.

The HTTP 1.0 specification attempted to offer a means of maintaining an open connection between client and host via the Keep-Alive header. This header is, however, somewhat dangerous. In their draft HTTP 1.1 specification, authors Fielding, Frystyk, Berners-Lee, Gettys, and Mogul point out that an HTTP 1.0 client may send Keep-Alive notices to a proxy that does not understand the notices. The result is most undesirable: a dead proxy waiting for a close connection that never comes. The draft spec describes the "buggy" HTTP 1.0 persistence implementation in detail. It closes by suggesting a new keyword, persist, that would only be valid in the context of an HTTP 1.1 message and would be the default for HTTP 1.1 messages. The spec's authors suggest a second keyword, close, to declare nonpersistence. The draft HTTP1.1 specification is available on line at

http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v11-spec-03.txt

SMTP

The Simple Mail Transport Protocol, SMTP, is one of the oldest Internet protocols, with RFC821 dating back to 1982. SMTP is one of the first protocols to use the request/response format seen in protocols developed later.

The request/response format specifies that client commands are entered on a new line, followed by a space, followed by parameters (which may be optional), and ending with a carriage return/line feed (CRLF) sequence-a format also known as Net ASCII. The response is formatted as a three-digit code, intended for program use, followed by a response string suitable for human consumption.

Listing 6.1 shows a sample SMTP session. This is the basic process of one client connecting to another via SMTP to deliver e-mail. This sample was executed by a human, but the process is essentially the same with two machines talking to each other programmatically.


Listing 6.1 A sample SMTP session
telnet edgar.stern.nyu.edu 25
Trying 328.422.597.696 ...
Connected to edgar.stern.nyu.edu.
Escape character is '^]'.
220 edgar.stern.nyu.edu Sendmail 4.1/1.34 ready at Mon, 17 Jun 96 18:26:00 EDT
help
214-Commands:
214-    HELO    MAIL    RCPT    DATA    RSET
214-    NOOP    QUIT    HELP    VRFY    EXPN
214-For more info use "HELP <topic>".
214-smtp
214-To report bugs in the implementation contact Sun Microsystems
214-Technical Support.
214-For local information contact postmaster at this site.
214 End of HELP info
rcpt to: ebt@panix.com
250 ebt@panix.com... Recipient ok
data
503 Need MAIL command
mail from: jsmith@edgar.stern.nyu.edu
250 jsmith@edgar.stern.nyu.edu... Sender ok
data
354 Enter mail, end with "." on a line by itself
this is a test. there is nothing wrong with your computer.
.
250 Mail accepted
quit
221 edgar.stern.nyu.edu delivering mail
Connection closed by foreign host.

Clearly, this is a difficult method for humans to send e-mail. It has fostered the development of wonderful inventions such as the Sendmail program, which any UNIX administrator loves to work with. There are also GUI client applications such as Eudora and Microsoft Exchange, which shield the user from the complexities of SMTP.

SMTP Response Codes

Though not required for use of SMTP and other controls, it is helpful for the developer to be familiar with the response codes. With most of the protocols, these response codes can be examined for very specific information necessary for debugging situations.

TIP
Enhancing the SMTP Text Response Codes. Some of the text response strings are not very informative. The developer should consider providing more informative text within a program based on the response code. This is particularly true of the Microsoft controls because they are new. At this stage, developers will need to maintain a do-it-yourself spirit to work with the Microsoft controls; they are not going to do all the parsing we need right off the bat.

With each protocol, the first digit of the three-digit code indicates a response group. The SMTP RFC does not spell out these groups explicitly, however. Table 6.3 indicates the general nature of each group for SMTP; the other protocols group response codes into similar schemes, although there are variations in the definition of each group. The second digit of the response code is not defined in the SMTP RFC, as it is with later protocols, in which this digit indicates the function or service referred to by the code.

Table 6.3 SMTP Response Code Groups
First Digit
Meaning
1nn
Informative message.
2nn
Command okay.
3nn
Command okay so far; continue.
4nn
Command was correct, but was aborted or not performed.
5nn
Command not implemented, incorrect, or other error.

Table 6.4 lists some common response codes, grouped numerically.

Table 6.4 SMTP Response Codes
Response Code
Response Message
211
System status, or system help reply.
214
Help message.
220
<domain> service ready.
221
<domain> service closing transmission channel.
250
Requested mail action okay, completed.
251
User not local; will forward to <forward-path>.
354
Start mail input; end with <CRLF>.<CRLF>.
421
<domain> Service not available, closing transmission channel.
450
Requested mail action not taken; mailbox unavailable.
451
Requested action aborted; local error in processing.
452
Requested action not taken; insufficient system storage.
500
Syntax error; command unrecognized.
501
Syntax error in parameters or arguments.
502
Command not implemented.
503
Bad sequence of commands.
504
Command parameter not implemented.
550
Requested action not taken; mailbox unavailable.
551
User not local; please try <forward-path>.
552
Requested mail action aborted; exceeded storage allocation.
553
Requested action not taken; mailbox name not allowed.
554
Transaction failed.

E-Mail Header Format

SMTP requires that in order for e-mail to be delivered, two header fields must be supplied: From and To. Listing 6.1 showed how the user tried to enter the text of a message without first supplying the From header, prompting SMTP to respond with error 503 Need MAIL command. Other header fields that can be supplied include Subject, CC, BCC, Errors-To, and Reply-To.

Reading and Receiving E-Mail: POP3

The Simple Mail Transport Protocol does not support the receipt of e-mail; that is, it does not support mail server functionality. Before the advent of the Post Office Protocol (POP) in the late 1980s, users logged on to the mailhost machine, which was running a mail server, to read e-mail. Now POP specifies a standard port and standard methods for users to read and/or receive e-mail without initiating a terminal session.

Typically, a home user can log on to the network using a SLIP or PPP connection. After authenticating with a username and password to the POP server, network mail can be retrieved at the home computer. This is a much more practical situation than logging on to the mailhost or setting up the microcomputer to be a mail server. The home user generally does not want to be in the business of providing network services. POP3, a variation of POP, is the protocol used for the Microsoft control.

POP and POP3 allowed for the development of various client programs that made it unnecessary for users to learn arcane UNIX-based programs. Many Windows users would likely find the mail program and others like it impossible to use, accustomed as they are to GUI clients such as Qualcomm's Eudora and Microsoft's Exchange and Internet Mail products. These friendlier client programs have fostered great numbers of new users on the Internet. Browsing the Web is great, but e-mail is a necessary function for all users. GUI e-mail client programs allow Internet access providers to sell accounts without having to implement the additional security mechanisms needed with shell accounts on a host system.

POP3 does not have the usual group of response codes and strings seen in the other protocols. The usual response is either -ERR followed by an informative text string, or +OK followed by a text string. Because the command set for a POP3 server is much smaller than the other protocols, extended codes aren't needed.

The Tried-and-True FTP

The File Transfer Protocol (FTP), as defined in RFC959 (circa 1985), amusingly states that FTP was designed to be used mainly by programs, not human users. Most pre-Web Internet participants, though, are no doubt quite familiar with using FTP in terminal mode.

FTP has long been one of the primary methods of transferring data, particularly data considered too lengthy to place in an e-mail message. With the advent of the World-Wide Web, however, FTP's role has been somewhat subsumed by Web browser client programs. Today Internet host machines that do not need to provide upload capabilities can still offer files for download simply by providing a link to the file on a Web site. Some recent Web servers, including Netscape, have even given users the ability to upload files via their browser.

The default "type" for file transfers, upon logging onto an FTP server, is ASCII. The developer will want to give users a choice of file transfer type, but programs using the FTP control may as well change the type to binary upon log-on, because transferring a binary file with the type set to ASCII will corrupt the file.

FTP Response Codes

FTP responses take the form

[reply code] [reply string]

where the reply string is a text description of the reply code, and the code is a three-digit number. In Table 6.5, which summarizes the response groups, the second digit (y) indicates the "function grouping" of the response. The third digit (z) references a specific message. The response groups have quirky definitions in RFC959 and are somewhat indecipherable. Refer to the RFC for the complete list of errors if the response group proves insufficient for debugging.

Table 6.5 FTP Response Code Groups from RFC959
1yz
Positive Preliminary reply
2yz
Positive Completion reply
3yz
Positive Intermediate reply
4yz
Transient Negative Completion reply
5yz
Permanent Negative Completion reply

NNTP

The Network News Transfer Protocol (NNTP) was originally developed to cut down on network traffic generated by rapidly multiplying e-mail lists. In today's Internet, with Web browsers able to access Usenet, network news accounts for a significant percentage of Internet traffic-particularly with the advent of news readers that can uudecode data on the fly, interpret HTML tags, and perform other such tasks.

NNTP is one of the interactive protocols that you can try out manually via Telnet (if you have access to a news server). By Telnetting to port 119 (assuming the server uses that standard port), logging in if necessary, and typing help, you'll receive an arcane and daunting list of commands. Fortunately, the NNTP control provides easy access to the NNTP commands.

An NNTP command can give two types of responses: textual and status. Textual responses follow a status response and are terminated by a period on a line by itself. (There are a few additional minor rules.) NNTP status response format is similar to what we've already seen-a three-digit status code followed by a text string explaining the code.

Status response groups are indicated by the first digit, as shown in Table 6.6, from RFC977. The second digit of the response code indicates the related function, as listed in Table 6.7.

Table 6.6 NNTP Response Code Groups (from RFC977)
First Digit
Meaning
1nn
Informative message.
2nn
Command okay.
3nn
Command okay so far; send the rest of it.
4nn
Command was correct, but couldn't be performed for some reason.
5nn
Command not implemented, or incorrect, or a serious program error occurred.

Table 6.7 NNTP Response Code Function Categories (from RFC977)
Second Digit
Meaning
n0n
Connection, setup, and miscellaneous messages.
n1n
Newsgroup selection.
n2n
Article selection.
n3n
Distribution functions.
n4n
Posting.
n8n
Nonstandard (private implementation) extensions.
n9n
Debugging output.

HTTP and HTML

Both the HTTP and HTML controls use the Hypertext Transfer Protocol. HTTP is another command/response protocol that you can experience directly via Telnet to a Web server at port 80. Usually no log-in is necessary, and typing the command GET / will display the raw HTML for the Web site's home page. Then the HTTP server closes the connection.

In terms of requests and responses, the HTTP specification is considerably more complicated than other protocols. It has the usual response code/response message we've already discussed, plus a number of standard response headers that a client application needs. Consult RFC1945 for full details on the header formats.

Table 6.8 summarizes the response code groups for HTTP as defined in RFC1945.

Table 6.8 Response Code Groups for HTTP, from RFC1945
First Digit
Meaning
1xx
Informational; not used, but reserved for future use.
2xx
Success; the action was successfully received, understood, and accepted.
3xx
Redirection; further action must be taken in order to complete the request.
4xx
Client error; the request contains bad syntax or cannot be fulfilled.
5xx
Server error; the server failed to fulfill an apparently valid request.

Unlike the other protocols discussed in this chapter, the HTTP protocol leaves the second digit of response code undefined. A number of suggested values and text strings are defined in RFC1945, but these can be overridden at the local level. Furthermore, the specification allows for extended code groups that can also be locally defined.

TCP

We've discussed TCP's position in a lower network layer than the other protocols (except UDP). Using the TCP OCX and some imagination, you could probably re-create and/or enhance the functionality of the NNTP, POP3, SMTP, and FTP OCXs. We won't do that here. Instead, we'll focus our attention on another application layer protocol, the Telnet protocol.

Microsoft did not create a Telnet OCX; the NT environment does not contain a Telnet service. At least one third-party vendor does, however. To try out their Slnet Telnet Service for Windows NT, see

http://www.seattlelab.com/

There are at least a couple of difficulties inherent in creating a Telnet service under the Windows NT environment. First, in the NT command console, a user can start up both character-based and GUI applications. So the Telnet server would have to distinguish between them and disallow execution of the GUI programs. Second, authentication under NT is considerably more complex than it is in other environments-notably UNIX, where character-based files are consulted to authenticate users.

To create a Telnet server, then, you'd have to create some "helper" applications to get past these difficulties. Although we will not attempt to create robust UNIX-like versions of Telnet server and client applications, in the following sections we will explore this topic with the TCP control.

UDP

The User Datagram Protocol (UDP) was discussed at the outset of this chapter. A unidirectional protocol, UDP is most commonly used for such services as routing, DNS, and the like. It is also used for a few miscellaneous services found in the UNIX environment.

One of these UNIX services is talk, with which one or more users can communicate interactively (on a local machine) via a character-based window. Our UDP example demonstrates one method of implementing a Talk client that uses the User Datagram Protocol to exchange data.

For Further Reading

If you want to invest in a thick reference book chock-full of information on the nuts and bolts of how bits fly around the Internet, pick up TCP/IP Illustrated, vol. I, by W. Richard Stevens (Addison-Wesley Press, 1994). This excellent volume covers all the Inter-networking protocols in great detail, making use of diagnostic software on various platforms to illuminate the operations of the protocols at all four layers. Volumes II and III are also available.

A shorter, on-line reference (which necessarily omits some details and is a little dated but still quite educational) is Charles Hendrick's Introduction to the Internet Protocols, published under the auspices of Rutgers University, at

http://oac3.hsc.uth.tmc.edu/staff/snewton/tcp-tutorial/

The Internet Assigned Naming Authority has a plethora of information on line at its Web site:

http://www.iana.org/iana/overview.html

This hardworking group must coordinate port numbers, protocol numbers, domain names, and routing system information numbers-in short, quite a juggling task.

Using the Internet Control Pack

The latest version of the Internet Control Pack is available at

http://www.microsoft.com/icp/

As mentioned above, these controls come to Microsoft by way of NetManage. At this writing, NetManage had posted a "final" release of their version of the controls on their Web site at

http://www.netmanage.com/

When we couldn't get a couple of things to work with the Microsoft version, we tried this final release from NetManage and got better results. We expect that the version on the Microsoft Web site will be updated shortly.

Installing the Internet Control Pack

From the Microsoft site you can download an EXE file containing the controls. Executing this file expands and copies the controls to the \System32 directory for NT users or the \System directory on Win95 systems. Registry entries are created for the following files: WINSCK.OCX, FTPCT.OCX, SMTPCT.OCX, POPCT.OCX, NNTPCT.OCX, HTTPCT.OCX, and HTMLOCX, as well as a library file, NMOCOD.DLL.

A help file is also installed. This is the sole source of documentation for the controls.

NOTE
If you installed and used the beta 1 version of the controls, refer to the above-mentioned Microsoft Web site for installation notes regarding converting to the beta 2 and later versions of the Microsoft controls.

Adding Controls to Applications

Adding a control to a VB project is a simple matter of choosing Tools | Custom Controls, and then selecting the desired controls. The controls are then added to the VB Toolbox and can be inserted in a form.

Most likely you will also need to access the DocInput, DocOutput, and other collections. Go to the Tools | Reference dialog and check the box for the Microsoft Internet Support Objects item. This will include a reference to the NMOCOD.DLL library that was installed with the ICP.

The HTML control is the only one that can be resized, because it is the only control that can use the screen to draw its data. For all of the other controls, the developer has to create text or other boxes to display data to the user.

CAUTION
The Microsoft documents state that these controls require two machines to test out, implying that the user has control of two machines. But not everyone has administrative privileges to a second Internet-connected machine. With several of the controls, it is possible to test a program on a single machine using the loopback address 127.0.0.1. When that is not possible (for example, with the NNTP or SMTP/POP3 controls), you will want to exercise maximum caution when testing. There is nothing more annoying to a system administrator than to find a machine slowly dying because of process overload or "out-of-socket" errors

Visual C++

There are a number of ways to add a control to a Visual C++ project. The method used will depend on whether the application is dialog based. All of our examples are dialog based, so we will explain the process here. (For further information on adding OLE controls to existing applications, see Inside Visual C++, by David J. Kruglinski, Microsoft Press, 1996.)

When creating a new project with AppWizard, turn on the Support for OLE Controls option. This will add a call to AfxEnableControlContainer when the application is initialized. For existing projects, you can add the call manually, as shown here:

// CExectrlApp initialization
BOOL CExectrlApp::InitInstance()
{
	AfxEnableControlContainer();

After adding a control to a dialog in the resource editor, you then need to add a member variable to the project. At this point the ClassWizard will prompt you to insert the control into the project. Class Wizard then creates and inserts the appropriate source and header files for the control.

When you look at the files created, it may seem that several functions have not been generated correctly. For example, the following is from the file httpct.h, generated after inserting the HTML control into a project:

// Operations
public:
	// method 'QueryInterface' not emitted because of invalid return type or parameter type
	unsigned long AddRef();
	unsigned long Release();
	// method 'GetTypeInfoCount' not emitted because of invalid return type or parameter type
	// method 'GetTypeInfo' not emitted because of invalid return type or parameter type
	// method 'GetIDsOfNames' not emitted because of invalid return type or parameter type
	// method 'Invoke' not emitted because of invalid return type or parameter type
	void AboutBox();

All those "method not emitted" lines are normal, part of the wrapper class mechanism. So if you encounter difficulties using the controls, this is not the reason. The methods in the control itself are all directly accessible. Later, in the sample applications, we will show you how to access methods and properties in the other classes generated.

The Keys to the Internet Control Pack

There are several important keys to working with the controls: setting up the VARIANT data type, using the various support objects, and understanding how events occur.

VARIANTs

A VARIANT in VC++ is a structure that consists of a variable, the letters vt, a few reserved variables, and then a union. VARIANTs are used to exchange data between OLE automation servers and clients.

The VARIANT structure, when set up with some data, will consist of two members: the type of data being exchanged, and the actual data to exchange (the reserved variables can be ignored). The Visual C++ reference material describes VARIANTs completely; for our purposes here, the question is how to get data into a VARIANT that will work. Just about everything will throw a C++ exception at run-time (the program will compile, though!), which is quite frustrating. Tracing down into the program, the exception is usually "Unsupported variant types."

For most of our examples, we used the same method to set up the VARIANTs. In the case of sending data, we used the following:

VARIANT [variant name]
[variant name].vt = [variant type]
[variant name].[variant type] = [value]

For example, in the TCP dialog at the end of this chapter, the Connect() function requires that two VARIANTs be set with the hostname and port to which we want to connect. If we have a CString variable, say m_host, that contains the value we want to pass to the Connect function, we put that value into a VARIANT like this:

CString m_host = "edgar.stern.nyu.edu";
VARIANT vtHost;
vtHost.vt = VT_BSTR;
vtHost.bstrVal = m_host.AllocSysString ( );

This breaks down as follows: vt is the first member of the VARIANT structure, which holds the VARIANT type. The third statement sets vt to the appropriate value matching the type of data we want to pass, in this case bstrVal, which is a length-prefixed, null-terminated string. On the right side of the statement, AllocSysString, a member of the CString class, is used to convert the string from a CString to a bstrVal. Assuming we've initialized vtPort in the same fashion, we can then call the Connect function with the statement Connect(vtHost,vtPort).

To get data out of a VARIANT, the function that retrieves the data will typically have two parameters, data and type. You will only have to set the value of the type parameter. Although we expect to receive a certain type of data, what we found worked most often was to simply set the type parameter to VT_ERROR. For example, in the POP3 example, we used

CString m_MBody;
VARIANT data,type;
type.vt=VT_ERROR;
cDocOutput.GetData(&data,type);
m_MBody=data.bstrVal;

Now we have the data from the GetData function in a CString variable named m_MBody-just what we wanted.

Our purpose here has been to merely give you an idea of what works in conjunction with the controls, but you'll find the overall topic of using VARIANTs to be very rich indeed. If you run into difficulties with "Unsupported variant types," the best bet is to study the reference pages in VC++ and try setting your VARIANTs to different types.

ICP Support Objects

If you've tried inserting one of the controls into a VC++ dialog and created a member variable, you will have noticed that several other classes were created in addition to the control's class: CDocHeader, CDocHeaders, CDocInput, CDocOutput, CDocError, and CDocErrors. These are the ICP support objects. (The TCP and UDP controls do not use these objects.) In our sample applications we employ these objects only minimally in VC++, but gaining access to them may cause you much consternation. There they are in your project, but no matter what you try, nothing seems to work.

We have used the DocHeaders and DocInput classes in the SMTP example, and the DocOutput class in the NNTP and POP3 examples. Here's what you need to do: Create an object of the type you need in your application. For instance,

CDocOutput cDocOutput;

will create a CDocOutput type object. Now you can get access to the functions in the CDocOutput class.

Let's look at the first few lines from the OnDocOutputPop function in our POP3 example:

void CPopDlg::OnDocOutputPop(LPDISPATCH DocOutput) {
	DocOutput->AddRef();
	cDocOutput = DocOutput;
	lDocOutputState = cDocOutput.GetState();

The OnDocOutputPop function is called when the DocOutput event occurs, which we explain in the next section. When this function is called, a variable of the type DocOutput is passed to it with whatever data is available. As you can see, we can set our cDocOutput object equal to the data passed into the function, and then call member functions of the DocOutput class. (Later we will explain the AddRef call.)

Everything in these objects is accessible in VC++; nevertheless, we can see why Microsoft states that these controls are designed for use in Visual Basic. Later in the chapter you'll study an example of an FTP Client, developed with VB. In this example, accessing the DocOutput object is very straightforward.

Events

The third piece of the ICP puzzle is the processing of events. An event can be either a user action, such as clicking a button on a form, or it can be something outside the user's control, such as an event coming from the Internet server with which the application is communicating.

The user-generated events are easy to deal with because you define what events the user can generate. What the Internet server can do is another matter. After your application sends some piece of data, any number of events can and will occur, and not always in the order that you expect.

The simplest way to handle these events is to use the ClassWizard to generate functions that are defined in the application's message map. For example, in the UDP dialog, we added a function to respond to the Data Arrival event. It starts off as follows:

void CUdpDlg::OnDataArrivalUdpControl(long bytesTotal)
{
	TRACE("CUdpDlg::OnDataArrivalUdpControl\n");

This function gets called whenever data arrives. The bonus is that we now know how much data has arrived, through the bytesTotal variable passed into the function.

In the same application, we have an OnSend function (that we created); it gets called when the user clicks on the Send button. If we had attempted to read the response from the server in this function, instead of the appropriate OnDataArrivalUdpControl function, we would never see any data or we would get unpredictable results.

Another thing to understand about events is the order in which they occur. This varies from control to control, depending on the protocol. Our best advice is that you add a function for each possible event and put a TRACE statement with those functions. This will allow you to see what events are occurring, and in what order, in response to your application's actions. For example, in the POP3 dialog we at first tried to automate the Connect/Authenticate sequence into one step. To do this, we were placing code in the OnStateChangedPop function. When this didn't work, we discovered that the OnProtocolStateChangedPop function was not in the correct place.

To give you a perspective of how these ICP keys work together, let's start off by looking at a couple of programs developed in ICP's natural habitat, Visual Basic. The methods outlined in these examples should work just as well with HTML and HTTP controls.

NOTE
We are grateful to Joe La Valle for developing our Visual Basic examples.

A Visual Basic FTP Client

Our first sample application is an FTP Client similar to the popular WS_FTP program, featuring a point-and-click interface for transferring files. Figure 6.1 shows the application's VB form. This application shows how simple it is in VB to get access to the Internet support objects, one of the ICP keys.

Figure 6.1 : The FTP Client starting point.

In the FTP program, we need to use the DocOutput object when data is coming to our application from the remote host. This incoming data will trigger the FTP control's DocOutput event. By adding the reference to Internet support objects when creating the VB project, this user-defined data type is defined for us.

When the FTP1 DocOutput event occurs, the function declaration includes this data type as an incoming parameter:

Private Sub FTP1_DocOutput(ByVal DocOutput As DocOutput)

Then a couple of lines further into the procedure, we have easy access to the properties and methods in this DocOutput object. For instance, the State property can be used to determine what action to take on the DocOutput event:

Select Case DocOutput.State
Case icDocBegin
        Debug.Print "DocOUT:Begin"
        FTP1.Tag = ""

and so on. This is pretty straightforward compared to what we will see later when using the DocOutput object in Visual C++.

Listings 6.2 through 6.4 contain all the source code for Joe's FTP Client. (Listings 6.3 and 6.4 appear later in this chapter.)


Listing 6.2 FTP Application Form
'== Connect Button
Private Sub btnConnect_Click()
' Connect
FTP1.Connect txtHostname.Text, txtPort.Text
' Wait for Something to Happen
Do While (FTP1.State = prcConnecting) Or (FTP1.State = prcResolvingHost) Or _
      (FTP1.State = prcHostResolved)
    DoEvents
Loop
    
' If there was an error, quit the sub
If (FTP1.State <> prcConnected) Then Exit Sub
    
' Wait and See if anything happens to status
Do While (FTP1.State = prcConnected) And (FTP1.ProtocolState = ftpBase)
   DoEvents
Loop
' Authenticate connection
FTP1.Authenticate txtUserid.Text, txtPassword.Text
End Sub


'== DisConnect Button
Private Sub btnDisconnect_Click()
' Terminate Connection
FTP1.Quit
' Clear Fields
RemoteFileList.Clear
RemoteDirList.Clear
' Update Display
StatusBar.Panels(1).Text = "DisConnected"
End Sub


'== Button to transfer files from remote host
Private Sub btnFromRemote_Click()
If RemoteFileList.Text = "" Then Exit Sub
' Get the file
FTP1.GetFile RemoteFileList.Text, LocalDirList.Path & "\" & RemoteFileList.Text
' Set the status
StatusBar.Panels(1).Text = FTP1.ReplyString
' Update the display
LocalFileList.Refresh
End Sub


'==Button to list files on the remote host
Private Sub btnListFiles_Click()
' Set the Pointer to look like it is waiting
frmFtpMain.MousePointer = 11
' Clear fields for iew items
RemoteFileList.Clear
RemoteDirList.Clear
' Add the root path always
RemoteDirList.AddItem "./"
'  Do the List
FTP1.List RemotePath

End Sub

'== Button to transefer files from remote host
Private Sub btnToRemote_Click()
' Transfer the file selected in the Local File Listing
FTP1.PutFile LocalFileList.FileName, "./" & LocalFileList.FileName

' ==Display the Reply after the transfer
StatusBar.Panels(1).Text = FTP1.ReplyString
' List the Files

End Sub

'== Form load Subprocedure
Private Sub Form_Load()
' Set the Remote Path
RemotePath = "./"
' Set the Status
StatusBar.Panels(1).Text = "Not Connected"

End Sub

'== When the user clicks on the local directory list, it 
'== updates the local file listing.
Private Sub LocalDirList_Change()
' Update the Local File List
LocalFileList.Path = LocalDirList.Path
End Sub


'== When the user enters the local file area, 
the user can '== now transfer files to the remote.
Private Sub LocalFileList_Click()
' Allow the transfer of files (Putfile)
btnToRemote.Enabled = True
End Sub

'== When the user double clicks on the 
Remote Directory '== list, the directory and file listings are updated
Private Sub RemoteDirList_DblClick()

' Set Path
RemotePath = RemoteDirList.Text
' Clear list boxes
RemoteFileList.Clear
RemoteDirList.Clear
' Call the List Command
btnListFiles_Click
End Sub

'== When the user clicks on the remote file listing, 
'== they now have the ability to send files to the 
'== local machine.
Private Sub RemoteFileList_Click()
btnFromRemote.Enabled = True
End Sub

Listing 6.3 is the code for the various FTP control events that we have chosen to respond to. We're never very big on error-checking, but in this example we use the Error object to provide the user a message when an error occurs.


Listing 6.3 FTP Control code
'== Authenticate the session

Private Sub FTP1_Authenticate()
    StatusBar.Panels(1).Text = FTP1.ReplyString
End Sub

Private Sub FTP1_Connect()
btnListFiles.Enabled = True
End Sub


'== This is where the output is returned 
Private Sub FTP1_DocOutput(ByVal DocOutput As DocOutput)
    Dim Data As String
    Dim length As Long
    
    Select Case DocOutput.State
    Case icDocBegin
        Debug.Print "DocOUT:Begin"
        FTP1.Tag = ""
    Case icDocData
        Select Case FTP1.Operation
        Case ftpFile
        Case ftpList, ftpNameList
            ' Get the Data here
            Call DocOutput.GetData(Data)
            ' Determine length
            length = Len(Data)
            ' Set the Global Variable
            FTPListData = Data
            ' Do the parsing and get the listing here
            GetListing
        End Select
        
    Case icDocEnd
        ' No more data, change mouse pointer back
        frmFtpMain.MousePointer = 0
        Debug.Print "DocOUT:End"
    Case icDocError
        ' Error
        frmFtpMain.MousePointer = 0
        Debug.Print "DocOUT:Error"
    Case icDocHeaders
        Debug.Print "DocOUT:Headers"
    Case icDocNone
    Case Else
    End Select
End Sub

'==Trap the errors here
Private Sub FTP1_Error(Number As Integer, Description As String, 
Scode As Long, Source As String, HelpFile As String, HelpContext As Long, 
CancelDisplay As Boolean)
    StatusBar.Panels(1).Text = Description                 
' Update status with error description...
End Sub

Parsing

Along with the FTP control and the form code, several subprocedures are in the module FTPAPP.BAS (Listing 6.4). They illustrate how to parse the output from the remote host-probably the most difficult aspect of using the Internet controls (once you understand the ICP keys, of course). With FTP, the format of the output is fairly predictable, so parsing is mainly just a matter of studying the FTP specification to determine what to do. Later we will look at the difficulties encountered when dealing with other protocols.


Listing 6.4 FTP application subprocedures
'== General Program Definitions done here
Option Explicit

Public FTPListData
Public FTPReady
Public RemotePath As String
' The types we can receive in the GetListing SubProc
Public Const FTFOLDER = "Folder"
Public Const FTFILE = "File"
Public Const FTSHORTCUT = "Shortcut"
Public Const FTCHARDEV = "Charicter Device"
Public Const FTBLOCKDEV = "Block Device"
Public Const FTUNIXDS = "Unix Domain Socket (BSD)"
Public Const FTNAMEDPIPE = "Named Pipe (ATT)"
Public Const FTPDIR = "<DIR>"
Public Const FTPSHORTCUT = "->"
Public Const FMTDATETIME = "mm/dd/yy hh:mm AMPM"
' The FTP Type, we don't use the full power of this structure in this example
' Feel free to experiment!!!
Type FTPType
    FileName As String
    Date As String
    Size As Long
    Type As String
    Attrib As String
End Type



'== This subprocedure does the real grunt work, it 
'==  takes the listing returned by FTP1_DocOutput and 
'== parses it, then displays the results.

Sub GetListing()
Dim FTPItem As String
Dim FileInfo As FTPType
Dim eoLine As Long
Dim Attr As Integer
Dim FormattedFile As String
Const Spaces = "                    "

On Error GoTo cError

' Check First Line
If (UCase(Mid(FTPListData, 1, 5)) = "TOTAL") Then
' Find out where the eol is
  eoLine = InStr(1, FTPListData, vbCrLf)
' Take the first line
  FTPListData = Mid(FTPListData, eoLine + 2)
End If
        
' Repeat until empty
Do While (FTPListData <> "")
   eoLine = InStr(1, FTPListData, vbCrLf)
' Adjust end of line if needed
If (eoLine < 1) Then eoLine = Len(FTPListData)
          
  FTPItem = Mid(FTPListData, 1, eoLine - 1)
    If (FTPItem = "") Then
         Exit Do
    End If
            
' Parse the info
Call ParseFTPType(FTPItem, FileInfo)
            
' Is Current Item A Sub Directory Only...
Select Case FileInfo.Type
            
   Case FTFOLDER
        frmFtpMain!RemoteDirList.AddItem FileInfo.FileName

   Case FTSHORTCUT
        frmFtpMain!RemoteFileList.AddItem FileInfo.FileName
   Case Else
   ' Print the File Lists HEre
     If frmFtpMain!chkDetails.Value = vbChecked Then
        frmFtpMain!RemoteFileList.AddItem FileInfo.FileName 
		& "    " & FileInfo.Date & "    " & FileInfo.Size
     Else
        frmFtpMain!RemoteFileList.AddItem FileInfo.FileName
     End If
   End Select
' Remove Previous Item & vbCrLf Char
   FTPListData = Mid(FTPListData, eoLine + 2)
        
Loop
        
Exit Sub
    
    
cError:
    Resume Next

End Sub




'== This subprocedure parses and formats the data from 
'== GetListing, it sorts out related file information and 
'== formats the file listing.

Sub ParseFTPType(SearchString As String, FileInfo As FTPType)
Dim Line As String
ReDim TheList(0) As String
Dim pos As Long, i As Long, j As Long
Dim DateField As Long
Dim Lower As Long, Upper As Long
Dim DateString As String
Dim char As String * 1

On Error GoTo ListError
    
' Copy the SearchString parm with space
Line = Trim(SearchString)

' Do until no more data exists
Do While (Line <> "")
' Go through each character
 For j = 1 To Len(Line)
    Select Case Mid(Line, j, 1)
        ' Search for misc characters
      Case " ", vbNullChar, vbCr, vbLf, vbBack, _
           vbTab, vbVerticalTab, vbFormFeed
        ' Done
      Case Else
             If (j > 1) Then Line = Mid(Line, j)
           Exit For
    End Select
 Next
        
 ' Get Next Space
 pos = InStr(1, Line, " ") - 1
 If (pos < 1) Then pos = Len(Line)
 ' Increase size of  TheList
 ReDim Preserve TheList(i) As String
 TheList(i) = Mid(Line, 1, pos)
 i = i + 1
 ' Cut out leading spaces
 Line = LTrim(Mid(Line, pos + 2))
Loop
    

' Get the lower bounds of The List
Lower = LBound(TheList)
' Get the upper bounds of The List
Upper = UBound(TheList)
    
' Clear Date & Time
FileInfo.Date = ""
' Clear attributes
FileInfo.Attrib = ""
' Initialize file size
FileInfo.Size = -1
' Name should be the last
FileInfo.FileName = TheList(Upper)
FileInfo.Type = FTFILE

' Check for Unix types
char = Mid(TheList(Lower), 1, 1)
Select Case char
   ' Directory
    Case "d", "D"
        FileInfo.Type = FTFOLDER
    ' ShortCut
    Case "l", "L"
        FileInfo.Type = FTSHORTCUT
    ' File
    Case "-", "S", "T"
        FileInfo.Type = FTFILE
End Select
    
    Select Case char
    Case "d", "l", "c", "b", "p", "s", "-", "D", "L", "S", "T"
        FileInfo.Attrib = TheList(Lower)
    End Select
'Check to see what the content is checking from Right to Left
For i = (Upper - 1) To (Lower + 1) Step -1
' Has A Date Been Found Yet?
 If (FileInfo.Date = "") Then
'  >= 2 Items In List
   If (i > Lower) Then
       ' Copy Date String
       DateString = TheList(i - 1) & " " & TheList(i)
         If IsDate(DateString) Then DateField = 1
   ElseIf ((i - 1) > Lower) Then
         DateString = TheList(i - 2) & " " & TheList(i - 1) & " " & TheList(i)
             If IsDate(DateString) Then DateField = 2
   End If
 End If
        
' Determine Information Type
 Select Case True
    Case (DateField > 0)
      ' Get  Date/Time
         FileInfo.Date = Format$(DateString, FMTDATETIME)
         i = i - DateField
         DateString = ""
         DateField = 0
     ' Microsoft Directory Tag
    Case TheList(i) = FTPDIR
     ' Directory
         FileInfo.Type = FTFOLDER
    Case (IsNumeric(TheList(i)) And (FileInfo.Size = -1))
     ' Size
         FileInfo.Size = TheList(i)
    Case TheList(i) = FTPSHORTCUT
     ' Are There >= 2 Items In List
         If (i > Lower) Then
             FileInfo.FileName = TheList(i - 1)
         End If
    Case Else
    End Select
Next
    Exit Sub
ListError:
' Ignore Error And Resume
   Resume Next
End Sub

Figure 6.2 shows the FTP application after transferring a file.

Figure 6.2 : FTP application after sending a file to the remote host.

A Visual Basic Chat Client

As an example of a TCP application, we present a Chat application where the user can send data to port 1007 and communicate to another user who is running the same application on another host. Once the connection is made, the two users can have a Chat session in a manner strongly reminiscent of the UNIX talk service.

This example demonstrates how easy it is to get at the VARIANT data type mentioned earlier. In the TCP control's DataArrival event function, the code looks like this:

Private Sub TCPCtl_DataArrival(Index As Integer, ByVal bytesTotal As Long)
Dim vtData As String
' First string is the data, second is the data type
TCPCtl(Index).GetData vtData, vbString
' You must check for certain characters that are
' reflected onto the Incoming screen.
Select Case Asc(vtData)

As you can see, all that is necessary here is to Dim the variable that we will use to hold the data-vtData in this case. On calling the TCP control's GetData method, we specify that we want to see the data as a vbString. From there we can use VB's various string-handling functions to use the data.

NOTE
In VC++, declaring VARIANTs takes a bit more work. Not only that, but in VC++ all parameters are required, and if you specify the wrong VARIANT type the program will crash. You'll study this later

Figure 6.3 shows the Chat application at start-up. In Figure 6.4 you can see one user's side of a chat. Listing 6.5 is the code.

Figure 6.3 : The opening screen of the Chat application.

Figure 6.4 : One side of a Chat session.



Listing 6.5 Chat Application
Public SocketInstance As Integer

Private Sub cmdConnect_Click()
If txtHostName.Text = "" Then
Exit Sub
End If
' Unloading the instance that is listening
Unload TCPCtl(SocketInstance)
' Increment the Instance Counter.. Do I have to?
SocketInstance = SocketInstance + 1
' Load New Instance
Load TCPCtl(SocketInstance)
' Do the connection
With TCPCtl(SocketInstance)
.RemoteHost = txtHostName.Text
.RemotePort = 1007
.Connect
End With
End Sub


Private Sub Form_Load()
' The Control is Indexed at 0 by design, VB
' does not allow you to unload or modify the
' Instance created at design time, so, you must
' Load another Instance and go from there
SocketInstance = 1
Load TCPCtl(SocketInstance)
TCPCtl(SocketInstance).LocalPort = 1007   
' No firewall usage here!
TCPCtl(SocketInstance).Listen
End Sub


Private Sub cmdCloseConnection_Click()
End
End Sub

' This code is the TCP Control portion. The TCP Control handles all of the ' TCP related tasks.

Private Sub TCPCtl_Close(Index As Integer)
'  In this section we handle a disconnection.
TCPCtl(Index).Close
Unload TCPCtl(Index)
SocketInstance = SocketInstance - 1
lblStatus.Caption = "Disconnected"
txtReceived.Enabled = False
txtReceived.Text = ""
txtSent.Text = ""
End Sub

Private Sub TCPCtl_Connect(Index As Integer)
If TCPCtl(Index).State = sckConnected Then
 lblStatus.Caption = "Connected!"
End If
End Sub

Private Sub TCPCtl_ConnectionRequest(Index As Integer, ByVal requestID As Long)
' Increment the Instance Count and Load Another
SocketInstance = SocketInstance + 1
Load TCPCtl(SocketInstance)
TCPCtl(SocketInstance).Accept requestID
If TCPCtl(SocketInstance).State = sckConnected Then
 lblStatus.Caption = "Connected"
 txtReceived.Enabled = True
 txtSent.Enabled = True
Else
' Reset, probably not needed
 MsgBox TCPCtl(SocketInstance).State
 MsgBox "Problem Connecting"
 Unload TCPCtl(SocketInstance)
 SocketInstance = SocketInstance - 1
End If
End Sub

Private Sub TCPCtl_DataArrival(Index As Integer, ByVal bytesTotal As Long)
Dim vtData As String
' First string is the data, second is the data type
TCPCtl(Index).GetData vtData, vbString
' You must check for certain characters that are
' reflected onto the Incoming screen.
Select Case Asc(vtData)
Case 13: txtReceived.Text = txtReceived.Text & vbCrLf   ' Enter Key Received
Case 8:  txtReceived.Text = Left(txtReceived.Text, Len(txtReceived.Text) - 1) ' BackSpace Received
Case 9: txtReceived.Text = txtReceived.Text & vbTab     ' Tab Received
Case Else: txtReceived.Text = txtReceived.Text & vtData
End Select
End Sub


Private Sub txtReceived_KeyPress(KeyAscii As Integer)
TCPCtl(SocketInstance).SendData Chr(KeyAscii)
End Sub


Private Sub txtSent_KeyPress(KeyAscii As Integer)
' Sending Data
TCPCtl(SocketInstance).SendData Chr(KeyAscii)
End Sub

Using the Controls in Visual C++

As of this writing, Microsoft had not released any documentation or examples of using the ActiveX controls in Visual C++. Nevertheless, once we got the swing of things, we found the controls to be useful in VC++. It's a relatively simple matter to put together simple Internet client applications with the controls. Using them hides the Winsock API and provides you with a series of common events and objects with which you can program.

Speaking of the Microsoft documentation, bear in mind that there is an important difference between using the controls in VB and using them in VC: The Help file for the controls describes some methods as having parameters that are optional. In VC++, however, there are no optional parameters. What we did was just initialize a dummy placeholder parameter in order to call the function properly. To see an example of this, look at the SendDoc function in our first attempt at programming the controls, the SMTP Client application demonstrated in the upcoming section.

NOTE
In the examples that follow, we provide the complete source code listings only for the files that we edited ourselves. Remember that when you insert one of the controls into your project, by default a number of other machine-generated files are created. We didn't edit these files, so we haven't provided them here.

VC++ SMTP Client

Our SMTP Client does one thing: sends a piece of e-mail. First we collect data from the user with our dialog form, shown in Figure 6.5.

Figure 6.5 : The input form for the SMTP Client application.

When the user clicks on the Send Mail button, the data is collected and then the SendDoc function is called. To send e-mail, we first need to set up e-mail headers, including the From and To headers. We also send the message body, of course.

The data collected is put into the DocHeaders and DocInput objects. To understand how we get the data into these objects, take a look at the various header files created when you insert the control into your project. The first thing we do is create an object of the type CDocInput:

CDocInput docInput = m_smtp.GetDocInput();

Now look at the declaration of the SMTP control's GetDocInput function:

CDocInput GetDocInput();

So, we've created the CDocInput type object and indicated where its data is coming from. Next we create an object of the type CDocHeaders, and in the process indicate where the headers are coming from-the GetHeaders member of the CDocInput class:

CDocHeaders DocHeaders = docInput.GetHeaders();

Again, notice that the declaration of the GetHeaders function of the CDocInput class indicates it will return data of the type CDocHeaders.

Now we can add the headers collected from the user by calling the Add member function of the CDocHeaders class:

DocHeaders.Add("To", m_to);

With our headers established, all we have to do is call the SetData function of the CDocInput object we've created. This function requires VARIANT type data, so our code looks like this:

text = m_message;
mBody.bstrVal = text.AllocSysString();
docInput.SetData(mBody);

After that we call the SendDoc function, with a dummy VARIANT parameter, and off the e-mail goes. We added a function for the OnDocInputSmtp event, and when SendDoc is called you can see a TRACE statement printed for each header that was input into the dialog form.

Listings 6.6 and 6.7 show the header and source files for our SMTP application.

Note that we used the default VC++ text box control to get the message body. Also, to enter a new line into the text, you need to type the Ctrl + Enter key sequence. Below this text box is a status bar for displaying the changes in the state of the e-mail transaction when the StateChanged event occurs.


Listing 6.6 smtpDlg.h
// smtpDlg.h : header file
//
#include "docheader.h"
#include "docheaders.h"
#include "docinput.h"

/////////////////////////////////////////////////////////////////////////////
// CSmtpDlg dialog
//{{AFX_INCLUDES()
#include "smtpct.h"
//}}AFX_INCLUDES

class CSmtpDlg : public CDialog
{
// Construction
public:
     CSmtpDlg(CWnd* pParent = NULL);     // standard constructor

// user added
public:
     CHAR * chProtCode;
//     CTime theTime;
     CString csMessageID;
     CString text;
     LONG port;

//     CDocInput docInput;
//     CDocHeaders DocHeaders;
     VARIANT mBody;


// Dialog Data
     //{{AFX_DATA(CSmtpDlg)
     enum { IDD = IDD_SMTP_DIALOG };
     Csmtpct     m_smtp;
     CString     m_cc;
     CString     m_from;
     CString     m_mailhost;
     CString     m_message;
     CString     m_subject;
     CString     m_to;
     //}}AFX_DATA

     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CSmtpDlg)
     protected:
     virtual void DoDataExchange(CDataExchange* pDX);     // DDX/DDV support
     //}}AFX_VIRTUAL

// Implementation
protected:
     HICON m_hIcon;

     // Generated message map functions
     //{{AFX_MSG(CSmtpDlg)
     virtual BOOL OnInitDialog();
     afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
     afx_msg void OnPaint();
     afx_msg HCURSOR OnQueryDragIcon();
     afx_msg void OnSend();
     afx_msg void OnProtocolStateChangedSmtp(short ProtocolState);
     afx_msg void OnStateChangedSmtp(short State);
     afx_msg void OnClear();
     virtual void OnOK();
     afx_msg void OnDocInputSmtp(LPDISPATCH DocInput);
     DECLARE_EVENTSINK_MAP()
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
};


Listing 6.7 smtpDlg.cpp
// smtpDlg.cpp : implementation file
//

#include "stdafx.h"
#include "smtp.h"
#include "smtpDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
     CAboutDlg();

// Dialog Data
     //{{AFX_DATA(CAboutDlg)
     enum { IDD = IDD_ABOUTBOX };
     //}}AFX_DATA

     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CAboutDlg)
     protected:
     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
     //}}AFX_VIRTUAL

// Implementation
protected:
     //{{AFX_MSG(CAboutDlg)
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
     //{{AFX_DATA_INIT(CAboutDlg)
     //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(CAboutDlg)
     //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
     //{{AFX_MSG_MAP(CAboutDlg)
          // No message handlers
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CSmtpDlg dialog

CSmtpDlg::CSmtpDlg(CWnd* pParent /*=NULL*/)
     : CDialog(CSmtpDlg::IDD, pParent)
{
     //{{AFX_DATA_INIT(CSmtpDlg)
     m_cc = _T("");
     m_from = _T("");
     m_mailhost = _T("");
     m_message = _T("");
     m_subject = _T("");
     m_to = _T("");
     //}}AFX_DATA_INIT
     // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
     m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CSmtpDlg::DoDataExchange(CDataExchange* pDX)
{
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(CSmtpDlg)
     DDX_Control(pDX, ID_SMTP, m_smtp);
     DDX_Text(pDX, IDC_CC, m_cc);
     DDX_Text(pDX, IDC_FROM, m_from);
     DDX_Text(pDX, IDC_MAILHOST, m_mailhost);
     DDX_Text(pDX, IDC_MESSAGE, m_message);
     DDX_Text(pDX, IDC_SUBJECT, m_subject);
     DDX_Text(pDX, IDC_TO, m_to);
     //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CSmtpDlg, CDialog)
     //{{AFX_MSG_MAP(CSmtpDlg)
     ON_WM_SYSCOMMAND()
     ON_WM_PAINT()
     ON_WM_QUERYDRAGICON()
     ON_BN_CLICKED(ID_SEND, OnSend)
     ON_BN_CLICKED(IDCLEAR, OnClear)
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CSmtpDlg message handlers

BOOL CSmtpDlg::OnInitDialog()
{
     CDialog::OnInitDialog();

     // Add "About..." menu item to system menu.

     // IDM_ABOUTBOX must be in the system command range.
     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
     ASSERT(IDM_ABOUTBOX < 0xF000);

     CMenu* pSysMenu = GetSystemMenu(FALSE);
     CString strAboutMenu;
     strAboutMenu.LoadString(IDS_ABOUTBOX);
     if (!strAboutMenu.IsEmpty())
     {
          pSysMenu->AppendMenu(MF_SEPARATOR);
          pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
     }

     SetIcon(m_hIcon, TRUE);               // Set big icon
     SetIcon(m_hIcon, FALSE);          // Set small icon
     
     // TODO: Add extra initialization here

     SetDlgItemText(IDC_STATE, "Waiting for Send command");
     mBody.vt = VT_BSTR;
     port = 25;     

     return TRUE;  // return TRUE  unless you set the focus to a control
}

void CSmtpDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
     if ((nID & 0xFFF0) == IDM_ABOUTBOX)
     {
          CAboutDlg dlgAbout;
          dlgAbout.DoModal();
     }
     else
     {
          CDialog::OnSysCommand(nID, lParam);
     }
}


void CSmtpDlg::OnPaint() 
{
     if (IsIconic())
     {
          CPaintDC dc(this); // device context for painting

          SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

          // Center icon in client rectangle
          int cxIcon = GetSystemMetrics(SM_CXICON);
          int cyIcon = GetSystemMetrics(SM_CYICON);
          CRect rect;
          GetClientRect(&rect);
          int x = (rect.Width() - cxIcon + 1) / 2;
          int y = (rect.Height() - cyIcon + 1) / 2;

          // Draw the icon
          dc.DrawIcon(x, y, m_hIcon);
     }
     else
     {
          CDialog::OnPaint();
     }
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CSmtpDlg::OnQueryDragIcon()
{
     return (HCURSOR) m_hIcon;
}

void CSmtpDlg::OnSend() 
{
     UpdateData(TRUE);
     CTime theTime;
     theTime = CTime::GetCurrentTime();
     csMessageID = 
     "< ICP TEST " + theTime.Format( "%A, %B, %d, %Y" )+" >";

               
//     Initialize an object of type CDocInput
     CDocInput docInput = m_smtp.GetDocInput();

//     Now use the CDocInput function GetHeaders
//     to collect our headers
     CDocHeaders DocHeaders = docInput.GetHeaders();
     DocHeaders.Clear();

//  Collect all our form data and put it into
//     our DocHeaders collection

     DocHeaders.Add("To", m_to);
     DocHeaders.Add("CC", m_cc);
     DocHeaders.Add("From", m_from);
     DocHeaders.Add("Subject", m_subject);
     DocHeaders.Add("Message-ID", csMessageID);

//     Get the message body
     text = m_message;

//     and put it into the right type of variable
     mBody.bstrVal = text.AllocSysString();

//     Now add it to the docInput object
     docInput.SetData(mBody);
     
     m_smtp.SetRemotePort(port);
     m_smtp.SetRemoteHost(m_mailhost);

//     e is just a dummy variable: we've already set up
//     everything for the SendDoc method, but it still
//     requires parameters to be present. So we pass
//     the empty 'e' to it.
     char e;
     m_smtp.SendDoc(     (VARIANT&)e, // URL
                         (VARIANT&)e, // Headers
                         (VARIANT&)e, // InputData 
                         (VARIANT&)e, // InputFile 
                         (VARIANT&)e); // OutPutFile
}

BEGIN_EVENTSINK_MAP(CSmtpDlg, CDialog)
    //{{AFX_EVENTSINK_MAP(CSmtpDlg)
     ON_EVENT(CSmtpDlg, ID_SMTP, 554 /* ProtocolStateChanged */, OnProtocolStateChangedSmtp, VTS_I2)
     ON_EVENT(CSmtpDlg, ID_SMTP, 553 /* StateChanged */, OnStateChangedSmtp, VTS_I2)
     ON_EVENT(CSmtpDlg, ID_SMTP, 1016 /* DocInput */, OnDocInputSmtp, VTS_DISPATCH)
     //}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

void CSmtpDlg::OnProtocolStateChangedSmtp(short ProtocolState) 
{
          
     CString csProtocolStateString=
     m_smtp.GetProtocolStateString();
     SetDlgItemText(IDC_STATUS, csProtocolStateString);
     _itoa(ProtocolState, chProtCode, 10);
     SetDlgItemText(IDC_STATUS_CODE, chProtCode);

}

void CSmtpDlg::OnStateChangedSmtp(short State) 
{
     _itoa(State, chProtCode, 10);
     SetDlgItemText(IDC_STATUS_CODE, chProtCode);
     switch ( State)
     { 
          case 0:
          SetDlgItemText(IDC_STATE, "Waiting for Send command");
          break;
          case 6:
          SetDlgItemText(IDC_STATE, "Done");
          OnClear();
          break;
          default:
          SetDlgItemText(IDC_STATE, "Working");
          break;
     }
}


void CSmtpDlg::OnClear() 
{
     // Clear all input boxes.
     SetDlgItemText(IDC_MAILHOST, "");
     SetDlgItemText(IDC_FROM, "");
     SetDlgItemText(IDC_TO, "");
     SetDlgItemText(IDC_CC, "");
     SetDlgItemText(IDC_SUBJECT, "");
     SetDlgItemText(IDC_MESSAGE, "");
}

void CSmtpDlg::OnOK() 
{
     CDialog::OnOK();
}

void CSmtpDlg::OnDocInputSmtp(LPDISPATCH DocInput) 
{
     TRACE("OnDocInput\n");
     
}

VC++ POP3 Mini-Reader

The converse of sending e-mail is reading it. Thus, to use the POP3 control, we will use the CDocOutput object instead of the CDocInput object used in the previous example. Our Mini-Reader application allows you to connect to a POP3 server and list e-mail, using the list control supplied with VC++. It's illustrated in Figure 6.6.

Figure 6.6 : Reading e-mail with the POP3 Mini-Reader application.

Before examining CDocInput, let's look at how we connect to the server. As shown in Figure 6.7, our form has a Connect button, which opens another dialog for the mail host, userid, and password.

Figure 6.7 : The POP3 Connect dialog.

In the OnConnect function, we simply call the SetRemoteHost and SetRemotePort functions, and then call Connect. To authenticate the user, we have to wait for the protocol state to change. By adding a function mapped to the OnProtocolStateChanged event, our application will supply the mail host with the userid and password at the proper time. The ICP Help file lists the various protocol states to look out for, and in this case we are looking for the prcAuthorization state.

The next step is to provide some function to retrieve messages or message headers. The POP3 control includes a function called TopMessage, which presumably will retrieve only the top lines of a message. We couldn't find a server that supports this function, however, so we resorted to downloading all the messages, headers and body. (We did say Mini-Reader!)

For the downloading operation, we call the RetrieveMessage function. Now comes the difficult step: Calling RetrieveMessage triggers the DocOutput event, but it doesn't happen in a vacuum-other events can and will occur while the e-mail download is occurring.

Let's look at the beginning of our version of this function:

void CPopDlg::OnDocOutputPop(LPDISPATCH DocOutput) {
     DocOutput->AddRef();
     cDocOutput = DocOutput;
     lDocOutputState = cDocOutput.GetState();
     type.vt=VT_ERROR;
     switch ( lDocOutputState ){

The important thing to note here is the call to AddRef. When this function is called, we are getting handed a copy of the incoming data in the DocOutput object. We need to call AddRef here because another event will call another function and we will lose the address of our DocOutput object. Simply copying DocOutput to a globally defined object will not do.

Now that we safely have our DocOutput object, the rest of the function will collect the data and put it into the list control. First we check the protocol state with the switch statement, which causes the appropriate statements to be executed. Because the DocOutput object separates the data into defined chunks instead of one big blob, we can put the various headers into the CDocHeaders object by calling the GetHeaders member of CDocOutput:

cDocHeaders = cDocOutput.GetHeaders();

From there, we use the GetText, Item, and GetValue members of CDocHeaders to retrieve individual headers and insert them into the list control:

csText = cDocHeaders.GetText();
csHeader = "Subject";
vtHeader.bstrVal = csHeader.AllocSysString();
cDocHeader = cDocHeaders.Item(vtHeader);
csHeaderValue = cDocHeader.GetValue();
m_list.SetItemText(shMessageCount,0,(LPCTSTR)csHeaderValue);

To display a message, the user can double-click on an item in the list, in which case we call the GetData member of CDocOutput to retrieve the message body:

cDocOutput.GetData(&data,type);
m_MBody=data.bstrVal;

Listings 6.8 through 6.11 show the source code for the relevant files from the POP3 Dialog application.


Listing 6.8 popDlg.h
// popDlg.h : header file
//
#include "docoutput.h"
#include "docheader.h"
#include "docheaders.h"

#define S_LIST     1
#define S_MBODY 2
/////////////////////////////////////////////////////////////////////////////
// CPopDlg dialog
//{{AFX_INCLUDES()
#include "popct.h"
//}}AFX_INCLUDES

class CPopDlg : public CDialog
{
// Construction
public:
     CPopDlg(CWnd* pParent = NULL);     // standard constructor

// User stuff
     int state;
     CString tempuserid;
     CString temppassword;
     BOOL bConnectState;
     BOOL bLogon;
     CHAR chState[4];
     CDocOutput cDocOutput; 
     CDocHeaders cDocHeaders;
     CDocHeader cDocHeader;
     LONG lDocOutputState;
     
     LONG lBytesReceived;
     CString csText;
     CString csReplyString;
     CString csHeader;
     CString csHeaderValue;
     CString csMessageText;

     int shMessageCount;
     int shMessageNumber;

     VARIANT vtHeader;
     VARIANT vtData, vtType;

     void ShowDoc(CDocOutput locDocOutput);
     void RetrieveMessage(int); 
     void ShowMessage(UINT uiMessage);
     CString     m_mailhost,m_password,m_userid;
// end user stuff


// Dialog Data
     //{{AFX_DATA(CPopDlg)
     enum { IDD = IDD_POP_DIALOG };
     CListCtrl     m_list;
     Cpopct     m_pop;
     CString     m_MBody;
     //}}AFX_DATA

     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CPopDlg)
     protected:
     virtual void DoDataExchange(CDataExchange* pDX);     // DDX/DDV support
     //}}AFX_VIRTUAL

// Implementation
protected:
     HICON m_hIcon;

     // Generated message map functions
     //{{AFX_MSG(CPopDlg)
     virtual BOOL OnInitDialog();
     afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
     afx_msg void OnPaint();
     afx_msg HCURSOR OnQueryDragIcon();
     afx_msg void OnConnect();
     afx_msg void OnErrorPop(short Number, BSTR FAR* Description, 
	 long Scode, LPCTSTR Source, LPCTSTR HelpFile, long HelpContext, 
	 BOOL FAR* CancelDisplay);
     afx_msg void OnStateChangedPop(short State);
     afx_msg void OnBusyPop(BOOL isBusy);
     afx_msg void OnGetheaders();
     afx_msg void OnDocOutputPop(LPDISPATCH DocOutput);
     afx_msg void OnProtocolStateChangedPop(short ProtocolState);
     afx_msg void OnRefreshMessageCountPop(long Number);
     afx_msg void OnDblclkList(NMHDR* pNMHDR, LRESULT* pResult);
     afx_msg void Logon();
     afx_msg void OnQuit();
     DECLARE_EVENTSINK_MAP()
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
};


Listing 6.9 popDlg.cpp
// popDlg.cpp : implementation file
//

#include "stdafx.h"
#include "pop.h"
#include "popDlg.h"
#include "connect.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
     CAboutDlg();

// Dialog Data
     //{{AFX_DATA(CAboutDlg)
     enum { IDD = IDD_ABOUTBOX };
     //}}AFX_DATA

     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CAboutDlg)
     protected:
     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
     //}}AFX_VIRTUAL

// Implementation
protected:
     //{{AFX_MSG(CAboutDlg)
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
     //{{AFX_DATA_INIT(CAboutDlg)
     //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(CAboutDlg)
     //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
     //{{AFX_MSG_MAP(CAboutDlg)
          // No message handlers
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPopDlg dialog

CPopDlg::CPopDlg(CWnd* pParent /*=NULL*/)
     : CDialog(CPopDlg::IDD, pParent)
{
     //{{AFX_DATA_INIT(CPopDlg)
     m_MBody = _T("");
     //}}AFX_DATA_INIT
     // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
     m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
     state=0;
     m_mailhost="";
}

void CPopDlg::DoDataExchange(CDataExchange* pDX)
{
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(CPopDlg)
     DDX_Control(pDX, IDC_LIST, m_list);
     DDX_Control(pDX, ID_POP, m_pop);
     DDX_Text(pDX, IDC_OUTPUT, m_MBody);
     //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CPopDlg, CDialog)
     //{{AFX_MSG_MAP(CPopDlg)
     ON_WM_SYSCOMMAND()
     ON_WM_PAINT()
     ON_WM_QUERYDRAGICON()
     ON_BN_CLICKED(IDCONNECT, OnConnect)
     ON_BN_CLICKED(IDGETHEADERS, OnGetheaders)
     ON_NOTIFY(NM_DBLCLK, IDC_LIST, OnDblclkList)
     ON_BN_CLICKED(IDAUTHENTICATE, Logon)
     ON_BN_CLICKED(IDQUIT, OnQuit)
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPopDlg message handlers

BOOL CPopDlg::OnInitDialog()
{
     CDialog::OnInitDialog();

     // Add "About..." menu item to system menu.

     // IDM_ABOUTBOX must be in the system command range.
     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
     ASSERT(IDM_ABOUTBOX < 0xF000);

     CMenu* pSysMenu = GetSystemMenu(FALSE);
     CString strAboutMenu;
     strAboutMenu.LoadString(IDS_ABOUTBOX);
     if (!strAboutMenu.IsEmpty())
     {
          pSysMenu->AppendMenu(MF_SEPARATOR);
          pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
     }

     // Set the icon for this dialog. The framework does this automatically
     //  when the application's main window is not a dialog
     SetIcon(m_hIcon, TRUE);               // Set big icon
     SetIcon(m_hIcon, FALSE);          // Set small icon
     
     // TODO: Add extra initialization here

     // Set up our list control with some columns
     m_list.InsertColumn(0, "Subject", LVCFMT_LEFT, 200, 2);
     m_list.InsertColumn(1, "From", LVCFMT_LEFT, 200, 3);
     m_list.InsertColumn(2, "Date", LVCFMT_LEFT, 100, 4);
     m_pop.SetTimeout(0,10000000);
     return TRUE;  // return TRUE  unless you set the focus to a control
}

void CPopDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
     if ((nID & 0xFFF0) == IDM_ABOUTBOX)
     {
          CAboutDlg dlgAbout;
          dlgAbout.DoModal();
     }
     else
     {
          CDialog::OnSysCommand(nID, lParam);
     }
}


void CPopDlg::OnPaint() 
{
     if (IsIconic())
     {
          CPaintDC dc(this); // device context for painting

          SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

          // Center icon in client rectangle
          int cxIcon = GetSystemMetrics(SM_CXICON);
          int cyIcon = GetSystemMetrics(SM_CYICON);
          CRect rect;
          GetClientRect(&rect);
          int x = (rect.Width() - cxIcon + 1) / 2;
          int y = (rect.Height() - cyIcon + 1) / 2;

          // Draw the icon
          dc.DrawIcon(x, y, m_hIcon);
     }
     else
     {
          CDialog::OnPaint();
     }
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CPopDlg::OnQueryDragIcon()
{
     return (HCURSOR) m_hIcon;
}

void CPopDlg::OnConnect() 
{
     Connect connect(this,&m_mailhost,&m_userid,&m_password);
     if(connect.DoModal()==IDOK){
          m_pop.SetRemoteHost((LPCTSTR)m_mailhost);
          m_pop.SetRemotePort(110);
          VARIANT dummy;
          dummy.vt=VT_ERROR;
          bConnectState=0;
          m_pop.Connect(dummy,dummy);//vtRemoteHost,vtRemotePort);
     }
}

void CPopDlg::Logon() {
     TRACE("Authenticating\n");
     TRACE("done LOGON\n");
}


BEGIN_EVENTSINK_MAP(CPopDlg, CDialog)
    //{{AFX_EVENTSINK_MAP(CPopDlg)
     ON_EVENT(CPopDlg, ID_POP, -608 /* Error */, 
	 OnErrorPop, VTS_I2 VTS_PBSTR VTS_I4 VTS_BSTR VTS_BSTR VTS_I4 VTS_PBOOL)
     ON_EVENT(CPopDlg, ID_POP, 553 /* StateChanged */, OnStateChangedPop, VTS_I2)
     ON_EVENT(CPopDlg, ID_POP, 555 /* Busy */, OnBusyPop, VTS_BOOL)
     ON_EVENT(CPopDlg, ID_POP, 1017 /* DocOutput */, OnDocOutputPop, VTS_DISPATCH)
     ON_EVENT(CPopDlg, ID_POP, 554 /* ProtocolStateChanged */, 
	 OnProtocolStateChangedPop, VTS_I2)
     ON_EVENT(CPopDlg, ID_POP, 2471 /* RefreshMessageCount */, 
	 OnRefreshMessageCountPop, VTS_I4)
     //}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()


void CPopDlg::OnStateChangedPop(short State) {
//     _itoa(State, chState, 10);
//     SetDlgItemText(IDC_STATUS, chState);

     switch (State)
     {
     case 4:
          break;
     }
     
}

void CPopDlg::OnErrorPop(short Number, BSTR FAR* Description, 
long Scode, LPCTSTR Source, LPCTSTR HelpFile, 
long HelpContext, BOOL FAR* CancelDisplay) 
{
     
     CHAR * chError;
     _itoa(Number, chError, 10);
     SetDlgItemText(IDC_STATUS, chError);
     CString error=*Description;
     SetDlgItemText(IDC_REPLY_LABEL, error);
}


void CPopDlg::OnBusyPop(BOOL isBusy) {
     csReplyString = "Busy...";
}

void CPopDlg::RetrieveMessage(int message) {
     if(message<shMessageNumber){
          m_pop.RetrieveMessage((SHORT)(message+1));
     }

}

void CPopDlg::OnGetheaders() {
          shMessageCount=0;
     state=S_LIST;
     if(shMessageCount<shMessageNumber){
          RetrieveMessage(shMessageCount);
     }

}


void CPopDlg::ShowDoc(CDocOutput locDocOutput){
     lBytesReceived = locDocOutput.GetBytesTransferred();
}

int l;
char *str;
VARIANT data,type;

void CPopDlg::OnDocOutputPop(LPDISPATCH DocOutput) {
     DocOutput->AddRef();
     cDocOutput = DocOutput;
     lDocOutputState = cDocOutput.GetState();
     type.vt=VT_ERROR;
     switch ( lDocOutputState ){
     case 0:
          break;
     case 1:
          break;
     case 2:
          break;
     case 3:
          switch(state){
          case S_LIST:
               m_list.InsertItem(shMessageCount,"1");
               m_list.SetItemData(shMessageCount,shMessageCount);
               
               vtHeader.vt = VT_BSTR;
               
               cDocHeaders = cDocOutput.GetHeaders();
               csText = cDocHeaders.GetText();
               
               csHeader = "Subject";
               vtHeader.bstrVal = csHeader.AllocSysString();
               cDocHeader = cDocHeaders.Item(vtHeader);
               csHeaderValue = cDocHeader.GetValue();
               m_list.SetItemText(shMessageCount,0,(LPCTSTR)csHeaderValue);
          
               csHeader = "From";
               vtHeader.bstrVal = csHeader.AllocSysString();
               cDocHeader = cDocHeaders.Item(vtHeader);
               csHeaderValue = cDocHeader.GetValue();
               m_list.SetItemText(shMessageCount,1,(LPCTSTR)csHeaderValue);
               
               csHeader = "Date";
               vtHeader.bstrVal = csHeader.AllocSysString();
               cDocHeader = cDocHeaders.Item(vtHeader);
               csHeaderValue = cDocHeader.GetValue();
               m_list.SetItemText(shMessageCount,2,(LPCTSTR)csHeaderValue);
               break;
          case S_MBODY:
               cDocOutput.GetData(&data,type);
               m_MBody="";
               m_MBody=data.bstrVal;
               UpdateData(0);
               break;
          }
          break;
     case 4:
          TRACE("Case 4 ERROR\n");
          break;
     case 5:
          switch(state){
          case S_LIST:
               shMessageCount++;
               RetrieveMessage(shMessageCount);
               break;
          }
          break;
     default:
          break;
     }
}


void CPopDlg::OnDblclkList(NMHDR* pNMHDR, LRESULT* pResult) {
     int item=m_list.GetNextItem(-1,LVNI_SELECTED);
     state=S_MBODY;
     RetrieveMessage(item);
     *pResult = 0;
}

void CPopDlg::ShowMessage(UINT uiMessage)
{
     RetrieveMessage(1); 
// 1st call to get message body
     vtData.vt = VT_BSTR;
     vtType.vt = VT_ERROR;     
     cDocOutput.GetData(&vtData,vtType);
     csMessageText = vtData.bstrVal;
     SetDlgItemText(IDC_OUTPUT, csMessageText);

}

void CPopDlg::OnProtocolStateChangedPop(short ProtocolState) 
{
     _itoa(ProtocolState, chState, 10);
     SetDlgItemText(IDC_STATUS, chState);

     VARIANT dummy;
     TRACE("OnProtocolStateChangedPop\n");
     switch(ProtocolState){
     case 1:
          if(bConnectState==FALSE){
               m_pop.SetUserId((LPCTSTR)m_userid);
               m_pop.SetPassword((LPCTSTR)m_password);
               dummy.vt=VT_ERROR;
               m_pop.Authenticate(dummy,dummy);
          }
          break;
     case 2:
          bConnectState=TRUE;
          break;
     }
}


void CPopDlg::OnRefreshMessageCountPop(long Number) 
{
     shMessageNumber=Number;
}


void CPopDlg::OnQuit() 
{
//     m_pop.Cancel();
     m_list.DeleteAllItems();
     m_pop.Quit();
     
}

The connect.h in Listing 6.10 and connect.cpp in Listing 6.11 are for the dialog in which the user enters the hostname, userid, and password.


Listing 6.10 connect.h
// connect.h : header file
/////////////////////////////////////////////////////////////////////////////
// Connect dialog
class Connect : public CDialog
{
public:
     Connect(CWnd* pParent,CString*,CString*,CString*);   // standard constructor
     CString *host,*password,*ui;
// Dialog Data
     //{{AFX_DATA(Connect)
     enum { IDD = IDD_DIALOGCONNECT };
     CString     m_host;
     CString     m_password;
     CString     m_userid;
     //}}AFX_DATA
// Overrides
     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(Connect)
     protected:
     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
     //}}AFX_VIRTUAL

// Implementation
protected:

     // Generated message map functions
     //{{AFX_MSG(Connect)
     virtual void OnOK();
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
};


Listing 6.11 connect.cpp
// connect.cpp : implementation file
//

#include "stdafx.h"
#include "pop.h"
#include "connect.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// Connect dialog


Connect::Connect(CWnd* pParent,CString *h,CString *u,CString *p)
     : CDialog(Connect::IDD, pParent)
{
     host=h;
     password=p;
     ui=u;
     //{{AFX_DATA_INIT(Connect)
     m_host = _T("");
     m_password = _T("");
     m_userid = _T("");
     //}}AFX_DATA_INIT
}


void Connect::DoDataExchange(CDataExchange* pDX)
{
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(Connect)
     DDX_Text(pDX, IDC_MAILHOST, m_host);
     DDX_Text(pDX, IDC_PASSWORD, m_password);
     DDX_Text(pDX, IDC_USERID, m_userid);
     //}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(Connect, CDialog)
     //{{AFX_MSG_MAP(Connect)
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// Connect message handlers

void Connect::OnOK() 
{
     // TODO: Add extra validation here
     UpdateData(1);
     *host=m_host;
     *password=m_password;
     *ui=m_userid;
     CDialog::OnOK();
}

VC++ NNTP Reader

The previous examples introduced methods for using the CDocOuput and CDocInput objects, and we didn't pay much attention to supplying options for events that the user can generate. Our example program for NNTP uses the CDocOutput object also, but in this example we'll examine how to handle the events that occur as a result of user actions.

With the NNTP control, we want to allow the user to initiate the following events: connecting to the server; downloading a list of the available newsgroups; listing articles in a selected newsgroup; and displaying an individual article that the user selects.

Connecting to the Server

As in the POP3 example, clicking on the Connect button brings up a dialog for the user to enter the hostname and port, usually 119, of the news server.

Note that you cannot connect to just any news server. Typically, the news server will look at your local IP number to determine if you are an authorized user of the news server. Your Internet Service Provider probably has a news server that you can access. Also, you'll find a few public-domain news servers listed at

http://www.lipsia.de/~michael/lists/pubservers.html

However, if you must use a public server, take great care-these servers are frequently overwhelmed with traffic.

NOTE
Assuming the program successfully connects, a global variable, connected, is set to 1 and used throughout the program to maintain the status of the connection. This is necessary because of how the NNTP control processes what should be two separate methods, Quit and Cancel. The documents imply that the Cancel method will do only that-cancel whatever event is in process. Afterward, the program should still be connected to the server. We found this not to be the case, however; Cancel also disconnects from the server. Therefore, when calling another user-generated function such as listing articles, we have to determine if we need to reconnect to the server. (It's possible this is a bug that will be corrected in a future release of ICP.

Downloading the Newsgroups

Once connected, the user can click the Load button to begin downloading the list of newsgroups, which are then put into a tree control. To get to the body of an article, you double-click first on a folder to see the subgroups, then on a group to list articles, and then on an article. The article text is placed into the RichEdit control at the bottom of the dialog. (If you don't have the RichEdit control on your system, you can substitute a static text box control, as we did with the SMTP and POP3 dialogs.)

Notes on our work:

  • In developing functions to handle these events, we discovered that the NNTP control delivered by Microsoft would not list the articles in a group. By using the "final" version of the control from NetManage, this function magically began to work.
  • Earlier we mentioned the parameters called "optional" in the ICP Help file. This conflicts with the fact that when using the controls in VC++, everything is required. For example, when calling the GetArticleHeaders function, we found that the "optional" first and last group numbers had to be set to the correct values; otherwise, the function would only retrieve the first article.

Listings 6.12 through 6.15 show the source code for our NNTP client. Figure 6.8 shows the News client after successfully connecting to a news server and listing articles in a newsgroup. In Figure 6.9, an article is displayed in the text box.

Figure 6.8 : Our NNTP client, listing articles.

Figure 6.9 : Displaying the text of an article with the NNTP client.


Listing 6.12 NewsDlg.h
// NewsDlg.h : header file
//
//{{AFX_INCLUDES()
#include "richtext.h"
//}}AFX_INCLUDES
#define V_CONNECT     0
#define V_GROUPLIST     1
#define V_GETARLIST     3
#define V_GETGROUP     4
#define V_GETARTICL 5

#include "nntpct.h"

class CNewsDlg : public CDialog
{
// Construction
public:
     CNewsDlg(CWnd* pParent = NULL);     // standard constructor

// Dialog Data
     //{{AFX_DATA(CNewsDlg)
     enum { IDD = IDD_NEWS_DIALOG };
     CTreeCtrl     m_TreeArt;
     CTreeCtrl     m_Tree;
     CButton     m_Ok;
     int          m_Count;
     CString     m_Status;
     Cnntpct     m_Nntp;
     CRichtext     m_Text;
     //}}AFX_DATA

     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CNewsDlg)
     protected:
     virtual void DoDataExchange(CDataExchange* pDX);     // DDX/DDV support
     //}}AFX_VIRTUAL

// Implementation
protected:
     HICON m_hIcon;
     int state,connected,resume,treep,resumeart;
     int last_count,last_countart,last_countartid,gcount,acount,port;
     int dbl_clicktree,dbl_clickarttree;
     CString host;
     void LoadGroups();
     int ParseGroups(char*,int);
     int ParseGroup(char*);
     int ParseArticles(char*,int);
     int ParseArticle(char*);
     void SetText(char*);
     void Connect();
     char group[400],*grtree[20],article[400];
     HTREEITEM tree[20];


     // Generated message map functions
     //{{AFX_MSG(CNewsDlg)
     virtual BOOL OnInitDialog();
     afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
     afx_msg void OnPaint();
     afx_msg HCURSOR OnQueryDragIcon();
     afx_msg void OnButtonload();
     afx_msg void OnDblclkTree(NMHDR* pNMHDR, LRESULT* pResult);
     virtual void OnOK();
     afx_msg void OnButtonstop();
     afx_msg void OnErrorNntp(short Number, BSTR FAR* Description, 
	 long Scode, LPCTSTR Source, LPCTSTR HelpFile, 
	 long HelpContext, BOOL FAR* CancelDisplay);
     afx_msg void OnStateChangedNntp(short State);
     afx_msg void OnProtocolStateChangedNntp(short ProtocolState);
     afx_msg void OnBusyNntp(BOOL isBusy);
     afx_msg void OnDocOutputNntp(LPDISPATCH DocOutput);
     afx_msg void OnSelectGroupNntp(LPCTSTR groupName, long firstMessage, long lastMessage, long msgCount);
     afx_msg void OnButtonconnect();
     afx_msg void OnDestroy();
     afx_msg void OnDblclkTreearticles(NMHDR* pNMHDR, LRESULT* pResult);
     DECLARE_EVENTSINK_MAP()
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
};


Listing 6.13 NewsDlg.cpp
// NewsDlg.cpp : implementation file
//
#include "stdafx.h"
#include "News.h"

#include "docoutput.h"

#include "Connect.h"
#include "NewsDlg.h"
#include  <WinNls.h>

#include <AFXPRIV.H>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
     CAboutDlg();

// Dialog Data
     //{{AFX_DATA(CAboutDlg)
     enum { IDD = IDD_ABOUTBOX };
     //}}AFX_DATA

     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CAboutDlg)
     protected:
     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
     //}}AFX_VIRTUAL

// Implementation
protected:
     //{{AFX_MSG(CAboutDlg)
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
     //{{AFX_DATA_INIT(CAboutDlg)
     //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(CAboutDlg)
     //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
     //{{AFX_MSG_MAP(CAboutDlg)
          // No message handlers
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CNewsDlg dialog

CNewsDlg::CNewsDlg(CWnd* pParent /*=NULL*/)
     : CDialog(CNewsDlg::IDD, pParent)
{
     state = -1;
     connected=-1;
     resume=0;
     resumeart=last_countartid=0;
     gcount=acount=0;
     last_count=last_countart=0;
     treep=0;
     dbl_clicktree=dbl_clickarttree=0;
     port=119;
     host="beyond.escape.com";
     //{{AFX_DATA_INIT(CNewsDlg)
     m_Count = 0;
     m_Status = _T("");
     //}}AFX_DATA_INIT
     // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
     m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CNewsDlg::DoDataExchange(CDataExchange* pDX)
{
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(CNewsDlg)
     DDX_Control(pDX, IDC_TREEARTICLES, m_TreeArt);
     DDX_Control(pDX, IDC_TREE, m_Tree);
     DDX_Control(pDX, IDOK, m_Ok);
     DDX_Text(pDX, IDC_EDITCOUNT, m_Count);
     DDX_Text(pDX, IDC_EDITSTATUSLINE, m_Status);
     DDX_Control(pDX, IDC_NNTP, m_Nntp);
     DDX_Control(pDX, IDC_RICHTEXTCTRL, m_Text);
     //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CNewsDlg, CDialog)
     //{{AFX_MSG_MAP(CNewsDlg)
     ON_WM_SYSCOMMAND()
     ON_WM_PAINT()
     ON_WM_QUERYDRAGICON()
     ON_BN_CLICKED(IDC_BUTTONLOAD, OnButtonload)
     ON_NOTIFY(NM_DBLCLK, IDC_TREE, OnDblclkTree)
     ON_BN_CLICKED(IDC_BUTTONSTOP, OnButtonstop)
     ON_BN_CLICKED(IDC_BUTTONCONNECT, OnButtonconnect)
     ON_WM_DESTROY()
     ON_NOTIFY(NM_DBLCLK, IDC_TREEARTICLES, OnDblclkTreearticles)
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CNewsDlg message handlers

BOOL CNewsDlg::OnInitDialog()
{
     CDialog::OnInitDialog();

     // Add "About..." menu item to system menu.

     // IDM_ABOUTBOX must be in the system command range.
     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
     ASSERT(IDM_ABOUTBOX < 0xF000);

     CMenu* pSysMenu = GetSystemMenu(FALSE);
     CString strAboutMenu;
     strAboutMenu.LoadString(IDS_ABOUTBOX);
     if (!strAboutMenu.IsEmpty())
     {
          pSysMenu->AppendMenu(MF_SEPARATOR);
          pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
     }

     // Set the icon for this dialog. The framework does this automatically
     //  when the application's main window is not a dialog
     SetIcon(m_hIcon, TRUE);               // Set big icon
     SetIcon(m_hIcon, FALSE);          // Set small icon
     
     // TODO: Add extra initialization here
     return TRUE;  // return TRUE  unless you set the focus to a control
}

void CNewsDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
     if ((nID & 0xFFF0) == IDM_ABOUTBOX)
     {
          CAboutDlg dlgAbout;
          dlgAbout.DoModal();
     }
     else
     {
          CDialog::OnSysCommand(nID, lParam);
     }
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon. For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CNewsDlg::OnPaint() 
{
     if (IsIconic())
     {
          CPaintDC dc(this); // device context for painting

          SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

          // Center icon in client rectangle
          int cxIcon = GetSystemMetrics(SM_CXICON);
          int cyIcon = GetSystemMetrics(SM_CYICON);
          CRect rect;
          GetClientRect(&rect);
          int x = (rect.Width() - cxIcon + 1) / 2;
          int y = (rect.Height() - cyIcon + 1) / 2;

          // Draw the icon
          dc.DrawIcon(x, y, m_hIcon);
     }
     else
     {
          CDialog::OnPaint();
     }
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CNewsDlg::OnQueryDragIcon()
{
     return (HCURSOR) m_hIcon;
}

BEGIN_EVENTSINK_MAP(CNewsDlg, CDialog)
    //{{AFX_EVENTSINK_MAP(CNewsDlg)
     ON_EVENT(CNewsDlg, IDC_NNTP, -608 /* Error */, 
	 OnErrorNntp, VTS_I2 VTS_PBSTR VTS_I4 VTS_BSTR VTS_BSTR VTS_I4 VTS_PBOOL)
     ON_EVENT(CNewsDlg, IDC_NNTP, 553 /* StateChanged */, OnStateChangedNntp, VTS_I2)
     ON_EVENT(CNewsDlg, IDC_NNTP, 554 /* ProtocolStateChanged */, 
	 OnProtocolStateChangedNntp, VTS_I2)
     ON_EVENT(CNewsDlg, IDC_NNTP, 555 /* Busy */, OnBusyNntp, VTS_BOOL)
     ON_EVENT(CNewsDlg, IDC_NNTP, 1017 /* DocOutput */, OnDocOutputNntp, VTS_DISPATCH)
     ON_EVENT(CNewsDlg, IDC_NNTP, 3 /* SelectGroup */, 
	 OnSelectGroupNntp, VTS_BSTR VTS_I4 VTS_I4 VTS_I4)
     //}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

long st;
VARIANT data,type;
unsigned int l;
char *str;

void CNewsDlg::OnDocOutputNntp(LPDISPATCH DocOutput) {
     DocOutput->AddRef();
     CDocOutput out =DocOutput;
     st=out.GetState();
     type.vt=VT_ERROR;//VT_UI1     ;//VT_BSTR;//VT_BYREF|VT_ARRAY|VT_UI1;
     switch(st){
     case 0://icDocNone:
          break;
     case 1://icDocBegin:
          break;
     case 2://icDocHeaders:
          break;
     case 3://icDocData:
          switch(state){
          case V_GROUPLIST:
               out.GetData(&data,type);//,type);
               l=SysStringLen(data.bstrVal);
               str=new char[l];
               WideCharToMultiByte(CP_ACP, 0, data.bstrVal, l, str, l, NULL, NULL);
               ParseGroups(str,l);
               SysFreeString(data.bstrVal);
               delete str;
               break;
          case V_GETARLIST:
               out.GetData(&data,type);//,type);
               l=SysStringLen(data.bstrVal);
               str=new char[l+1];
               WideCharToMultiByte(CP_ACP, 0, data.bstrVal, l, str, l, NULL, NULL);
               str[l]='\0';
               ParseArticles(str,l);
               SysFreeString(data.bstrVal);
               delete str;
               break;
          case V_GETARTICL:
               out.GetData(&data,type);//,type);
               l=SysStringLen(data.bstrVal);
               str=new char[l+1];
               WideCharToMultiByte(CP_ACP, 0, data.bstrVal, l, str, l, NULL, NULL);
               str[l]='\0';
               SetText(str);
               SysFreeString(data.bstrVal);
               delete str;
               break;
          }
          break;
     case 4://icDocError:
          break;
     case 5://icDocEnd:
          state=0;
          break;
     }
//     DocOutput->Release();
}


void CNewsDlg::OnStateChangedNntp(short State) {
     if(state == V_CONNECT){
          if((State==6)&&(connected==0)){
               MessageBox("Can not connect to the news Server");
          }
          else if(State == 4){
          }
     }
}

void CNewsDlg::LoadGroups(){
     if(connected==1){
          state=V_GROUPLIST;
          m_Status="Loading Groups. Loaded:";
          m_Count=0;
          UpdateData(0);
          m_TreeArt.DeleteAllItems( );
          m_Nntp.ListGroups();
     }else 
          MessageBox("Problem Connecting!");

}


void CNewsDlg::OnButtonload(){
     LoadGroups();
}
CString er;
void CNewsDlg::OnErrorNntp(short Number, BSTR FAR* Description, 
long Scode, LPCTSTR Source, LPCTSTR HelpFile, long HelpContext, 
BOOL FAR* CancelDisplay) {
     er=(*Description);
}

int CNewsDlg::ParseGroups(char *groups,int length){
     int count=0;
     if(resume==2){ //we already got the group name but have to remove extra info first
          while((count<length)&&(groups[count]!='\n'))
               count++;
          count++;
     }else if(resume == 1){//we did not get full group's nae
     }else
          last_count=0;//ok we've got all stuff letst do next group
     while(count<length){
          resume=ParseGroup(groups+count);
          while((count<length)&&(groups[count]!='\n'))
               count++;
          count++;
     }
     return 1;
}


int CNewsDlg::ParseGroup(char *g){
     int count=0,gtreecount=1,cmp;
     HTREEITEM root,item;
     CString s;
     MSG message;
     try
    {
          while(g[count]==' ')
               count++;
          grtree[0]=group;
          while(g[count]!=' '){
               while((g[count]!='\0')&&((g[count]<32)||(g[count]>122)))
                    count++;
               if(g[count]=='\0')
                    return 1;
               group[last_count]=g[count];
               if(g[count]=='.'){
                    grtree[gtreecount]=group+count+1;
                    gtreecount++;
                    group[last_count]='\0';
               }else
                    group[last_count]=g[count];
               count++;
               last_count++;
          }
          group[last_count]='\0';
          gcount++;
          m_Count=gcount;
          UpdateData(0);
          if(::GetMessage(&message,NULL,0,0)){
               ::TranslateMessage(&message);
               ::DispatchMessage(&message);
          }
          count=0;
          if(gtreecount>1)
               gtreecount--;
          if(lstrcmp(grtree[1],"aapg")==0){
               gcount-=1;
               gcount+=1;
          }
          while((count<gtreecount)&&(count<treep)&&(grtree[count]!=NULL)){
               item=tree[count];
               s=m_Tree.GetItemText(tree[count]);
               cmp=s.CompareNoCase(grtree[count]);
               switch(cmp){
               case -1: //tree[count] < grtree[count]
                    item=m_Tree.GetNextItem(tree[count],TVGN_NEXT);
                    if(item!=NULL)
                         s=m_Tree.GetItemText(item);
                    else
                         s="";
                    while((item!=NULL)&&(s.CompareNoCase(grtree[count])==-1)){//grtree[count]>s)){
                         item=m_Tree.GetNextItem(item,TVGN_NEXT);
                         if(item!=NULL)
                              s=m_Tree.GetItemText(item);
                         else
                              s="";
                    }
                    if(grtree[count]==s)
                         tree[count+1]=m_Tree.GetNextItem(item,TVGN_CHILD);
                    else
tree[count+1]=m_Tree.GetNextItem(tree[count],TVGN_CHILD);
                    break;
               case 1://tree[count] > grtree[count]
                    item=m_Tree.GetNextItem(tree[count],TVGN_PREVIOUS);
                    if(item!=NULL)
                         s=m_Tree.GetItemText(item);
                    else
                         s="";
                    while((item!=NULL)&&(s.CompareNoCase(grtree[count])==1)){//grtree[count]<s)){
                         item=m_Tree.GetNextItem(item,TVGN_PREVIOUS);
                         s=m_Tree.GetItemText(item);
                    }
                    if(grtree[count]==s)
                         tree[count+1]=m_Tree.GetNextItem(item,TVGN_CHILD);
                    else
                         tree[count+1]=m_Tree.GetNextItem(tree[count],TVGN_CHILD);
                    break;
               }
               if(grtree[count]!=s){
                    if(count==0)
                         root=TVI_ROOT;
                    else
                         root=tree[count-1];
                    tree[count]=m_Tree.InsertItem(grtree[count],root,TVI_SORT);
               }else if(item!=NULL)
                    tree[count]=item;
               count++;
          }
          if(gtreecount==1)
               gtreecount--;

          if(count==0)
               root=TVI_ROOT;
          else
               root=tree[count-1];
          while(count<gtreecount){
               tree[count]=root=m_Tree.InsertItem(grtree[count],root,TVI_SORT);
               count++;
          }
          if(treep<count)
               treep=count;
          root=m_Tree.InsertItem(grtree[count],root,TVI_SORT);
          last_count=0;
          while(g[count]!='\n'){
               if(g[count]=='\0')
                    return 2;
               count++;
          }
    }
    catch( char * str )
    {
        TRACE("Exception raised: %s \n",str);
    }
    catch( int num )
    {
        TRACE("Exception raised: %d \n",num);
    }
    

     return 0;
}

int CNewsDlg::ParseArticles(char *articles,int length){
     int count=0;
     while(count<length){
          resumeart=ParseArticle(articles+count);
          while((count<length)&&(articles[count]!='\n'))
               count++;
          count++;
     }
     return 1;
}


int CNewsDlg::ParseArticle(char *g){
     int count=0,artid;
     char id[20];
     HTREEITEM item;
     MSG message;
     while(g[count]==' ')
          count++;
     if(resumeart==1) {//did not get the id
     }else if(resumeart==2){//did not get the article
     }
     while(g[count]!=' '){
          if(g[count]=='\0')
               return 2;
          id[last_countartid++]=g[count];
          count++;
     }
     id[last_countartid]='\0';
     artid=atoi(id);
     count++;
     while(g[count]!='\n'){
          if(g[count]=='\0')
               return 1;
          if((g[count]!=10)&&(g[count]!=13)){
               article[last_countart]=g[count];
               last_countart++;
          }
          count++;
     }
     article[last_countart]='\0';
     m_Count=++acount;
     UpdateData(0);
     if(::GetMessage(&message,NULL,0,0)){
          ::TranslateMessage(&message);
          ::DispatchMessage(&message);
     }
     item=m_TreeArt.InsertItem(article,TVI_ROOT,TVI_SORT);
     m_TreeArt.SetItemData(item,artid);
     last_countart=0;
     last_countartid=0;
     while(g[count]!='\n'){
          if(g[count]=='\0')
               return 2;
          count++;
     }
     return 0;
}

void CNewsDlg::OnDblclkTree(NMHDR* pNMHDR, LRESULT* pResult) {
     HTREEITEM item;
     CString label;
     item=m_Tree.GetSelectedItem();
     if(!m_Tree.ItemHasChildren(item)){
          label=m_Tree.GetItemText(item);
          while(item!=NULL){
               item=m_Tree.GetParentItem(item);
               if(item!=NULL)
                    label=m_Tree.GetItemText(item)+"."+label;
          }
          m_Status="Loading Articles. Loaded: ";
          m_Count=0;
          UpdateData(0);
          if((connected==0) && (host!="")){
               Connect();
               dbl_clicktree=1;
          }
          if(connected==1){
               m_TreeArt.DeleteAllItems( );
               m_Nntp.SelectGroup((LPCTSTR)label);
          }
     }
//     *pResult = 0;
}

void CNewsDlg::OnOK() {
     if(connected==1)
          m_Nntp.Quit();
     CDialog::OnOK();
}


void CNewsDlg::OnProtocolStateChangedNntp(short ProtocolState) {
     if((state==V_CONNECT)&&(ProtocolState==1)){
          connected=1;
          m_Status="Connected";
          UpdateData(0);
          if((dbl_clicktree==1)||(dbl_clickarttree==1)){
               dbl_clickarttree=dbl_clicktree=0;
               OnDblclkTree(NULL,NULL);
          }
     }else if(ProtocolState==0)
          connected=0;
}



void CNewsDlg::OnButtonstop() {
     m_Nntp.Cancel();
//     m_Nntp.Quit();
}

void CNewsDlg::OnBusyNntp(BOOL isBusy) 
{
     
}

void CNewsDlg::OnSelectGroupNntp(LPCTSTR groupName, 
long firstMessage, long lastMessage, long msgCount) {
     VARIANT start,end;
     start.vt=end.vt=VT_I4;
     start.lVal=firstMessage;
     end.lVal=lastMessage;
     state=V_GETARLIST;
     m_Nntp.GetArticleHeaders("subject", start, end);
     
}


void CNewsDlg::OnButtonconnect() {
     CConnect connect(this,&host,&port);
     if(connect.DoModal()==IDOK){
          m_Nntp.SetRemoteHost((LPCTSTR)host);
          m_Nntp.SetRemotePort(port);
          Connect();
     }
}

void CNewsDlg::Connect(){
     VARIANT var;
     var.vt=VT_ERROR;//VT_EMPTY;
     state = V_CONNECT;
     m_Nntp.SetTimeout(0,1000000);
     m_Nntp.Connect(var,var);
}

void CNewsDlg::OnDestroy() 
{
     m_Nntp.Cancel();
     m_Nntp.Quit();
     connected==0;
     CDialog::OnDestroy();
}

void CNewsDlg::OnDblclkTreearticles(NMHDR* pNMHDR, LRESULT* pResult) {
     HTREEITEM item;
     CString label;
     long id;
     VARIANT articleNumber;
     item=m_TreeArt.GetSelectedItem();
     label=m_TreeArt.GetItemText(item);
     m_Status="Loading Article "+ label;
     m_Count=0;
     UpdateData(0);
     if((connected==0) && (host!="")){
          Connect();
          dbl_clickarttree=1;
     }
     if(connected==1){
          m_Text.SetText("");
          id=m_TreeArt.GetItemData(item);
          articleNumber.vt=VT_I4;
          articleNumber.lVal=id;
          state=V_GETARTICL;
          m_Nntp.GetArticleByArticleNumber(articleNumber);
     }
//     *pResult = 0;
}

void CNewsDlg::SetText(char *text){
//     long textend=m_Tree.GetTextLength( );
//     m_Tree.SetSel( textend,textend);
//     m_Tree.ReplaceSel(text);
     CString old=m_Text.GetText();
     m_Text.SetText((LPCTSTR)(old+text));
}


Listing 6.14 Connect.h
// Connect.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CConnect dialog

class CConnect : public CDialog
{
// Construction
public:
     CConnect(CWnd* pParent,CString*,int*);   // standard constructor
     CString *h;
     int *p;
// Dialog Data
     //{{AFX_DATA(CConnect)
     enum { IDD = IDD_DIALOGCONNECT };
     CString     m_Host;
     int          m_Port;
     //}}AFX_DATA


// Overrides
     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CConnect)
     protected:
     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
     virtual  BOOL OnInitDialog( );
     //}}AFX_VIRTUAL

// Implementation
protected:

     // Generated message map functions
     //{{AFX_MSG(CConnect)
     virtual void OnOK();
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
};


Listing 6.15 Connect.cpp
// Connect.cpp : implementation file
//

#include "stdafx.h"
#include "News.h"
#include "Connect.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CConnect dialog


CConnect::CConnect(CWnd* pParent,CString *host,int *port)
     : CDialog(CConnect::IDD, pParent)
{
     //{{AFX_DATA_INIT(CConnect)
     m_Host = _T("");
     m_Port = 0;
     //}}AFX_DATA_INIT
     h=host;
     m_Host=*h;
     p=port;
     m_Port=*p;
}


BOOL CConnect::OnInitDialog(){
     CDialog::OnInitDialog();
     UpdateData(0);
     return TRUE;  // return TRUE  unless you set the focus to a control
}

void CConnect::DoDataExchange(CDataExchange* pDX)
{
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(CConnect)
     DDX_Text(pDX, IDC_EDITHOST, m_Host);
     DDX_Text(pDX, IDC_EDITPORT, m_Port);
     //}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CConnect, CDialog)
     //{{AFX_MSG_MAP(CConnect)
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CConnect message handlers

void CConnect::OnOK() 
{
     UpdateData(1);
     *h=m_Host;
     *p=m_Port;
     CDialog::OnOK();
}

VC++ TCP Explorer

The two Winsock controls, TCP and UDP, are in one sense easier to program than the other controls, because they don't use any of the Internet support objects such as DocInput or DocOutput. This means all of the methods are directly accessible. They are, however, lower-level controls that require you to parse essentially everything that comes in except the packet header.

Just for our own amusement, we decided to put together a couple of simple examples with the TCP and UDP controls. The first of these is a TCP Explorer with which you can attempt a connection to a specified remote server and port number. Upon connecting, you can then attempt to send data to the server.

We included some minimal code for negotiating a Telnet connection. Programming for the Telnet protocol, as specified in various RFCs, can be quite complicated. Our approach was to stop all negotiation with the remote server and establish a "dumb terminal" connection. This approach worked with a few servers we tried, but not with all of them. Figure 6.10 shows sample Finger output.

Figure 6.10: A successfully established Telnet session.

The application will also work with various other common TCP ports, including port 79, usually reserved for Finger requests. If you can establish a connection to a server with this port, simply type the userid of the person you wish to Finger to issue the request, as shown in Figure 6.11. Other ports we tried include the chargen port, 19, and the timeserver port, 37.

Figure 6.11: TCP Explorer used as a Finger client.

You can also connect to a Web server and issue a command such as GET /, which will retrieve the raw HTML for the specified page. To display data received from the remote server we used the Grid control, scrolling the data up the screen. See Figure 6.12.

Figure 6.12: The raw HTML power of the Microsoft home page.

The TCP Explorer source code is shown in Listings 6.16 and 6.17.


Listing 6.16 tcpDlg.h
// tcpDlg.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CTcpDlg dialog
//{{AFX_INCLUDES()
#include "tcp1.h"
#include "gridctrl.h"
//}}AFX_INCLUDES

#include <afxtempl.h>

class CTcpDlg : public CDialog
{
// Construction
public:
     CTcpDlg(CWnd* pParent = NULL);     // standard constructor

     
// to hold window display data
     CArray<CString,CString> tcpArray;

// user added
     SHORT State;
     LONG SocketHandle;
     LONG BytesReceived;
     CString csWindowText;

// various variable value things
     LONG lLocalPort;
     LONG lRemotePort;
     CString csStatusLine;
     CString csStatusName;
     char szState[10];
     char szLocalPort[5];
     char szRemotePort[5];
     char szSocketHandle[10];
     char szBytesReceived[12];

     CString csRemoteHostName;
     CString csRemoteHostIP;
     CString csLocalHostName;
     CString csLocalHostIP;

// Arguments for Connect
     VARIANT vtHost;
     VARIANT vtPort;

// Arguments for GetData
     VARIANT vtData;
     VARIANT vtType;
     VARIANT vtMaxlen;
     CString csForString;

// Arguments for SendData
     VARIANT vtCode;
// argument for datagrid
     VARIANT vtNewRow;
// user entered command
     VARIANT vtCommand;

// Non afx methods
     void UpdateWindow();
     void SendIAC(SHORT shIAC);     


// Dialog Data
     //{{AFX_DATA(CTcpDlg)
     enum { IDD = IDD_TCP_DIALOG };
     CTCP     m_tcp;
     CString     m_host;
     CString     m_port;
     CString     m_command;
     CGridCtrl     m_datagrid;
     //}}AFX_DATA

     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CTcpDlg)
     protected:
     virtual void DoDataExchange(CDataExchange* pDX);     // DDX/DDV support
     //}}AFX_VIRTUAL

// Implementation
protected:
     HICON m_hIcon;

     // Generated message map functions
     //{{AFX_MSG(CTcpDlg)
     virtual BOOL OnInitDialog();
     afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
     afx_msg void OnPaint();
     afx_msg HCURSOR OnQueryDragIcon();
     afx_msg void OnConnect();
     afx_msg void OnConnectTcp();
     virtual void OnCancel();
     afx_msg void OnCloseTcp();
     afx_msg void OnDisconnect();
     virtual void OnOK();
     afx_msg void OnErrorTcp(short Number, BSTR FAR* Description, 
	 long Scode, LPCTSTR Source, LPCTSTR HelpFile, 
	 long HelpContext, BOOL FAR* CancelDisplay);
     afx_msg void OnDataArrivalTcp(long bytesTotal);
     DECLARE_EVENTSINK_MAP()
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
};


Listing 6.17 tcpDlg.cpp
// tcpDlg.cpp : implementation file
//

#include "stdafx.h"
#include "tcp.h"
#include "tcpDlg.h"
#include <stdio.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
     CAboutDlg();

// Dialog Data
     //{{AFX_DATA(CAboutDlg)
     enum { IDD = IDD_ABOUTBOX };
     //}}AFX_DATA

     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CAboutDlg)
     protected:
     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
     //}}AFX_VIRTUAL

// Implementation
protected:
     //{{AFX_MSG(CAboutDlg)
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
     //{{AFX_DATA_INIT(CAboutDlg)
     //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(CAboutDlg)
     //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
     //{{AFX_MSG_MAP(CAboutDlg)
          // No message handlers
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTcpDlg dialog

CTcpDlg::CTcpDlg(CWnd* pParent /*=NULL*/)
     : CDialog(CTcpDlg::IDD, pParent)
{
     //{{AFX_DATA_INIT(CTcpDlg)
     m_host = _T("");
     m_port = _T("");
     m_command = _T("");
     //}}AFX_DATA_INIT
     // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
     m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CTcpDlg::DoDataExchange(CDataExchange* pDX)
{
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(CTcpDlg)
     DDX_Control(pDX, ID_TCP, m_tcp);
     DDX_Text(pDX, IDC_HOST, m_host);
     DDV_MaxChars(pDX, m_host, 80);
     DDX_Text(pDX, IDC_PORT, m_port);
     DDX_Text(pDX, IDC_COMMAND, m_command);
     DDV_MaxChars(pDX, m_command, 80);
     DDX_Control(pDX, IDC_DATAGRID, m_datagrid);
     //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CTcpDlg, CDialog)
     //{{AFX_MSG_MAP(CTcpDlg)
     ON_WM_SYSCOMMAND()
     ON_WM_PAINT()
     ON_WM_QUERYDRAGICON()
     ON_BN_CLICKED(IDC_CONNECT, OnConnect)
     ON_BN_CLICKED(ID_DISCONNECT, OnDisconnect)
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTcpDlg message handlers

BOOL CTcpDlg::OnInitDialog()
{
     CDialog::OnInitDialog();

     // Add "About..." menu item to system menu.

     // IDM_ABOUTBOX must be in the system command range.
     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
     ASSERT(IDM_ABOUTBOX < 0xF000);

     CMenu* pSysMenu = GetSystemMenu(FALSE);
     CString strAboutMenu;
     strAboutMenu.LoadString(IDS_ABOUTBOX);
     if (!strAboutMenu.IsEmpty())
     {
          pSysMenu->AppendMenu(MF_SEPARATOR);
          pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
     }

     // Set the icon for this dialog. The framework does this automatically
     //  when the application's main window is not a dialog
     SetIcon(m_hIcon, TRUE);               // Set big icon
     SetIcon(m_hIcon, FALSE);          // Set small icon
     
     // TODO: Add extra initialization here

// set up our display array
tcpArray.SetSize(24);

// set up our data grid
// whatever a "twip" is, we need alot of them...
m_datagrid.SetColWidth(0,24000);

// for Connect
     vtHost.vt = VT_BSTR;
     vtPort.vt = VT_BSTR;
// for GetData
     vtData.vt = VT_BSTR ;
     vtType.vt = VT_ERROR; // 10;
// for SendData
     vtCode.vt = VT_UI1;
// for OnOK
     vtCommand.vt = VT_BSTR;

     return TRUE;  // return TRUE  unless you set the focus to a control
}

void CTcpDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
     if ((nID & 0xFFF0) == IDM_ABOUTBOX)
     {
          CAboutDlg dlgAbout;
          dlgAbout.DoModal();
     }
     else
     {
          CDialog::OnSysCommand(nID, lParam);
     }
}

void CTcpDlg::OnPaint() 
{
     if (IsIconic())
     {
          CPaintDC dc(this); // device context for painting

          SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

          // Center icon in client rectangle
          int cxIcon = GetSystemMetrics(SM_CXICON);
          int cyIcon = GetSystemMetrics(SM_CYICON);
          CRect rect;
          GetClientRect(&rect);
          int x = (rect.Width() - cxIcon + 1) / 2;
          int y = (rect.Height() - cyIcon + 1) / 2;

          // Draw the icon
          dc.DrawIcon(x, y, m_hIcon);
     }
     else
     {
          CDialog::OnPaint();
     }
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CTcpDlg::OnQueryDragIcon()
{
     return (HCURSOR) m_hIcon;
}


void CTcpDlg::OnConnect() 
{
// User clicked on connect button.
// Could add some checking of hostname/ip and port number
     UpdateData(TRUE);
     vtHost.bstrVal = m_host.AllocSysString ( );
     vtPort.bstrVal = m_port.AllocSysString ( );
     m_tcp.Connect(vtHost,vtPort);
     
}

BEGIN_EVENTSINK_MAP(CTcpDlg, CDialog)
    //{{AFX_EVENTSINK_MAP(CTcpDlg)
     ON_EVENT(CTcpDlg, ID_TCP, 1151 /* Connect */, OnConnectTcp, VTS_NONE)
     ON_EVENT(CTcpDlg, ID_TCP, 1155 /* Close */, OnCloseTcp, VTS_NONE)
     ON_EVENT(CTcpDlg, ID_TCP, -608 /* Error */, 
	 OnErrorTcp, VTS_I2 VTS_PBSTR VTS_I4 VTS_BSTR VTS_BSTR VTS_I4 VTS_PBOOL)
     ON_EVENT(CTcpDlg, ID_TCP, 1051 /* DataArrival */, OnDataArrivalTcp, VTS_I4)
     //}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

void CTcpDlg::OnConnectTcp() 
{
     State = m_tcp.GetState();     

     csRemoteHostName  = m_tcp.GetRemoteHost();
     csRemoteHostIP = m_tcp.GetRemoteHostIP();
     csLocalHostName = m_tcp.GetLocalHostName();
     csLocalHostIP = m_tcp.GetLocalIP();
     State = m_tcp.GetState();
     _itoa(State, szState, 10);

     SetDlgItemText( IDC_CONNECTION_STATUS, "Connected" );
     SetDlgItemText( IDC_RHOSTNAME, csRemoteHostName );
     SetDlgItemText( IDC_PROTSTATE, szState );
     NextDlgCtrl();

}

void CTcpDlg::OnCancel() 
{
     CDialog::OnCancel();
     m_tcp.Close();     
}

void CTcpDlg::OnCloseTcp() 
{
     SetDlgItemText( IDC_CONNECTION_STATUS, "Disconnected" );
     SetDlgItemText( IDC_RHOSTNAME, "---" );
     SetDlgItemText( IDC_BYTES, "0" );
     UpdateWindow();
}

void CTcpDlg::OnDisconnect() 
{
     m_tcp.Close();
}


void CTcpDlg::OnOK() 
{
     // get value of m_command and send to host
     // if in connected state.
     UpdateData(TRUE);
     
     vtCommand.bstrVal = m_command.AllocSysString ( );
     m_tcp.SendData(vtCommand);

     vtCode.iVal = 13;
     m_tcp.SendData(vtCode);

     vtCode.iVal = 10;
     m_tcp.SendData(vtCode);

     SetDlgItemText( IDC_COMMAND, "" );

}

void CTcpDlg::OnErrorTcp(short Number, BSTR FAR* Description, 
long Scode, LPCTSTR Source, LPCTSTR HelpFile, 
long HelpContext, BOOL FAR* CancelDisplay) 
{
     SetDlgItemText( IDC_CONNECTION_STATUS, "Error..." );
}

void CTcpDlg::OnDataArrivalTcp(long bytesTotal) 
{
     if( m_tcp.GetState() < 1) { return; }

     BytesReceived = m_tcp.GetBytesReceived();
     _itoa(BytesReceived, szBytesReceived, 10);

     SetDlgItemText( IDC_BYTES, szBytesReceived );
     
     m_tcp.GetData ( &vtData, vtType, vtMaxlen );
     UCHAR c, d;
     int linelength=79; int charcounter=0;
     csForString = ""; 
     for ( int i=0; i<BytesReceived; i++)
     { 
           c = (UCHAR)vtData.bstrVal[+i];

          if( c == 255)
          {
               d = (UCHAR)vtData.bstrVal[+i+1];
               switch (d)
               {
                   case 251:
                    case 252:
                         SendIAC(255); SendIAC(252); // won't do it
                         SendIAC( (UCHAR)vtData.bstrVal[+i+2] );
                         i = i+3;
                         break;
                    case 253:
                    case 254:
                         SendIAC(255); SendIAC(252);  // same refusal
                         SendIAC( (UCHAR)vtData.bstrVal[+i+2] );
                          i = i+3; 
                         break;
                    default :
                         i = i+3;
                         SendIAC(255); 
                         SendIAC(249);     // try sending break on 
                                             // anything else
                         break;
               }
          }


          if( c>31 && c<127) 
          { csForString = csForString + (CHAR)c; }
          if( c==13)
          { 
               csForString = csForString + (CHAR)c + (CHAR)10 ; 
               tcpArray.InsertAt(1,csForString,1);
               csForString="";          
          }

          charcounter++;

     }
     tcpArray.InsertAt(1,csForString,1);
     UpdateWindow();
}

void CTcpDlg::UpdateWindow() 
{
     CString csTemp; 
     CString csWText;
     csTemp=""; 
     SHORT OldRow = 0;
     int DisplayLimit=23;
     
     vtNewRow.vt = VT_I2;
     vtNewRow.iVal = 23;

     int iUpper = tcpArray.GetUpperBound();
     int j;
     if(iUpper<DisplayLimit) { j = iUpper; } else { j = DisplayLimit; }
     for(int i = j; i>0; i--)
     {
          csTemp = tcpArray.GetAt(i);
          csWText = csWText + csTemp;
          m_datagrid.AddItem(csTemp, vtNewRow);
          m_datagrid.RemoveItem(OldRow);
     }
}

void CTcpDlg::SendIAC(SHORT shIAC) 
{

     // Some common codes.
     // See RFC 854 for complete details.
     // 243  Break
     // 249  Go ahead
     // 251     WILL (option code)
     // 252  WON'T (option code)
     // 253  DO  (option code)
     // 254  DON'T  (option code)

     vtCode.iVal = shIAC;
     m_tcp.SendData(vtCode);
}

VC++ UDP Chat

Now for our UDP example. It's the venerable Chat application, which allows two copies of the program to exchange UDP packets, the data of which is then displayed in the receiving program's text box control. To set this up, we run two copies of the program, on the same or separate machines. We input the appropriate IP numbers of the machine to which the program wishes to connect, along with the respective port numbers. Figure 6.13 shows an example of the application.

Figure 6.13: Talking to myself with the UDP dialog.


Listing 6.18 udpDlg.h
// udpDlg.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CUdpDlg dialog
//{{AFX_INCLUDES()
#include "udpcrtl.h"
//}}AFX_INCLUDES

class CUdpDlg : public CDialog
{

// Construction
public:
     CUdpDlg(CWnd* pParent = NULL);     // standard constructor

// User added
public:
     VARIANT vtData;
     VARIANT vtType;
     CString csDlgText;
     void OnOK();     

// Dialog Data
     //{{AFX_DATA(CUdpDlg)
     enum { IDD = IDD_UDP_DIALOG };
     CUDP     m_udp;
     long     m_port;
     CString     m_rhostip;
     CString     m_rhostname;
     CString     m_senddata;
     long     m_rport;
     //}}AFX_DATA

     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CUdpDlg)
     protected:
     virtual void DoDataExchange(CDataExchange* pDX);     // DDX/DDV support
     //}}AFX_VIRTUAL

// Implementation
protected:
     HICON m_hIcon;

     // Generated message map functions
     //{{AFX_MSG(CUdpDlg)
     virtual BOOL OnInitDialog();
     afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
     afx_msg void OnPaint();
     afx_msg HCURSOR OnQueryDragIcon();
     afx_msg void OnListen();
     afx_msg void OnErrorUdpControl(short Number, BSTR FAR* Description, 
	 long Scode, LPCTSTR Source, LPCTSTR HelpFile, 
	 long HelpContext, BOOL FAR* CancelDisplay);
     afx_msg void OnDataArrivalUdpControl(long bytesTotal);
     afx_msg void OnStop();
     afx_msg void OnSend();
     DECLARE_EVENTSINK_MAP()
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
};


Listing 6.19 udpDlg.cpp
// udpDlg.cpp : implementation file
//

#include "stdafx.h"
#include "udp.h"
#include "udpDlg.h"
#include <stdlib.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
     CAboutDlg();

// Dialog Data
     //{{AFX_DATA(CAboutDlg)
     enum { IDD = IDD_ABOUTBOX };
     //}}AFX_DATA

     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CAboutDlg)
     protected:
     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
     //}}AFX_VIRTUAL

// Implementation
protected:
     //{{AFX_MSG(CAboutDlg)
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
     //{{AFX_DATA_INIT(CAboutDlg)
     //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(CAboutDlg)
     //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
     //{{AFX_MSG_MAP(CAboutDlg)
          // No message handlers
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CUdpDlg dialog

CUdpDlg::CUdpDlg(CWnd* pParent /*=NULL*/)
     : CDialog(CUdpDlg::IDD, pParent)
{
     //{{AFX_DATA_INIT(CUdpDlg)
     m_port = 0;
     m_rhostip = _T("");
     m_rhostname = _T("");
     m_senddata = _T("");
     m_rport = 0;
     //}}AFX_DATA_INIT
     // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
     m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CUdpDlg::DoDataExchange(CDataExchange* pDX)
{
     CDialog::DoDataExchange(pDX);
     //{{AFX_DATA_MAP(CUdpDlg)
     DDX_Control(pDX, ID_UDP_CONTROL, m_udp);
     DDX_Text(pDX, IDC_PORT, m_port);
     DDV_MinMaxLong(pDX, m_port, 0, 99999);
     DDX_Text(pDX, IDC_RHOSTIP, m_rhostip);
     DDX_Text(pDX, IDC_RHOSTNAME, m_rhostname);
     DDX_Text(pDX, IDC_SEND_DATA, m_senddata);
     DDX_Text(pDX, IDC_RPORT, m_rport);
     DDV_MinMaxLong(pDX, m_rport, 0, 99999);
     //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CUdpDlg, CDialog)
     //{{AFX_MSG_MAP(CUdpDlg)
     ON_WM_SYSCOMMAND()
     ON_WM_PAINT()
     ON_WM_QUERYDRAGICON()
     ON_BN_CLICKED(IDC_LISTEN, OnListen)
     ON_BN_CLICKED(IDC_STOP, OnStop)
     ON_BN_CLICKED(IDC_SEND, OnSend)
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CUdpDlg message handlers

BOOL CUdpDlg::OnInitDialog()
{
     CDialog::OnInitDialog();

     // Add "About..." menu item to system menu.

     // IDM_ABOUTBOX must be in the system command range.
     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
     ASSERT(IDM_ABOUTBOX < 0xF000);

     CMenu* pSysMenu = GetSystemMenu(FALSE);
     CString strAboutMenu;
     strAboutMenu.LoadString(IDS_ABOUTBOX);
     if (!strAboutMenu.IsEmpty())
     {
          pSysMenu->AppendMenu(MF_SEPARATOR);
          pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
     }

     // Set the icon for this dialog. The framework does this automatically
     //  when the application's main window is not a dialog
     SetIcon(m_hIcon, TRUE);               // Set big icon
     SetIcon(m_hIcon, FALSE);          // Set small icon
     
     // TODO: Add extra initialization here
     
     return TRUE;  // return TRUE  unless you set the focus to a control
}

void CUdpDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
     if ((nID & 0xFFF0) == IDM_ABOUTBOX)
     {
          CAboutDlg dlgAbout;
          dlgAbout.DoModal();
     }
     else
     {
          CDialog::OnSysCommand(nID, lParam);
     }
}

void CUdpDlg::OnPaint() 
{
     if (IsIconic())
     {
          CPaintDC dc(this); // device context for painting

          SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

          // Center icon in client rectangle
          int cxIcon = GetSystemMetrics(SM_CXICON);
          int cyIcon = GetSystemMetrics(SM_CYICON);
          CRect rect;
          GetClientRect(&rect);
          int x = (rect.Width() - cxIcon + 1) / 2;
          int y = (rect.Height() - cyIcon + 1) / 2;

          // Draw the icon
          dc.DrawIcon(x, y, m_hIcon);
     }
     else
     {
          CDialog::OnPaint();
     }
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CUdpDlg::OnQueryDragIcon()
{
     return (HCURSOR) m_hIcon;
}

BEGIN_EVENTSINK_MAP(CUdpDlg, CDialog)
    //{{AFX_EVENTSINK_MAP(CUdpDlg)
     ON_EVENT(CUdpDlg, ID_UDP_CONTROL, -608 /* Error */, 
	 OnErrorUdpControl, VTS_I2 VTS_PBSTR VTS_I4 VTS_BSTR VTS_BSTR VTS_I4 VTS_PBOOL)
     ON_EVENT(CUdpDlg, ID_UDP_CONTROL, 1051 /* DataArrival */, 
	 OnDataArrivalUdpControl, VTS_I4)
     //}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

void CUdpDlg::OnOK() 
{
     // do nothing
}

void CUdpDlg::OnListen() 
{
     
UpdateData(TRUE);
m_udp.SetLocalPort(m_port);
     
}

void CUdpDlg::OnErrorUdpControl(short Number, BSTR FAR* Description, 
long Scode, LPCTSTR Source, LPCTSTR HelpFile, 
long HelpContext, BOOL FAR* CancelDisplay) 
{
     TRACE("CUdpDlg::OnErrorUdpControl\n");
}

void CUdpDlg::OnDataArrivalUdpControl(long bytesTotal) 
{
     TRACE("CUdpDlg::OnDataArrivalUdpControl\n");

     //void CUDP::GetData(VARIANT* data, const VARIANT& type)
     vtData.vt = VT_ARRAY;
     vtType.vt = VT_ERROR;

     HRESULT hr;
     CHAR HUGEP * hpChar;
     m_udp.GetData(&vtData, vtType);
     TRACE("Past GetDataMethod...\n");
     hr = SafeArrayAccessData(vtData.parray, (void HUGEP* FAR*)&hpChar);

     for ( int i=0; i<bytesTotal; i++)
     { 
          if(hpChar[i]>31 && hpChar[i]<128)
          {
               TRACE("%c ", hpChar[i]);
               csDlgText = csDlgText + hpChar[i];

          }
     
     }
     csDlgText = csDlgText + (CHAR)13 + (CHAR)10;
     SetDlgItemText(IDC_CONNECTIONS, csDlgText);

     m_rhostip = m_udp.GetRemoteHostIP();
     SetDlgItemText(IDC_RHOSTIP, m_rhostip);

} 
///////

void CUdpDlg::OnStop() 
{
     // Setting the local port to 0 doesn't do anything.
     // There seems to be now way to turn off "listening"
     // without exiting the dialog application.
     // So all we accomplish here is to clear the output window.
     m_udp.SetLocalPort(0);     
     csDlgText="";
     SetDlgItemText(IDC_CONNECTIONS, csDlgText);
     UpdateData(TRUE);
     
}

void CUdpDlg::OnSend() 
{     
     UpdateData(TRUE);
     
     m_udp.SetRemoteHost(m_rhostname);
     m_udp.SetRemotePort(m_rport);
     
     CString csCommand = m_senddata;
     VARIANT vtCommand;
     vtCommand.vt = VT_BSTR;
     vtCommand.bstrVal = csCommand.AllocSysString();
     m_udp.SendData(vtCommand);

     m_udp.SetLocalPort(m_rport);
}

In Control with ICP

For us, the Internet Control Pack proved to be quite a challenge to use in Visual C++. As released by NetManage and Microsoft (right around the time we were writing this), no example programs or documentation were available. In various newsgroups, we followed message chains from people struggling to use the controls in VC++, and some of those people came to the conclusion that the controls were defective and simply did not work. With much perseverance, however, and using our admittedly simple dialog applications, we managed to prove that these controls do indeed work.

Once you get past the initial difficulties of dealing with the VARIANT data type, responding to control events, and accessing the support objects, the ICP controls prove to be quite functional and useful. A few minor bugs remain to be worked out, but we expect those will be resolved shortly. Meanwhile, though, the controls hide the Winsock API, providing a clean way to work with the various popular Internet protocols. We look forward to doing more development with these controls in the future.



HomeAbout UsSearchSubscribeAdvertising InfoContact UsFAQs
Use of this site is subject to certain Terms & Conditions.
Copyright (c) 1996-1998 EarthWeb, Inc.. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EarthWeb is prohibited.
Please read the Acceptable Usage Statement.
Contact reference@developer.com with questions or comments.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.

Click here for more info

Click here for more info