首页  编辑  

拨号网络函数使用

Tags: /超级猛料/Network.网络通讯/FTP和拨号/   Date Created:

Creating your own dial-up application with RAS Printer Friendly Version

By Ken Kyler

Editor's note: Get the source code for this article here <http://www.kyler.com/pubs/DDJ9855.ZIP> .

It's not difficult to add remote computing capabilities to your application using Microsoft's Remote Access Service (RAS). However, it does require an understanding of the RAS API. You can learn more about RAS by reading " Exploring the Remote Access Service (RAS) <http://www.kyler.com/pubs/ddj9854.html> " in this issue. In this article, we're going to build a nearly full-featured dial-up application. In the process, we'll demonstrate many of the RAS API features. The finished project can replace Microsoft's Dial-Up Networking application.

        A quick overview of RAS API

The RAS API includes structures, data types, and functions for accessing the RAS feature set. The structures hold information regarding phonebooks, phonebook entries, and connections. You'll use the following structures, in our application:

        RasConn

        RasDialParams

        RasEntry

        RasEntryName

The RAS enumerated types are RasConnState and RasProjection. You'll use them to return status values and specify a particular authentication protocol.

The RAS API functions are divided into those that manage phonebooks and those that manage connections. Of the former group, you'll use the following in our application:

        RasCreatePhonebookEntry

        RasDeleteEntry

        RasEditPhonebookEntry

        RasEnumEntries

        RasGetEntryDialParams

        RasGetEntryProperties

        RasRenameEntry

Of the latter group, you'll use these to manage our connections:

        RasDial

        RasGetErrorString

        RasGetErrorString

We'll also create two new procedures, RasCallBack and RasGetPhonebookEntries, and a function, GetParam. The procedures handle connections and retrieve phonebook entries, while the function retrieves data describing a phonebook entry.

        Building our application

We're going to build our application in pieces, according to functionality. First, we'll create the skeleton routines. Then, we'll add the code for retrieving and maintaining phonebook entries. We'll finish by adding routines for managing connections. Let's get started by closing any open files, then select New Application from the File menu. Next, select Save All from the File menu. Save Unit1.pas as RasAppf.pas and Project1.dpr as RasApp.dpr . Add the controls in Table A to the new form.

Table A: The application controls

Control Quantity Name
TPanel 1 Panel1
TListBox 1 ListBox1
TStatusBar 1 StatusBar1
TEdit 4 eUserName, ePassWord, ePhone, eIPAddress
TLabel 5 Label1 through Label5
TButton 6 btnNew, btnEdit, btnRename, btnDelete, BtnDial, btnHangUp

Arrange the controls on the form as shown in Figure A. For StatusBar1, set its Align , SimplePanel , and SizeGrip properties to alTop, True, and False, respectively. Change the Sorted property of ListBox1 to True and the PasswordChar property of ePassWord to *. With Panel1, set its BevelInner , Caption , and Color properties to bvLowered, Connect Status, and clRed, respectively. Change the captions of the buttons, labels, and panel to match their appearance in Figure A.

Figure A: This is the RAS application interface.

Now we're going to create the skeleton of the application. The code you need to add is highlighted in color in Listing A. Start by adding the Ras unit to the uses clause. In the form's private declarations, add the names of our new function and procedure, GetParams and RasGetPhoneBookEntries, respectively. In the variable declaration, add the names of our two new variables, RasConn and RasDialParams. The variable RasConn will receive the RAS connection handle. RasDialParams is a data structure containing parameters used by the RasDial function to establish a remote access connection.

Listing A: RasAppf.pas

unit RasAppf;

interface

uses

 Windows, Messages, SysUtils, Classes,

       Graphics, Controls, Forms, Dialogs,

       ExtCtrls, StdCtrls, ComCtrls, Ras;

