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.
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
Layer | Protocols
|
Application layer (highest) | Telnet, FTP, SMTP, NNTP, and others
|
Transport layer | TCP, UDP, and others
|
Network layer | IP and others
|
Link layer (lowest) | Device drivers and interface cards
|
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.
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
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.
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.
|
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.
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 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 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
|
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.
|
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.
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.
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.
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.
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.
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 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
|
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.
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.
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.
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.
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.
|
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
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.
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
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.
|
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");
}
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();
}
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();
}
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);
}
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);
}
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.