EPS Home


6605 Cypresswood Dr., Suite 300
Spring, TX 77379
Voice: (+1) 832-717-4445
Fax: (+1) 832-717-4460
Email: info@eps-software.com

 


The sometimes connected nature of computers means that we need to build software to work smoothly as networks connect and disconnect. This article shows how you can build network-aware software using the Network Location Awareness APIs.

The importance of enabling your software to identify the current network connections and change behavior has never been greater, as more people use laptops, notebooks, and Tablet PCs. With the growth of connectivity and online technology come applications that require network connectivity for their supported features, ranging from regular scans for software updates to online data synchronization. Applications that call through to Web services and utilize Internet technologies are becoming commonplace.

As these applications run on Mobile PCs, it is important that they can cope with joining and disconnecting from networks.

"
The importance of enabling your software to identify the current network connections and change behavior has never been greater.
"

There are three states with which an application needs to cope:

  1. There is no network connection.
  2. There is a single network connection.
  3. There are multiple network connections.

A well-behaved software application needs to be able to transition smoothly among these three states. These transitions occur in four possible scenarios:

  1. A network connection becomes available when there was not one previously.
  2. A new network connection becomes available when one (or more) was already available.
  3. A network connection becomes unavailable (drops out), leaving no connections.
  4. A network connection becomes unavailable, leaving one (or more) connections.

In an ideal world, the end user should not have to interact when network transitions occur. It is often good practice to provide some non-obtrusive feedback to the user to identify the current state.

Current Networks

A good first step is to work out what current network connections are available to the application. The NLA (Network Location Awareness) API provides three methods to help identify the current connections:

  • WSALookupServiceBegin: Initiates iterating through the currently connected networks
  • WSALookupServiceNext: Accesses the next available network (or lets the user know that there are no more network connections)
  • WSALookupServiceEnd: Finishes iterating through the networks.

As these methods are part of the Windows Sockets API, you need to call the WSAStartup method to initialise the process for using the Windows Sockets Library. You must call the WSACleanup method when you have finished using the library.

The WSALookupServiceBegin and WSALookupServiceNext methods both use a structure called a WSAQUERYSET. It is used in the WSALookupServiceBegin method to constrain or filter the results returned. The details for each network connection are then returned in a WSAQUERYSET structure from the WSALookupServiceNext method.

Doing this in managed code (C# in this example) requires that you use the Platform Invoke (P/Invoke) to import the Win32 API functions.

Listing 1 shows the code needed to define the structures required in C#. Once defined, the methods can be called to build a list of currently connected networks.

The WSALookupServiceBegin method creates a dataset of the current connections and the WSALookupServiceNext iterates through each network in that dataset. Finally, calling WSALookupServiceEnd indicates that you have finished with that dataset and that you no longer need it maintained.

Listing 2 shows a method that returns an ArrayList of the name of each network connection. Of course, you will probably want more information than just the name of the network. You may notice that in this code listing, a large block of memory is allocated to marshal the network information structure. Later, you’ll read how to extract more information about each network and how to allocate the correct amount of memory for the returned information.

Knowing the Network Changes

Now that you have an idea of how to get a list of the current network connections, it would be great if your application knew when the connections changed. A dirty solution would be to poll and keep requesting the current changes. This is not a good idea. Most of the devices that use this code are Mobile PCs, and power consumption is an issue for Mobile PCs (see my other article in this issue on Power Management), so polling is not a good solution.

"
The WSALookupServiceBegin method creates a dataset of the current connections.
"

What you need is some form of notification. The NLA API provides a function called WSANSPIoctl that provides what you need. The WSANSPIoctl function can be used to block the current thread until a change in the currently connected networks has transpired. The function requires that you first call WSALookupServiceBegin to build a list of the current connections. You can then call WSANSPIoctl, which can be used to block the current thread until there is a change in that list. The code in Listing 3 demonstrates this in the WaitForNetworkChanges method. As this method is going to block until there is a change, run this method in a background worker thread.

Digging Deeper into the Network Information

The last field in the WSAQUERYSET structure is a pointer to some binary data. This binary data contains more information about the network connection and is not of a fixed size. It can contain a variable amount of information depending on what features of the network are currently supported. This is why, in the first example, I simply allocated a large block of memory. This is obviously not the best thing to do here.

Looking in the MSWSock.h file, you can discover that the NLA_BLOB structure is represented as a structure with nested structures and a union of other structures.

In order to make your life easier, I have represented the NLA_BLOB structure as a number of C# classes and structures in Listing 4. You can see that in order to represent the union, I have used the LayoutKind.Explicit and the FieldOffset attributes to indicate to the CLR exactly how I expect the memory to be laid out.

To extract the data from the pointer field in the WSAQUERYSET, it will be necessary to pin the memory so that it can be mapped between the managed and unmanaged code. In C#, this means that you need to mark your code as unsafe. This needs to be done around the code blocks and for the whole project from the build configuration in the project properties.

To retrieve the network information, you need to start by marshalling the last field (a pointer) in the WSAQUERYFIELD to a BLOB object, as defined in Listing 4.

protected BLOB GetBlob(WSAQUERYSET qsResult)
{
    BLOB blob = new BLOB();
    Marshal.PtrToStructure(qsResult.lpBlob, blob);
    return blob;
}

You can then use the pInfo field in the BLOB class to marshal the data into an instance of an NLA_Info class and extract the data, which is shown in the two methods in Listing 5.

"
The WSANSPIoctl function can be used to block the current thread until a change in the currently connected networks has transpired.
"

Remember that the NLA_Info object contains a nextOffset field within the NLA_Header structure. This nextOffset field indicates the byte offset to the next NLA_Info object in memory. In order to read out all of the network information for a specific connection, you will need to repeat the process for each object, as shown here.

BLOB blob = GetBlob(qsResult);
byte* pInfo = (byte*)blob.pInfo;
NLA_Info info;
do
{
    info = SetNLAInfo(new IntPtr(pInfo));
                
    pInfo += info.header.nextOffset;

}while (0 != info.header.nextOffset);

In the first method you examined (Listing 2), a large block of memory was allocated in order to read in the network information. This is obviously not a good practice. You can refine that now by making two calls to the WSALookupServiceNext function. In the first call, pass in an empty buffer and a null pointer to the WSAQUERYSET parameter. Doing this causes the WSALookupServiceNext function to return the size of the buffer required for the network information. You can then use this to allocate the correct amount of memory and begin extracting the details of the network connection, which is done in Listing 6.

If you have put all of this together, you should now be retrieving some information about the network connections currently available to your application. There is one more step you can take that provides you with a fuller set of information. The first call to WSALookupServiceBegin uses the second parameter (dwControlFlags) to determine what information to return. The code thus far has used the LUP_RETURN_ALL flag that returns all the known information about the currently connected networks. You can combine this with the LUP_DEEP flag to force the network connections to be queried further. Be aware that you should only use this flag when calling WSALookupServiceBegin and not on subsequent calls to WSALookupServiceNext. Using the LUP_DEEP flag creates network traffic and forces the current set of networks to be rebuilt, and doing this on a call to WSALookupServiceNext causes a problem as the set of networks you are iterating through will have changed. The code snippet below demonstrates using the flags for the WSALookupServiceBegin and then changing the flags back before iterating through the collection of networks.

dwControlFlags = 0x0FF1;//LUP_RETURN_ALL|LUP_DEEP

int nResult = 
    WSALookupServiceBegin(qsRestrictions,
        dwControlFlags, ref valHandle);

if (0 !=nResult)
{
    CheckResult(nResult);
}

dwControlFlags = 0x0FF0; 
while (0 == nResult)
{. . .

Conclusion

In this article, you have learned how to import the unmanaged NLA functions from the Windows Sockets Library. You have also explored how to use the various unmanaged structures that are required to use those functions. You discovered how to become aware of changes to the network connections, and you can now retrieve extra information on the connected networks by marshalling the unmanaged structures into managed classes and structures.

"
The end user should not have to interact when the network transitions occur.
"

You can now build applications that are aware of the current connections and the features of those connections. This should enable you to create more valuable software that provides a better user experience.

Neil Roodyn



Listing 1 Defining the NLA methods and structures in C#
    [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);


Listing 2 Get the current network connections
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 dwBuffer = = 0x10000; 
        IntPtr pBuffer = Marshal.AllocHGlobal(dwBuffer);
                
        WSAQUERYSET qsResult = new WSAQUERYSET() ;
                    
        result = WSALookupServiceNext(valHandle, dwControlFlags, 
                ref dwBuffer, pBuffer);
        
        if (0==result)
        {
            Marshal.PtrToStructure(pBuffer, qsResult);
            networkConnections.Add(
                qsResult.szServiceInstanceName);
        }
        else
        {
            CheckResult(result);
        }
        Marshal.FreeHGlobal(pBuffer);
    }       

    result = WSALookupServiceEnd(valHandle);

    return networkConnections;
}


