Поделиться через


Network Awareness on Windows Vista

Network Awareness on Windows Vista

Learn about writing a network-aware application on Windows Vista.

Network Awareness

Today's computing environment is more interconnected than ever before. This connectivity is only likely to increase in the future. Today's applications are used in many different scenarios:

  1. One stable connected network. A LAN connection to a desktop machine at a workplace is this scenario.
  2. One network that is intermittently connected and disconnected. A wireless user roaming around an office or a building or any wireless hotspot is in this scenario.
  3. Disconnected entirely. Ten years ago this was the common case. Today it's becoming increasingly rare, and often is only used when someone turns off their networking hardware in order to do their work without interruption or to save power.

Beyond the simple connected/disconnected state of a network, there are other network properties that are often relevant. The state of each of these properties can be used at decision points in your application to enable or disable features or show alternate user interface.

  1. Is this a specific network (by name or id)? Maybe your application needs to present different UI or different features when it's on a specific network. Maybe the network name is a good way to determine if features should be enabled or disabled.
  2. Is a particular resource (printer, network share, etc) available? You want to provide different options for printing or saving or opening a file or other network resource if the network is not available.
  3. Internet connectivity? Does your application pull data from the Internet?
  4. Intranet (domain) connectivity? Is this a managed network, often maintained by a workplace?
    • If so, am I authenticated by the domain?
  5. How fast is the network I'm connected to? Is there a faster network available with the necessary network properties?

Windows Vista introduces new interfaces that can be used to obtain detailed information about these network characteristics and more. With Windows Vista's INetworkListManager interface it is easy to enumerate all networks (INetwork) a machine has ever seen, or just the connected networks, or just the disconnected networks. The INetworkListManager interface also makes it easy to enumerate the network interfaces (INetworkInterface) on a machine.

The INetwork interface is used to determine the properties of a network connection: name, description, ID, managed/authenticated, connected/disconnected, and more. In Windows Vista it is possible that a single network is connected to several interfaces, so through an INetwork you can also enumerate the INetworkInterfaces being used.

The INetworkInterface interface tells you the relevant properties of an interface: ID, GUID, Type (managed, authenticated), and State (connected, disconnected, V4 Local, V4 Internet, V6 Local, V6 Internet). V4 Local means Internet Protocol version 4 (IPv4) local access. V4 Internet means IPv4 with internet access. V6 Local and V6 Internet mean IPv6.

Checking for current network conditions

Sometimes an application will not need to concern itself with the network conditions until a particular user event requires network connectivity. An example of this would be any application in which the user can create and save a document. Only when the document is saved is the network status important, and even then it's only important if the user chooses to save to a network location. In this situation the network only needs to be viewed after the OK button of the Save dialog is pressed with a network save path, or when the user browses to a network path inside the Save dialog.

The following code shows how to determine the characteristics of a network synchronously. It is important to keep in mind that you would not want to use this technique that's called in a tight loop, but it is fine to use this to determine the current conditions. The reason for this is that after enumerating through the networks you can keep your application aware of changes to the set of networks without re-enumerating. Code that walks through all networks or all connected networks really only needs to occur a single time, like on startup or initialization of your application. Subsequent network changes can be handled below, see Making your application network aware in real time.

First you need to obtain the INetwork you're interested in. You can get this by enumerating the networks using the INetworkListManager:

void FillNetworkList(INetworkListManager *pNetworkListManager, NP_ENUM_NETWORK EnumFlag)
{
    HRESULT hr = S_OK;

    IEnumNetwork *pEnumNetworks;
    INetwork *pNetwork;
    DWORD dwCountFetched;

    hr = pNetworkListManager->EnumNetworks(EnumFlag, &pEnumNetworks);
    if (SUCCEEDED(hr))
    {
        hr = pEnumNetworks->Next(1, &pNetwork, &dwCountFetched);
        while (S_OK == hr)
        {
            PrintNetworkDetail(pNetworkListManager, pNetwork);
            pNetwork->Release();
            hr = pEnumNetworks->Next(1, &pNetwork, &dwCountFetched);
        }

        pEnumNetworks->Release();
    }
}

