Using the Connection Manager
How to effectively use the Connection Manager API to connect to an arbitrary network.
Contents
- Introduction
- What is Connection Manager?
- Establishing a Connection
- Connecting to a Particular Network
- Connecting to a Particular URL
- Connecting to a Particular Interface
- Reading the Status of All Connections
- Further Reading
Introduction
About once a month, somebody in the MSDN mobile development forums asks a variation on the question: "How can I choose which network adapter my mobile application uses?" The answer is Connection Manager. In this article, we will explore different methods of using the Connection Manager API to establish communications with arbitrary networks. The attached sample application will list all Connection Manager entries, and demonstrates connecting to any of them.
What is Connection Manager?
Your typical Windows Mobile device has lots of different connectivity options like WiFi, Cellular, Bluetooth, IrDa, etc... Microsoft realized early in the development of Pocket PC that this would produce a host of variables that any application wishing to communicate with the outside world would have to contend with:
- Each of them communicates at different speeds.
- At any given time, only a subset of them will be available. If you're on the highway, WiFi isn't likely to work.
- They can cost money. Some cellular data plans charge by the megabyte.
- Some may communicate with different networks. Your Bluetooth connection to a printer won't get you to the Internet.
Connection Manager was created to transparently manage all of these variables so that application developers can focus on what they want to send and not how they want to send it.
You can even see what state the Connection Manager is in by looking at the title bar. These screenshots show a Windows Mobile 6.1 device in the various stages of connecting to a WAP network.
Establishing a Connection
There are three ways of defining the type of network connection you wish to make with Connection Manager:
- Connect to a Particular Network - You know what network you want to use (e.g., the cellular WAP network).
- Connect to a Particular URL - You know the URL of the host you wish to communicate with (e.g., http://www.codeproject.com).
- Connect to a Particular Interface - You know the network adapter you want to use (e.g., your WiFi adapter)
Connecting to a Particular Network
This is the simplest case as we can use one of the four pre-defined meta-networks:
IID_DestNetInternet
- The Internet! (requires fully-qualified domain names).IID_DestNetCorp
- Same as the Internet, but also supports proxy servers, SOCKS, VPN, and WINS.IID_DestNetWAP
- Your basic cellular data network.IID_DestNetSecureWAP
- A cellular data network that requires authentication (PAP, CHAP, etc...).
Note: When using the pre-defined GUIDs, be sure to include InitGuid.h before you include connmgr.h, or you will get linker errors.
error LNK2001: unresolved external symbol IID_DestNetInternet
We have two choices of ways to establish our connection: ConnMgrEstablishConnectionSync()
and ConnMgrEstablishConnection()
. The synchronous function will block until the connection is made or the timeout is reached. The asynchronous version will post a message to a specified HWND
when the connection is complete. For simplicity's sake, we will use ConnMgrEstablishConnectionSync()
in this example.
CONNMGR_CONNECTIONINFO info = { 0 };
info.cbSize = sizeof( CONNMGR_CONNECTIONINFO );
info.dwParams = CONNMGR_PARAM_GUIDDESTNET;
info.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE;
// allow connection manager to use any proxy it needs to make the connection
info.dwFlags = CONNMGR_FLAG_PROXY_HTTP |
CONNMGR_FLAG_PROXY_SOCKS4 |
CONNMGR_FLAG_PROXY_SOCKS5 |
CONNMGR_FLAG_PROXY_WAP;
// specify the connection destination GUID
info.guidDestNet = IID_DestNetInternet;
DWORD status = 0;
HANDLE connection = NULL;
// Establish the connection. hr will either be E_FAIL or S_OK. Extended error
// information is stored in the status variable.
HRESULT hr = ::ConnMgrEstablishConnectionSync( &info,
&connection,
30000,
&status );
if( S_OK == hr )
{
// use the connection wisely, then release it when you are finished
::ConnMgrReleaseConnection( connection, 1 );
}
Connecting to a Particular URL
This is a fairly common scenario: you know the host you want to connect to, and you don't care how you get there. For this, we will use ConnMgrMapURL()
to ask the connection planner to compute the best route to a given URL. With the default connection manager settings (i.e., no specific routes configured), the following URL patterns will direct you to the given destination meta-networks:
Pattern | Destination | Example |
---|---|---|
*://*.*/* | IID_DestNetInternet |
http://www.codeproject.com |
*://*/* | IID_DestNetCorp |
http://codeproject |
wsp://*/* | IID_DestNetWAP |
wap://codeproject |
wsps://*/* | IID_DestNetSecureWAP |
wsps://codeproject |
Let's revisit our previous example and alter it to support for URL mapping:
const wchar_t* url = L"http://www.codeproject.com";
CONNMGR_CONNECTIONINFO info = { 0 };
info.cbSize = sizeof( CONNMGR_CONNECTIONINFO );
info.dwParams = CONNMGR_PARAM_GUIDDESTNET;
info.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE;
// allow connection manager to use any proxy it needs to make the connection
info.dwFlags = CONNMGR_FLAG_PROXY_HTTP |
CONNMGR_FLAG_PROXY_SOCKS4 |
CONNMGR_FLAG_PROXY_SOCKS5 |
CONNMGR_FLAG_PROXY_WAP;
// use the connection planner to map our URL to a destination network
if( S_OK == ::ConnMgrMapURL( url, &info.guidDestNet, NULL ) )
{
DWORD status = 0;
HANDLE connection = NULL;
// Establish the connection. hr will either be E_FAIL or S_OK. Extended error
// information is stored in the status variable.
HRESULT hr = ::ConnMgrEstablishConnectionSync( &info,
&connection,
30000,
&status );
if( S_OK == hr )
{
// use the connection wisely, then release it when you are finished
::ConnMgrReleaseConnection( connection, 1 );
}
}
Connect with a Particular Interface
This method is useful when you or your user knows which interface they want to use. For example, you definitely want to communicate over the WiFi interface even though a desktop-passthrough interface is available. There are two methods to achieve this:
- Connect by interface name - If you want to give the user the option to select the connection interface, this would be the preferred method as "My AT&T Network" is much easier for a user to understand than the seemingly random string of hex characters that is a GUID.
- Connect by interface GUID - This method involves slightly less code, and so may be preferred if your application performs the network selection programmatically.
Connect by Interface Name
The name of the interface is given by the szAdapterName
parameter of CONNMGR_CONNECTION_DETAILED_STATUS
. For some interfaces, though, that parameter may be NULL
. In that case, use the szDescription
parameter. For tips on retrieving this structure, fast-forward to Reading the Status of All Connections.
Once you know the name of the interface you wish to connect with, you can use the ConnMgrMapConRef()
API to map that name to a GUID, as follows:
// Get the interface you want to connect with. This can
// be a programmatic choice or a user selection.
const CONNMGR_CONNECTION_DETAILED_STATUS* status = GetDesiredInterface();
CONNMGR_CONNECTIONINFO info = { 0 };
info.cbSize = sizeof( CONNMGR_CONNECTIONINFO );
info.dwParams = CONNMGR_PARAM_GUIDDESTNET;
info.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE;
// allow connection manager to use any proxy it needs to make the connection
info.dwFlags = CONNMGR_FLAG_PROXY_HTTP |
CONNMGR_FLAG_PROXY_SOCKS4 |
CONNMGR_FLAG_PROXY_SOCKS5 |
CONNMGR_FLAG_PROXY_WAP;
// Get the destination meta-network GUID for the adapter with the given name. If the
// adapter doesn't have a name, the description usually works. If the adapter is a proxy,
// be sure to set the correct reference type.
if( S_OK == ::ConnMgrMapConRef(
( CM_CONNTYPE_PROXY == status->dwType ) ? ConRefType_PROXY : ConRefType_NAP,
( NULL != status->szAdapterName ) ? status->szAdapterName : status->szDescription,
&info.guidDestNet ) )
{
DWORD status = 0;
HANDLE connection = NULL;
// Establish the connection. hr will either be E_FAIL or S_OK. Extended error
// information is stored in the status variable.
HRESULT hr = ::ConnMgrEstablishConnectionSync( &info,
&connection,
30000,
&status );
if( S_OK == hr )
{
// use the connection wisely, then release it when you are finished
::ConnMgrReleaseConnection( connection, 1 );
}
}
Connect by Interface GUID
The destination network for a particular interface is given by the dwDestNet
parameter of CONNMGR_CONNECTION_DETAILED_STATUS
. Since it is already in GUID format, we don't need to use one of the mapping functions. We will use it directly:
// Get the interface you want to connect with.
// This can be a programmatic choice or a user selection.
const CONNMGR_CONNECTION_DETAILED_STATUS* status = GetDesiredInterface();
CONNMGR_CONNECTIONINFO info = { 0 };
info.cbSize = sizeof( CONNMGR_CONNECTIONINFO );
info.dwParams = CONNMGR_PARAM_GUIDDESTNET;
info.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE;
// allow connection manager to use any proxy it needs to make the connection
info.dwFlags = CONNMGR_FLAG_PROXY_HTTP |
CONNMGR_FLAG_PROXY_SOCKS4 |
CONNMGR_FLAG_PROXY_SOCKS5 |
CONNMGR_FLAG_PROXY_WAP;
info.guidDestNet = status->dwDestNet;
DWORD status = 0;
HANDLE connection = NULL;
// Establish the connection. hr will either be E_FAIL or S_OK. Extended error
// information is stored in the status variable.
HRESULT hr = ::ConnMgrEstablishConnectionSync( &info,
&connection,
30000,
&status );
if( S_OK == hr )
{
// use the connection wisely, then release it when you are finished
::ConnMgrReleaseConnection( connection, 1 );
}
Reading the Status of All Connections
The status is stored in a linked list of CONNMGR_CONNECTION_DETAILED_STATUS
structures that can be retrieved with the ConnMgrQueryDetailedStatus()
function. I have a construct I like to use for this purpose that I will share. It provides the strong exception guarantee in that it will not leak memory or alter your program state if it fails. Note that at the end, rather than doing a full copy of the internal buffer to the supplied buffer, we just do a swap, which is far more efficient.
/// Populate the given buffer with the current status of all adapters registered
/// with connection manager.
/// Returns NULL on failure.
const CONNMGR_CONNECTION_DETAILED_STATUS* GetAdaptersStatus( std::vector< BYTE >* buffer )
{
std::vector< BYTE > int_buffer( sizeof( CONNMGR_CONNECTION_DETAILED_STATUS ) );
DWORD buffer_size = int_buffer.size();
CONNMGR_CONNECTION_DETAILED_STATUS* status =
reinterpret_cast< CONNMGR_CONNECTION_DETAILED_STATUS* >( & int_buffer.front() );
HRESULT hr = ::ConnMgrQueryDetailedStatus( status, &buffer_size );
if( HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) == hr )
{
int_buffer.resize( buffer_size );
status =
reinterpret_cast< CONNMGR_CONNECTION_DETAILED_STATUS* >( & int_buffer.front() );
hr = ::ConnMgrQueryDetailedStatus( status, &buffer_size );
}
if( S_OK == hr )
{
buffer->swap( int_buffer );
return status;
}
return NULL;
}
// retrieve the current status.
std::vector< BYTE > buffer;
const CONNMGR_CONNECTION_DETAILED_STATUS* status = GetAdaptersStatus( &buffer );
// iterate over the CONNMGR_CONNECTION_DETAILED_STATUS linked list
const CONNMGR_CONNECTION_DETAILED_STATUS* cur = status;
for( cur; NULL != cur; cur = cur->pNext )
{
// ...
}
Further Reading
This article just scratches the surface of Connection Manager. If you're interested in other topics, I recommend the following articles: