Ó
Rick Strahl, West Wind Technologies, 1999
http://www.west-wind.com/
Reworking your applications to run over the Web is one way to take advantage of the Internet, but it's also an intense undertaking that requires you to completely rethink the way your applications work. In many situations, you can take advantage of Internet functionality at a much lower level simply by integrating specific functionality directly into your applications. This document describes several ways that allow you to build Internet functionality into your existing applications with relatively little effort giving you big usability enhancements with very little code.
In this article you'll find out how to:
¨ Access Internet and Windows monikers using the ShellExecute API
¨ Integrate SMTP Email, FTP and basic HTTP
¨ Access database files on a server over the Internet
¨ Access SQL Server over the Internet using TCP/IP
¨ Use DCOM over the Internet and TCP/IP
¨ Use Microsoft RDS to access data and COM objects on a Web server
For finding out more about building distributed applications over HTTP see the following article:
Building distributed applications
over HTTP with wwIPStuff
Fat client's been getting a bad rap as outdated technology that doesn't scale. While this may be true, it's also a fact that vast majority of applications continue to be standalone or network apps running on client machines. Business applications often need flexibility that cannot do without a rich client environment and database functionality that uses local functionality to provide this environment.
Thin client is in, but more and more there's also a drive towards building applications that share logic and data access between the client and server tiers. I call this medium client applications, which use traditional development tools to implement distributed applications that share the responsibility of logic between client and server utilizing the Internet as a network.
In this context, this article discusses ways that you can take advantage of Internet technologies without giving up the flexibility and functionality that you are used to with Visual FoxPro.
The first and most easy step for Internet integration is enhancement of existing applications by adding basic Internet features:
¨ Plugging in minor features
Small enhancements for basic Internet protocols can bring big functionality
improvements to existing applications.
¨ Web links, Email, FTP, HTTP access
Applications can easily take advantage of features like Hyperlinks, Windows Shell and
Extension access, which are really easy to implement via the Windows Shell API. Email
continues to be an important part of most user's Internet use and providing email can
provide a valuable feedback mechanism for
status reports, notifications, error handling and many more occasions. Moving files across
locations is another feature that many applications require. By using FTP or HTTP servers
to hold these files and give people distributed access you can make your applications much
more flexible and allow it to share data with other users anywhere. HTTP access is also
very powerful for retrieving and interacting with existing Web content. The beauty is that
all of this functionality can be integrated rather easily with very little code.
¨ Web Browser Control
The IE Web browser control opens up a whole new way to think about user interface for
applications. First, the control allows you to integrate a custom browser into your
application, one that can be easily driven through the application and can be fully
controlled through the application allowing you to tightly control the content that the
custom browser accesses. But it can also serve as a great mechanism to display rich
content for an application and even as a way to ease development of Web centric
applications that may have to live both as a standalone application on the desktop (or on
a CD) as well as a Web application accessed purely through a browser and Web server.
The Internet has opened up the idea of a global network into a reality that your applications can take advantage of. However, with all the hype about distributed applications and Web server centric applications there hasn't been much discussion about accessing data over the Internet and driving logic of server applications from the client.
¨ Accessing Web content
One of the most powerful concepts of the Internet is the ability to have client side
applications accessing Web content. By utilizing rich client side applications that can
communicate with servers over the Internet be it for pulling simple content or even for
driving applications, you gain a whole new layer of possibilities to your applications.
It's actually quite easy to gather Internet content and use it as an information source in
your applications.
¨ Accessing data
Along the same lines it's possible to access data over the Internet. TCP/IP is a
standard network protocol that is supported by Windows and it's possible to access data
directly over the Internet as with any other network connection. There are also ways to
use middleware tools and components that can give your client side application the ability
to access data from the server. I'll show some examples of this using pure FoxPro code on
both client and server as well as Microsoft's Remote Data Service (RDS) which provides
very similar functionality in a system component.
¨ Building distributed applications with VFP on client
*and* server
The Web is powered by HTTP. HTTP is a relatively simple protocol to work with. With
it, it's easy to create complex applications that can be driven and/or shared by both
client and the server. In this scenario, applications have the ability to work offline, or
use the Web as a live data/content feed. This can relate to either your own Web
application running on a Web server, or any other resource availlable on the Internet. For
example, it's easy to download all sorts of information like stock information, weather
info, news etc. all of which you can use to customize and enhance your applications with.
Finally you can build truly distributed applications with VFP where the client application
controls part of the logic and the server part of the other. The power of this mechanism
is that you can mix and match client and server technologies, or you can even use VFP
clients talking to VFP server to cross the Internet boundary.
Let's start by looking at enhancing existing applications. I'll cover the following:
¨ Shell API
¨ SMTP Email
¨ FTP
¨ Basic programmatic Web access
One of the easiest and powerful features of the Windows operating system is the Shell API. This API is not Internet specific but it provides some cool features for Internet use. It provides a mechanism for "executing" any kind of document that Windows knows about. Using this API you can "run" a Word document or an .EXE file, access a URL in your default browser, force an e-mail window to pop up, or bring up your favorite HTML editor to edit an HTML document. The interface works through a mechanism called monikers, which is essentially the Windows way to parse command lines. If Windows can recognize a moniker, it will attempt to execute it the same as it does when you double-click on a document with a known extension in the Windows Explorer.
The Shell API is implemented in several different ways, the simplest of which is an API call you use in your Visual FoxPro applications. To try it, create the following function:
FUNCTION ShellExec
LPARAMETER lcLink, lcAction, lcParms
lcAction = IIF(EMPTY(lcAction), "Open", lcAction)
lcParms = IIF(EMPTY(lcParms), "", lcParms)
DECLARE INTEGER ShellExecute ;
IN SHELL32.dll ;
INTEGER nWinHandle, ;
STRING cOperation, ;
STRING cFileName, ;
STRING cParameters, ;
STRING cDirectory, ;
INTEGER nShowWindow
DECLARE INTEGER FindWindow ;
IN WIN32API ;
STRING cNull,STRING cWinName
RETURN ShellExecute(FindWindow(0, _SCREEN.caption), ;
lcAction, lcLink, ;
lcParms, SYS(2023), 1)
and then try the following links (adjust the paths as necessary):
ShellExec("http://www.west-wind.com/")
ShellExec("ftp://ftp.west-wind.com/")
You'll find that in both instances the ShellExecute API brings up the currently selected default browser (yes, this can be Netscape), and attempts to load the page you specified from the Web. You can also force your e-mail client to pop up:
ShellExec("mailto:billg@microsoft.com")
with Billy's name already entered into the To: field and the message window ready for you to start typing your latest reason for hating Windows. Although the Shell API can be used to access any link that you could use in an HTML document's HREF link, you can also access other things on your system:
*** Open Explorer in c:\temp
Shellexec("c:\temp\")
*** Runs VFP
ShellExec("c:\vstudio\vfp98\vfp6.exe")
*** Opens a word document
ShellExec("c:\Webbook\Chapter7\Chapter7.doc")
*** Opens a Zip file in WinZip
ShellExec("c:\downloads\southpark.zip")
You can also cause documents to be edited and printed, if the document type supports it:
*** Visual Interdev comes up
ShellExec("http://www.west-wind.com/", "Edit")
ShellExec("c:\Webbook\Chapter7\Chapter7.doc", "Print")
The former causes the resulting HTML from the West Wind site to be opened in your favorite HTML editor (if you have one configured), ready for editing. The latter fires up Microsoft Word and prints the requested document.
Because it has the ability to bring up a Web browser, this API is an easy way to bring people to your Web site by clicking on a button or menu option. You can easily integrate this functionality to take users to information pages, update pages, and of course, order pages to buy more stuff from you. You can even use it to display customized information, or information that's updated on a regular basis, simply by using naming conventions for the HTML pages on the server (how about a tip of the day with HTML pages, such as Tip102098.htm?). You can also cause the request to be dynamic through the URL query string by querying a dynamic server link such as an ASP page or FoxISAPI request:
ShellExecute("http://www.west-wind.com/wwThreads/default.asp?MsgId=_SA012344")
This takes you directly to the specified message on this message board application. You could conceivably display query information on the server in the same fashion.
In addition, the API makes it easy to show HTML in your applications, either from file or from a string. Here's a useful little function that uses ShellExecute to display an HTML string in the browser:
FUNCTION ShowHTML
LPARAMETERS lcHTML, lcFile, loWebBrowser
lcHTML = IIF(EMPTY(lcHTML), "", lcHTML)
lcFile = IIF(EMPTY(lcFile), SYS(2023) + "\_HTMLView.htm",lcFile)
STRTOFILE(lcHTML, lcFile)
IF VARTYPE(loWebBrowser) = "O"
*** If a browser object was passed use it
*** instead of an external browser window
loWebBrowser.Navigate(lcFile)
ELSE
ShellExecute(lcFile)
ENDIF
RETURN
*EOP ShowHTML
You can simply pass any HTML string to this function and it will render it in the browser and pop it open. If you have an existing instance of a WebBrowser object or an IE COM object (more on this later), you can render the HTML within that object instead of in the external window controlled by ShellExecute.
You can also call the Shell API's COM interface to access all sorts of interesting Windows functionality:
o = CREATEOBJECT("Shell.Application")
o.Open("http://www.west-wind.com/")
*** Fire up Windows Dialogs modally
o.SetTime()
o.FindFiles()
o.ShutDownWindows()
o.FileRun()
o.ControlPanelItem("Network")
"Open" works very much the same as the ShellExecute API call, only it always uses the Open action verb. There's no Edit or Print option, so you have to use ShellExecute for those operations. However, the Shell API also includes access to most Windows desktop operationsbut keep in mind that these operations all require user intervention of some sort. These methods bring up dialogs in which the user must fill in information or click a button to continue. What does all this have to do with the Internet? Not muchbut you might find these related operations useful.
wwIPStuff is a class library that provides common Internet protocols, wrapped in a single, easy-to-use class library. The class library can be downloaded from http://www.west-wind.com/files/wwipstuff.zip and is available as fully functional shareware from West Wind Technologies. The library was originally built to provide functionality that would be commonly required in the course of Web application development. It includes support for SMTP e-mail, FTP functionality, domain name lookup and reverse lookup, the ability to dial a RAS connection, and a number of other features related to driving HTTP connections through code. The HTTP features are described in great detail later in this article.
E-mail continues to be the most popular activity on the Internet, and more and more Internet applications are taking advantage of an e-mail interface. A few common uses I frequently use email for:
¨ Sending content of any sort to a user
¨ Sending confirmation and status notices for things like orders placed, orders shipped etc.
¨ Notification of errors in applications
¨ Sending output of long running reports to users when the reports complete
wwIPStuff implements outgoing SMTP e-mail via a DLL interface in wwIPStuff.dll, which contains a C++ e-mail class that performs the socket operations of communicating with the mail server. The VFP code simply acts as the front end to a function with a large parameter/class property interface that passes the information to the DLL for processing.
To install, simply make sure that you have wwIPStuff.vcx/.vct, wwUtils.prg, wconnect.h and wwIPStuff.dll in the current path or somewhere along the Visual FoxPro SET PATH. Using the class itself is very simple. You have to set only a few relevant properties on the wwIPStuff object to send a message:
SET CLASSLIB TO wwIPSTUFF ADDITIVE
SET PROCEDURE TO wwUtils ADDITIVE
loIP = CREATEOBJECT("wwIPStuff")
loIP.cMailServer = "your.mailserver.com" && or IP address
loIP.cSenderEmail = "yourname@yours.com"
loIP.cSenderName = "Jimmy Roe "
loIP.cRecipient = "jroe@roe.com "
loIP.cCCList = "jbox@com.com,ttemp@temp.com"
loIP.cBCCList = "somebody@nowhere.com"
loIP.cSubject = "wwIPStuff Test Message"
loIP.cMessage = "Test Message body" + CHR(13)
*loIP.cAttachment = "c:\temp\pkzip.exe"
*** Wait for completion
llResult = loIP.SendMail()
IF !llResult
WAIT WINDOW loIP.cErrorMsg
ELSE
WAIT WINDOW NOWAIT "Message sent..."
ENDIF
wwIPStuff is a minimalist SMTP client that lets you use the class interface to set the e-mail properties and the SendMail() method to actually submit the e-mail message to the mail server. You can specify the sender's e-mail name (note that you can potentially spoof an e-mail addressthat's the way SMTP happens to work) and then supply recipient lists. You can provide either a single e-mail address or a list. When using the latter, separate each primary recipient, CC and Blind CC recipient with commas. The actual e-mail message consists of a subject line and message body, which can be of any length. You can optionally attach one file to the message; files are encoded in MIME format.
E-mail messages can be sent in two different modes: Synchronous or Asynchronous. Plain SendMail() waits for completion of the Send operation, and then returns error or completion information along with a valid .T. or .F. result. If .F. is returned, you can check the cErrorMsg property for the error that occurred.
SendMailAsync() sends the message without waiting for a result. The operation runs on a separate thread, so it appears to your application that the return is instant. When running Web applications in particular, this is the preferred way to send messages, to avoid blocking the server while waiting for the mail sending operation to complete.
Note that SMTP is a server-based protocol, which means that messages sent to it are submitted but not necessarily processed immediately. This means that, even with the synchronous SendMail() call that returns a good status code, there's no guarantee that the e-mail message actually got to its recipient. The only way you'll find out about an invalid e-mail address or a closed mailbox is by the returned mail error that will arrive in your Inbox.
wwIPStuff provides a number of FTP functions that facilitate file uploads and downloads. The FTP functions are implemented using VFP code that talks to the system WinInet.dll FTP functions. I don't have enough room here to print the source code, but you can look at the full VFP source code in wwIPStuff.vcx.
To download a file via FTP, use the following code:
SET CLASSLIB TO wwIPStuff ADDITIVE
SET PROCEDURE TO wwUtils ADDITIVE
loFTP = CREATEOBJECT("wwFTP")
lnResult = loFTP.FTPGetFile("ftp.westwind.com", "pkunzip.exe", ;
"c:\temp\pkunzip.exe")
IF lnResult # 0
? loFTP.cErrorMsg
RETURN
ENDIF
FTPGetFile() is a pure synchronous call that grabs an online file and downloads it to a local file. Because it's synchronous and doesn't provide any transfer-status feedback on long files, this function might make users think that their machine has locked up.
To work around this problem, there's FTPGetEx(), which supports event-method calls to allow you to cancel a download and handle a status display. To do so, you have to either subclass wwFTP or drop it onto a form and implement the OnFTPBufferUpdate() method. The full code looks like this:
SET CLASSLIB TO wwIPStuff additive
SET PROCEDURE TO wwUtils additive
PUBLIC o
o = CREATEOBJECT("myFTP")
WAIT WINDOW NOWAIT "Alt-x to abort download..."
ON KEY LABEL ALT-X o.lCancelDownload = .T.
IF o.FTPConnect("ftp.west-wind.com") # 0
? o.nError
? o.cErrorMsg
RETURN
ENDIF
IF o.FtpGetFileEx("wconnect.zip","c:\temp\wconnect.zip") # 0
? o.nError
? o.cErrorMsg
RETURN
ENDIF
ON KEY LABEL ALT-X
RETURN
DEFINE CLASS myFtp AS wwFTP
FUNCTION OnFTPBufferUpdate
LPARAMETER lnBytesDownloaded, lnBufferReads, lcCurrentChunk
DO CASE
CASE lnBufferReads > 0
WAIT WINDOW "Bytes read: " + TRANSFORM(lnBytesDownloaded) NOWAIT
*** DoEvents && Handle a UI event like Cancel Button Click
CASE lnBufferReads = -1
WAIT WINDOW "Download aborted..." TIMEOUT 2
ENDCASE
RETURN
ENDDEFINE
Here I'm using the Alt-X ON KEY LABEL command to trap an abort of the download by setting the FTP object's lCancelDownload flag to .T. When the flag is set, the FTP code aborts when the next chunk of data is to be fetched.
In order to display status information during the download, the method receives a running total of downloaded bytes. The parameters to this method hold the running total of bytes, the number of reads that have occurred so far, and the current chunk of data that was read. The last item is useful for building output incrementally for non-binary or other streamed data that can be read as it comes in. The lnBufferReads parameter contains a number greater than 0 while reading, and -1 if an error occurred or the operation was aborted (that is, lCancelDownload was set).
If you need to control other UI elements, such as a form with a Cancel button, you need to make sure that you call DoEvents in the update routine so that the UI event can fire and run.
You can also upload files in the same manner using the FTPSend and FTPSendEx methods. Here's an example of FTPSend (the SendEx version provides the same OnFTPBufferUpdate "events" as the FTPGetFileEx method):
o = CREATEOBJECT("wwFTP")
lnResult =
o.FTPSendFile("ftp.west-wind.com", "c:\temp\pkunzip.exe", ;
"/pkunzip.exe", "username", "password")
IF lnResult # 0
? o.cErrorMsg
ENDIF
Note that the username and password are required only if you're uploading files to a restricted directory that doesn't allow uploads to anonymous users. This is generally the case with FTP uploads, but it's entirely up to the site administrator to set these permissions.
In addition to these functions, you can get a FTP directory listing and delete files on the server using the aFTPDir and FTPDeleteFile methods of the wwFTP object. Take a look at the class library for and the included HTML Help file more details about how to call these methods.
WwIPStuff's strongest feature is its HTTP functionality. I'll talk more about this further on in this article, but in its basic form it provides an easy mechanism for retrieving Web content from any HTTP URL.
The easiest way to get started is with the HTTPGet method, which is a simple way to retrieve Web content into a string with a single method call. To use it you can simple do:
O=CREATEOBJECT("wwIPStuff")
*** Simple retrieval of data
lcResult = o.HTTPGet(http://www.west-wind.com/)
lcResult = o.HTTPGet(http://localhost/datafile.dat)
lcResult = o.HTTPGet(http://localhost/report.pdf)
which retrieves the entire content of the Web page into a string. Note that you can pull HTML down or any kind of content that the Web server makes available. The latter two requests are pulling binary data from the server some datafile and an Adobe PDF document, which you can easily retrieve and then save to a file with VFP's new STRTOFILE function.
It's also possible to POST information to a server. POST is HTTP's standard mechanism to send information to the server. You use POST operations whenever you submit an HTML form on a Web page over the Internet. The browser encodes the form data into a POST buffer which is sent to the Web server which in turn can retrieve these variables as part of the Web request. In Active Server Pages and Web Connection you'd use the Request.Form() collection to retrieve this data in a server application.
To post data use code like the following:
lcData = ""
lnSize = 0
o.HTTPConnect(www.west-wind.com,rstrahl,Password)
o.AddPostKey(Name,Rick)
o.AddPostKey(Company,West Wind Technologies)
lnResult = o.HTTPGetEx(/scripts/wc.dll?PostTest,;
@lcData,@lnSize)
o.HTTPClose()
Unlike the HTTPGet operation which handles everything in a single method call HTTPGetEx requires several method calls to open, add data to the input buffer, send and finally close the connection to the Web server. Note that you have to pass in a data buffer and size parameter by reference these values are set by the method call. You can specify a pre-sized buffer and size which will limit the amount of data retrieved to that amount of bytes. Setting the buffer to a Null string ("") and the size to 0 means that the buffer is dynamically sized. Using a fixed size may be useful if you only want to 'ping' a site or retrieve only a header.
Although HTTPGetEx primarily is there for providing POST functionality, you don't have to actually POST any variables to the server. The method also provides much more control over the HTTP request so you may also use it for plain GET operation that require more control:
¨ Secure connections over HTTPS/SSL
¨ Password validation via Basic Authentication
¨ Dynamically sized input buffers
¨ Event notification while a download is in progress
¨ Retrieve the HTTP Header (cHTTPHeaders property)
In general HTTPGetEx is much more flexible, but HTTPGet() is obviously easier to use. Use HTTPGet when you simply need to retrieve data without any special options. In all other cases you can use HTTPGetEx.
WwIPStuff provides a host of other methods and objects related to HTTP access including:
¨ Running SQL statements over HTTP (requires VFP server code)
¨ Transferring files over HTTP
¨ Asynchronous operation of HTTPGetAsync and HTTPGetExAsync
You can find out more by looking at the wwIPStuff.chm HTML Help file.
Ive discussed providing data to client applications in great detail in this book. Ive shown pure server side solutions that handle delivering content made up of data to the client in display form and Ive shown how to transport data over HTTP using RDS and wwIPStuff. However, there is another, potentially easier way to access data over the Internet by using the TCP/IP protocol. TCP/IP is just another network protocol that Windows supports so its possible to use the Internet as a giant network to access data and resources directly over the Internet.
For example, its entirely possible for me to connect to my Web server in Oregon from my house in Hawaii using the following:
USE \\www.west-wind.com\d$\webapps\wwdemo\guest.dbf
Note: Using IP address and domain names in UNC resource references works only with Windows NT (Win2000). For Win95/98 you have to set LMHOSTS entries to map IP addresses to Netbios names.
The above command will connect to my Web server and open the Guest.dbf file which I can then peruse like any other VFP data table. Although this works just fine, the process is slow even on a small table. The problem is that remote connections like this take a while to make over the Internet. Using a 56k dial up connection it takes approximately 1 minute for the connection to be made to the above file containing about 200 records. In this case the overhead is the connection time. If your files contain lots of data with index files, opening a file on the server will pull down the index information over the slow connection further slowing down the connect time.
Once the initial connection has been made and the table is open though, operation is reasonably fast, but it depends on the size of the data file. If the index is small and cached locally look ups can be very fast. However, if the index is not all downloaded you can see long delays as VFP pulls down more index data to perform the lookup. On slow connections VFPs indexing and Rushmore optimization that requires them actually can become a handicap requiring more data than necessary to be downloaded initially.
Here you can see the problem with a file based database tool like VFP it does all of its processing on the client side, so while the data sits on the server with a slow connection the client needs to pull all the necessary data to perform lookups and get the data itself, which may require significant amounts of the data locally causing much traffic to occur over the network connection. In this scenario a SQL Server that performs the processing on the server and then returns only the results is much more efficient. Ill look at this in the next section.
You should notice an important point now if you tried to connect to my Web server using the USE and UNC syntax above youd fail. And thats a good thing you obviously shouldnt have access to tables on my Web server since you dont have permissions on that machine. For applications that want to use TCP/IP file access, this means that you need to properly set up NT security using a specific NT account that has the required access rights. This username can either be a generic application account thats hidden in the binary executable of the application, or a particular user password thats provided by the user. Note that NT can validate security across the Internet connection automatically. If Im logged on as rstrahl on my local machine and that same account exists on the server with the same password, passthrough validation allows me access on the server automatically without having to logon. If I try to connect with a different username the NT password dialog will pop up requiring me to enter a valid user account for the server. The behavior is identical to the way youd use a LAN connection with NT or Win9x.
Another related issue is data security. Once youre connected and data is transferring over the Internet connection, that data isnt in any way encrypted. This means anyone with a protocol analyzer can grab the data off the wire.
Just as you can access DBF files over the Internet you can also access a SQL server in the same fashion. If youre using Microsoft SQL Server you can configure that server to run using TCP/IP as its network protocol (with SQL Server 7.0 this is the default protocol SQL Server 6.5 uses Named Pipes by default and will carry forward that setting if upgraded to 7.0). Once youve configured the server with TCP/IP you could then connect to the server like this:
lnHandle = SQLSTRINGCONNECT(driver={SQL Server};server=www.west-wind.com; + ;
database=Pubs;uid=sa;pwd=;)
SQLEXEC(lnHandle,SELECT * FROM Authors)
BROWSE
This also works as expected. However, just like with direct VFP table access connecting can take a long time