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.


MIND


This article assumes you're familiar with C++.
Download the code (96KB)

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
      Before WinInet came into existence, developers only had one interface to dial-up functionality: the RAS API. So why haven't you heard of RAS before? If you use Windows 95, you're probably more familiar with the term "dial-up networking." Both terms refer to the same underlying dial-up functionality. The system dial-up functions available to you as a developer make up what is known as the RAS API.
      The general RAS functionality is contained in rasapi32.dll. To use the general RAS functions, you must link against rasapi32.lib and include ras.h. The RAS common dialogs, which are only supported on Windows NT® 4.0, are implemented in rasdlg.dll. To use this functionality, you must link against rasdlg.lib and include rasdlg.h. Many of the RAS APIs are only supported on Windows NT (and not on Windows 95). I'll try to make it clear which functions are available on Windows 95, Windows NT, or both. Nevertheless, the best source of documentation on RAS compatibility is found in the Visual C++® 5.0 help system.
      The WinInet dial-up functions are contained in wininet.dll. In C++, you can take advantage of the WinInet API by linking against wininet.lib and including wininet.h. wininet.dll is currently distributed with Microsoft Internet Explorer. Assuming you've installed Internet Explorer 3.x (or greater), you should be able to find wininet.dll in your Windows system directory. The WinInet lib and header files began shipping as part of Visual C++ 4.2. You can also get the WinInet development files by downloading the Internet Client SDK (more recently known as the Internet Author's Toolkit) from Microsoft's Web site, or from the SBN Web Snapshot CD included in last month's MIND.
      Once you've got the development tools described above, you're ready to start exploring the powerful dial-up capabilities made available by the Windows operating system. Now it's just a matter of deciding exactly what you want to accomplish and what's the best strategy to make it happen.

WinInet Dial-up Functions
      As mentioned, WinInet offers a high-level interface to certain aspects of the RAS API. Figure 1 lists the dial-up functions offered by the WinInet API. Although the WinInet functions have their problems, at times they can offer pragmatic solutions.
      As you'll see, using the WinInet dial-up functions greatly reduces the amount of code you'll need to write. For example, if you want to simply initiate the user's default phonebook entry without any user intervention, you can use the InternetAutodial function:


 InternetAutodial(INTERNET_AUTODIAL_FORCE_UNATTENDED, 0);
Figure 2: WinInet Autodialing
Figure 2: WinInet Autodialing

Figure 3: User Name and Password
Figure 3: User Name and Password

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).
       If the user selects the LAN option, InternetAutodial assumes that the user's machine has a direct connection to the Internet and does nothing. If the user selects the modem option, InternetAutodial will use the phonebook entry settings specified in the Dial-Up Settings dialog (see Figure 5) to establish the dial-up connection.

Figure 4: Connection settings
Figure 4: Connection settings


      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.

Figure 5: Dial-up settings
Figure 5: Dial-up settings


      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.

Figure 6: RasJazz
Figure 6: RasJazz


      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).

Figure 8: Main Dial-up Dialog
Figure 8: Main Dial-up Dialog


      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.

Figure 9: Phonebook selection
Figure 9: Phonebook selection


      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.
      Now let's look at some sample code. The RasJazz dialog contains a button labeled Phonebook Dialog, which invokes the following handler:

     void CRasJazzDlg::OnPhonebookDlg() 
     {
          RASPBDLG raspbdlg;
          raspbdlg.dwSize = sizeof(RASPBDLG);
          raspbdlg.hwndOwner = GetSafeHwnd();
          //continue initializing structure
          RasPhonebookDlg(NULL, NULL, &raspbdlg);
     }
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.


 BOOL RasDialDlg( 
     LPTSTR lpszPhonebook,   // pointer to the full path and
                             // file name of the phonebook 	
     LPTSTR lpszEntry,       // pointer to the name of the                     
                             // phonebook entry to dial 
     LPTSTR lpszPhoneNumber, // pointer to
                             // replacement phone number 
                             // to dial 
     LPRASDIALDLG lpInfo     // pointer to a structure that 
                             // contains additional parameters
 ); 
      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:

     void CRasJazzDlg::OnDialDlg() 
     {
           RASDIALDLG rasdialdlg;
           rasdialdlg.dwSize = sizeof(RASDIALDLG);
           rasdialdlg.hwndOwner = GetSafeHwnd();
           //continue initializing structure
           RasDialDlg(NULL, "ReliaNet", "", &rasdialdlg);
     }
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.
Figure 10: Dialing
Figure 10: Dialing
      RasDialDlg displays a stream of dialog boxes that provide the status of the dialing operation (see Figure 10). The same stream of dialogs will appear if you press Dial on the main dial-up networking dialog.