With the above code, your options for EnumFlag are:

  • NP_ENUM_NETWORK_CONNECTED: connected networks only
  • NP_ENUM_NETWORK_DISCONNECTED: disconnected networks only
  • NP_ENUM_NETWORK_ALL: connected and disconnected networks

When you have obtained the INetwork *pNetwork, you can look into the properties of the network using the INetwork interface methods:

interface INetwork : IUnknown
{
    HRESULT GetName ([out, string] LPWSTR *ppszNetworkName);
    HRESULT GetDescription ([out, string] LPWSTR *ppszDescription);
    HRESULT SetDescription ([in, string] const LPWSTR pszDescription);
    HRESULT GetId ([out] GUID *pguidNetworkId);
    HRESULT Rename ([in, string] const LPWSTR pszNetworkNewName);
    HRESULT GetIcon ([out] DWORD *pdwBytes,
                     [out, size_is(, *pdwBytes)] BYTE **ppIconData);
    HRESULT SetIcon ([in] DWORD dwBytes,
                     [in, size_is(dwBytes)] const BYTE *pIconData);

    HRESULT GetType ([out] NP_NETWORK_TYPE *pNetworkType);
    HRESULT GetTimeCreatedAndConnected (
                 [out] DWORD *pdwLowDateTimeCreated,
                 [out] DWORD *pdwHighDateTimeCreated,
                 [out] DWORD *pdwLowDateTimeConnected,
                 [out] DWORD *pdwHighDateTimeConnected);

    HRESULT GetState ([out] NP_NETWORK_STATE *pfState);
    HRESULT EnumNetworkInterfaces (
                 [out] IEnumNetworkInterface **ppEnum);
}

GetType returns whether the network is managed and/or authenticated:

typedef enum tagNP_NETWORK_TYPE
{
    NP_NETWORK_MANAGED         = 0x01,
    NP_NETWORK_AUTHENTICATED   = 0x02
} NP_NETWORK_TYPE;

GetState returns connected or disconnected, as well as the Internet Protocol this network is using. Note that this is also how you determine internet connectivity:

typedef enum tagNP_NETWORK_STATE
{
    NP_NETWORK_CONNECTED              = 0x01,
    NP_NETWORK_DISCONNECTED           = 0x02,
    NP_NETWORK_CONNECTIVITY_V4_LOCAL     = 0x4,
    NP_NETWORK_CONNECTIVITY_V4_INTERNET  = 0x8,
    NP_NETWORK_CONNECTIVITY_V6_LOCAL     = 0x10,
    NP_NETWORK_CONNECTIVITY_V6_INTERNET  = 0x20
} NP_NETWORK_STATE;

Note that after you call any of these INetwork methods, you must free the memory using CoTaskMemFree on the value you obtained. For example:

LPWSTR pszName = NULL;
hr = pNetwork->GetName(&pszName);
if (SUCCEEDED(hr))
{
    SendMessage(g_hwndList, LB_ADDSTRING, 0, (LPARAM)pszName);
    // Do whatever you want to do with the pszName here
    CoTaskMemFree(pszParm);
}

Making your application net aware in real-time

After enumerating the networks on startup, an application will often need to maintain awareness of network connectivity and network changes. An example of this kind of application would be an email application. If one network (or more) is available the application might query the server for new messages and upload messages the user has sent while disconnected. If a faster network becomes available the application may want to change over to using that one and may even choose to download more data for client-side caching. If a network has a domain controller and the user is authenticated, other features may be enabled.

To keep track of network changes asynchronously, it is necessary to run a network events event sink in a background thread. This thread can receive network changes at any time, and it is necessary to keep these transitions from affecting your users unless absolutely necessary.

This topic makes reference to two files, EventHandler.cpp and EventHandler.h, that you can use in your own application. These files remove much of the legwork associated with setting up the background thread and getting the event sinks working. If you take these files and call their initialization methods in your application you will then only need to determine which network events are important to your users.