type

 TForm1 = class(TForm)

   Label2: TLabel;

   Label3: TLabel;

   Label4: TLabel;

   Label5: TLabel;

   Label1: TLabel;

   btnNew: TButton;

   btnEdit: TButton;

   btnRename: TButton;

   btnDelete: TButton;

   btnHangUp: TButton;

   btnDial: TButton;

   ListBox1: TListBox;

   StatusBar1: TStatusBar;

   eUserName: TEdit;

   ePassWord: TEdit;

   ePhone: TEdit;

   eIPAddress: TEdit;

   Panel1: TPanel;

   procedure FormCreate(Sender: TObject);

   procedure FormCloseQuery

       (Sender: TObject; var CanClose: Boolean);

   procedure ListBox1Click(Sender: TObject);

   procedure btnDialClick(Sender: TObject);

   procedure btnHangUpClick(Sender: TObject);

   procedure btnNewClick(Sender: TObject);

   procedure btnRenameClick(Sender: TObject);

   procedure btnEditClick(Sender: TObject);

   procedure btnDeleteClick(Sender: TObject);

 private

   { Private declarations }

   procedure RasGetPhoneBookEntries;

   function  GetParams: boolean;

 public

   { Public declarations }

 end;

var

 Form1: TForm1;

// Pointer to variable to receive connection handle

 RasConn: THRasConn;

// Pointer to calling parameters

 RasDialParams: TRasDialParams;

implementation

{ $R *.DFM}

procedure RasCallBack(msg: Integer; state: TRasConnState;

   dwError: Longint); stdcall;

var

 S: string;

 cTxt: Array[0..255] of Char;

begin

 with Form1 do begin

    if (dwError <> 0) then begin

       RasGetErrorString(dwError, cTxt, 256);

       S := cTxt;

    end

    else

    case state of

       RASCS_OpenPort:

          S := 'The comm port is about to be opened.';

       RASCS_PortOpened:

          S := 'The comm port has been opened successfully.';

       RASCS_ConnectDevice:

          S := 'A device is about to be connected.';

       RASCS_DeviceConnected:

          S := 'A device has connected successfully.';

       RASCS_AllDevicesConnected:

          S := 'All devices have successfully connected.';

       RASCS_Authenticate:

          S := 'The authentication process is starting.';

       RASCS_AuthNotify:

          S := 'An authentication event has occurred.';

       RASCS_AuthRetry:

          S := 'New validation attempt requested.';

       RASCS_AuthCallback:

          S := 'The remote server has requested a callback.';

       RASCS_AuthChangePassword:

          S := 'The client has requested a password change.';

       RASCS_AuthProject:

          S := 'The projection phase is starting.';

       RASCS_AuthLinkSpeed:

          S := 'The link-speed calculation phase is starting.';

       RASCS_AuthAck:

          S := 'An authentication request

               is being acknowledged.';

       RASCS_ReAuthenticate:

          S := 'Reauthentication (after callback) is starting.';

       RASCS_Authenticated:

          S := 'The client has successfully

               completed authentication.';

       RASCS_PrepareForCallback:

          S := 'The line will disconnect

               in preparation for callback.';

       RASCS_WaitForModemReset:

          S := 'Delaying; getting ready for a callback.';

       RASCS_WaitForCallback:

          S := 'Waiting for an incoming

               call from the remote server.';

       RASCS_Projected:

          S := 'Projection result information is available.';

       RASCS_StartAuthentication:

          S := 'Windows 95 only:

               User authentication initiated.';

       RASCS_CallbackComplete:

          S := 'Windows 95 only: Client has been called back.';

       RASCS_LogonNetwork:

          S := 'Windows 95 only:

               Client is logging on to the network.';

       RASCS_Connected: begin

          S := 'Successful connection.';

          Panel1.Color := clGreen;

       end;

       RASCS_Disconnected:

          S := 'Disconnection or failed connection.';

    end;

    StatusBar1.SimpleText := S;

 end;

end;

procedure TForm1.RasGetPhoneBookEntries;

var

 RasEntryName: array[1..20] of TRasEntryName;

 i, x, BufSize, Entries: DWord;

