Struct
Written 1999 by Christof Lange and Mark Wilden
Placed into Public Domain by The Foxpert!

INTRODUCTION	1
GDI.SCX	1
GDI2.SCX	3
RESCHANGE.SCX	4
MSGBOX.PRG	6
OSVERSION.PRG	6
PRINTERINFO.PRG	6
PRTRANGE.PRG	7
SYSTEMTIME.PRG	7
GETUNC.PRG	8
FINDFILES.PRG	8
Introduction
This document contains details about all sample programs provided in the package. It explains what they do, how they do it and how you call them (don't try to simply execute them). I tested all these programs in Windows 98, since that's the operating system I use. Unfortunately, API programming is much more coupled to the operating system than plain Visual FoxPro programming. Thus, if you don't see the correct results on other platforms, please don't be disappointed, but send me a note. If you can figure out what's different on your platform that's even better, because it lets me fix the problem more easily. And, on Windows NT all samples use the ANSI version of all API functions, that means, they don't make use of Unicode. The struct library does support Unicode to the extend that was possible for me to test, so you should be able to rewrite these samples to call the Unicode version, if you need. 
All samples require that the current directory is the one with the Struct.VCX, Convert.FLL and WinStruct.VCX. If you are not in that directory, you must have these files in the path.
These samples are just these, samples. The functionality they offer isn't always useful in a real-world application, but I think they demonstrate quite well what you can do with API programming and how you can implement structures in your own application.
GDI.SCX
Did you have had the need for some more complicated user interfaces? Visual FoxPro makes it easy, for example, to put text on the form formatted with different fonts, sizes, styles and colors. But what do you do if you need to print some text sideways, or even rotated by a certain degree? The GDI, the Graphic Device Interface, of Windows provides a lot of features.
Important for the understanding of the GDI is that everything you paint via the GDI is not persistent. When you resize a window or another window overlaps your form, Windows doesn't automatically redraw the area; it leaves it blank. It's your job to react accordingly in this situation and take care of repainting the empty area. That is what the Paint event in Visual FoxPro is used for. 
The key to access the GDI is the device context (DC). A device context is a handle to a virtual bitmap where you can print text, draw lines, display bitmaps and so on. A device context can be created for the client area of a form by calling the GetDC() API function. This is what a form is to the Visual FoxPro developer. But you can also create a device context for the entire form including the border and title bar, thus adding for example a bitmap to the title bar. The function you need to call is GetWindowDC(). Device contexts are used far more general than that. You can retrieve a device context for the desktop, but also for a paper in the printer. Once you have the device context, you can call the same functions to display whatever you want, no matter whether it's displayed on a form or printed on any Windows printer.
To run the GDI.SCX sample enter DO FORM GDI.SCX in the Command Window. A Visual FoxPro form is displayed that contains rotated text - without using any ActiveX control!
In the Load event, a LOGFONT structure is created. This structure describes the characteristics of the desired TrueType font. If you request a font in Windows, it's not sure that this font is installed on the computer. Therefore Windows has got a fontmapper that tries to find the closest match to the font you have specified in this structure and loads this font. On exciting feature of this mapper is that it can also create a rotated font by specifying a degree in the LOGFONT structure. CreateFontIndirect() takes this structures and creates the appropriate font. The return value is a handle that you can use to activate the font. Since creating a font takes some time, we do it once in the Load event and release the font in the Destroy event. Releasing a font is important, as is releasing any GDI handle you open or create. If you forget this, your application might cause memory leaks.
The Paint event is comparable simple. It activates a font with the SelectObject() function and prints some strings at the same position using differently rotated fonts. As Visual FoxPro fires the Paint event whenever you have to redraw the form, you can resize and minimize the form, as you like. No rule without an exception, though. 
Excursion - The Paint event: The Paint event behaves rather strange in Visual FoxPro. It's supposed to fire whenever part of the form needs to be repainted. Usually that is if the contents changed or a window that covered part of the form is moved away. This works quite nicely, when only Visual FoxPro is involved. But if you activate another application that is positioned on top of the form and then switch back to Visual FoxPro, Paint behaves strangely. When the Visual FoxPro form is the active form, the Paint event does not fire, thus, the area that has been covered by the other application remains empty. If it's not the active form, Paint does fire.
To work around that you can use the Visual FoxPro HWND control that ships with Visual FoxPro. It's an ActiveX control that provides you with a Windows window inside your form that has got its own Paint event and its own HWND - the window handle. Here the Paint event behaves exactly the other way round. If the form is active, the Paint event does fire, if the form is not active, the Paint event doesn't fire.
The GDIHWND.Scx sample demonstrates a possibility to work around this problem by combining the Form's paint event and the Paint event of the Visual FoxPro HWND control. The drawback of the ActiveX control solution is that it makes the drawing operation rather sluggish. It flickers when you move another window over it (assuming the Show form contents while dragging option in Windows is activated). Checking whether the active application is Visual FoxPro can reduce it, but it doesn't fix the problem entirely. Also, the ActiveX control is sensitive to keystrokes, it causes flickering, as well. Finally, you have the typical problems of ActiveX controls, that is, you depend on the proper installation and configuration of this ActiveX control and you can't display any FoxPro controls on top of the Visual FoxPro HWND control. It's up to you which solution you prefer.
GDI2.SCX
Have you ever been told that Visual FoxPro is slow? If you believe this, then start this demo by typing DO FORM GDI2.SCX in the Command Window. This sample form uses the GDI to display polygons in all sizes and colors. On my computer, Visual FoxPro displays between 90 and 100 polygons per second. Since this is done in a timer that fires every 10 ms, it's pretty much 100%.
The purpose of this sample is to demonstrate how to use arrays of structures, in this case an array of POINT struc-tures. The Polygon() API function expects an array of points, one for each line of the polygon. With the struct class you implement such an array in two structures. ArrayPoint contains the array and is defined as "o:Points". The POINT structure is defined in PointX. The X at the end of the name is necessary, because Point is a reserved word in Visual FoxPro that cannot be used as a class name in a VCX. 
In the Load event of GDI2.SCX we initialize the struct class by loading the class library and declaring all API functions. We also initialize the random number generator of Visual FoxPro. The Init event is more interesting. Here we initialize the arrays by simply re-dimensioning the array and assigning each element a reference to one distinct PointX structure. Whenever you use an array and re-dimension it, you have to call the Requery() method. This is necessary, because a different size of an array actually means you changed the definition of the structure, not just its values. The reminder of Init retrieves the window handle for the form and starts the timer.
The timer performs the actual job. As shown in the GDI.SCX sample we first obtain a device context to the form. Next we determine 3 arbitrary positions on the form. They determine the size and position of the triangular polygon. As you notice assigning a value to a structure that is part of an array is nothing different from assigning a value to some property of some control on a form. We use
.ArrayPoint.Points[2].X = Int( Rand()*.Width )
to set the X-position of the second point where ArrayPoint is the structure object that holds the array and Points[2] holds a reference to a PointX structure object. X is a property of the latter, in other words, a member of the POINT structure.
To fill objects, Windows uses brushes, not just colors. A brush can consist of just a color, a so-called solid brush. A stock brush is similar to a solid brush, but offers a number of predefined colors. These colors are different shades of gray as well as the colors defined by the user in the Windows color scheme. By using these brushes you can easily respect the user's configuration. A brush cannot only be a single color, but can also consist of a pattern. A hatch brush uses one of six pre-defined patterns, a pattern brush allows you define any pattern you want to fill any shape provided by the GDI. This sample uses a solid brush using a random color.
A brush is a GDI object. A single number, the object handle, identifies these. Whenever you create a GDI object, memory is allocated on the GDI heap memory, either the 16-bit heap, or the 32-bit heap. To avoid memory leaks especially in the precious 16-bit GDI heap, you have to delete the object when you don't need it anymore. To activate an object, you have to select it. Only one object of one type can be selected at a time. That means, only one brush, font, etc. can be active. It's considered good manner if you activate the previously active object when you are done with the object you selected before.
To paint the polygon, the Polygon() API function is called. It receives a device context, as most GDI function do, a pointer to the structure with points and the number of points we defined in the structure. The polygon is filled with the current brush and surrounded using the current pen, another GDI object that defines how lines are drawn.
To avoid dangling references and errors, the QueryUnLoad event sets all references within the array to NULL and disables the timer.
ResChange.SCX
A common question on the online fora is how one can change the resolution in which Windows is running. Typically this request is followed by the requirement that the application needs at least a certain resolution to run. Let me clarify one thing first: Only in very rare circumstances an application should change the resolution or color depth. Usually it's up to the user to configure her system, and not the job of the application. In my opinion, an application should respect whatever setting is activated by the user, since there's a reason why the user set her computer up as it is. If your application requires a certain setting, you could check the resolution upon startup using SYSMETRIC() and the number of colors using the GetDeviceCaps API function. Exceptions can be applications that run in a corporate environment where the company defines what the computer of their employees should look like.
Nonetheless, it might be useful to know how easy it is to change the resolution and how it's possible to figure out all video modes, a computer supports. When you start ResChange.SCX, you get a list with all possible modes with the current mode being selected. By choosing a different mode and clicking on Apply, you can change the resolution and/or color depth. Depending on the setup of your computer you might be prompted to restart the system. This sample performs exactly the same task as the left-click menu of the display icon that you can display in the systray of Windows.
By the way, the code used in this sample is from a VB 4 sample program that you can find in the Microsoft Knowledgebase. Since VB 4 supports structures, but not much beyond that, it's a great resource for Visual FoxPro developers, too, when you use the structure class. Using VB 5 and VB 6 samples it's sometimes more difficult, because those versions offer things like call-back functions and message handling, that is yet not supported by Visual FoxPro.
I skip the Load event because here we only initialize the struct class and you've probably seen these two lines quite a bit already. The interesting part is the Init event. EnumDisplayProperties() fills a DEVMODE structure with either the current settings or with the information of a certain mode.
Before you can call this function using an empty DEVMODE structure you have to initialize two members (or properties in the Visual FoxPro object): dmSize and dmDriverExtra. The first contains the size of the structure, as this might vary among the different versions of Windows. Every version added a couple of properties. The SizeOf() method of the struct class returns this size for the current object. DmDriverExtra defines how much space you reserved for additional driver information. Since you don't need them, just set dmDriverExtra to 0.
In a loop ResChange.SCX calls EnumDisplaySettings to retrieve all possible modes. When you reached the last mode, the API function returns 0, thus, this is the condition to exit the loop. The rest of the code formats the list nicely. For example, a line is inserted whenever the color depth changes, just like the display icon menu does. DmBitsPerPels is the number of pixels used for the color information. 8 bit means you are running on 256 colors, 16 bit is Hi-Color and more than that is True-Color. Rarely used, but still available is 4 bit or 16 colors. Less than that is not supported by current versions of Windows.
AddToListbox() uses the current DEVMODE structure to add a readable text to the list box. DmPelsWidth and dmPelsHeight contain the resolution in pixels, dmBitsPerPel is used to format the color information.
Both, the Apply and the OK button call the ApplySettings() method if you selected a different mode. To make that easier, Init stores the DEVMODE structures of all possible modes in an array. We use that string to fill the struct object that represents the DEVMODE structure with the proper values. The dmFields property is set to the properties that we like to have changed, the resolution and the number of bits per pixel for the color. ChangeDisplaySettings() takes this structure to change the display. With an additional parameter you can specify whether it's only a temporarily modification or whether the new setting should be stored in the registry.
If you know the resolutions that are supported by the computer, all you have to do is to call EnumDisplaySettings to get the current solution, change the 3 properties mentioned above and call ChangeDisplaySettings to make this change persistent.
MsgBox.Prg
After all this fancy stuff this example is probably less fascinating, and you are less likely to integrate it into your application. It displays a Windows message box using the MessageBoxIndirect() function, which only receives one paramter. Often Windows provides two versions of a function, one receives all parameters individually, the other is usually named like the first one, but Indirect is added to the name. The second function receives a pointer to a structure. In the world of API programming, where the number of parameters can't change in a new version of the product, using a structure is a nice possibility to gain some flexibility. When called from Visual FoxPro, the direct version is easier to use.
Although it's a quite simple sample, you can't do it without the struct class or some other heap memory classes. The reason is that the caption as well as the message text are stored as pointer to strings. Not the string itself, but the address of the string is included into the structure. No matter, how long the string actually is, it will always occupy 4 bytes in the structure. Since C++ uses the position within the structure to access a member and not the name as Visual FoxPro does in objects, this is the only possibility to include variable length strings in a structure.  The sample itself is pretty much self-explaining, because the complexity of allocating memory for these strings is hidden in the struct class. To define such a member you use the "pz" token in the cMembers property.
OSVersion.Prg
OS() provides some information about the operating system your application is running on. On some occasions you need more than that, or you need those information in a format that's easier to handle for an application than a string. The Windows API provides the GetVersionEx() function that fills a structure with minor and major version number, platform ID and the build number. On NT you additional get information about which Service Pack you've got installed.
OSVersion.Prg demonstrates how to use this function. It mainly instantiates a OSVERSIONINFO structure and sets the dwOSVersionInfoSize to the size of the structure. As usual, SizeOf() returns this information. It's important that this number matches what Windows expects, otherwise you wouldn't get any result back.
PrinterInfo.Prg
A lot of information are available about the printer installed on a computer. Within VFP the PRTINFO() and APRINTERS() function return some of them, but much more is available. GetPrinter() is not only a Visual FoxPro function, but also a function of the printer spooler that fills several PRINTER_INFO_x structures where x can range from 1 to 7 (without 6). To avoid naming conflicts between the Visual FoxPro function and the API function, all printer spooler functions are preceded by WS_ in the ApiDef.Prg. WS stands for WinSpool.Drv, because that's the library in which these functions are stored, they are not part of the libraries that you can access via the Win32API keyword in the DECLARE command.
This sample collects information about all installed printers and displays the name, a description and the comment in a BROWSE window. Since the API function identifies a printer by its name APRINTERS() is used to get an array with all installed printers. To retrieve information or to control a printer, you have to open it first. WS_OpenPrinter receives the name of the printer and returns a handle in the second parameter. On Windows NT the third parameter can contain a security structure that is necessary if you want to modify printer settings. The handle can be used to call subsequent API functions. As with all handles, you have to close the handle when you don't need it anymore, otherwise you either have a resource leak or might prevent other applications from using the handle. WS_ClosePrinter() takes care of this.
In all previous samples we used GetString() to receive a string that can be used as a structure in API calls. This sample introduces three new methods: GetPointer(), SetPointer() and FreePointer(). GetPointer() converts the structure just like GetString(). Additionally it copies the structure into a memory block and returns its address, instead of the structure itself. In the API declaration you use INTEGER instead of STRING@ when you use this method.
A look at the PRINTER_INFO_1 structure explains why it is necessary to take this route. The structure only contains pointers to the actual strings. These strings have to be stored somewhere. Since Windows doesn't allocate memory for your application, you have to provide the necessary memory along with your structure. To make that easier, WS_GetPrinter() receives a pointer to a buffer, not a pointer to a structure. A buffer is simply a huge block of memory. The first bytes are allocated by the structure, the rest can be used by Windows. Since you allocated the memory before, Windows knows that it can access this memory block without causing an exception. Why can't you use a string for that? Visual FoxPro handles memory on its own. If it's necessary, it will move a string around in memory. Because this renders the pointers unusable, you need some static memory that is controlled by yourself.
Whew, that sounds difficult, therefore, let's make up a simple rule: Whenever a structure contains a string pointer and Window writes to that string, you have to call GetPointer(). And, whenever Windows wants a buffer that contains a structure, you need GetPointer(). The method that corresponds to SetString() is SetPointer(). It takes an address and uses the memory block to fill the structure. As the struct component doesn't handle memory for you now anymore, you have to tell the struct object when you don't need that block of memory anymore. FreePointer() is the method you call.
Back to the sample. After you obtained a handle to the printer, getting the desired information is just a matter of calling the API function WS_GetPrinter() and storing the 3 properties into the cursor. If something didn't work, WS_GetPrinter() returns a value different from 0. Finally, a BROWSE takes care of displaying the result.
PrtRange.Prg
When you can PrtRange.Prg you have to pass the name of a printer as it's returned by GETPRINTER(). Since documentation is only for the weak and real programmers don't need them, you probably have found that already by trial and error, didn't you? <g> 
It doesn't use structures, but it shows a further interesting possibility of API functions. This program determines the printable area on a page. As you probably know from your own bad experience, different printers have different printable areas, and too often they are the reason why WYSIWYG is often what you see is anything but what you get.
SystemTime.Prg
To get the current local system time you can use the Visual FoxPro function DATETIME(). But setting the time is not possible with native FoxPro means. That's where an API function comes in handy. A pair of functions gets and sets the time: GetLocalTime() and SetLocalTime(). "Local" doesn't refer to the clients as opposed to the server in this context. Rather it means that you get the time in your local time zone, not the time in Coordinated Universal Time (UTC) format.
If you are familiar with datetime values, the structure SYSTEMTIME is easy to understand. It contains members for the year, month, day, hour, minute, second and millisecond. This sample merely reads the current time, changes the minute member to 1 minute back and sets the new time. If it happened to be something like 9:00 on your system, no change is performed, because SetLocalTime() ignores negative minutes. It's just a sample, anyhow.
GetUNC.Prg
If you use Visual SourceSafe to manage your versions or to handle multiple developers working on the same project you might have noticed that SourceSafe doesn't store the drive letter for the server database. Even if you map a network drive to, say, N: and select a database in N:\VSS-Data, VSS would store \\YOURSERVER\VOLUME\VSS-Data instead. On of the big advantages of this approach is that you don't have to map the network drive in order to access the database and it doesn't matter from which computer on the network you access the server. Visual SourceSafe does always find the right database file.
Explorer does something similar. In the treeview it usually displays the full network name followed by the drive letter in parenthesis. The GetUNC.Prg sample does something very similar. It displays a BROWSE window with your entire network drives including the UNC (Universal Naming Convention) name and the connection name.
The key is the Windows Networking function WNetGetUniversalName(). It takes a drive letter and a pointer to a buffer containing a REMOTE_NAME_INFO structure. It then fills this structure with the UNC name and the connection name; or returns an error when the drive is not a network drive. Because this function receives a buffer, we have to use GetPointer()/SetPointer() instead of GetString() and SetString().
Before creating a cursor, GetUNC.Prg copies the structure to memory allocating 1000 byte. This should be sufficient for most network connection names. Then it loops through all possible drives, that is C: to Z:, the floppy disk drives are ignored here, since they are hardly network drives. Within this loop, WNetGetUniversalName() is called and on success the returned names are inserted into the cursor. Special about this sample program is how the buffer is used. Instead of creating a new buffer every time, or maybe even creating a new structure object on every pass, we re-use the buffer. This saves time and makes more efficiently use of Windows resources. Since the API function doesn't expect any values in the structure, it simply doesn't care whether there's the result of a previous call, or not. Be aware that not every function behaves this way. In doubt, create a new buffer after releasing the previous one and after you changed the properties in the structure object back to their initial value.
FindFiles.Prg
Windows manages three dates for each file, the date and time when it has been created, last modified and last accessed (even in read-only mode). ADIR() and FTIME() only give you the date and time of the last modification, but sometimes you need more than that. You probably guessed that the API can help you and you are quite right in that. Additionally it offers the benefit of returning the file names in the case they have been stored and not in all upper-case as ADIR() does. Furthermore, you get the short 8.3 name along with the long file name.
The approach of the API is different. Instead of calling a function once that returns an array of files, you have to call a function once for each file. Actually, you first call FindFirstFile passing it the search parameters. This function internally stores these parameters and returns you a handle. Subsequent calls are done to FindNextFile. This time passing the handle, instead of a file mask. Windows uses this handle to "remember" the search parameters. As with all handles you have to close it after you finished your search. FindClose() takes care of that.
On the surface, this program is quite simple. Windows fills a WIN32_FIND_DATA structure on every API call with the information about the next file. The relevant members of this structure are inserted into a cursor. As the file size might easily exceed 4 GB, a LONG cannot hold the number. A 64-bit value is used in the structure and converted to a numerical number upon saving the file size in the cursor. When FindNextFile returns 0, there's no further file and the loop is terminated. A BROWSE displays the result cursor. As I mentioned before, re-using a structure saves precious resources and speeds up the operation. Since the file name and the alternative file name in 8.3 notation aren't stored as pointers to string, rather are part of the structure, you have to reset the values before you use the structure for the next file.
The tricky part is getting the most interesting information, the different file time stamps. They are stored in FILETIME structures. Therefore, FindFiles.Prg does first create a WIN32_FIND_DATA structure followed by 3 instances of FILETIME. These objects are assigned to the proper members in the first structure. So far it's easy, because the Struct class handles substructures. 
It's interesting how Windows saves the time stamps. They are stored in 100st nanoseconds since January 1, 1601 in Coordinated Universal Time (UTC) format. In other words, a value of 24 indicates that the file has been created 2.4 milliseconds after midnight January 1, 1601, when your computer is located, for instance, in the UK. If it's on the East Coast, you have to subtract the time zone difference of 5 hours.
Fortunately Windows can handle the conversion quite well, though it's not a single conversion. First of all, we have to convert the UTC file time to local file time, taking the time zone into account. FileTimeToLocalFileTime() takes a FILETIME structure and fills another one with the local time. In the next step we convert those nanoseconds to a handier format, the SYSTEMTIME structure, which contains members for year, month, day, hours, minute, second and millisecond. We loose some precision here, but who really cares in which nanosecond a file has been created. The FileTimeToSystemTime() API function takes a FILETIME structure and fills a SYSTEMTIME structure with the proper values.
In Visual FoxPro we have a variable type for datetime values. The DATETIME() function in Visual FoxPro 6.0 accepts up to 6 parameters, the components that make up the time, and returns a single datetime value. This function is used to create a value we can store in a cursor. In Visual FoxPro 5.0 you have to write such a function yourself, taking different date and time formats into account.
RAS.SCX
The RAS API is the Remote Access Service API from Microsoft. From the point of view of a user it's usually shows up as the Dial-Up network. Many applications these days need Internet access, and in every one of these applications you should be able to establish a connection and disconnect after you're done. The RAS API provides just these features. While there are many ActiveX controls out there to control RAS, you usually have some additional problems to solve, like licensing issues, versioning problems, not properly registered ActiveX controls, and so on. By using the API, you change these problems in favor of a bit more work when writing your application.
The RAS.SCX form is really only a sample form, it makes the API functions of Windows accessible, but doesn't do a lot of error checking. In your application you should carefully follow all the remarks for these functions that you can find in the Platform SDK on MSDN. Be especially careful of return values and buffer sizes.
When you launch this form, it's empty. The first thing you should do is to select a connection. The combo shows all connections that are defined on your computer (on Windows 9x) or are contained in the default phone book (on Windows NT or Windows 2000). The sample always uses the default phone book. This combo is filled in its Init event. The structure that is used here is RASENTRYNAME_Array. Just like the ArrayPOINT structure in the GDI sample, the purpose of this structure is to manage an array of RASENTRYNAME structures. But there's more to it.
In all structures so far, we only added the properties and set the cMember property either directly or via the GetCMembers method. However, we can add more functionality; that's what I did with the RASENTRYNAME_Array structure. In the GDI.SCX sample we re-dimensioned the array and created a number of POINT objects that we saved in the array object. This class, on the other hand, manages all these member objects automatically. The cRASENTRYNAME property specifies the class that contains the RASENTRYNAME structure, just in case you want to use a subclass. In its Init, one RASENTRYNAME structure is created for the first, and only, element of the Entry array. To change the dimension of this array, you can call the SetDimension() method. It takes the new dimension and releases objects that are not needed anymore, or creates new ones as needed. It also takes care of re-querying the structure definition. The Count() method returns the number of elements in that array. 
The RASENTRYNAME structure has some code in it, too. Because its dwSize member always holds the size of the structure, and you probably won't like to call the SizeOf() method every time you create the object, it sets this property right in the Init() event.
Filling the combo box with all available connections is a 3-step process. In the first step, we ask the RasEnumEntries() API functions, how many connections are defined. Calling the function and only passing one element in the array does this. Afterwards lnCount contains the number of elements. Now, we re-dimension the array accordingly and call RasEnumEntries() again. Count(), SizeOf() and GetString() give us all the information that this functions needs. SetString() takes care of updating all properties in all RASENTRYNAME structures in the array. Finally, we simply loop through the Entry array and add szEntryName to the combo box. The RASENTRYNAME_Array and the RASDIALPARAMS have been created before in the Load event of the form.
When you establish a connection, you have to know more than the connection name. The user name and the password are required, as well. If you click on the "Retrieve" button right to the connection combo, it retrieves exactly this type of information. The RasGetEntryDialParams() API function fills a RASDIALPARAMS structure with the last saved values of a connection. Since all controls on the form are directly bound to the RASDIALPARAMS structure, refreshing the form is all we have to do, to have the form reflect those retrieved values. The phone number cannot be obtained this way, because when you dial a RAS connection, the RAS API automatically uses the saved phone number, if you leave the szPhoneNumber member empty in the RASDIALPARAMS structure.
But it is stored in the properties of the connection. The RASENTRY structure contains all the values you can modify via the Property Window of a connection. The RasGetEntryProperties() API functions fills this structure with the properties of the specified connection. The second "Retrieve" button calls this function and stores the local phone number in the szPhoneNumber member of the RASDIALPARAMS structure. Because country code, area code and local phone number are stored in separate properties, it also formats the entire phone number nicely and displays it beside the command button.
When you connect to the RAS server there are several ways of doing so. On Windows NT you have the possibility to display series of dialogs when you dial, similar to those that are displayed when you select "Dial" form the connection using the RasDialDlg() function. The sample only implements the way that both, Windows NT and Windows 9x support. The RasDial() function takes a RASDIALPARAMS structure and returns a handle. In other development tools that support call back functions, you can also specify a call back function that is used to display some progress. This is called asynchronous connection; Visual FoxPro only supports synchronous connections. 
You've seen handles all the time before, so doesn't surprise you that RasDial() returns a handle, too. This handle is passed by reference and must be used to close the connection, after you're done with it. It's really important to do so, because otherwise you might get the "Connection is in use by another application" error message when you try to establish a connection from a different application. Only re-starting the computer would fix this. Even if RasDial() returns with an error it might have assigned you a handle already. In this case, too, you have to close the connection. In other words, whenever the handle is not 0, you must call RasHangUp().
This API function is used to disconnect. Its rather cruel in the way it works, because it terminates the connection under all circumstances, even if another application started using your connection, as well. RasHangUp() receives the handle that RasDial() returned. 
