Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Parallel

Asynchronous Communication andVisual FoxPro


Dr. Dobb's Sourcebook May/June 1997: Asynchronous Communication and Visual FoxPro

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

Listing One

001 *!*        Sendit.PRG  *!*
002
003  SET PROC TO sendit.prg
004
005 *!*        Function to Dial the Connection
006
007 DECLARE INTEGER RasDial ;
008        IN C:\WINDOWS\SYSTEM\RASAPI32.DLL ;
009        INTEGER @, INTEGER @, STRING @, INTEGER , INTEGER , INTEGER @
010 
011 *!*        Function to return any errors from the connection
012
013 DECLARE INTEGER RasGetErrorString ;
014        IN C:\WINDOWS\SYSTEM\RASAPI32.DLL ;
015        INTEGER , STRING @ , INTEGER
016 
017 *!*        Function to hang up the phone
018
019 DECLARE INTEGER RasHangUp ;
020        IN C:\WINDOWS\SYSTEM\RASAPI32.DLL ;
021        INTEGER
022 
023 *!* Map a hard drive (volume name) to a drive letter
024 
025 DECLARE INTEGER WNetAddConnection IN Win32API ;
026        STRING @ , STRING @ , STRING @
027 
028 *!* Un-map a local drive letter from a remote drive.
029 
030 DECLARE INTEGER WNetCancelConnection IN Win32API ;
031        STRING @ , INTEGER
032 
033 Zero_value = 0
034 Connection_Handle = 0 && Connection Handle that RASAPI32.DLL gives back
035                       && We will use this to hang up the phone.
036 
037 Double_word_size = CHR(28)+CHR(4)+CHR(0)+CHR(0) 
038                              && Value of 1052 represented as a string
039 Connection_Name = PADR('My Office'+CHR(0),257)
040 Connection_Number = PADR('555-1212'+CHR(0),129)
041 Callback_Number = PADR(''+CHR(0),129)
042 User_Name = PADR('RDH'+CHR(0),257)
043 Connection_Password = PADR('MyPassword'+CHR(0),257)
044 Domain_name = PADR(''+CHR(0),16)
045 
046 String_to_pass = Double_word_size + ;
047        Connection_Name + ;
048        Connection_Number + ;
049        Callback_Number + ;
050        User_Name + ;
051        Connection_Password + ;
052        Domain_name
053 
054 String_to_pass = PADR(String_to_pass,LEN(String_to_pass)+
055                                      (4 - LEN(String_to_pass) % 4))
056 WAIT 'Dialing ' + SUBSTR(Connection_Name,1,50) WINDOW NOWAIT
057 
058 Return_Value=RasDial(Zero_value,Zero_value,@String_to_pass,Zero_value, ;
059        Zero_value, @Connection_Handle)
060           
061 IF !EMPTY(Return_Value)                 
062     Message_Buffer = SPACE(256)     && Placeholder for error message
063     IF !EMPTY(RasGetErrorString(Return_Value,@Message_Buffer,
064                                          LEN(Message_Buffer)))
065           WAIT 'Unknown error' WINDOW
066     ELSE          
067           WAIT ALLTRIM(Message_Buffer) WINDOW
068     ENDIF
069 ENDIF
070 Drive_letter = 'W:'
071 Volume_name = '\\My Office\C'
072 Drive_Password  = ''
073 
074 IF !EMPTY(WNetAddConnection(@Volume_name, @Drive_Password, @Drive_letter))
075        WAIT 'Drive not mapped' WINDOW
076 ELSE
077        WAIT 'Drive mapped.... ' + ALLTRIM(Volume_name) + ' to ' + ;
078 "WINDOW TIMEOUT 3" vice "WINDOW 078 TIMEOUT 3"
079 ENDIF
080 
081 *------------------------------------------------------------------------
082 
083 COPY FILE C:\autoexec.bat TO W:\Temp\Autoexec.bat
084  
085 IF FILE('\\My Office\C\Temp\Autoexec.bat')
086        WAIT 'Copied Successfully' WINDOW TIMEOUT 5
087 ELSE
088        WAIT 'Not Copied' WINDOW
089 ENDIF
090 
091 *!*         Un-map a local drive letter from a remote drive
092 
093 Drive_letter = 'W:'
094 
095 IF !EMPTY(WNetCancelConnection(Drive_letter, .T.))
096        WAIT 'Un-mapping Failed' WINDOW NOWAIT
097 ELSE
098        WAIT 'Un-mapped ' + Drive_letter + '.' WINDOW NOWAIT
099 ENDIF
100 
101 Return_Value = RasHangUp(Connection_Handle)     && hangup the connection
102 
103 WAIT '' TIMEOUT .5
104 WAIT 'Process Complete.' WINDOW
106
107 Function Convert_to_Double_Word
108
109 Double_word_size = ''
110 Number_to_convert = 1052
111  FOR i = 1 TO 4
112    Double_word_size = Double_word_size + CHR(Number_to_convert % 256)
113    Number_to_convert = INT(Number_to_convert / 256)
114  NEXT
115  RETURN Double_word_size

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.