I remember one nice feature of Windows 95 that seems to have been eliminated in Windows 98 and Windows 2000. When you establish a dial-up connection, Windows 95 shows you the dialog in Figure 1.

Figure 1 - Windows 95 "Connected To" Dialog
But when I used Windows 95, I didn't really appreciate how useful this dialog was -- it provides a clear indication that I am connected and an easy way to disconnect. I could do an Alt-Tab to check for the dialog's icon, switch to it, and then disconnect. I never walked away from my PC having forgotten that I was connected (at 1 cent per minute, I might add).
To my knowledge, neither Windows 98 nor Windows 2000 provides such a useful dialog. Windows 2000, for instance, displays the rather ludicrous dialog shown in Figure 2.

Figure 2 - Windows 2000 "Connect To" Dialog
The dialog seems to be saying: "I won't make it easy for you to disconnect. You'll have to find the Network Connection folder, or locate a stupid little icon on the notification area of the taskbar (often called the system tray). At my screen resolution (1600 by 1200), that little icon is very small indeed. Also, I keep the taskbar hidden so it doesn't waste screen space. Surely, it would have been a simple matter for Microsoft to have placed a Disconnect button on this dialog!
This irritating state of affairs led me to write a VB program that would display the dialog box shown in Figure 3.

Figure 3 - My "Connect To" Dialog
My little dial-up application has the following properties:
At first glance, this application may seem like a very simple use of Internet-related API functions. However, as we get into the details, we will discover some very subtle points in API programming under VB.
Microsoft offers two areas within the Win32 API that are related to dial-up connections and networking in general. One is Remote Access Service, or RAS, and the other is the Web Workshop. The documentation for each of these resides in the MSDN library.
Here is part of what Microsoft says about RAS:
The Remote Access Service (RAS) API . . . is used to create client applications that can display any of the Routing and RAS common dialog boxes, start and end a remote access connection, manipulate phone-book entries and network addresses that are mapped to phone-book entries, and get information about existing RAS connection status or RAS-capable devices.
Unfortunately, the documentation for the Web Workshop is poor. The term Web Workshop is not in the MSDN index, for example, and searching for the term produces a mere 500 entries. Access to the Web Workshop can be made through MSDN's Contents tab. The Web Workshop's top node lies under the Web Services node which, in turn, is under the Platform SDK node.
I could not find a statement describing the purpose of the Web Workshop. However, I did find three Web Workshop API functions that will suit the needs of my little program: InternetDial, InternetHangUp and InternetGetConnectedStateEx. These functions are exported by a DLL called Wininet.dll, which appears to be installed when the user installs Internet Explorer version 4 or later.
The InternetDial function requires the name of a dial-up connection to dial. However, I could find nothing within Web Workshop that returns the list of available dial-up connections (or connectoids) on a given system. On the other hand, there is a RAS function called RASEnumEntries which will do just that.
The Web Workshop API functions we will need are shown below. The VC++ declarations come directly from the MSDN documentation. Of course, we need to translate these declarations into VB. We will discuss this translation process briefly here. For more information, allow me to suggest my book, Win32 API Programming with Visual Basic, published by O'Reilly & Associates.
The function used to make a dial-up connection is called, appropriately enough, InternetDial. The MSDN documentation gives the following VC++ declaration:
DWORD InternetDial(
IN HWND hwndParent, // Handle of parent window
IN LPTSTR lpszConnectoid, // Address of a string variable with
// name of dial-up connection
IN DWORD dwFlags, // Unsigned long containing flags
OUT LPDWORD lpdwConnection, // Address of unsigned long
// containing the ID number of
// connection
IN DWORD dwReserved // Must be 0
);
We translate this into VB as follows:
Declare Function InternetDial Lib "Wininet.dll" ( _
ByVal hwndParent As Long, _
ByVal lpszConnectoid As String, _
ByVal dwFlags As Long, _
dwConnection As Long, _
ByVal dwReserved As Long) _
As Long
Before discussing this translation, note that Microsoft thoughtfully provides an indication of the direction of each parameter. The keyword IN means that we must set the parameter for use by the function. The keyword OUT means that the function will fill in the variable with information for us, but we must still declare an appropriate variable for the function to set.
The hwndParent parameter is declared as
IN HWND hwndParent, // Handle of parent window
We will set this to the handle of the VB project's main (and only) form. Because the function expects the actual value, rather than the address of a variable containing the value, we must pass the argument by value
ByVal hwndParent As Long
Note that the VC++ HWND data type is a synonym for the data type HANDLE, which is a pointer data type. Put another way, HWND is an unsigned 4-byte integer data type. (I discuss data types in detail in my book.) On the other hand, a VB Long variable is interpreted as signed. However, this will not be a problem because all we do is pass this value to the InternetDial function, without interpreting it in VB one way or the other.
The second parameter is declared as
IN LPTSTR lpszConnectoid, // Address of a string variable with
// name of dial-up connection
As the comment indicates, this is the address of a string. In VB, we pass this as
ByVal lpszConnectoid As String
The reasons are a bit involved, so I refer you to my book for a detailed discussion of passing string variables to API functions.
The dwFlags parameter is declared as
IN DWORD dwFlags, // Unsigned long containing flags
A DWORD is an unsigned long value (range: 0-65535). However, it turns out that we only need to deal with values in the lower half of this range (0-32768), in which unsigned and signed variables agree, so we can simply use a VB Long variable. Again, we pass the argument by value
ByVal dwFlags As Long
According to the documentation, the dwFlags argument can be one of the following values:
INTERNET_AUTODIAL_FORCE_ONLINE: Forces an online connection.
INTERNET_AUTODIAL_FORCE_UNATTENDED: Forces an unattended Internet dial-up. If user intervention is required, the function will fail.
INTERNET_DIAL_FORCE_PROMPT: Ignores the "dial automatically" setting and forces the dialing user interface to be displayed.
INTERNET_DIAL_UNATTENDED: Connects to the Internet through a modem, without displaying a user interface, if possible. Otherwise, the function will wait for user input.
INTERNET_DIAL_SHOW_OFFLINE: Shows the Work Offline button instead of Cancel button in the dialing user interface.
The MSDN documentation says that the VC++ header file (where these constants are defined) is Wininet.h. However, this file does not contain declarations for either INTERNET_DIAL_FORCE_PROMPT or INTERNET_DIAL_SHOW_OFFLINE, nor do any of the other 762 include files on my system! Fortunately, we only need the flag INTERNET_DIAL_UNATTENDED, which is declared in VB as
Public Const INTERNET_DIAL_UNATTENDED = & H8000
(To find this value, I did a search of the text file wininet.h.)
The lpdwConnection parameter is declared as
OUT LPDWORD lpdwConnection, // Address of unsigned long containing
// the ID number of connection
As mentioned earlier, the OUT keyword tells us that the function will return a value in this argument, so we only need to declare an appropriate long variable
dwConnection As Long
Because the function is expecting the address of the variable, not its value, we pass the argument by reference (the default). Also, according to the declaration, the InternetDial function will place an unsigned long (DWORD) in the variable dwConnection. Even though VB interprets long variables as signed, there is no problem here because we simply pass this ID along to another API function, without bothering to interpret it
To terminate a connection established with InternetDial, we use the InternetHangUp function (be careful with capitalization here-all API function names are case sensitive!)
The VC++ declaration of this function is
DWORD InternetHangUp(
IN DWORD dwConnection, // Connect ID from InternetDial
// function
IN DWORD dwReserved // Must be 0
);
We translate this to VB as
Declare Function InternetHangUp Lib "Wininet.dll" ( _
ByVal dwConnection As Long, _
ByVal dwReserved As Long) _
As Long
Our dial-up application also monitors the state of the connection, using a timer control to test the connection every second. This is done by calling the InternetGetConnectedStateEx function, defined in the documentation as
BOOL InternetGetConnectedStateEx(
OUT LPDWORD lpdwFlags,
OUT LPTSTR lpszConnectionName,
IN DWORD dwNameLen,
IN DWORD dwReserved
);
This translates to VB as
Declare Function InternetGetConnectedStateEx Lib "Wininet.dll" ( _
lpdwFlags As Long, _
ByVal lpszConnectionName As String, _
ByVal dwNameLen As Long, _
ByVal dwReserved As Long) _
As Long
According to the documentation, the OUT parameter lpdwFlags returns "the connection description" and its value can be a combination of the following constants:
INTERNET_CONNECTION_CONFIGURED: Local system has a valid connection to the Internet, but it may or may not be currently connected.
INTERNET_CONNECTION_LAN: Local system uses a local area network to connect to the Internet.
INTERNET_CONNECTION_MODEM: Local system uses a modem to connect to the Internet.
INTERNET_CONNECTION_MODEM_BUSY: No longer used.
INTERNET_CONNECTION_OFFLINE: Local system is in offline mode.
INTERNET_CONNECTION_PROXY: Local system uses a proxy server to connect to the Internet.
INTERNET_RAS_INSTALLED: Local system has RAS installed.
As with some other constants, there do not seem to be declarations for INTERNET_CONNECTION_CONFIGURED, INTERNET_CONNECTION_OFFLINE and INTERNET_RAS_INSTALLED.
Moreover, it seems to me that these constants are more appropriately related to the properties of a dial-up connection that are independent of its connected state. For instance, INTERNET_CONNECTION_MODEM tells us whether or not this connection uses a modem, but not whether or not the modem connection is currently live, which the function name InternetGetConnectedStateEx would lead us to expect.
Fortunately, however, the function itself returns True if the dial-up connection has been established and False otherwise.
The Win32 API provides a function named SetWindowPos that can be used for a variety of purposes. Besides the obvious one of setting a window's position (and size), it can also make the window topmost. A topmost window will remain above any nontopmost window, which includes most application windows. (There may be more than one topmost window at a time, in which case each has a pecking order.)
The code
SetWindowPos Me.hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW + SWP_NOMOVE + SWP_NOSIZE
is as close to self-explanatory as such code ever gets. It makes the window with handle Me.hwnd topmost, shows the window in its normal configuration, but does not move the window nor change its size. (The intermediate arguments, which are set to 0 here, would otherwise specify the size and location of the window.)
You may have noticed the Options button on the main Dial-Up dialog in Figure 3. Clicking this button will display a small dialog box wherein the user can specify the name of a dial-up connection, as well as paths to the email and web browser applications that are triggered by the Mail and Web buttons, respectively.
Thus, we now come to the issue of getting the names of all dial-up connections on a given system. As I mentioned earlier, the RAS function RASEnumEntries will handle this. The trick, however, is in the implementation.
The RAS documentation refers to dial-up connections as phone book entries. The function RASEnumEntries is declared as follows:
DWORD RasEnumEntries (
LPCTSTR reserved, // reserved, must be NULL
LPTCSTR lpszPhonebook, // pointer to full path and
// file name of phone-book file
LPRASENTRYNAME lprasentryname, // buffer to receive
// phone-book entries
LPDWORD lpcb, // size in bytes of buffer
LPDWORD lpcEntries // number of entries written
// to buffer
);
We translate this into VB as
Public Declare Function RasEnumEntries Lib "Rasapi32.dll" Alias "RasEnumEntriesA" ( _
ByVal reserved As Long, _
ByVal lpszPhonebook As String, _
lprasentryname As utRASENTRYNAME, _
lpcb As Long, _
lpcEntries As Long _
) As Long
This function requires some constants
Public Const RAS_MaxEntryName = 256
Public Const MAX_PATH = 260
' For errors
Public Const RASBASE = 600
Public Const ERROR_INVALID_SIZE = (RASBASE + 32)
Public Const ERROR_BUFFER_TOO_SMALL = (RASBASE + 3)
Public Const ERROR_NOT_ENOUGH_MEMORY = 8
It also requires a rather nasty structure (user-defined type) called RASENTRYNAME, whose VC++ declaration is
typedef struct _RASENTRYNAME {
DWORD dwSize;
TCHAR szEntryName[RAS_MaxEntryName + 1];
#if (WINVER >= 0x500)
DWORD dwFlags;
CHAR szPhonebookPath[MAX_PATH + 1];
#End If
}
There are two reasons why I refer to this structure as nasty-one is obvious and the other is very, very subtle.
The obvious one is the conditional statement within the structure definition. Under Windows 95 and Windows NT 4.0, the WINVER constant has the value 0x400 (or &H400 in VB speak). Under Windows 98 and Windows 2000, the value is 0x500. Thus, our little dial-up program gets a new wrinkle, since we must determine which version of Windows is being used on the client system.
Fortunately, the code for doing so is not involved. We simply call the GetVersionEx API function, feeding it the address of an OSVERSIONINFO structure. The structure declaration
typedef struct _OSVERSIONINFO{
DWORD dwOSVersionInfoSize;
DWORD dwMajorVersion;
DWORD dwMinorVersion;
DWORD dwBuildNumber;
DWORD dwPlatformId;
TCHAR szCSDVersion[ 128 ];
}
translates easily into VB
Type utOSVERSIONINFO
dwOSVersionInfoSize As Long ' Set this value
dwMajorVersion As Long
dwMinorVersion As Long
dwBuildNumber As Long
dwPlatformId As Long
szCSDVersion(1 To 128) As Byte
End Type
especially since we care only about the dwMajorVersion member. The main thing to note is that, according to the documentation, we must set the value of the dwOSVersionInfoSize member. All the other members are set by the GetVersionEx function.
The other issue is, as I mentioned, very, very subtle, so please buckle your seat belts.
As I discuss at some length in my book Win32 API Programming with Visual Basic (pages 58-60), when storing a user-defined type in memory, VB pads the structure with extra bytes so that each field begins on its natural boundary. Thus, for instance, a VB Long field must start at a memory address that is a multiple of 4. On the other hand, the Win32 API generally does not expect its structures to be so aligned.
For most structures, the fields align naturally on natural boundaries, without the need for padding, and so there is nothing to worry about. However, some structures, such as _RASENTRYNAME, do not have this property. Normally, when we have such a structure, we must do something to prevent VB from aligning its fields.
However, and this is the subtle point, if you look in the VC++ header file for RAS, which is named RAS.h, you will find the include directive:
#include <pshpack4.h>
The include file pshpack4.h includes the following comment:
This file turns 4 byte packing of structures on. (That is, it disables automatic alignment of structure fields.)
In other words, in this case, it is expected that the fields within the RAS structures defined in RAS.h are aligned on their natural boundaries! Thus, in this case, we want to let VB do its alignment trick, so we can simply translate the _RASENTRYNAME into VB without worrying about field alignment. Let's consider the Windows version 5 case:
Public Type utRASENTRYNAMEVer5
dwSize As Long
szEntryName(1 To RAS_MaxEntryName + 1) As Byte ' ANSI_
entry point means use Bytes
dwFlags As Long
szPhonebookPath(1 To MAX_PATH + 1) As Byte
End Type
In this case, VB will put some padding in the structure between the szEntryName field and the dwFlags field, as shown in Table 1.
| Field | Relative Addresses |
| dwSize: | 0, 1, 2, 3 (4 bytes) |
| szEnterName | 4, ..., 260 (257 bytes) |
| padding | 261, 262, 263 (3 bytes) |
| dwFlags | 264, 265, 266, 267 (4 bytes, aligned) |
| szPhonebookPath | 268, ..., 528 (261 bytes) |
Table 1 - Padding of a VB structure
Indeed, while this declaration of utRASENTRYNAMEVer5 works, we could also do the padding ourselves and use the declaration
Public Type utRASENTRYNAMEVer5
dwSize As Long
szEntryName(1 To RAS_MaxEntryName + 1) As Byte ' ANSI_
entry point means use Bytes
Padding1(1 To 3) As Byte
dwFlags As Long
szPhonebookPath(1 To MAX_PATH + 1) As Byte
End Type
Now, you might think that this is the end of the story, but it isn't. According to the VC++ declaration, the lprasentryname parameter is a buffer that receives phone book entries. In other words, it is a pointer to an array of utRASENTRYNAMEVer5 structures, each of which must be aligned on natural boundaries. In particular, each structure must start at an address that is a multiple of 4. Again, VB does this alignment for us, but we could also do it ourselves, by using the following declaration:
Public Type utRASENTRYNAMEVer5
dwSize As Long
szEntryName(1 To RAS_MaxEntryName + 1) As Byte ' _
ANSI entry point means use Bytes
Padding1(1 To 3) As Byte
dwFlags As Long
szPhonebookPath(1 To MAX_PATH + 1) As Byte
Padding2(1 To 3) As Byte
End Type
Now perhaps you see why I refer to all this as very, very subtle!
Once we have a workable declaration of the _RASENTRYNAME structure, the rest is more-or-less straightforward. The following procedure calls RASEnumEntries in the Windows 98/2000 case:
Sub GetConnectionsVer5()
' Get the RAS phone book entries
' and put them in the combo box.
Dim i As Integer
Dim sTemp As String
Dim lret As Long
Dim lBufferSize As Long
Dim cRasEntries As Long
Dim lRasEntrySize As Long
Dim RasBuffer() As utRASENTRYNAMEVer5
' Start with single entry buffer, which
' may very well be too small
ReDim RasBuffer(0)
' Get structure size
lRasEntrySize = LenB(RasBuffer(0))
' Set dwSize field as required
RasBuffer(0).dwSize = lRasEntrySize
' Try it out
lret = RasEnumEntriesVer5(0, vbNullString, RasBuffer(0), _
lBufferSize, cRasEntries)
' If the buffer is too small, then adjust it
' cRasEnties is the number of phone book entries returned
If lret = ERROR_BUFFER_TOO_SMALL Then
ReDim RasBuffer(cRasEntries - 1)
' Don't forget to reinitialize first entry!
RasBuffer(0).dwSize = lRasEntrySize
' Try it again
lret = RasEnumEntriesVer5(0, vbNullString, RasBuffer(0),_
lBufferSize, cRasEntries)
If lret <> ERROR_SUCCESS Then
MsgBox "Error connecting.", vbCritical
Exit Sub
End If
End If
' Put in list
cboConnections.Clear
For i = 0 To cRasEntries - 1
cboConnections.AddItem StrConv(RasBuffer(i).szEntryName,_
vbUnicode) & "/"
Next
End Sub
As you can see, what started out to be a simple little program that "wraps" a few Internet API functions has exposed some interesting subtleties related to structure packing. Welcome to the world of Win32 programming under VB.
The complete code for this project is available available here, on O'Reilly's web site, and on my web site, which I hope you will visit. There you will find some software, more articles and some sample chapters from my books. Thanks for listening.
Steven Roman is a Professor Emeritus of mathematics at the California State University, Fullerton. He has taught at a number of other universities, including the Massachusetts Institute of Technology, the University of California at Santa Barbara, and the University of South Florida.
Dr. Roman received his B.A. degree from the University of California at Los Angeles and his Ph.D. from the University of Washington. Dr. Roman has authored 32 books, including a number of books on mathematics, such as Coding and Information Theory, Advanced Linear Algebra, and Field Theory, published by Springer-Verlag. He has also written a series of fifteen small books entitled Modules in Mathematics, designed for college liberal arts students.
Besides his books for O'Reilly ( Win32 API Programming with Visual Basic, Access Database Design and Programming, Writing Word Macros, Writing Excel Macros, and Developing Visual Basic Add-ins), Dr. Roman has written two other computer books, entitled Concepts of Object-Oriented Programming with Visual Basic and Understanding Personal Computer Hardware, an in-depth look at how PC hardware works, both published by Springer-Verlag. Dr. Roman is interested in combinatorics, algebra, and computer science.
Copyright © 2009 O'Reilly Media, Inc.