HomePage   Delphi Library  

 

Dealing with network connections

by Kent Reisdorph

Network and Internet programming is becoming more and more common. An application may have to connect to a remote machine, transfer a file, and then disconnect again. File transfers can be performed via FTP, or simply by connecting to a particular drive on a remote machine and copying a file in the usual ways.

In this article, we'll show you how to establish a connection to a remote machine and how to terminate that connection when you're done with it. We'll also show you how to enumerate network connections. Along the way, we'll introduce you to the WNet family of Windows API functions.

 Using the networking dialogs

Mapping a drive on a remote machine can be performed in two different ways. Some applications, for example, will want to publicly announce their intention to map a network drive and rely on the user to provide the connection parameters. For those applications, you can simply use the built-in Windows dialogs to map a network drive. Two dialogs are available for your use: the Map Network Drive dialog and the Disconnect Network Drive dialog. Figure A shows the Windows 95/98 Map Network Drive dialog. Figure B shows the Windows 95/98 Disconnect Network Drive dialog.

The Windows NT dialogs look slightly different than their Windows 95/98 counterparts, but perform the same tasks. Putting these dialogs to work in your programs is really quite simple, as you'll find out in the following sections.

Mapping a network drive

The Map Network Drive dialog is invoked with a call to WNetConnectionDialog. For example:

WNetConnectionDialog

(Handle, RESOURCETYPE_DISK);

Yes, it's really that easy. The return value from WNetConnectionDialog is 0 if the connection succeeds, or $FFFFFFFF if the connection fails. If the connection fails, you should call GetLastError to determine the reason for the failure. Reasons for failure to connect include a bad remote machine name, a bad share name, an invalid password, or no network available. If a password is required to make the connection, Windows will display a second dialog box asking for the password.

Disconnecting a network drive

Disconnecting a network drive with the Disconnect Network Drive dialog is just as easy. Here's the code:

WnetDisconnectDialog

(Handle, RESOURCETYPE_DISK);

The return value from WNetDisconnectDialog is the same as described for WNetConnectionDialog. If WNetDisconnectDialog returns $FFFFFFFF you should call GetLastError to find out what went wrong. WNetDisconnectDialog may fail if the network is unavailable, but should otherwise succeed since you're choosing from a Windows-defined set of network connections. By the way, you can also remove a network printer using this function. To do so, pass RESOURCETYPE_PRINT in the second parameter instead of RESOURCETYPE_DISK. If your program is the type that can allow the user to choose a connection, then you should certainly use the network dialogs provided by Windows.

Connecting via code

Some applications need to map to a network drive entirely via code. For example, you might want to map a drive via code so the user is unaware that the connection is taking place. Not all users need to know everything about your network. Mapping a drive via code allows you to enforce a certain amount of security and still allow your program to do what it needs to do.

A connection can be made either with or without a local drive mapping. For example, you can connect to a shared drive on a network and then perform operations against that drive using the network shares UNC name. Let's say you've successfully connected to a network share called \\bigserver\secret$. In that case, you could copy a file from that drive like this:

CopyFile('\\bigserver\secret$\data.txt',

'c:\data\data.txt', False);

You don't need a drive letter in this case. In other cases it's advantageous to map a network share to a local drive letter. In that case, you can map a network share to any available local drive letter. You'll have to find a drive letter that isn't currently in use, but we'll discuss that a little later. Connecting a network share via code requires using one of the WNetAddConnection functions. There are three such functions: WNetAddConnection, WNetAddConnection2, and WNetAddConnection3. The WNetAddConnection function is provided for backwards compatibility with earlier versions of Windows and shouldn't be used. The difference between WNetAddConnection2 and WNetAddConnection3 is only that the latter provides an extra parameter for specifying a window handle. This handle is used as the owner window for any dialog boxes that Windows displays during connection. For our example, we'll use WNetAddConnection2 to map a drive. First, look at the code and then we'll explain how it works. Here's the code:

