Network Awareness in Windows XP
This topic introduces using the Network Location Awareness (NLA) API from managed code. NLA allows applications to identify current logical network connections and be notified of changes to the current network connections.
Summary
This topic provides the information you need to enable your Microsoft .NET Framework application to detect if there is one or more network connections present, and how you can find out more about the connected networks, such as the names and speed of connection, or whether the network supports internet connectivity. This topic also provides the techniques you'll need to enable your application to receive alerts of changes to the current network status.
Note The method for obtaining network awareness information listed in this topic has been changed starting with Windows Vista and through Windows 7 with the NLM API set. For more information about the NLM API set, see Network Awareness in Windows Vista and Windows 7. The methods described for Windows XP will be deprecated at a future date. For your application to support both Windows XP and subsequent Windows versions, it should use the method in this topic, but when the application runs on Windows Vista and later versions it should use the NLM API set.
The "Sometimes Connected" Way of Working
Applications running on portable computers must take into account the fact that they will not always have a network connection available to them. Many users now use networks at home as well as at work, and your application may want to distinguish between the two and behave differently. Many mobile PCs support multiple modes of network access, including wireless network connectivity, Ethernet ports, and even Bluetooth capability. This fact, in combination with connections to Virtual Private Networks (VPNs), means that a mobile PC may have multiple network connections open at the same time. Your application may need to know this and connect through the most appropriate available network, or otherwise respond intelligently. For example, your application should not repeatedly attempt to connect to the internet if no available network connection supports internet connectivity.
The advent of wireless networks requires that notebook and Tablet PC applications support being sometimes connected (also called occasionally connected). As users walk around their office with a Tablet PC, you want your application to seamlessly carry on working for them as they enter and leave networks.
There are three states that a mobilized application must operate within:
- There is no network connection.
- There is a single network connection.
- There are multiple network connections.
A well-behaved software application needs to be able to transition smoothly between these three states. These transitions create four possible scenarios:
- A network connection becomes available when there was not one previously available.
- A new network connection becomes available when one (or more) was already available.
- The network connection disappears (drops out), leaving no connections.
- A network connection disappears, leaving one or more connections.
Ideally, handling these scenarios should not involve any user interaction. It may, however, be important to provide some visual indication to the user that a connectivity event has occurred.
Certain features of your application may only be available when a network connection is present. You may only want some features available when a particular network connection (such as a VPN) is available.
Consider a couple of situations where an application can benefit from being aware of network location.
In the first case imagine Bob, a tech-support person in a large company. Bob has a notebook computer, and he is constantly moving between floors and between office buildings in the same corporate park. Bob has a couple of applications open on his computer the whole time: his e-mail application and an issue reporting tool to report the fixes he has made. Both applications require access to the company network. When Bob moves throughout the company's offices, sometimes he can't get network access; for example, in the warehouse there is no wireless network access. Even so, Bob needs to be able to answer e-mail messages and add reports to the system on fixes he has made.
Bob's e-mail application should download and upload messages between the mail server and his laptop whenever Bob is connected, but a message that hasn't completely transferred when connectivity disappears should not be lost. The e-mail application needs to cope with aborted message transfers.
Similarly, Bob's issue reporting tool connects to a database on a company server. The database contains all the records of technical issues and fixes made by Bob and his colleagues on the tech-support team. This system is the way most of Bob's work is routed to him. He collects an open issue from the issue reporting tool and gets the details from the database; he then goes to the suspected location of the fault to fix it. When Bob leaves a location that has network connectivity, the reporting application keeps the last fifty records Bob was looking at in a local cache. Bob can view and update these records locally on his laptop. The next time the application detects a network connection, it attempts to synchronize all of Bob's changes with the database. If the network connection drops out, any records that didn't get fully synchronized are cached until a connection is detected again.
Bob wants his applications to:
- Roam between wireless networks in his company seamlessly.
- Gracefully handle network disconnections and reconnections.
- Not interrupt his work when the network status changes.
The second situation is that of Jenny, who works for an insurance company and spends most of her time out of the office, usually only popping into the office for Monday morning meetings. She sells insurance and visits claim sites to help her customers make claim reports. Jenny has a Tablet PC and is more often disconnected than she is connected. Jenny relies on two applications for her day to day work: an e-mail application, and an application that contains all the forms her insurance company uses. The e-mail application will connect and upload or download e-mail messages whenever it detects an internet connection. Jenny will often stop at a café at lunch time to pick up and answer e-mail messages. The company's forms application will only connect to the company server when Jenny is connected through a VPN or directly on the company network. Jenny has VPN access set up at home where she has a fast broadband connection, but the connection speed in the cafés she visits for lunch tend to be slow with the company VPN, so she doesn't even bother trying to connect during the day.
In this second situation, the insurance company application detects when it has a connection to a known network before attempting to synchronize the forms Jenny has filled in for her customers. The e-mail application, on the other hand, is less particular, and as soon as the application detects any network connection, it attempts to synchronize Jenny's e-mail with the company mail server.
Jenny wants her application to:
- Hide the features that are unavailable when she is not connected to the corporate network.
- Use the best connection available to download and upload e-mail messages.
- Not interrupt her work.
An application that is only sometimes connected should attempt to provide the user with continuous operation of the application as the mobile PC connects and disconnects from networks. Features of the application that require network access could be hidden from the user or work on a local cache of the data. Either way, the network features should not degrade the user's experience with the application.
Querying the Currently Connected Networks
As a first step to providing seamless network support in your application, it is useful to know which networks the application can currently access.
The .NET Framework does not provide this information inherently. In order to get this information, your code must call native Win32 methods.
Version 2 of the Microsoft Windows Sockets Library that is shipped with the Win32 Platform SDK has a number of functions that you can use to detect the currently connected networks. They are:
- WSALookupServiceBegin, which initiates iteration through the currently connected networks.
- WSALookupServiceNext, which gets the next available network (or lets you know that there are no more network connections).
- WSALookupServiceEnd, which finishes iteration through the networks.
Before these methods can be successfully invoked, you must make a call to the WSAStartup method to initialize the process for using the Windows Sockets Library. You should also remember to call the WSACleanup method when you have finished using the library.
You can create a simple loop to iterate through the currently available networks using these functions. The pseudo code looks like this:
WSAStartup
WSALookupServiceBegin;
Do
WSALookupServiceNext
While there are more connected networks
WSALookupServiceEnd
WSACleanup
To do this in C#, you use Platform Invoke (commonly known as P/Invoke) and two structures. The WSAData structure is used by the WSAStartup method to receive information about the implementation of the Windows Sockets library. You also must define the WSAQUERYSET structure that is used by both the WSALookupServiceBegin and the WSALookupServiceNext functions. In the example code that follows, you can see these structures defined as a C# classes; this is done because a C# class is always passed by reference, making the call to the methods simpler. Note that in the WSAStartup method, the second parameter doesn't have to be marked as an [out] parameter or use the ref keyword. The second parameter is simply an instance of a WSAData class:
[StructLayout(LayoutKind.Sequential)]
public class WSAData
{
public Int16 wVersion;
public Int16 wHighVersion;
public String szDescription;
public String szSystemStatus;
public Int16 iMaxSockets;
public Int16 iMaxUdpDg;
public IntPtr lpVendorInfo;
}
[ StructLayout( LayoutKind.Sequential, CharSet=CharSet.Auto )]
public class WSAQUERYSET
{
public Int32 dwSize = 0;
public String szServiceInstanceName = null;
public IntPtr lpServiceClassId;
public IntPtr lpVersion;
public String lpszComment;
public Int32 dwNameSpace;
public IntPtr lpNSProviderId;
public String lpszContext;
public Int32 dwNumberOfProtocols;
public IntPtr lpafpProtocols;
public String lpszQueryString;
public Int32 dwNumberOfCsAddrs;
public IntPtr lpcsaBuffer;
public Int32 dwOutputFlags;
public IntPtr lpBlob;
}
[DllImport("Ws2_32.DLL", CharSet = CharSet.Auto,
SetLastError=true)]
private extern static
Int32 WSAStartup( Int16 wVersionRequested, WSAData wsaData);
[DllImport("Ws2_32.DLL", CharSet = CharSet.Auto,
SetLastError=true)]
private extern static
Int32 WSACleanup();
[DllImport("Ws2_32.dll", CharSet = CharSet.Auto,
SetLastError=true)]
private extern static
Int32 WSALookupServiceBegin(WSAQUERYSET qsRestrictions,
Int32 dwControlFlags, ref Int32 lphLookup);
[DllImport("Ws2_32.dll", CharSet = CharSet.Auto,
SetLastError=true)]
private extern static
Int32 WSALookupServiceNext(Int32 hLookup,
Int32 dwControlFlags,
ref Int32 lpdwBufferLength,
IntPtr pqsResults);
[DllImport("Ws2_32.dll", CharSet = CharSet.Auto,
SetLastError=true)]
private extern static
Int32 WSALookupServiceEnd(Int32 hLookup);
With these methods defined, you call them as shown in the following example code. This code snippet gets the name of each network and places it in a list:
public virtual ArrayList GetConnectedNetworks()
{
ArrayList networkConnections = new ArrayList();
WSAQUERYSET qsRestrictions;
Int32 dwControlFlags;
Int32 valHandle = 0;
qsRestrictions = new WSAQUERYSET();
qsRestrictions.dwSize = Marshal.SizeOf(typeof(WSAQUERYSET));
qsRestrictions.dwNameSpace = 0; //NS_ALL;
dwControlFlags = 0x0FF0; //LUP_RETURN_ALL;
int result = WSALookupServiceBegin(qsRestrictions,
dwControlFlags, ref valHandle);
CheckResult(result);
while (0 == result)
{
Int32 dwBufferLength = 0x10000;
IntPtr pBuffer = Marshal.AllocHGlobal(dwBufferLength);
WSAQUERYSET qsResult = new WSAQUERYSET() ;
result = WSALookupServiceNext(valHandle, dwControlFlags,
ref dwBufferLength, pBuffer);
if (0==result)
{
Marshal.PtrToStructure(pBuffer, qsResult);
networkConnections.Add(
qsResult.szServiceInstanceName);
}
Marshal.FreeHGlobal(pBuffer);
}
result = WSALookupServiceEnd(valHandle);
return networkConnections;
}
You will notice that in the previous GetConnectedNetworks
code, a large buffer of memory is allocated to read in the WSAQUERYSET structure (a class in the managed code). This is not overly efficient, and in later in this article, you will discover how to allocate only the amount of memory required.
The data in the allocated memory buffer is then marshalled into the WSAQUERYSET class. This allows for the variation in the amount of data being received. Later in this article, you will discover how to use the extra data that is returned. For the moment, only the name of the connected network is utilized.
Your mobilized application should get a list of the currently connected networks when it first starts up. The application can then enable or disable the features that require network access, along with other behavioral modifications the application should make based on connectivity, such as accessing a local data cache.
You can then write the code that will change the application's behavior when the current network connections change. In order to make the changes, your code must be notified of changes to the available networks. The following topic shows how to receive notifications when a change occurs to the currently connected networks.
Receiving Notifications When Things Change
The previous code shows you how to determine the networks that are currently available. You can then adjust your application to behave in an appropriate way. Of course, you will want to be notified of any changes to the state of network connectivity. You do this by polling for the currently connected networks. One way to do this is to create a worker thread that rebuilds the list of currently connected networks, but there is a better way of doing this.
There is another function in the Windows Sockets Library that can be called to find out if anything has changed since the last call to WSALookupServiceBegin. The WSANSPIoctl function is used to return immediately and provide information as to whether there has been a change to the connected networks. This method can be polled, and you can act upon any changes in the currently connected networks from within your code.
A better solution exists by using the same WSANSPIoctl function as a blocking method that only returns when a change has occurred. You can use this in a worker thread that then calls a delegate method in your code.
The sample code that follows demonstrates this by creating an event that gets fired whenever there is a change in the network connections:
[DllImport("Ws2_32.dll", CharSet = CharSet.Auto,
SetLastError=true)]
private extern static Int32 WSANSPIoctl(
Int32 hLookup,
UInt32 dwControlCode,
IntPtr lpvInBuffer,
Int32 cbInBuffer,
IntPtr lpvOutBuffer,
Int32 cbOutBuffer,
ref Int32 lpcbBytesReturned,
IntPtr lpCompletion );
public delegate void NetworkChangedEventHandler(object sender,
NetworkChangedEventArgs e);
private Int32 monitorLookup = 0;
protected void WaitForNetworkChanges()
{
WSAQUERYSET qsRestrictions = new WSAQUERYSET();
Int32 dwControlFlags;
qsRestrictions.dwSize = Marshal.SizeOf(typeof(WSAQUERYSET));
qsRestrictions.dwNameSpace = 0; //NS_ALL;
dwControlFlags = 0x0FF0; //LUP_RETURN_ALL;
int nResult = WSALookupServiceBegin(qsRestrictions,
dwControlFlags, ref monitorLookup);
Int32 dwBytesReturned = 0;
UInt32 cCode = 0x88000019; //SIO_NSP_NOTIFY_CHANGE
nResult = WSANSPIoctl(monitorLookup, cCode,
new IntPtr(), 0, new IntPtr(), 0,
ref dwBytesReturned,
new IntPtr());
if (0 != nResult)
{
CheckResult(nResult);
}
nResult = WSALookupServiceEnd(monitorLookup);
monitorLookup=0;
}
public event NetworkChangedEventHandler NetworkChanged;
protected void NetworkMonitor()
{
int nResult = 0;
while (0 == nResult)
{
WaitForNetworkChanges();
ArrayList networks = GetConnectedNetworks();
NetworkChangedEventArgs eventArgs =
new NetworkChangedEventArgs(networks);
try
{
if (null != NetworkChanged)
{
NetworkChanged(this, eventArgs);
}
}
catch (Exception ex)
{
Console.Out.WriteLine(ex.ToString());
}
}
}
public virtual void StartNetworkMonitor()
{
monitorThread = new Thread(new
ThreadStart(this.NetworkMonitor) );
monitorThread.Name = "Network Monitor";
monitorThread.IsBackground = true;
monitorThread.Start();
}
To stop the thread from running, this code call Abort on the thread. In your application, you may wish to call Interrupt and use an exception handler to catch a ThreadInterruptedException. The call to WSALookupServiceEnd forces the WSANSPIoctl function to return.
public virtual void StopNetworkMonitor()
{
if (monitorThreadRunning.WaitOne(0, true))
{
monitorThreadRunning.Reset();
monitorThread.Abort();
WSALookupServiceEnd(monitorLookup);
}
}
The NetworkChangedEventArgs
used in the previous code is a simple class that contains an ArrayList of the networks that are currently connected.
public class NetworkChangedEventArgs : EventArgs
{
private ArrayList networkList;
public NetworkChangedEventArgs(ArrayList networks)
{
networkList = networks;
}
public ArrayList Networks
{
get{ return networkList; }
}
}
The NetworkMonitor
method in the example code shown has an endless loop. In production code, you would want to use a thread synchronization flag to end the thread when the application is closing down.
Validating the Application
One of the issues to address when writing network awareness code is how to test the methods. Manually, it is quite easy to pull the cable out of your PC and then plug it back in again. A good test technique is to have a secondary network connection through a USB adaptor. You can then set this up so it is on your desk and you can easily unplug and plug it in to test your implementation of the methods discussed.
Most modern applications require that there be some form of automated and unit testing. It is recommended that you encapsulate the NLA components into a class. You can then create a mock class that presents the same interface as the NLA class and use the mock NLA class in your unit tests.
In the full sample, the NLA methods are encapsulated within an NLA class. A mock version of this NLA class can then be defined as follows. Notice the extra two methods, Setup
and ChangeNetworks
, which exist to allow the test code to manipulate the networks:
public class NLAMock : NLA
{
private ArrayList networkConnections;
public NLAMock()
{
networkConnections = new ArrayList();
}
public void Setup(ArrayList networks)
{
networkConnections = networks;
}
public void ChangeNetworks(ArrayList networks)
{
networkConnections = networks;
NetworkChangedEventArgs eventArgs =
new NetworkChangedEventArgs(networks);
try
{
NetworkChanged(this, eventArgs);
}
catch (Exception ex)
{
Console.Out.WriteLine(ex.ToString());
}
}
public override ArrayList GetConnectedNetworks()
{
return networkConnections;
}
public override void StartNetworkMonitor()
{}
public override void StopNetworkMonitor()
{}
public override event
NetworkChangedEventHandler NetworkChanged;
}
Using the knowledge gained so far, it is possible to write an application that can perform network operations when a network is present and can also cope with a disconnected state. Such an application must handle the four scenarios mentioned at the beginning:
- A network connection becomes available when there was not one previously available.
- A new network connection becomes available when one (or more) was already available.
- The network connection disappears (drops out), leaving no connections.
- A network connection disappears, leaving one or more connections.
The Data Structures
In the beginning of this article, you discovered that a WSAQUERYSET object that returns from the WSALookupServiceNext method contains the name of the network. This WSAQUERYSET object also contains other useful information.
The Windows Sockets Library presents most of the interesting information in variable sized data blobs. This is not particularly helpful for managed code developers. Even less helpful is the use of complex structures nested in other structures and in union with other complex structures. The NLA_BLOB
structure is represented as follows:
typedef struct _NLA_BLOB {
struct {
NLA_BLOB_DATA_TYPE type;
DWORD dwSize;
DWORD nextOffset;
} header;
union {
// header.type -> NLA_RAW_DATA
CHAR rawData[1];
// header.type -> NLA_INTERFACE
struct {
DWORD dwType;
DWORD dwSpeed;
CHAR adapterName[1];
} interfaceData;
// header.type -> NLA_802_1X_LOCATION
struct {
CHAR information[1];
} locationData;
// header.type -> NLA_CONNECTIVITY
struct {
NLA_CONNECTIVITY_TYPE type;
NLA_INTERNET internet;
} connectivity;
// header.type -> NLA_ICS
struct {
struct {
DWORD speed;
DWORD type;
DWORD state;
WCHAR machineName[256];
WCHAR sharedAdapterName[256];
} remote;
} ICS;
} data;
}
The first nested structure is the header, which contains an NLA_BLOB_DATA_TYPE
that identifies the structures in the union that contain data in this instance of a BLOB. The NLA_BLOB_DATA_TYPE
is an enumerated type:
typedef enum _NLA_BLOB_DATA_TYPE {
NLA_RAW_DATA = 0,
NLA_INTERFACE = 1,
NLA_802_1X_LOCATION = 2,
NLA_CONNECTIVITY = 3,
NLA_ICS = 4,
}
For the sake of brevity, this article will focus on the NLA_INTERFACE
and NLA_CONNECTIVITY
types. Exploring these two nested structures will teach you everything you need to know in order to use the other structures. These two structures include value data types, strings, and enumerated data types.
Before learning how to decode this structure in C#, here is a definition of the enumerated types we will want to use:
typedef enum _NLA_CONNECTIVITY_TYPE {
NLA_NETWORK_AD_HOC = 0,
NLA_NETWORK_MANAGED = 1,
NLA_NETWORK_UNMANAGED = 2,
NLA_NETWORK_UNKNOWN = 3,
}
typedef enum _NLA_INTERNET {
NLA_INTERNET_UNKNOWN = 0,
NLA_INTERNET_NO = 1,
NLA_INTERNET_YES = 2,
}
You have probably spotted that the NLA_BLOB
structure is not a fixed size, so the different nested structures in the union may take up different amounts of memory. There are two further complications to work with here. First, there can be a number of these NLA_BLOB
structures linked together in memory, with the nextOffset
value in the header providing the only mechanism to get to the next structure. Secondly, the types in the union do not match; the managed code-default marshalling system has an issue with handling unions that contain both value types and reference types. You can find out more about this topic in the Unions Sample on MSDN.
In order to start extending the example given earlier in this article, you must define managed code structures and classes that map to the structures in the Windows Sockets Library. In order to clarify the naming of the classes, the unmanaged BLOB
maps to the BLOB
class and the NLA_BLOB
maps to the NLA_Info
class. The nested structures used in this example are then each explicitly defined. In C#, the way to define a union of these nested structures in the NLA_Info
class is to use the Explicit and the FieldOffsetAttribute keywords:
[StructLayout(LayoutKind.Sequential, Pack=4)]
public class BLOB
{
public UInt32 cbSize;
public IntPtr pInfo;
}
[ StructLayout( LayoutKind.Explicit, Pack=4 )]
public class NLA_Info
{
[ FieldOffset( 0 )]
public NLA_Header header;
[ FieldOffset( 12 )]
public NLA_InterfaceData interfaceData;
[ FieldOffset( 12 )]
public NLA_Connectivity connectivity;
}
[StructLayout(LayoutKind.Sequential, Pack=4)]
public struct NLA_Header
{
public UInt32 type;
public UInt32 size;
public UInt32 nextOffset;
}
[StructLayout(LayoutKind.Sequential, Pack=4)]
public struct NLA_InterfaceData
{
public UInt32 type;
public UInt32 speed;
public IntPtr adapterName;
}
[StructLayout(LayoutKind.Sequential, Pack=4)]
public struct NLA_Connectivity
{
public UInt32 type;
public UInt32 internet;
}
To use these structures in C#, you must mark your project to Allow Unsafe Code Blocks. This can be done from the Build Configuration properties tab in the Project Properties window. Unsafe code blocks will be required to walk through the memory.
If you are not comfortable with how pointers work and memory is allocated, then consider finding another developer who can work through this with you and ensure that the logic is correct and no memory leaks have been introduced.
The Network Class
It is a good idea to create a class that will encapsulate the additional information about the network connections. Let's call this class Network
. Within the Network
class, three enumerations are defined that map to enumerated types in the Windows Sockets Library. You can use these to discover features of the network:
The NLA_CONNECTIVITY_TYPE
maps to the Windows Sockets enumerated type of the same name, and contains information on the type of network.
The NLA_INTERNET
enumeration will be used to determine if the network connection supports internet connectivity. NLA_INTERNET
also maps to the enumerated structure of the same name in the Windows Sockets library.
The NLA_DATA_TYPE
also maps to the unmanaged enumerated type in the Windows Socket library, and is used to determine the type of data available in the structure received from the Windows Sockets Library.
public enum NLA_CONNECTIVITY_TYPE
{
NLA_NETWORK_AD_HOC = 0,
NLA_NETWORK_MANAGED = 1,
NLA_NETWORK_UNMANAGED = 2,
NLA_NETWORK_UNKNOWN = 3,
}
public enum NLA_INTERNET
{
NLA_INTERNET_UNKNOWN = 0,
NLA_INTERNET_NO = 1,
NLA_INTERNET_YES = 2,
}
public enum NLA_DATA_TYPE
{
NLA_RAW_DATA = 0,
NLA_INTERFACE = 1,
NLA_802_1X_LOCATION = 2,
NLA_CONNECTIVITY = 3,
NLA_ICS = 4,
}
The Network
class has two methods that do most of the work in extracting the extra information about the network connection. The first, the AddInfo
method, extracts information from an instance of an NLA_Info
class, as shown here:
protected void AddInfo(NLA_Info info)
{
NLA_DATA_TYPE type = (NLA_DATA_TYPE)info.header.type;
switch (type)
{
case NLA_DATA_TYPE.NLA_INTERFACE:
this.speed = info.interfaceData.speed;
break;
case NLA_DATA_TYPE.NLA_CONNECTIVITY:
this.internet =
(NLA_INTERNET)info.connectivity.internet;
this.connectionType =
(NLA_CONNECTIVITY_TYPE)info.connectivity.type;
break;
}
}
Next, the SetNLAInfo
method creates an NLA_Info
object from a memory pointer and uses the previous method to extract the information. Notice that this code is marked as unsafe and uses a pointer offset to extract the adaptor name from the IntPtr within the NLA_INTERFACE
structure. You do this because it is not possible to overlap value types and reference types in a managed class. The union you implemented in the NLA_Info
class cannot contain reference types and so you can only retrieve a pointer to the string containing the adaptor name:
unsafe public NLA_Info SetNLAInfo(IntPtr pInfo)
{
NLA_Info info = new NLA_Info();
try
{
Marshal.PtrToStructure(pInfo, info);
AddInfo(info);
if (NLA_DATA_TYPE.NLA_INTERFACE ==
(NLA_DATA_TYPE)info.header.type )
{
byte* p = (byte*)pInfo;
p += 20;
adaptor = Marshal.PtrToStringAnsi(new IntPtr(p));
}
}
catch(Exception ex)
{
Debug.WriteLine(ex.Message);
}
return info;
}
To provide the Network
class with the pInfo IntPtr
to the NLA_Info
(an unmanaged NLA_BLOB), you must add some code to extract the pointer from the WSAQUERYSET data structure returned. First, get the BLOB
structure from the WSAQUERYSET. In the sample code this is done in the GetBlob
method that is a member of the NLA
class:
protected BLOB GetBlob(WSAQUERYSET qsResult)
{
BLOB blob = new BLOB();
Marshal.PtrToStructure(qsResult.lpBlob, blob);
return blob;
}
This GetBlob
method can then be used to retrieve the pointer to the NLA_Info
information and fill an instance of a Network
class. The GetNetworkDetails
method in the NLA
class uses the WSAQUERYSET object to extract the BLOB. The pInfo
pointer of the BLOB
is then used to set the information in the network. The NLA_Info
information is contained as a linked list in memory so the do-while loop iterates through each of the structures in the linked list:
unsafe protected Network GetNetworkDetails(WSAQUERYSET qsResult)
{
Network network =
new Network(qsResult.szServiceInstanceName ) ;
BLOB blob = GetBlob(qsResult);
byte* pInfo = (byte*)blob.pInfo;
NLA_Info info;
do
{
info = network.SetNLAInfo(new IntPtr(pInfo));
pInfo += info.header.nextOffset;
}while (0 != info.header.nextOffset);
return network;
}
In the first part of this article, the code allocated a large fixed block of memory to receive the WSAQUERYSET data that describes the current network connection. This is extremely inefficient and there is no guarantee that the amount of memory allocated is enough for the data being received.
So, let's change the way this method is called. In the following code, the first call to WSALookupServiceNext provides the amount of memory required in the bufferSize
variable. The bufferSize
is then used to allocate an unmanaged memory block. The call can then be made again with a pointer to the newly allocated memory. This newly allocated memory can then be marshalled into a WSAQUERYSET object.
The first sample was simply allocating a large amount of memory (64K!) in order to ensure that there was enough space to store the network details. This new code is only allocating what is required.
A call is then made to the GetNetworkDetails
method, previously discussed. That method returns a Network
object with the required information on the network:
while (0 == result)
{
Int32 bufferSize = 0;
WSAQUERYSET qsResult = new WSAQUERYSET() ;
IntPtr pqsResult = new IntPtr();
//get the required buffer size for the network information
result = WSALookupServiceNext(valHandle, dwControlFlags,
ref bufferSize, pqsResult);
if (0 != result && bufferSize > 0 )
{
pqsResult = Marshal.AllocHGlobal(bufferSize);
// find the next network in the data set
result = WSALookupServiceNext(valHandle, dwControlFlags,
ref bufferSize, pqsResult);
// if a valid network is returned
if (0==result)
{
Marshal.PtrToStructure(pqsResult, qsResult);
//get network details
Network network = GetNetworkDetails(qsResult);
networkConnections.Add(network);
}
Marshal.FreeHGlobal(pqsResult);
}
}
Forcing a Deeper Search
At this point, you have code that is capable of reading a set of information of interest about each network connection. It is also quite likely that the code doesn't actually retrieve all of the information you are looking for.
Earlier in this article you discovered that when calling WSALookupServiceBegin you pass a set of control flags. In the previous example the controls flags were set to 0x0FF0
, which is the LUP_RETURN_ALL flag. You may have assumed that this would return all the information available about all of the currently connected networks. This assumption is not correct.
There are number of these flags defined in the winsock2.h file, as shown here:
#define LUP_DEEP 0x0001
#define LUP_CONTAINERS 0x0002
#define LUP_NOCONTAINERS 0x0004
#define LUP_NEAREST 0x0008
#define LUP_RETURN_NAME 0x0010
#define LUP_RETURN_TYPE 0x0020
#define LUP_RETURN_VERSION 0x0040
#define LUP_RETURN_COMMENT 0x0080
#define LUP_RETURN_ADDR 0x0100
#define LUP_RETURN_BLOB 0x0200
#define LUP_RETURN_ALIASES 0x0400
#define LUP_RETURN_QUERY_STRING 0x0800
#define LUP_RETURN_ALL 0x0FF0
#define LUP_RES_SERVICE 0x8000
The LUP_RETURN_ALL flag will return some information about all of the networks but it will not walk through the information contained within each network connection structure. To get everything, you must combine the LUP_RETURN_ALL flag with the LUP_DEEP flag. That will force the data being retrieved to be rebuilt from the network drivers. Doing this causes network traffic to be generated, but it will provide a richer set of data.
It is important to know that using the LUP_DEEP flag should only be performed on the call to WSALookupServiceBegin; the subsequent calls to WSALookupServiceNext do not require the LUP_DEEP flag to be set. Making the call to WSALookupServiceNext with LUP_DEEP in the control flags is likely to cause an error, because the current data set being examined will be dropped from memory and then refreshed, losing track of where in the data set your code is currently pointing.
In order to clarify this point, the code needs to look something like this:
dwControlFlags = 0x0FF1; // LUP_RETURN_ALL | LUP_DEEP
int nResult = WSALookupServiceBegin(qsRestrictions,
dwControlFlags, ref valHandle);
if (0 !=nResult)
{
CheckResult(nResult);
}
dwControlFlags = 0x0FF0;
while (0 == nResult)
{. . .
Putting It All Together
You now have the ability to receive notifications, in your managed code, of changes in the network connections, and to encapsulate the information about the connected networks into a collection of Network
objects. Each object provides properties describing the network's features. The sample application shows the currently connected networks in a list box. Double clicking on one of the connections displays a dialog box with the details of the connection.
Conclusion
You have learned how to import the unmanaged functions from the Windows Sockets Library. You have also explored how to use the various unmanaged structures that are required to use those functions.
First you discovered how to use the .NET Framework event model to raise events when the network connections changed, and then you continued to explore the features of the NLA library. You saw how to retrieve extra information about the connected networks and encapsulate the information into a Network
class. Using that Network
class, you can now create application code that will carry out network operations when certain network features are available.
Related Topics
Power Management in Windows XP
Send comments about this topic to Microsoft
Build date: 2/8/2011