RasMonitorDlg
      RasMonitorDlg displays the Dial-Up Networking Monitor dialog (see Figure 11). This function is even simpler than the previous two:


 BOOL RasMonitorDlg( 
     LPTSTR lpszDeviceName,    // pointer to the name of the device to display 
                               // initially 
     LPRASMONITORDLG lpInfo    // pointer to structure that contains input and  
                               // output parameters 
 ); 

Figure 11: Dial-up Monitor
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:

     void CRasJazzDlg::OnMonitorDlg() 
     {
          RASMONITORDLG rasmonitordlg;
          rasmonitordlg.dwSize = sizeof(RASMONITORDLG);   
          rasmonitordlg.hwndOwner = GetSafeHwnd();
          //continue initializing structure
          RasMonitorDlg(NULL, &rasmonitordlg);
     }
      If you pass NULL for the first parameter, RasMonitorDlg uses the first enumerated device.

RasEntryDlg
      RasEntryDlg displays the phonebook entry property sheet (see Figure 12). From here, users can edit all properties associated with a phonebook entry including basic, server, and security settings. The following is RasEntryDlg's function definition:


 BOOL RasEntryDlg( 
     LPTSTR lpszPhonebook, // pointer to the full path and  
                           // file name of the phonebook file
     LPTSTR lpszEntry,     // pointer to the name of the  
                           // phonebook entry to edit, copy, 
                           // or create 
     LPRASENTRYDLG lpInfo  // pointer to a structure that 
                           // contains additional parameters
 ); 
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:

     void CRasJazzDlg::OnEntryDlg() 
     {
         RASENTRYDLG rasentrydlg;
         rasentrydlg.dwSize = sizeof(RASENTRYDLG);
         rasentrydlg.hwndOwner = GetSafeHwnd();
         //continue initializing structure
         RasEntryDlg(NULL, "ReliaNet", &rasentrydlg); 
     }

Figure 12: Phonebook Entry
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 RasDial and RasHangUp functions give you total control over the RAS dial-up behavior and let you provide your own user interface or none at all. RasDial simply takes care of establishing a connection between two computers without displaying any user interface. While RasDial is supported on both Windows NT 4.0 and Windows 95, some of the parameters are ignored on Windows 95.


DWORD RasDial( 
 LPRASDIALEXTENSIONS lpRasDialExtensions, // pointer to 
                                          // function 
                                          // extensions 
                                          // data 
 LPTSTR lpszPhonebook,                    // pointer to full 
                                          // path and  
                                          // file name of 
                                          // phonebook file 
 LPRASDIALPARAMS lpRasDialParams,         // pointer to 
                                          // calling 
                                          // parameters data 
 DWORD dwNotifierType,                    // specifies type 
                                          // of RasDial event 
                                          // handler 
 LPVOID lpvNotifier,                      // specifies a 
                                          // handler for 
                                          // RasDial events 
 LPHRASCONN lphRasConn                    // pointer to 
                                          // variable to 
                                          // receive 
                                          // connection 
                                          // handle 
);
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 parameter—also ignored on Windows 95—allows 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.

 typedef struct _RASDIALPARAMS { 
     DWORD dwSize; 
     TCHAR szEntryName[RAS_MaxEntryName + 1]; 
     TCHAR szPhoneNumber[RAS_MaxPhoneNumber + 1]; 
     TCHAR szCallbackNumber[RAS_MaxCallbackNumber + 1]; 
     TCHAR szUserName[UNLEN + 1]; 
     TCHAR szPassword[PWLEN + 1]; 
     TCHAR szDomain[DNLEN + 1] ; 
 #if (WINVER >= 0x401) 
     DWORD dwSubEntry; 
     DWORD dwCallbackId;
 #endif
 } RASDIALPARAMS;
      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
      Ending a RAS connection is much simpler than beginning one. In fact, to end a RAS connection you simply call RasHangUp with the handle to the active RAS connection that you wish to disconnect. For example, assuming hConnection contains a handle to an active RAS connection, the following line of code would suffice:


    DWORD dwRet = RasHangUp(hConnection);
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:

 RASCONNSTATUS RasConnStatus;
 RasConnStatus.dwSize = sizeof(RASCONNSTATUS);
 RasConnStatus.dwError = 0;
 while (ERROR_INVALID_HANDLE != RasConnStatus.dwError)
 {
     Sleep(0);
     RasGetConnectStatus(hConnection, &RasConnStatus);
 }
      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
      Windows NT 4.0 supports a new feature called AutoDial. When an application attempts to establish a connection with a network address, AutoDial can automatically establish a dial-up networking connection using a specific phonebook entry. You can test AutoDial by simply opening a command prompt and typing:


 ping www.microsoft.com
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.