procedure TForm1.MapBtnClick

(Sender: TObject);

var

NR : TNetResource;

begin

FillChar(NR, SizeOf(NR), 0);

NR.dwType := RESOURCETYPE_DISK;

NR.lpLocalName := 'N:';

NR.lpRemoteName := '\\computer\share';

WNetAddConnection2(

NR, 'password', 'username', 0);

end;

First, we declare an instance of the TNetResource record and initialize all its members to 0. Then, we set the dwType field to RESOURCETYPE_DISK because we're connecting to a disk drive. Next, we set the lpLocalName field to N: because we're mapping the share to local drive N. Note that you must provide the colon following the drive letter or the function call will fail. The next line in this example sets the lpLocalName field to the computer name and share to which we want to connect. Finally, we call WNetAddConnection2 to make the connection. The first parameter to WNetAddConnection2 is the TNetResource record. The second and third parameters specify the password and user name used to make the connection. The final parameter determines whether the connection should be persistent. A persistent connection will be remembered when the user shuts down and restarts Windows. In this case, we're passing 0, so the connection isn't remembered. To establish a persistent connection, pass CONNECT_UPDATE_PROFILE for the final parameter.

If WNetAddConnection2 succeeds, it returns 0. If it fails, it returns an error code. A connection could fail for any number of reasons, including a bad user name or password, access denied, network busy or no network detected, and so on. You should check the return value from WNetAddConnection2 before attempting to use the connection. If the call to WNetAddConnection2 is successful, you can use the mapped drive letter just like a local drive.

You can also establish a connection without mapping to a local drive letter. Simply pass an empty string to the lpLocalName field of the TNetResource record.

Disconnecting a network connection

Once you've connected to a network share and finished your business, you should disconnect. Disconnecting an existing connection is accomplished by calling the WNetCancelConnection2 function. (There's a WNetCancelConnection function but it's provided for backwards compatibility and shouldn't be used.) Here's how a call to WNetCancelConnection2 looks:

WNetCancelConnection2('N:', 0, False);

The first parameter to WNetCancelConnection2 is the name of the connection to cancel. In this example, we're disconnecting the connection mapped to local drive N. To disconnect a share that's not mapped to a local drive letter, provide the computer and share name when you call WNetCancelConnection2:

WNetCancelConnection2(

'\\computername\sharename', 0, False);

The second parameter of WNetCancelConnection2 is used to determine connection persistence. If the connection was initially connected as persistent, then passing 0 for this parameter will cancel the connection but will leave the connection in the list of remembered connections. If you pass CONNECT_UPDATE_PROFILE for this parameter, Windows will remove the connection from the remembered connections list.

The final parameter of WNetCancelConnection2 determines whether or not the connection is unconditionally terminated. If you pass False for this parameter, the call to WNetCancelConnection2 will fail if a user has a file open on the share. If you pass True, the connection will be broken, even if the user has files open on the share.

Enumerating connections

Sometimes you have to enumerate existing connections to determine the state of one or more connections. We can't explain every conceivable use for enumerating connections, so we'll only show you how to enumerate active and remembered connections. Enumerating connections starts with a call to WNetOpenEnum. A call to WNetOpenEnum looks like this:

var

EnumHandle : DWORD;

begin

WNetOpenEnum(RESOURCE_CONNECTED,

RESOURCETYPE_DISK, 0, nil, EnumHandle);

The first parameter is used to determine the type of the enumeration. If you pass RESOURCE_CONNECTED for this parameter, then only the active connections will be enumerated. If you want to enumerate remembered (persistent) connections, pass RESOURCE_REMEMBERED for this parameter. The second parameter is used to specify the resource type to enumerate. Here we pass RESOURCETYPE_DISK to enumerate the active disk connections. We pass 0 for the third parameter to tell Windows to enumerate all resources. The fourth parameter is used to tell Windows what resource to enumerate. In this case, we just pass nil so Windows will enumerate the root of the network. (You could pass an instance of a TNetResource record to enumerate a specific network container, but that subject is beyond the scope of this article.) Finally, we pass EnumHandle for the last parameter. If WNetOpenEnum returns successfully, this variable will contain a handle we can use for the next step. If WNetOpenEnum returns successfully, you can then call WNetEnumResource to enumerate the connections. You pass the handle obtained in the call to WNetOpenEnum, a pointer to a buffer (an array of TNetResource records), variables for the number of resources you want to enumerate, and the required size of the buffer that Windows needs to carry out the enumeration. For example:

var

NR : array [0..30] of TNetResource;

EnumHandle : DWORD;

Count, Size : DWORD;

begin

Res := WNetOpenEnum(RESOURCE_CONNECTED,

RESOURCETYPE_DISK, 0, nil, EnumHandle);

Count := $FFFFFFFF;

Size := SizeOf(TNetResource) * 30;

WNetEnumResource(

EnumHandle, Count, @NR, Size);

First, we declare an array of 30 TNetResource records. This code is cheating somewhat, in that it assumes no more than 30 connections will exist. Note that we set the value of Count to $FFFFFFFF before calling WNetEnumResource. This value tells Windows to enumerate all available connections. We also set the Size variable to the size of our TNetResource array. Finally, we call WNetEnumResource to tell Windows to enumerate the connections. WNetEnumResource will return 0 if the enumeration succeeds. A return value other than 0 indicates an error. (We haven't been checking the return value in the examples up to this point, but you should always check the return value from the various WNet functions and take appropriate action if an error occurs.) If WNetEnumResource is successful, the Count variable will contain the number of connections found. You can then use a simple for loop to enumerate the connections:

for I := 0 to Pred(Count) do

Memo1.Lines.Add(NR[I].lpRemoteName);

This code adds the remote name of each connection to a memo. After enumerating connections, you must call WNetCloseEnum to free the memory Windows allocated for the enumeration:

WNetCloseEnum(EnumHandle);

Enumerating connections can be a bit more complex than the simple examples presented here. Much of the time, however, you may not even need to enumerate connections.

Getting a local drive letter

Before you can map a local drive letter to a network share, you must determine what drive letters are available. You can't map a network share to a drive letter that's already in use. This includes removable drives (floppy, CD-ROM, or Zip drives), fixed drives (hard disks), and currently mapped network drives. Fortunately, you can easily determine the next available drive letter with a simple for loop. Here's how it looks in pseudo-code:

for I := 'D' to 'Z' do begin

DriveType := GetDriveType;

if DriveType = Fixed, Variable, or CD

Continue;

Res := WNetGetConnection;

if Res = ERROR_NOT_CONNECTED then

{ Drive letter is available }

if Res = ERROR_CONNECTION_UNAVAIL then

{ Drive is remembered but not connected}

end;

This pseudo-code checks the drive type by calling the Windows API function GetDriveType. If the drive isn't a hardware drive then the WNetGetConncetion function is called to get the status of the connection. If WNetGetConnection returns ERROR_NOT_CONNECTED, the drive letter can be used to map a network share. See the DrivesBtnClick method in the listing at the end of this article for the actual code used to determine if a drive letter is available.

Conclusion

Connecting to a network share requires a bit of work, especially when you haven't done it before. With the information presented in this article you'll be able to easily map network drives from your Delphi applications. Listing A contains the source code for our example program's main form. The program allows you to map network drives and disconnect those drives again. It also shows how to enumerate connections, and how to display the connection information for all drives on your system.

Listing A: WNetU.PAS

unit WNetU;

interface

uses

Windows, Messages, SysUtils, Classes,

Graphics, Controls, Forms, Dialogs,

StdCtrls;

type

TForm1 = class(TForm)

EnumBtn: TButton;

Memo1: TMemo;

AddDlgBtn: TButton;

RmvDlgBtn: TButton;

DrivesBtn: TButton;

GroupBox1: TGroupBox;

PathEdit: TEdit;

Label1: TLabel;

Label2: TLabel;

DriveEdit: TEdit;

MapBtn: TButton;

Label3: TLabel;

Label4: TLabel;

UsernameEdit: TEdit;

PasswordEdit: TEdit;

UnMapBtn: TButton;

Label5: TLabel;

procedure AddDlgBtnClick(Sender: TObject);

procedure RmvDlgBtnClick(Sender: TObject);

procedure EnumBtnClick(Sender: TObject);

procedure DrivesBtnClick(Sender: TObject);

procedure MapBtnClick(Sender: TObject);

procedure UnMapBtnClick(Sender: TObject);

procedure FormCreate(Sender: TObject);

private

{ Private declarations }

procedure ShowConnections;

public

{ Public declarations }

end;

var

Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);

begin

{ Show the connections on startup. }

ShowConnections;

end;

procedure

TForm1.AddDlgBtnClick(Sender: TObject);

begin

{ Show the Map Network Drive dialog. }

WNetConnectionDialog(

Handle, RESOURCETYPE_DISK);

{ Update the display. }

ShowConnections;

end;

procedure

TForm1.RmvDlgBtnClick(Sender: TObject);

begin

{ Show the Disconnect Network Drive dialog. }

WNetDisconnectDialog(

Handle, RESOURCETYPE_DISK);

ShowConnections;

end;

procedure TForm1.EnumBtnClick(Sender: TObject);

begin

ShowConnections;

end;

procedure

TForm1.DrivesBtnClick(Sender: TObject);

var

Res, Size : DWORD;

I : Char;

Buff : array [0..255] of Char;

S : string;

S2 : string;

DriveType : Integer;

begin

Memo1.Lines.Clear;

Size := SizeOf(Buff);

{ Enumerate the drives starting with D: }

for I := 'D' to 'Z' do begin

S := Format('%s:', [I]);

{ Find out the drive type. If it's a }

{ hardware drive then continue looking. }

DriveType := GetDriveType(PChar(S));

if (DriveType = DRIVE_FIXED) or

(DriveType = DRIVE_CDROM) or

(DriveType = DRIVE_RAMDISK) then begin

Memo1.Lines.Add(S + #9 +

'Fixed, removable, or CD-ROM drive');

Continue;

end;

{ Must be a network drive. Get the }

{ connection status. }

Res := WNetGetConnection(

PChar(S), Buff, Size);

{ Check the status. }

if Res = 0 then

{No error. Drive is already connected. }

S2 := 'Connected to ' + Buff;

if Res = ERROR_NOT_CONNECTED then

{Drive not connected, so it's available.}

S2 := 'Available';

if Res = ERROR_CONNECTION_UNAVAIL then

{Remembered but not connected, can't use}

S2 := 'Remembered but not connected ('

+ Buff + ')';

{ Show the connection in the memo. }

Memo1.Lines.Add(S + #9 + S2);

end;

end;

procedure TForm1.MapBtnClick(Sender: TObject);

var

NR : TNetResource;

Res : Cardinal;

Drive : array [0..255] of Char;

Path : array [0..255] of Char;

begin

{ Zero the memory. }

FillChar(NR, SizeOf(NR), 0);

{ Mapping a disk. }

NR.dwType := RESOURCETYPE_DISK;

{ Set the drive letter and path based on }

{ the contents of the corresponding edits. }

NR.lpLocalName :=

StrPCopy(Drive, DriveEdit.Text);

NR.lpRemoteName :=

StrPCopy(Path, PathEdit.Text);

{ Make the connection, passing the username }

{ and password in the corresponding edits. }

Res := WNetAddConnection2(NR,

PChar(PasswordEdit.Text),

PChar(UsernameEdit.Text), 0);

{ Report the connection results. }

if Res = 0 then

ShowMessage('Connected')

else

ShowMessage(

'Unable to connect. Error code '

+ IntToStr(Res) + '.');

{ Update the display. }

ShowConnections;

end;

procedure

TForm1.UnMapBtnClick(Sender: TObject);

var

Res : Cardinal;

Buff : array [0..255] of Char;

begin

{ Unmap the drive or connection. If the }

{ drive letter edit is empty then assume }

{ the path edit contains the name of the }

{ connection to terminate. }

if DriveEdit.Text <> '' then

StrPCopy(Buff, DriveEdit.Text)

else

StrPCopy(Buff, PathEdit.Text);

Res := WNetCancelConnection2(Buff, 0, False);

{ Show the results. }

if Res = 0 then

ShowMessage('Drive '+Buff+' disonnected.')

else

ShowMessage('Unable to disconnect. ' +

' Error code ' + IntToStr(Res) + '.');

{ Update the display. }

ShowConnections;

end;

{ Enumerate the connections and show the }

{ results in a memo. }

procedure TForm1.ShowConnections;

var

NR : array [0..30] of TNetResource;

EnumHandle : DWORD;

Res : Cardinal;

Count, Size : DWORD;

I : Integer;

Buff : array [0..255] of Char;

S : string;

begin

Memo1.Lines.Clear;

{ First enumerate the active conncections. }

Memo1.Lines.Add('Active Connections:');

Res := WNetOpenEnum(RESOURCE_CONNECTED,

RESOURCETYPE_DISK, 0, nil, EnumHandle);

if (Res <> 0) then begin

ShowMessage('Error');

Exit;

end;

Count := $FFFFFFFF;

Size := SizeOf(TNetResource) * 30;

Res := WNetEnumResource(

EnumHandle, Count, @NR, Size);

if (Res <> 0) then begin

if Res = ERROR_NO_MORE_ITEMS then

Memo1.Lines.Add('No active connections')

else begin

ShowMessage('Error');

Exit;

end;

end;

{ Iterate through the connections, and }

{ display the connection name in the memo. }

for I := 0 to Pred(Count) do begin

if NR[I].lpLocalName = '' then

S := '(none)'

else

S := NR[I].lpLocalName;

Memo1.Lines.Add(

S + #9 + NR[I].lpRemoteName);

end;

{ Close the enumeration. }

WNetCloseEnum(EnumHandle);

Memo1.Lines.Add('');

{ Now enumerate the remembered connections. }

Memo1.Lines.Add('Remembered Connections:');

Res := WNetOpenEnum(RESOURCE_REMEMBERED,

RESOURCETYPE_DISK, 0, nil, EnumHandle);

if (Res <> 0) then begin

ShowMessage('Error');

Exit;

end;

Count := $FFFFFFFF;

Size := SizeOf(TNetResource) * 30;

Res := WNetEnumResource(

EnumHandle, Count, @NR, Size);

if (Res <> 0) then begin

if Res = ERROR_NO_MORE_ITEMS then

Memo1.Lines.Add(

'No remembered connections')

else begin

ShowMessage('Error');

Exit;

end;

end;

for I := 0 to Pred(Count) do begin

Size := SizeOf(Buff);

Res := WNetGetConnection(

NR[I].lpLocalName, Buff, Size);

S := Format('%s%s', [NR[I].lpLocalName,

#9 + NR[I].lpRemoteName]);

if (Res = ERROR_CONNECTION_UNAVAIL) then

S := S + #9 + ' (Not connected)';

if Res = 0 then

S := S + #9 + ' (Connected)';

Memo1.Lines.Add(S);

end;

{ Close the enumeration. }

WNetCloseEnum(EnumHandle);

end;

end.

Copyright (c) 1999, Ziff-Davis Inc. All rights reserved. ZD Journals and the ZD Journals logo are trademarks of Ziff-Davis Inc. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.

Borland Home

(c) 2000 Inprise Corp. Shop | Search | Contact | Products | Services & Support | Downloads | Programs | Worldwide | Newsgroups | Community

Last Modified Wednesday, 03-Nov-99 16:18:39 PST.

 

Hosted by www.Geocities.ws

1