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. Simple file transfers can be performed by connecting to a particular drive on a remote machine and copying a file in the usual ways.
The WNet family of Windows API functions is used to establish network connections, terminate connections, and enumerate the current connections. Mapping a drive on a remote machine can be performed in two different ways: using the built-in Windows dialog boxes, or entirely in code. I will explain each of these methods in this article.
In some applications you will want to publicly announce your 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 dialog boxes to map a network drive. Two dialog boxes are available for your use: the Map Network Drive dialog box and the Disconnect Network Drive dialog box. Figure A shows the Windows 2000 Map Network Drive dialog box. Figure B shows the Windows 2000 Disconnect Network Drive dialog box.
Figure A:
The Map Network Drive dialog box.
Figure B:
The Disconnect Network Drive dialog box.
The Windows NT and Windows 95/98 dialog boxes look slightly different than their Windows 2000 counterparts but perform the same tasks.
Putting these dialog boxes to work in your programs is really quite simple, as you’ll find out in the following sections.
The Map Network Drive dialog box is invoked with a call to WNetConnectionDialog(). Just one line of code is required:
WNetConnectionDialog(
Handle, RESOURCETYPE_DISK);
It’s as simple as that. The return value of WNetConnectionDialog() is 0 if the connection succeeds or 0xFFFFFFFF 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 username and password are required to make the connection, Windows will display a second dialog box asking for that information.
Disconnecting a network drive with the Disconnect Network Drive dialog box is just as easy. The WNetDisconnectDialog() function displays this dialog. Here’s the code:
WNetDisconnectDialog(
Handle, RESOURCETYPE_DISK);
The return value of WNetDisconnectDialog() is the same as described for WNetConnectionDialog(). WNetDisconnectDialog() may fail if the network is unavailable, but otherwise should succeed since you are choosing from a Windows-defined set of network connections. By the way, you can also remove a network printer using this function. 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, you should certainly use the network dialog boxes as provided by Windows.
Some applications need to map to a network drive entirely via code. For example, you might want to map a drive programmatically 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 remote machine and then perform operations against that drive using the network share’s UNC name. For example, let’s say you have successfully connected to a network share called \\server\src$. In that case you could copy a file from that drive like this:
CopyFile("\\\\\server\\src$\\data.txt",
"c:\data\data.txt", false);
You don’t need a drive letter at all in this case. In other cases it is advantageous to map a network share to a local drive letter. If so, you can map a network share to any available local drive letter. You’ll have to find a drive letter that is not currently in use (I’ll explain how to do that later in the article).
Connecting a network share via code requires using one of the WNetAddConnection() functions. There are three such functions, called WNetAddConnection(), WNetAddConnection2(), and WNetAddConnection3(). The WNetAddConnection() function is provided for backwards compatibility with earlier versions of Windows and should not 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 dialogs that Windows displays during the connection process. This example uses WNetAddConnection2() to map a drive:
void __fastcall TForm1::MapBtnClick(TObject *Sender)
{
TNetResource NR;
ZeroMemory(&NR, sizeof(NR));
NR.dwType = RESOURCETYPE_DISK;
NR.lpLocalName = "N:";
NR.lpRemoteName =
"\\\\machine\\share";
DWORD Res = WNetAddConnection2(
&NR, "Password", "Username", 0);
}
The first two lines declare an instance of the TNetResource structure and initialize all its members to 0. Next, the dwType field is set to RESOURCETYPE_DISK, indicating that you are connecting to a disk drive. After that the lpLocalName field is assigned to the string "N:". This specifies that the connection will be mapped to the 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 you want to connect. Finally, the WNetAddConnection2() is called to make the connection.
The first parameter to WNetAddConnection2() is a pointer to a TNetResource structure. The second and third parameters are used to specify the password and username 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. Pass 0 for this parameter to create a temporary connection, or CONNECT_UPDATE_PROFILE to establish a persistent connection.
The WNetAddConnection2() function returns 0 if it succeeds, or an error code if the function fails . A connection could fail for any number of reasons including a bad username 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.
To establish a connection without mapping to a local drive letter, simply pass an empty string to the lpLocalName() field of the TNetResource structure. You can then access files and folders on the network share by using the UNC path rather than a local drive letter.
Once you have connected to a network share and have done your business, you should disconnect. Disconnecting an existing connection is accomplished by calling the WNetCancelConnection2() function. (There is a WNetCancelConnection() function but it is provided for backwards compatibility and should not 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. This example disconnects the connection mapped to local drive N. To disconnect a share that is not mapped to a local drive letter, provide the computer and share name when you call WNetCancelConnection2(). For example:
WNetCancelConnection2(
"\\\\computer\\share", 0, false);
The second parameter of WNetCancelConnection2() is used to determine connection persistence. If the connection was initially connected as persistent, 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 then Windows will remove the connection from the remembered connections list.
The final parameter of WNetCancelConnection2() determines whether 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.
Sometimes you have to enumerate existing connections to determine the state of one or more connections. I can’t explain every conceivable use for enumerating connections so I’ll only show you how to enumerate active and remembered connections. Enumerating connections starts with a call to WNetOpenEnum(). It looks like this:
HANDLE EnumHandle;
DWORD Res = WNetOpenEnum(
RESOURCE_CONNECTED, RESOURCETYPE_DISK,
0, 0, &EnumHandle);
The first parameter is used to determine the type of the enumeration. Pass RESOURCE_CONNECTED for this parameter to enumerate only the active connections. 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 I pass RESOURCETYPE_DISK to enumerate the active disk connections. I 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 0 so Windows will enumerate the root of the network. (You could pass an instance of a TNetResource structure to enumerate a specific network container, but that subject is beyond the scope of this article.) Finally, we pass the address of the EnumHandle variable in 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 structures), variables for the number of resources you want to enumerate, and the required size of the buffer that Windows needs in order to carry out the enumeration. For example:
TNetResource NR[30];
DWORD Count = 0xFFFFFFFF;
DWORD Size = sizeof(TNetResource) * 30;
Res = WNetEnumResource(
EnumHandle, &Count, &NR, &Size);
First I declare an array of 30 TNetResource structures. This code is cheating somewhat in that it assumes no more than 30 connections will exist. Note that I set the value of the Count variable to 0xFFFFFFFF before calling WNetEnumResource(). This value tells Windows to enumerate all available connections. I also set the Size variable to the size of the TNetResource array. Finally, I call WNetEnumResource() to tell Windows to enumerate the connections.
WNetEnumResource() will return 0 if the enumeration succeeded. A return value other than 0 indicates an error. (The code shown up to this point hasn’t had any error-checking code, 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 (DWORD i=0;i<Count;i++)
Memo1->Lines->Add(
NR[i].lpRemoteName);
This code adds the remote name of each connection to a memo component.
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.
Before you can map a local drive letter to a network share, you must determine what drive letters are available. You cannot map a network share to a drive letter that is 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:
for (char i='D';i<'Z';i++) {
String S = String(i) + ":";
int DriveType =
GetDriveType(S.c_str());
DWORD Res = WNetGetConnection(
S.c_str(), Buff, &Size);
if (Res == 0)
// No error. Drive is already connected.
if (Res == ERROR_NOT_CONNECTED)
// Drive not connected, so it’s available.
}
This code checks the drive type by calling the Windows API function GetDriveType(). If the drive is not assigned to a local device, the WNetGetConncetion() function is called to get the status of the connection. If WNetGetConnection() returns ERROR_NOT_CONNECTED then 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.
With the information presented in this article you’ll be able to easily map network drives from within your applications. Listing A contains the source code for our example program’s main form. The example program allows you to map network drives and disconnect those drives again. It shows how to enumerate connections, and how to display the connection information for all drives on your system. You can download the source code for the example program from our Web site: www.bridgespublishing.com.
Listing A: The example program’s main unit.
#include <vcl.h>
#pragma hdrstop
#include "MainU.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::AddDlgBtnClick(
TObject *Sender)
{
// Show the Map Network Drive dialog.
WNetConnectionDialog(
Handle, RESOURCETYPE_DISK);
// Update the display.
ShowConnections();
}
void __fastcall TForm1::RmvDlgBtnClick(
TObject *Sender)
{
// Show the Disconnect Network Drive dialog.
WNetDisconnectDialog(
Handle, RESOURCETYPE_DISK);
ShowConnections();
}
void __fastcall TForm1::EnumBtnClick(
TObject *Sender)
{
ShowConnections();
}
void __fastcall TForm1::DrivesBtnClick(
TObject *Sender)
{
Memo1->Lines->Clear();
char Buff[256];
DWORD Size = sizeof(Buff);
// Enumerate the drives starting with D:
for (char i='D';i<'Z';i++) {
String S = String(i) + ":";
// Find out the drive type. if (it"s a
// hardware drive) continue looking.
int DriveType = GetDriveType(S.c_str());
if ((DriveType == DRIVE_FIXED) ||
(DriveType == DRIVE_CDROM) ||
(DriveType == DRIVE_RAMDISK)) {
Memo1->Lines->Add(S +
"\tFixed, removable, or CD-ROM drive");
continue;
}
// Must be a network drive. Get the
// connection status.
DWORD Res = WNetGetConnection(
S.c_str(), Buff, &Size);
// Check the status.
String S2;
if (Res == 0)
// No error. Drive is already connected.
S2 = "Connected to " + String(Buff);
if (Res == ERROR_NOT_CONNECTED)
// Drive not connected, so it"s available.
S2 = "Available";
if (Res == ERROR_CONNECTION_UNAVAIL)
// Remembered but not connected, can't use
S2 = "Remembered but not connected ("
+ String(Buff) + ")";
// Show the connection in the memo.
Memo1->Lines->Add(S + "\t" + S2);
}
}
void __fastcall TForm1::MapBtnClick(
TObject *Sender)
{
TNetResource NR;
// Zero the memory.
ZeroMemory(&NR, sizeof(NR));
// Mapping a disk.
NR.dwType = RESOURCETYPE_DISK;
// Set the drive letter and path based on
// the contents of the corresponding edits.
char Drive[3];
char Path[256];
strcpy(Drive, DriveEdit->Text.c_str());
strcpy(Path, PathEdit->Text.c_str());
NR.lpLocalName = Drive;
NR.lpRemoteName = Path;
// Make the connection, passing the username
// and password in the corresponding edits.
DWORD Res = WNetAddConnection2(&NR,
PasswordEdit->Text.c_str(),
UsernameEdit->Text.c_str(), 0);
// Report the connection results.
if (Res == 0)
ShowMessage("Connected");
else
ShowMessage(
"Unable to connect. Error code "
+ IntToStr(Res) + ".");
// Update the display.
ShowConnections();
}
void __fastcall TForm1::UnMapBtnClick(
TObject *Sender)
{
// Unmap the drive or connection. if (the
// drive letter edit is empty) assume
// the path edit contains the name of the
// connection to terminate.
char Buff[256];
if (DriveEdit->Text != "")
strcpy(Buff, DriveEdit->Text.c_str());
else
strcpy(Buff, PathEdit->Text.c_str());
DWORD Res =
WNetCancelConnection2(Buff, 0, False);
// Show the results.
if (Res == 0)
ShowMessage("Drive " + String(Buff) + " disonnected.");
else
ShowMessage("Unable to disconnect. "
" Error code " + String(Res) + ".");
// Update the display.
ShowConnections();
}
void TForm1::ShowConnections()
{
TNetResource NR[30];
Memo1->Lines->Clear();
// First enumerate the active conncections.
Memo1->Lines->Add("Active Connections:");
HANDLE EnumHandle;
DWORD Res = WNetOpenEnum(RESOURCE_CONNECTED,
RESOURCETYPE_DISK, 0, 0, &EnumHandle);
if ((Res != 0)) {
ShowMessage("Error");
return;
}
DWORD Count = 0xFFFFFFFF;
DWORD Size = sizeof(TNetResource) * 30;
Res = WNetEnumResource(
EnumHandle, &Count, &NR, &Size);
if ((Res != 0)) {
if (Res == ERROR_NO_MORE_ITEMS)
Memo1->Lines->Add("No active connections");
else {
ShowMessage("Error");
return;
}
}
// Iterate through the connections, and
// display the connection name in the memo.
String S;
for (DWORD i=0;i<Count;i++) {
if (NR[i].lpLocalName == "")
S = "(none)";
else
S = NR[i].lpLocalName;
Memo1->Lines->Add(
S + "\t" + NR[i].lpRemoteName);
}
// 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, 0, &EnumHandle);
if ((Res != 0)) {
ShowMessage("Error");
return;
}
Count = 0xFFFFFFFF;
Size = sizeof(TNetResource) * 30;
Res = WNetEnumResource(
EnumHandle, &Count, &NR, &Size);
if ((Res != 0)) {
if (Res == ERROR_NO_MORE_ITEMS)
Memo1->Lines->Add(
"\tNo remembered connections");
else {
ShowMessage("Error");
return;
}
}
if (Res == 0) {
char Buff[512];
Size = sizeof(Buff);
for (DWORD i=0;i<Count;i++) {
Res = WNetGetConnection(
NR[i].lpLocalName, Buff, &Size);
S = String(NR[i].lpLocalName) +
"\t" + NR[i].lpRemoteName;
if ((Res == ERROR_CONNECTION_UNAVAIL))
S = S + "\t" + " (Not connected)";
if (Res == 0)
S = S + "\t" + " (Connected)";
Memo1->Lines->Add(S);
}
}
// Close the enumeration.
WNetCloseEnum(EnumHandle);
}