Figure 16: AutoDial Addresses
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:

 LPTSTR *lppAddresses = NULL;
 DWORD dwSize=0, dwAddresses=0;
 ULONG i;
 
 DWORD dwRet = RasEnumAutodialAddresses(lppAddresses, &dwSize, &dwAddresses);
 if (dwSize) {
     lppAddresses = (LPTSTR*)malloc(dwSize);
     dwRet = RasEnumAutodialAddresses(lppAddresses, &dwSize, &dwAddresses);
 }
 for (i=0; i<dwAddresses; i++)
     m_Addresses.AddString(lppAddresses[i]);
 //cleanup & free memory

Figure 17: New Address
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:

 RASAUTODIALENTRY lppAutoDialEntry[1];
 lppAutoDialEntry[0].dwSize = sizeof(RASAUTODIALENTRY);
 lppAutoDialEntry[0].dwFlags = 0;
 lppAutoDialEntry[0].dwDialingLocation = 1; //default location
 strcpy(lppAutoDialEntry[0].szEntry, m_strPhonebook.GetBuffer(0));
 DWORD dwRet = RasSetAutodialAddress(m_strAddress.GetBuffer(0), 0, 
                                 lppAutoDialEntry,  sizeof(RASAUTODIALENTRY), 1);
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.)

Figure 18: AutoDial Prefs
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 right—each 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:

 HKEY_CURRENT_USER\Software\Microsoft\RAS Autodial\Control
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.

Figure 19: AutoDial Prompt
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
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:

 BOOL WINAPI RASADFunc( 
     LPTSTR lpszPhonebook,     // pointer to full path and file name of phonebook 
     LPTSTR lpszEntry,                // pointer to the entry name to validate 
     LPRASADPARAMS lpAutodialParams,  // pointer to a RASADPARAMS structure 
     LPDWORD lpdwRetCode              // receives results of dialing operation 
 );     
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
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:

 RASENTRY Entry;
 Entry.dwSize = sizeof(RASENTRY);
 Entry.dwAlternateOffset = 0;
 strcpy(Entry.szAreaCode, m_strArea);
 strcpy(Entry.szLocalPhoneNumber, m_strPhone);
 strcpy(Entry.szDeviceName, m_strDevice);
 strcpy(Entry.szAutodialDll, m_strAutoDialDLL);
 strcpy(Entry.szAutodialFunc, m_strAutoDialFunc);
 DWORD dwSize = sizeof(Entry);
 DWORD dwRet = RasSetEntryProperties(NULL, 
                                 m_strPhonebook.GetBuffer(0), 
                                 &Entry, dwSize, NULL, 0);
      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
      In certain situations you may want to enumerate the current RAS connections or the available RAS devices. To do this, use RasEnumConnections and RasEnumDevices. These functions allow you to determine if the current connection and device are suitable for the task your program needs to perform.
      Another hot topic is how to determine when a RAS connection is established. You can probably come up with a slick mechanism to periodically enumerate the connections (using RasEnumConnections) and check the status of each one. Windows NT 4.0 offers a much more elegant solution: RasConnectionNotification.
      Using RasConnectionNotification, your application can register an event that the system will signal when the following RAS events occur: a new RAS connection is established, an existing RAS connection is terminated, RAS bandwidth is added, and RAS bandwidth is removed. The RasJazz dialog contains a Start Waiting for Notification button that spawns the following thread:


 UINT WaitForRasNotificationsThread(LPVOID lpvoid)
 {
     HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE,
                                 "RasNotification");
     if (hEvent)
         RasConnectionNotification((HRASCONN)INVALID_HANDLE_VALUE, 
                 hEvent, RASCN_Connection | RASCN_Disconnection | 
                 RASCN_BandwidthAdded | RASCN_BandwidthRemoved);
 
     if (WAIT_OBJECT_0 == WaitForSingleObject(hEvent, INFINITE))
     {
         ::MessageBox(NULL, "Ras notification recieved",
                            "Aaron's RAS Notification", 
                             MB_OK | MB_SETFOREGROUND );
         CloseHandle(hEvent);
     }
     return 0;
 }
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
      I've covered how to use the WinInet dialing functions along with the RAS API. I've shown you how to use the RAS common dialog functions, the RasDial/RasHangup functions, and the RAS phonebook entry functions. Furthermore, I've tried to expose you to some of the advanced RAS features like RAS AutoDial and RAS connection notifications.
      By now, you should be familiar enough with the dial-up services offered by Windows to take full advantage of them in your applications. I hope the problems and pitfalls I've touched upon will save you some time and headaches.

From the June 1998 issue of Microsoft Interactive Developer.