begin

 ListBox1.Clear;

 RasEntryName[1].dwSize := SizeOf(RasEntryName[1]);

 BufSize := SizeOf(RasEntryName);

 x := RasEnumEntries(nil, nil,

        @RasEntryName, BufSize, Entries);

 if (x = 0) or (x = ERROR_BUFFER_TOO_SMALL) then

   for i := 1 to Entries do

      if ( i < 21) and (RasEntryName[i]

               .szEntryName[0] <> #0) then

        ListBox1.Items.Add(StrPas

               (RasEntryName[i].szEntryName));

end;

function TForm1.GetParams: boolean;

var

 fp: LongBool;

 ErrNo, ESize, DSize: Longint;

 Entry: TRasEntry;

 S: string;

 cTxt: Array[0..255] of Char;

begin

 Result := false;

 if (ListBox1.ItemIndex < 0) then begin

   ShowMessage('Select a phonebook entry first!');

   Exit;

 end;

 with RasDialParams do begin

    dwSize := Sizeof(TRasDialParams);

    StrLCopy(szEntryName, PChar(ListBox1.Items

       [ListBox1.ItemIndex]), Ras_MaxEntryName);

 end;

 ErrNo := RasGetEntryDialParams(nil, RasDialParams, fp);

 if (ErrNo = 0) then

   with RasDialParams do begin

      eUserName.Text := szUserName;

      if fp then

        ePassword.Text := szPassword;

   end

 else begin

   RasGetErrorString(ErrNo, cTxt, 256);

   ShowMessage('RasGetEntryDialParams failed: ' + cTxt);

   Exit;

 end;

 ESize := 0;

 DSize := 0;

// Try to create the TRasEntry dynamically:

 Entry.dwSize := SizeOf(TRasEntry);

 RasGetEntryProperties(nil, PChar(ListBox1.Items

       [ListBox1.ItemIndex]), nil,

    ESize, nil, DSize);

 ErrNo := RasGetEntryProperties(nil, PChar(ListBox1.Items

       [ListBox1.ItemIndex]),

    @Entry, ESize, nil, DSize);

 if (ErrNo = 0) then with Entry do begin

   if (dwCountryCode <> null) and (szAreaCode <> '') then

     ePhone.Text := IntToStr(dwCountryCode) + '

       (' + szAreaCode +

     ') ' + szLocalPhoneNumber

   else if (szAreaCode <> '') then

     ePhone.Text := '(' + szAreaCode + ') '

                + szLocalPhoneNumber

   else

     ePhone.Text := szLocalPhoneNumber;

   with IPAddr do

     eIPAddress.Text := IntToStr(a) + '.'

               + IntToStr(b) + '.' +

     IntToStr(c) + '.' + IntToStr(d);

   Result := true;

   btnDial.Enabled := true;

 end

 else begin

    case RasGetErrorString(ErrNo, cTxt, 256) of

       0: S :=  cTxt;

       ERROR_INSUFFICIENT_BUFFER:

          S := 'ERROR_INSUFFICIENT_BUFFER';

       ERROR_INVALID_PARAMETER:

          S := 'ERROR_INVALID_PARAMETER';

// Error codes not defined in RasError.h

    else

       case ErrNo of

          ERROR_INVALID_USER_BUFFER:

             S := 'ERROR_INVALID_USER_BUFFER';

          ERROR_INVALID_PARAMETER:

             S := 'ERROR_INVALID_PARAMETER';

          ERROR_BUFFER_INVALID:

             S := 'ERROR_BUFFER_INVALID';

          ERROR_BUFFER_TOO_SMALL:

             S := 'ERROR_BUFFER_TOO_SMALL';

          ERROR_CANNOT_OPEN_PHONEBOOK:

             S := 'ERROR_CANNOT_OPEN_PHONEBOOK';

          ERROR_CANNOT_FIND_PHONEBOOK_ENTRY:

             S := 'ERROR_CANNOT_FIND_PHONEBOOK_ENTRY';

       else

          S := 'Unknown => ' + IntToStr(ErrNo);

       end;

    end;

    ShowMessage('RasGetEntryProperties failed with: ' + S);

 end;

end;

procedure TForm1.FormCreate(Sender: TObject);

begin

 RasConn := 0;

 btnHangUp.Enabled := false;

 RasGetPhoneBookEntries;

end;

procedure TForm1.FormCloseQuery

       (Sender: TObject; var CanClose: Boolean);

begin

 if (RasConn <> 0) then

   btnHangup.Click;

end;

procedure TForm1.btnDialClick(Sender: TObject);

var

 ErrNo: longint;

 S: string;

begin

 btnDial.Enabled := false;

 btnHangUp.Enabled := true;

 ErrNo := RasDial(nil, nil, RasDialParams, 0,

         @RasCallBack, RasConn);

 if (ErrNo <> 0) then begin

   case ErrNo of

       ERROR_BUFFER_TOO_SMALL: S := 'ERROR_BUFFER_TOO_SMALL';

       ERROR_NOT_ENOUGH_MEMORY: S := 'ERROR_NOT_ENOUGH_MEMORY';

       ERROR_BUFFER_INVALID: S := 'ERROR_BUFFER_INVALID';

   else

       S := 'Unknown error (' + IntToStr(ErrNo) + ')';

   end;

   ShowMessage('RasDial Failed with ' + S);

   btnHangUp.Click;

 end;

end;

procedure TForm1.btnHangUpClick(Sender: TObject);

begin

 if (RasConn <> 0) then

   RasHangUp(RasConn);

 RasConn := 0;

 StatusBar1.SimpleText := 'Call Terminated';

 Panel1.Color := clRed;

 btnDial.Enabled := true;

 btnHangUp.Enabled := false;

end;

procedure TForm1.btnNewClick(Sender: TObject);

begin

 if (RasCreatePhonebookEntry(Handle, nil) <> 0) then

   ShowMessage('Failed to create a new Phonebook Entry!');

// Refresh the list box

 RasGetPhoneBookEntries;

end;

procedure TForm1.btnRenameClick(Sender: TObject);

var

 New, Old: String;

begin

// Test for a selected entry

 if (ListBox1.ItemIndex >= 0) then begin

   Old := ListBox1.Items[ListBox1.ItemIndex];

   New := InputBox('Rename Phonebook Entry', 'Enter new name',

      ListBox1.Items[ListBox1.ItemIndex]);

   if (New <> '') then

     if (RasRenameEntry(nil, PChar(Old), PChar(New)) <> 0)

                then

       ShowMessage('RasRenameEntry failed.')

     else

       RasGetPhoneBookEntries;

 end

 else

   ShowMessage('Select a phonebook entry first!');

end;

procedure TForm1.btnEditClick(Sender: TObject);

begin

 if (ListBox1.ItemIndex >= 0) then begin

   if (RasEditPhonebookEntry(Handle, nil,

     PChar(ListBox1.Items[ListBox1.ItemIndex])) <> 0) then

       ShowMessage('Edit Phonebook Entry Failed.')

   else

     RasGetPhoneBookEntries;

 end

 else

   ShowMessage('Select a phonebook entry first!');

end;

procedure TForm1.btnDeleteClick(Sender: TObject);

begin

 if (ListBox1.ItemIndex >= 0) then begin

   if (Application.MessageBox(PChar(ListBox1.Items

       [ListBox1.ItemIndex] +

      ' connection will be removed.' + #13#10

               + 'Are you sure?'),

      PChar(Application.Title), MB_YESNO or

                MB_APPLMODAL or MB_ICONWARNING)

      = mrYes) then

      if (RasDeleteEntry(nil, PChar(ListBox1.Items

       [ListBox1.ItemIndex])) <> 0) then

        ShowMessage('RasDeleteEntry failed.')

      else

        RasGetPhoneBookEntries;

 end

 else

   ShowMessage('Select a phonebook entry first!');

end;

procedure TForm1.ListBox1Click(Sender: TObject);

begin

 if not GetParams then

   btnDial.Enabled := false;

end;

end.

        Fleshing out the skeleton

Let's continue filling in our framework by creating event shells. You need to create two for the form--OnCreate and OnCloseQuery. Start by switching to the form's Events tab in the Object Inspector. Next, double-click the form's OnCreate event. Now, double-click on the forms OnCloseQuery event. You've just created the necessary shells for the form. In FormCreate, the application initializes the RasConn variable, disables the hang-up button, and calls RasGetPhoneBookEntries, which initializes the list box. The FormCloseQuery code closes any open RAS connections. In Listing A, you can see the whole RasAppf.pas unit of your application. Complete the code in the event handler shells you just created by adding the highlighted code in Listing A.

We need to create other shells for the buttons and the list box. To do this, double-click on each of the six buttons you dropped on the form. (Every time you double-click, Delphi displays the editor, so you'll have to toggle back to the form.) Finally, double-click the list box. You'll complete these shells as we review their functional details.

        The RasCallBack procedure

The next routine we'll discuss is the callback. Your application uses a callback routine to asynchronously handle messages sent when a change of state occurs during a RAS connection process. The first parameter in the callback is the type of event that has occurred. Currently, the only event defined is WM_RASDIALEVENT. The second parameter is a RASCONNSTATE. Look up the possible return values in your Win32 Developers Reference help file. The last parameter indicates the error that has occurred, or zero if no error has occurred. The callback does a lot of work. It's here that you receive notice that a line is busy, that the connection was completed successfully, that you've logged on, etc. Each message may or may not require an action on your part. A lot depends on how feature-rich you want your application to be. Keep in mind that any code placed within the callback should be brief or should execute in a separate thread. The callback is, after all, a message handler. Refer to the highlighted code in Listing A to see how you declare the RasCallBack procedure in the RasAppf.pas unit.

        The RasGetPhonebookEntries procedure

The next routine we'll add is RasGetPhonebookEntries. It reads the phonebook and populates the list box with entries. It does this by calling the RasEnumEntries function. If successful, the function returns a zero. In this example, we're assuming the phonebook won't have more than 20 entries. The first parameter of RasEnumEntries is reserved and must be nil. Windows 95 ignores the second parameter, so pass a nil value. However, if you're running the application on Windows NT, pass the full path and filename of the phonebook file. This is because Windows 95 stores phonebook entries in the Registry rather than in a phonebook file. The third parameter is the array of TRasEntryName data structures. The fourth parameter is the size, in bytes, of the array of data structures specified by the third parameter. The fifth and last parameter will contain the number of phonebook entries. See Listing A and add the highlighted code for RasGetPhonebookEntries to the RasAppf.pas unit.

        The GetParams function

The purpose of GetParams is to retrieve the information about an entry. Every time a user selects an item in the list box, the ListBox1Click procedure calls GetParams function. Then GetParams calls the RasGetEntryDialParams and RasGetEntryProperties API functions. The RasGetEntryDialParams function retrieves connection information saved by the last successful call to the RasDial function for the selected phonebook entry. The first parameter is the phonebook, which is nil unless you are using Windows NT. The second parameter, the RasDialParams variable, is the structure that receives the connection parameters. The last parameter, a Boolean variable, is set to true when the user's password was retrieved with the entry. If RasGetEntryDialParams succeeds, the return value is zero. If the function fails, it returns a value indicating either invalid parameters, a missing or corrupt phonebook, or a missing entry.

The RasGetEntryProperties function is detailed in " Exploring the Remote Access Service (RAS) <http://www.kyler.com/pubs/ddj9854.html> " in this issue. In brief, it retrieves the parameters of a phonebook entry. It stores the information in the RasDialParams structure.

You call RasGetEntryDialParams twice for each selection . The first time, you pass the parameter lpRasEntry set to nil and lpdwEntryInfoSize set to zero. RasGetEntryDialParams returns the required buffer size in the lpdwEntryInfoSize variable. Then you call RasGetEntryDialParams again, this time passing lpRasEntry and the lpdwEntryInfoSize parameter returned from the first call. Now you have all the information you need to make a call to RasDial.

        The numbers add up

Now we're ready to discuss the purpose of all the buttons. Let's do the simplest task first--adding a phonebook entry using the RasCreatePhonebookEntry function. You pass only one parameter--the handle of Form1. If successful, Windows displays the New Phonebook Entry Wizard, as shown in Figure B.

Figure B: You call the New Phonebook Entry Wizard to add a telephone number.

After you complete the wizard dialogs, Windows makes a new phonebook entry. Finally, the code calls RasGetPhoneBookEntries to update the contents of ListBox1. Add the colored code in Listing A to the btnNewClick shell you created earlier.

        Name dropper

There will be circumstances that require your user to delete entries from the phonebook. When the user presses the Delete button, a dialog box requests confirmation. You'll use RasDeleteEntry to remove the unwanted entry. This function accepts two parameters. You'll set the former to nil, while the latter is a pointer to the entry the user selected in ListBox1. Again, the code finishes by updating the contents of ListBox1. Add the colored code in Listing A to the btnDeleteClick shell.

        Changing a name

After a user clicks the Rename button, the btnRenameClick event calls the RasRenameEntry API function. The function then opens the Rename Phonebook Entry Wizard dialog, where the user types the entry's new name. We explained the RasRenameEntry function in detail in " Exploring the Remote Access Service (RAS) <http://www.kyler.com/pubs/ddj9854.html> " in this issue. To summarize, it accepts three parameters and uses them to rename a phonebook entry. Add the colored code in Listing A to the btnRenameClick shell.

        Editing a phonebook entry

Of course, the user will want to change other fields in addition to the entry's name. The btnEditClick procedure calls the RasEditPhonebookEntry API function. The function then opens the Edit Phonebook Entry dialog, shown in Figure C, where the user enters the new information.

Figure C: The RasEditPhonebookEntry function opens the Edit Phonebook Entry dialog.

Your code passes three parameters to the function. The parameters are hwnd, lpszPhonebook, and lpszEntryName. They represent the handle of Form1, the path and filename of the phonebook file for Windows NT or nil for Windows 95, and the phonebook entry that you want to edit, respectively. Add the colored code in Listing A to the btnEditClick shell.

        Reach out and touch some server

The program finally makes the call to the RasDial function when the user presses the Dial button. The first parameter of RasDial is a TRasDialExtension structure. Windows 95 always uses the default behaviors for the TRasDialExtension options. The second parameter is a pointer to the full path and filename of phonebook file, or nil for Windows 95. The third parameter is the RasDialParams structure. It specifies the calling parameters for the RAS connection.

The fourth parameter specifies where to return messages. A value of zero sends messages to a TRasDialFunc callback function. A value of 1 sends the messages to a TRasDialFunc1 callback function. In this project, we're using a TRasDialFunc callback. The fifth parameter specifies the handler for RasDial events. We're using procedure RasCallBack to trap those events, so be sure to base the address of the procedure using the @ operator. The last parameter is the THRasConn variable that we created at the beginning. You must set this variable to zero before calling RasDial. If RasDial succeeds, it returns a handle to the RAS connection.

        Everybody has hangups

When the user clicks the Hangup button, the btnHangUpClick event calls the RasHangUp API function. You pass only one parameter to RasHangUp: RasConn. RasConn is the RAS connection handle returned by RasDial. The btnHangUpClick ends by updating the interface. Add the highlighted code in Listing A to the btnHangUpClick shell.

        Conclusion

You can now run the finished application to demonstrate many of the RAS API data structures and functions. In this article you've learned that it's not difficult to add remote computing capabilities to your application using Microsoft's Remote Access Service. Your application can use RAS to perform automated logins, data transfers, directory services, file exchanges, and more.

img_12691.bmp (520.9KB)
img_14296.bmp (534.3KB)
img_30138.bmp (5.1KB)
img_4730.bmp (560.3KB)