Contents
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.
Connection Manager is disconnected:
Connection Manager is connecting to a GPRS WAP network:
Connection Manager is connected to an EDGE GPRS 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;
info.dwFlags = CONNMGR_FLAG_PROXY_HTTP |
CONNMGR_FLAG_PROXY_SOCKS4 |
CONNMGR_FLAG_PROXY_SOCKS5 |
CONNMGR_FLAG_PROXY_WAP;
info.guidDestNet = IID_DestNetInternet;
DWORD status = 0;
HANDLE connection = NULL;
HRESULT hr = ::ConnMgrEstablishConnectionSync( &info,
&connection,
30000,
&status );
if( S_OK == hr )
{
::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;
info.dwFlags = CONNMGR_FLAG_PROXY_HTTP |
CONNMGR_FLAG_PROXY_SOCKS4 |
CONNMGR_FLAG_PROXY_SOCKS5 |
CONNMGR_FLAG_PROXY_WAP;
if( S_OK == ::ConnMgrMapURL( url, &info.guidDestNet, NULL ) )
{
DWORD status = 0;
HANDLE connection = NULL;
HRESULT hr = ::ConnMgrEstablishConnectionSync( &info,
&connection,
30000,
&status );
if( S_OK == hr )
{
::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:
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;
info.dwFlags = CONNMGR_FLAG_PROXY_HTTP |
CONNMGR_FLAG_PROXY_SOCKS4 |
CONNMGR_FLAG_PROXY_SOCKS5 |
CONNMGR_FLAG_PROXY_WAP;
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;
HRESULT hr = ::ConnMgrEstablishConnectionSync( &info,
&connection,
30000,
&status );
if( S_OK == hr )
{
::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:
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;
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;
HRESULT hr = ::ConnMgrEstablishConnectionSync( &info,
&connection,
30000,
&status );
if( S_OK == hr )
{
::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.
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;
}
std::vector< BYTE > buffer;
const CONNMGR_CONNECTION_DETAILED_STATUS* status = GetAdaptersStatus( &buffer );
const CONNMGR_CONNECTION_DETAILED_STATUS* cur = status;
for( cur; NULL != cur; cur = cur->pNext )
{
}
This article just scratches the surface of Connection Manager. If you're interested in other topics, I recommend the following articles: