Bob is the president of HC Software Enterprises and can be contacted at [email protected].
Sooner or later, most Visual FoxPro developers need to add some form of asynchronous file-transfer capability to their applications. This capability typically involves dialing up another PC, and physically moving files into the communications buffer using a third-party library. In this article, I'll demonstrate how you can use Visual FoxPro 5 to connect to a remote PC, establish a network connection, map its hard drive, and transfer files from one PC to another using familiar FoxPro functions such as GetFile(), COPY FILE, and the like. I'll also examine ways to develop fast, robust client/server type applications that allow users to go on the road and use standard phone lines to access data. I've used the technique presented here in a number of applications, including e-Scrip, a commercially available doctor/pharmacy communications program.
Of course, there are third-party tools that provide asynchronous file-transfer capabilities. However, none of these offer the flexibility of a network connection. Additionally, most are designed for Visual FoxPro Version 3 (or earlier), and don't work with Visual FoxPro 5. Another approach, of course, is to use FoxPro's RUNDLL command, but it, too, has problems. Users, for instance, have to interact with the networking features by clicking buttons. I examined a number of shareware tools that launch and disconnect a dial-up connection, but after one too many "Invalid Page Fault" messages, I decided to look elsewhere. Even if I could find a stable tool to dial the connection, it still would fall short of a total solution, because users would need a defined dial-up connection in their control panel for each machine they want to access -- I needed to assign the phone numbers dynamically.
Ultimately, I opted for Windows 95/NT built-in Dial-up Networking features. This approach means applications will withstand version and operating-system changes. However, information on accessing Dial-up Networking is hard to come by. Even basic knowledge, such as how to get a host machine to answer an incoming call, sends you to the Microsoft KnowledgeBase.
Configuring Host/Remote Machines
Listing One is code for connecting to a host machine, mapping its hard drive, and transferring a file. Dial-up Networking must be installed on both host and remote machines for this code to work. For the host machine to answer a call, it must be running Microsoft Plus (a Windows 95 add on). After you install Plus on the machine you intend to call, go to the Dial-up Networking dialog of that machine and choose Dial-up Server from the Connections menu. Click the Allow Caller Access button, then click OK.
At this point, it is a good idea to make a new connection to the "server" PC, and make sure you can connect to it. If you cannot successfully connect, go into the Networking dialogue from the Control Panel and:
- Select the Dial-up Adapter.
- Click Properties.
- Select the Bindings tab.
- Be sure that the following items exist, and are checked: IPX/SPX-compatible Protocol Dial-up adapter, NetBEUI Dial-up Adapter, and TCP/IP Dial-up Adapter.
If any of the above items are missing, you can install them from the network dialog by:
- Selecting the Dial-up Adapter.
- Clicking the Add button.
- Double-clicking Protocol.
- Clicking on Microsoft.
- Double-clicking the protocol that you are missing. If you are missing more than one, repeat this process until all three are installed.
After successfully connecting, you know the two machines are configured correctly. If not, the Microsoft KnowledgeBase has several good articles on troubleshooting Dial-up Networking connections.
Wrestling with DLLs
Windows lets you access certain features of the operating system through the use of DLLs. There is a rich collection of functions in these libraries, and browsing through Win32api.HLP (which comes with VFP 5) is worth your time. I will deal primarily with functions that access the Remote Access Server (RAS) features of Windows 95.
A DLL is just like a regular FoxPro library, except that you must "declare" the functions you want to use. This declaration tells FoxPro what sort of arguments the function will return, and what arguments it expects to receive; for instance, to declare the RasHangUp function (see lines 019-021 in Listing One) to make the phone hang up when you are finished with the connection:
- Issue the FoxPro command DECLARE.
- Tell FoxPro what datatype the function will return (INTEGER).
- Type the name of the function you want to call (RasHangUp).
- Tell FoxPro what DLL file the function is in (RASAPI32.DLL).
- Tell FoxPro what datatype the function expects to receive (INTEGER).
To find out what parameters each function expects, look in Win32api.HLP. Also, the Quick Info button in the help file will tell you what DLL file the function is in.
At this point, you can call the function (see line 101). If your function expects multiple arguments, you need to identify and separate them with commas. A more complicated example that illustrates this concept is a function that dials the phone (see lines 007-009). Notice that the function is expecting six parameters, and you must declare them all. The "@" sign means you will pass a variable to the function by reference rather than just passing its value. When you pass by reference, you pass the variable's memory address. The receiving function then goes to that memory address and reads what is there. You can assume that if the declaration in Win32api.HLP says the parameter is a pointer, then you will need to pass the variable by reference.
Notice that I am also passing a string by reference. You will need to create a string that contains information about the connection (phone number, password, and so on). This is tricky, though, because the first 4 bytes need to be the length of the string (1052 bytes) represented in a format C can understand. You can't just pass the value 1052, because C reads the entire 4 bytes as one value. The code Double_word_size=CHR(28)+CHR(4)+ CHR(0)+CHR(0) does let you fool C, however. (The formula to convert a number to a C double-word value is implemented in lines 107-115.)
It's all downhill from here. You just need to create the rest of the string and format it as instructed in the RASDIALPARAMS section of Win32api.HLP. When C reads a string, it begins at a memory address, then starts reading characters until it comes to the null character (CHR(0)). You must be sure that every argument you put into this string is terminated with the null character, then pad the rest out to the appropriate length (lines 039-044). Continue with all the other parameters and concatenate them into one string (lines 046-052). If you add up these lengths, you will find they total 1049 -- three short of what is expected. This is because C expects everything to be aligned in 4-byte chunks, so you need to pad the entire string to a value divisible by 4 (see line 054).
The length is now 1052 and RasDial (lines 058-059) will:
- Dial a computer at 555-1212.
- Use a connection in your Dial-up Networking phonebook named "My Office."
- Pass "RDH" as a user name.
- Use "MyPassword" as a password.
The phone number you pass takes precedence over whatever phone number is defined in the connection. If you leave it blank, the program will dial the number in the connection. At this point, you need to create a variable with a zero value that will accept the handle to the connection. When you call the RasDial function, it will not return control until it either succeeds or fails, which means your application will be locked for about a minute. This will probably not be a big issue for your users, but if it is, you can make a routine outside of your main application that simply waits for a condition to exist, then dials the connection.
RasDial will return a nonzero value if it fails, so you should trap for it and give the user a clue as to why it failed. You can get a fairly understandable error message by calling the RasGetErrorString function (lines 061-069).
Mapping theNetwork Connection
If all goes well, you should have a connection to another PC. To do anything with it, however, you must have "permission" to use its resources (that is, its hard drive). Also, you must know its volume name (computer name) so you can address it properly. Be sure that the hard drive of the PC with which you are connecting is shared as a network resource, and give the PC a name following this procedure:
- Double-click the network icon in the Windows control panel.
- Click the File and Print Sharing button.
- Be sure that the first option is checked and then click OK.
- Click the Identification tab, and enter a computer name (in my case, "My Office").
- Open the Windows Explorer and Right-Click the C: drive.
- Select Sharing....
- Click Share as: and leave the share name as "C."
Once you know the computer name, you can access its hard drive as though you were directly networked to it (though it will run slower). The standard way of addressing network resources is to precede the volume name with two backslashes, then type in the name of the resource. For example, when you set up the computer, you named it "My Office," and left the share name of the hard drive as "C." So if you wanted to test the existence of the Autoexec.bat file in the Temp directory, you could access it as shown in line 085. Another (more familiar) way to address network resources is by "mapping" them to a drive letter. You could map to the other hard drive, then refer to it with a letter (lines 070-079). Since this maps the volume to the letter "W," you can use the same command using a "W" as in line 083.
This same technique can be used to map volumes on normal networks. Just be sure that the computer you are calling does not have the same name as the one you are calling it with, otherwise you will be mapping to yourself.
You don't need to share the entire hard drive. Instead of sharing the C: drive, you could share a subdirectory. Then, when someone dials in, the root directory of the share name will point to the appropriate subdirectory in the root of the drive.
By now, it should be obvious that to transfer a file, you simply copy it from one machine to another using commands such as COPY FILE C:\Autoexec.bat to \\My Office\C\Autoexec.bat or COPY FILE C:\Autoexec.bat to W:\Autoexec.bat. Anytime you establish a permanent network map, the operating system attempts to reestablish it the next time it boots up. Since this is probably not desirable, you should clean up by unmapping the drive (lines 093-099). Finally, the last step is to kill the connection using the handle returned by RasDial (line 101).
The Win32api.HLP file recommends you "sleep" the program for 3000 milliseconds to give it time to terminate. You should respect this suggestion. You can either call the Sleep function in the API, or tell FoxPro to wait for a half second using the command WAIT '' TIMEOUT .5.
Conclusion
Since native Dial-up Networking supports TCP/IP, PPP, IPX/SPX, and the like, the methods I've described here can be easily adapted to connect with machines on the Internet, too. You could also develop a system that people on the road could dial in to and get live data wherever they are. Since phone connections are slow, you could send them data in small chunks by having a central PC accept calls and monitor a certain location on disk. The remote machine would dial in when the user needs data. You would create a text file with an SQL-SELECT statement inside and send it to the host PC. When the host PC finds the text file, it would process the SQL statement, and send the results back to the remote PC.
DDJ