Listing 3 Waiting for a change in connected networks
[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 );

   
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 = 2281701401; //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);
}


Listing 4 Classes and structures for more network details

    [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;
    } 


Listing 5 Extracting the extra network Information
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;
    }
}


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;
}


Listing 6 Allocating the correct amount of memory
Int32 bufferSize = 0;
WSAQUERYSET qsResult = new WSAQUERYSET() ;
IntPtr pqsResult = new IntPtr();
        
result = WSALookupServiceNext(valHandle, dwControlFlags, 
    ref bufferSize, pqsResult);
        
if (0!=result)
{
    pqsResult = Marshal.AllocHGlobal(bufferSize);
    result = WSALookupServiceNext(valHandle, dwControlFlags, 
        ref bufferSize, pqsResult);
    if (0==result)
    {
        Marshal.PtrToStructure(pqsResult, qsResult);
        //get network details
        
    }
    else
    {
        CheckResult(result);
    }
    Marshal.FreeHGlobal(pqsResult);
}


 

 

 

By: Neil Roodyn

Dr. Neil is an independent itinerant consultant, trainer, and author. He travels the world working with software companies. Dr. Neil loves Australia, where he spends the summer enjoying the Sydney lifestyle and helping software development teams get more productive. Dr. Neil spends his other summer each year flying between northern Europe and the USA working with software teams and writing about his experiences.

Neil brings his business and technical skills to the companies he works with to ensure he has happy customers.

You can find out more from his Web site http://www.Roodyn.com.

Fast Facts

There are three states with which an application needs to cope: 1. There is no network connection. 2. There is a single network connection. 3. There are multiple network connections.


Otto Berkes

Architect, Ultra-Mobile PC, Microsoft Corporation

The biggest change in my mobile computing over the past year has been due to my adoption of WWAN. It’s pretty amazing to be able to stay in touch and have full Internet access anywhere I go. I don’t think about hot spots any more—the hot spot is wherever I go.


Eliot Graff

Technical Editor, Developer Experience, Mobile Platforms Division, Microsoft Corporation

It’s absolute poetry when an application seamlessly connects, disconnects, synchs, and performs.


Mark Hopkins

Programmer Writer, Mobile Platforms Division, Microsoft Corporation

For me, the killer of the Tablet PC application is its mobility. Applications that gracefully handle the presence or absence of a network connection—and act accordingly—will always get higher consideration from me.