By using a background thread to receive network events, you are able to handle all network changes as silently as possible:

  • Cache data locally where possible, to avoid disruptions to your users when network transitions occur.
  • If a network is disconnected, defer data transfers until later, and provide appropriate user feedback if this is appropriate for your application. This can be something as simple as a connected or disconnected icon.
  • It might be useful to your users to know the relative speed of their network connection, through maybe a red/yellow/green indicator on your UI.
  • When connected, and the bandwidth is sufficient, catch up on pending transfers. Proactively transfer other information that may be needed later. No user feedback is necessary in this situation, unless it's to remove the above feedback that there are data transfers queued up and waiting for network connectivity.

Network transitions will happen frequently, especially if your application is running on a mobile PC. Avoid notifying your user of these transitions where possible:

  • Does this event directly impact the user's current task, and therefore demand the user's immediate attention, or can it be handled transparently?
  • Can this event be handled transparently by your application to improve speed or connectivity for your user?
  • There may be only certain events that are important to your application. For example, maybe you only care when a network with internet connectivity is connected or disconnected. Perhaps you cannot perform an operation until a high-bandwidth managed network is connected.

After receiving the network events a separate decision is where you want to handle these events. There are three options:

  1. Handle the event in the background thread
  2. Handle the event in the foreground (UI) thread
  3. Handle the event in another separate thread, not your network event sink but also not your UI thread.

Thread communication (through Windows messages, etc) will be necessary at some point in each of these techniques. Each of these solutions has costs and advantages, however:

If you process the event in the background network sink thread, any time you're running code under the event sinks (INotifyNetworkEvents and INotifyNetworkInterfaceEvents) you are blocking this event sink from sending any new events. So, for example, if you do a long operation on your network event sink thread to looking deeper into a network that was just connected, other networks (or this very network!) may have been connected or disconnected in the meantime. You will not be notified of these events until after you return from the previous event notification.

If you process the event in your UI thread, the well-known issues exist there. Any operation you perform will block the UI from sending or receiving any information to or from the user.

Processing the event in another background thread is probably the best option, especially for long-timespan events that result in small changes to the UI or the internals of your application. Network awareness fits this model well, especially with silent background network changes. Doing a network bandwidth test on a separate background thread, for example, can help you decide if a new network is worth changing over to, but the entire time you're receiving other network events and your UI is completely responsive to your user.

To register your application for network events, put the two files EventHandler.cpp and EventHandler.h into your project. Their implementation is mostly the second method above: they implement a single background thread that listens to network events and network interface events. Almost no processing is done inside the background thread beyond packaging the data for a PostMessage across to the foreground window. However, it would be trivial to change this to the first method; you would just need to decide there in EventHandler.cpp what network events were important and how they were important by looking into the parameters that are already being packaged up. It is up to you to decide how much processing is done inside the CNetworkEventSink and CNetworkInterfaceEventSink in EventHandler.cpp and how much you need to message to your UI thread.

Inside EventHandler.cpp there are a lot of placeholder comments in these files that will help you to place your own code.

The necessary initialization code is the following:

INetworkListManager *pNetworkListManager = NULL;
...
hr = CoCreateInstance(CLSID_CNetworkListManager, NULL,
        CLSCTX_LOCAL_SERVER, IID_INetworkListManager,
        (LPVOID *)&pNetworkListManager);
if (SUCCEEDED(hr))
{
    ...
    DWORD dwSinkThreadId = StartNetworkEventSinkThread(pNetworkListManager, g_hwndMain);
}

You should place this block of code in the initialization sequence for your application, or where you decide that network events are now important to your application. The communication mechanism used by the background thread in EventHandler.cpp and your application's main thread is a Windows message to the HWND passed in during initialization. Any other inter-thread communication model is suitable as well, of course.

Note that after you have created the INetworkListManager object above you can use it to synchronously enumerate the connected or disconnected networks, or enumerate the network interfaces using the methods described above.

At the end of your application's execution you will need to close the thread and release the CoCreated object:

PostThreadMessage(dwSinkThreadId, WM_QUIT, 0, 0);
pNetworkListManager->Release();
pNetworkListManager = NULL;

After you've built your application with these two files and these few lines of code, you will be well on your way to network awareness by receiving network events.

From here it is necessary to decide which of these network events really matter for your application, and how they need to be communicated (if at all) to the user or the foreground thread.

Send comments about this topic to Microsoft

Build date: 12/5/2008