This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
|
Dialing Up the Internet with RAS Aaron Skonnard |
A properly written Internet-enabled app will automatically connect to a user's ISP on an as-needed basis. The RAS API makes doing this simpler. |
Chances are you've wanted to make your Windows®-based application capable of tapping into the vast resources found on the Internet today. To make this a user-friendly reality, you need to be familiar with the dial-up services offered by Windows. Suppose you want to add the ability to send email from your Windows-based application. Using MAPI, this is a fairly simple task. You must decide, however, who will be responsible for establishing a connection with the user's ISP before attempting to send the email. The easiest solution (for you, anyway) is to let the user worry about it. Users are smart enough to know when they need to connect to the Internet, right? Nope. Most users don't have a clue. Thus, a well-written Internet-enabled application will automatically establish a connection with the user's ISP as needed.
Currently there are two dial-up development interfaces in Windows: the Windows Internet (WinInet) API and the Remote Access Service (RAS) API. WinInet is a newer high-level interface to RAS. I'll briefly cover the WinInet dial-up functions and discuss some of their problems. Then I'll dive deep into the RAS API, showing you how to take advantage of the RAS common dialogs as well as the low-level dialing functions. Not only will you learn how to programmatically begin and end a RAS connection, you'll also learn how to manipulate the system phonebook entries. Furthermore, I'll cover a few advanced RAS issues like AutoDial and connection notifications. After reading this, you'll understand the ins and outs of establishing a dial-up connection from your Windows-based application.
Prerequisites
WinInet Dial-up Functions
|
InternetAutodial(INTERNET_AUTODIAL_FORCE_UNATTENDED, 0);
When this function is called, the dialog shown in Figure 2 appears and the system begins calling your default phonebook entry. If you press Cancel, the dialog in Figure 3 appears, allowing you to modify your user name, password, and phonebook entry settings. To disconnect the connection initiated by InternetAutodial, use InternetAutodialHangup in the following manner:
InternetAutodialHangup(0);
So how does InternetAutodial know which phonebook entry is the default? The default phonebook entry is specified in the Internet Properties dialog (right-click on the Internet Explorer icon and select Properties). On the Connection tab, the user can either walk through a connection wizard or specify the desired Internet connection settings directly (see Figure 4).
As you can see, the WinInet dial-up functions are tightly coupled with the Internet Explorer 4.x system properties. This may seem convenient, but it's often problematic. If you want to use InternetAutodial to force an unattended dial-up connection, all the user information (user name, password, and so on) must be configured properly in the Dial-Up Settings dialog before you attempt the call. If user information is missing, the call will either fail or the user will be prompted to enter it (depending on the flag passed to InternetAutodial). Either way, this defeats the goal of establishing an unattended connection. Unfortunately, WinInet doesn't currently offer a method to determine whether any information is missing before attempting the call.
InternetDial and InternetDialHangUp are very similar to InternetAutodial and InternetAutodialHangup, the main difference being that InternetDial allows you to specify the phonebook entry you want to use. Although this requires you to know the system phonebook entries, WinInet doesn't offer a mechanism to enumerate the system phonebook. Later, I'll demonstrate how to do this using the RAS API.
As for the rest of the WinInet dial-up functions, you can explore them on your own. I'm not too keen on using the WinInet dial-up functions in production code until they improve. Not only aren't they as flexible as I'd like, they're not as stable either. If you need absolute control over the RAS phonebook entries and how a dial-up connection is established, the WinInet functions have little to offer. Plus, if there is one common complaint on the WinInet newsgroup, it's that the new WinInet dial-up functions don't always behave as documented.
Once you start looking at the RAS API, however, you'll appreciate how much the WinInet functions encapsulate and be tempted to use them anyway. I'm convinced that soon they'll be a better solution than the RAS API in many situations. Look for improvements to the WinInet dial-up functions in the next major release of Internet Explorer.
RAS
The RAS API is very flexible. You can take advantage of the RAS common system dialogs (used by dial-up networking) or you can use the lower-level RAS functions and provide your own user interface. Either way, RAS lets you control the process of establishing a dial-up connection with another computer.
I wrote a sample application for this article called RasJazz (available for download from the code link at the top of this page). RasJazz was written on Windows NT 4.0 using Visual C++ 5.0. Most of the functionality covered in RasJazz deals with RAS features offered only by Windows NT 4.0, so you must be running Windows NT 4.0 for RasJazz to function properly. If you're running Windows 95, you might want to download it anyway to look at the source code.
RasJazz simply displays a dialog box that gives you a menu to various RAS features
(see Figure 6). Let's begin by looking at the
RAS common dialog functions. As I already mentioned, the RAS common dialogs are contained in rasdlg.dll (you need to include rasdlg.h and link against rasdlg.lib), so if you're developing on Windows 95, you're
out of luck. Hopefully, with the release of
Windows 98 the RAS functionality will mirror that of Windows NT, but I haven't tested it at press time.
Figure 7 describes the RAS common dialog functions provided by rasdlg.dll. These functions allow you to take advantage of the dial-up networking dialog boxes offered by Windows NT 4.0. One advantage of this approach is that users who have experience with dial-up networking will benefit from the familiar UI of your application. Let's look at each in more detail.
RasPhonebookDlg
The RasPhonebookDlg function displays the main dial-up networking dialog. Double-clicking on the dial-up networking icon (found in My Computer) also displays this dialog (see Figure 8).
Here's the prototype for RasPhonebookDlg:
BOOL RasPhonebookDlg(
LPTSTR lpszPhonebook, // pointer to the full path and
// filename of the phonebook file
LPTSTR lpszEntry, // pointer to the name of the
// phonebook entry to highlight
LPRASPBDLG lpInfo // pointer to a structure that
// contains additional parameters
);
The first parameter lets you supply a path to the phonebook file to use. On Windows NT 4.0, a phonebook is stored as a text file with a .PBK extension. In the user preferences dialog, you can specify which phonebook file you want dial-up networking to use (see Figure 9). This has the same effect as passing in a different phonebook file to RasPhonebookDlg. On Windows 95, all phonebook information is stored in the registry, so the lpszPhonebook parameter is ignored.
The second parameter, lpszEntry, specifies the phonebook entry you want selected by default in the dial-up networking dialog. lpszEntry should contain the user-defined description of the phonebook entry.
The last parameter, lpInfo, is a pointer to a RASPBDLG structure that contains additional parameters associated with the dialog.
typedef struct tagRASPBDLG {
IN DWORD dwSize;
IN HWND hwndOwner;
IN DWORD dwFlags;
IN LONG xDlg;
IN LONG yDlg;
IN DWORD dwCallbackId;
IN RASPBDLGFUNC pCallback;
OUT DWORD dwError;
IN DWORD reserved;
IN DWORD reserved2;
} RASPBDLG;
Before using an instance of this structure, you must set dwSize to the size of the structure.
|
Notice that I pass in NULL for the first two parameters of RasPhonebookDlg. This means that it will use the system phonebook and select the first phonebook entry (alphabetically). With a few lines of code, you can create an interface to the powerful functionality of dial-up networking. Since RasPhonebookDlg doesn't return until the dialog closes, the user can perform any of the following tasks before continuing: dial an entry, edit an entry, create a new entry, delete an entry, clone an entry, modify modem properties, display the dial-up monitor, and even modify dial-up preferences. While this dialog is the most general of all and provides an interface to all the other common dialogs, in certain situations you may want to bypass it altogether. RasDialDlg The RasDialDlg function gives you a direct interface to dialing a phonebook entry. |
|
The first two parameters are identical to those of RasPhonebookDlg. The last parameter, lpInfo, is a pointer to a RASDIALDLG structure. While it has a different name, it's almost structurally identical to RASPBDLG. The parameter of interest here is lpszPhoneNumber. While each phonebook entry already has an associated phone number, this parameter allows you to provide a replacement phone number that will supercede the default from the phonebook. If you pass NULL for this parameter, RasDialDlg uses the associated number. The following is the handler for the RassJazz button labeled Dial Dialog: |
|
ReliaNet is the phonebook entry name for one of my local ISPs. If you download RasJazz, you'll want to replace this with your own phonebook entry name. Shortly,
I'll demonstrate how to enumerate the phonebook entries at runtime. This approach would obviously be a much better solution than hardcoding the entry name.
RasMonitorDlg
|
|
figure 11: Dial-up Monitor |
Since the Dial-Up Networking Monitor dialog monitors the status of different devices, you can specify the device name of interest in the first parameter. The second parameter, once again, is the additional parameter structure. RasJazz has a button labeled Monitor Dialog, which invokes the following handler: |
|
If you pass NULL for the first parameter, RasMonitorDlg uses the first enumerated device.
RasEntryDlg
|
|
The various parameters are just like those in the previous dialog functions. The RasJazz dialog contains a button labeled Entry Dialog, which invokes the OnEntryDlg handler: |
|
figure 12: Phonebook Entry |
The RAS common dialog functions offer an easy and consistent dial-up networking interface. While these functions are more flexible than the WinInet API, you're still restricted to the user interface and functionality provided by the dialogs. Although this is sufficient in most cases, you might be required to provide a custom user interface or tweak the standard functionality to fit your needs. Now let's take a look at some lower-level dial-up functions.
RasDial
|
|
The first parameter, lpRasDialExtensions, allows you to take advantage of dial-up networking's extended dialing features such as dialing prefixes, software compression, the ability to ignore the modem speaker, and pausing for a script. lpRasDialExtensions is ignored on Windows 95. The second parameteralso ignored on Windows 95allows you to specify the full path and file name of the phonebook file to be used. The next parameter, lpRasDialParams, is a pointer to a RASDIALPARAMS structure that contains most of the phonebook entry and user credential information. |
|
As with all other parameter structures, you must be sure to set dwSize to sizeof(RASDIALPARAMS) before passing it to RasDial. This structure is pretty much self-explanatory. A few notes: if you leave szEntryName empty, RasDial will simply attempt a direct modem connection, so you must provide a phone number in szPhoneNumber. And if you leave szUserName and szPassword empty, RAS uses the user name and password of the current logon session. The next two RasDial parameters, dwNotifierType and lpvNotifier, give you control over the blocking behavior of RasDial. Since RasDial typically takes a while to complete, it's usually better to make the call asynchronously. Otherwise, your application will be tied up waiting for the call to return. To take advantage of RasDial's asynchronous support, you must specify which callback mechanism you are using (dwNotifierType) and provide a pointer to the callback mechanism (lpvNotifier). Figure 13 describes the callback mechanism used by each dwNotifierType value. As you can see, there are three RasDial callback functions: RasDial, RasDial1, and RasDial2. Choose the one that best fits your needs (see the RAS documentation for more information). If you pass 0xFFFFFFFF in dwNotifierType, lpvNotifier must point to a valid window handle. In this case, instead of calling a callback function, RAS will send the specified window handle progress notification messages. Any one of these mechanisms allows you to give the user status information while dialing is taking place. The final RasDial parameter, lphRasConn, is a pointer to the handle of the established RAS connection. Since RasDial doesn't set this variable to NULL when the call fails, you should set it to NULL before calling RasDial. You'll need this handle to call RasHangUp. One common question on newsgroups is how to perform a dial-up connection behind the scenes without any user interface or user intervention. As you now know, RasDial is the key to that solution.
RasHangUp
|
|
While this appears very simple, there is one pitfall that snares many developers. After calling RasHangup, the connection state machine needs time to shut down properly. This shutdown time can take up to one or two seconds. If the system shuts the state machine down prematurely, the modem can be left in an
unstable state (like leaving the port open). A bad state can cause future RasDial calls to
fail. Therefore, after calling RasHangUp, your code shouldn't exit immediately. Instead,
you should give RAS time to reset the connection state. A simple solution is to call Sleep(3000) after returning from RasHangUp. A more fail-safe solution would be to use the following while loop. It won't break until the connection state has been reset properly: |
|
As mentioned, Windows 95 and Windows NT store phonebooks very differently. Windows 95 stores all phonebook information in the system registry, while Windows NT 4.0 uses .pbk files. On Windows 95 you only have one system phonebook. On Windows NT 4.0, you can have as many phonebooks as your heart desires. While the RasPhonebookDlg and RasEntryDlg functions are very useful for controlling phonebook entries (on Windows NT 4.0), there are a handful of other phonebook-specific functions that can be used on both Windows 95 and Windows NT 4.0 (see Figure 14). I could have included the first two functions, RasCreatePhonebookEntry and RasEditPhonebookEntry, in the RAS Common Dialogs section since they also provide a dialog interface. These two functions simply provide the system dialogs for creating and editing a phonebook entry. Applications written for Windows NT 4.0 should use RasEntry instead. One phonebook entry function that often causes developers grief is RasEnumEntries. Figure 15 illustrates how to use RasEnumEntries to populate a combobox (m_Phonebook) with all the system phonebook entries. The rest of the RAS phonebook entry functions are fairly straightforward. I'll let you peruse the details of this group on your own (see the Visual C++ 5.0 help system for more info).
AutoDial
|
|
If AutoDial is enabled and your machine
is configured properly, AutoDial should prompt you to establish a dial-up networking connection. The key to AutoDial is the AutoDial mapping database, which maps a network address to a RASAUTODIALENTRY structure that contains a phonebook entry and a dialing location. Network addresses can include IP addresses (207.68.137.65), Internet host names (www.microsoft.com), and even NetBIOS names. If AutoDial is enabled, addresses are added to the database automatically whenever the user connects to a network address over a RAS connection. Addresses are added to the database along with the properties of the active RAS connection. |
Figure6: AutoDial Addresses |
If you want to manipulate the AutoDial mapping database manually, you can use the RasSetAutodialAddress, RasGetAutodialAddress, and RasEnumAutodialAddresses functions. The RasJazz sample has a button labeled AutoDial Addresses, which displays the dialog shown in Figure 16. The dialog populates a listbox with all the addresses in your system AutoDial mapping database. Here's how RasEnumAutodialAddresses is used to populate the m_Addresses listbox: |
|
Figure7: New Address |
If you press Add on this dialog, you'll see the dialog in Figure 17. After you enter an address, select a phonebook entry, and press OK. The following code snippet inserts the new address into the AutoDial mapping database: |
|
Troubleshooting AutoDial
Before you start banging your head against your computer trying to get AutoDial to work, let me point out some problems that caused me grief. First of all, you should verify that AutoDial is enabled for the current dialing location. To do so, open the dial-up networking dialog and choose User Preferences. You should see the dialog in Figure 18. Make sure that AutoDial is checked for the current location. (To change the current location, use the Telephony applet in the Control Panel.) |
Figure8: AutoDial Prefs |
If your machine is on a network, you must disable your network card for AutoDial to function properly. Unfortunately, unplugging your network cable doesn't do the trick. To simulate not being on a network, you must create a new hardware profile and disable your network card in it. When you reboot your machine, be sure to select the No Network profile that you just created. With your network card disabled (and assuming everything else is configured properly), AutoDial should begin working. One last thing that threw me for a loop is how the system disables all AutoDial connections for the current logon session automatically. That's righteach time a new user logs on, AutoDial connections are disabled. To see if AutoDial is disabled on your system, look in the following registry location: |
|
If LoginSessionDisable is set to 1, AutoDial connections are disabled for the current logon session. Change this value to 0 and things should start working. The DisableConnectionQuery value controls whether or not AutoDial prompts the user before dialing the entry found in the AutoDial mapping database, as shown in Figure 19. This is the same dialog that is used if no entry is found in the AutoDial database. |
Figure9: AutoDial Prompt |
To programmatically control these registry settings, you can use RasGetAutodialParam and RasSetAutodialParam. I added a dialog to RasJazz that allows you to modify all the AutoDial control registry settings (see Figure 20). |
Figure 20: RasJazz AutoDial |
Using a Custom User Interface DLL
One of the coolest features of AutoDial (once it's set up properly) is the ability to provide a custom user interface DLL. To take advantage of this, you must implement a DLL with both ASCII and Unicode versions of the following entry point: |
|
RASADFunc should return TRUE to indicate that it took over the dialing or FALSE to allow the system to continue dialing. The RasJazz sample contains a project called MyAutoDial that implements this entry point (see Figure 21) and a custom AutoDial dialog (see Figure 22). |
Figure 22: Trick! | Figure 23: DLL path |
So how does AutoDial know which custom user interface DLL to call for a specific entry? You can associate a custom AutoDial DLL with each phonebook entry by calling RasSetEntryProperties. I added a dialog to RasJazz that allows you to modify certain properties of a phonebook entry including the AutoDial DLL path and function name (see Figure 23). Here's how to use RasSetEntryProperties: |
|
Once you associate a custom AutoDial
DLL with a phonebook entry, you can test
the DLL by trying to connect to a network address mapped to that entry. For example,
if www.microsoft.com is mapped to the phonebook entry Inconnect, typing "ping www.microsoft.com" from a command prompt should cause my custom AutoDial dialog to appear (see Figure 22).
RAS Connection Information
|
|
If you press the Start Waiting for Notification button and then manually begin a dial-up connection, RasJazz will display a message box when the RAS connection is established.
Wrap-up
|
From the June 1998 issue of Microsoft Interactive Developer.