无线电功率
深入了解如何监控和控制 Windows Mobile 设备无线通信系统的电源。
- 下载电源管理 API 演示源代码 - 16.7 KB
- 下载电源管理 API 演示 - 93.5 KB
- 下载电话 API 演示源代码 - 14.5 KB
- 下载电话 API 演示 - 29.4 KB
- 下载蓝牙应用程序开发 API 演示源代码 - 12.9 KB
- 下载蓝牙应用程序开发 API 演示 - 28.5 KB
- 下载无线设备电源管理 API 演示源代码 - 15.9 KB
- 下载无线设备电源管理 API 演示 - 31.7 KB
|
|
|
|
目录
引言
在本文中,我们将探讨在 Windows Mobile 设备中启用和禁用无线通信无线电的方法。我们将首先研究适用于单个无线电类型的方法,最后介绍一种用于管理所有无线电状态的不受支持的方法。
方法 | 支持的无线电类型 |
电源管理 API | WiFi |
电话 API | 蜂窝网络 (WWAN) |
BTH Util API | 蓝牙 |
OSSVCS | WiFi、WWAN、蓝牙 |
顺便说一句,附带的演示应用程序使用了 boost。我曾考虑仅使用 Windows Mobile 6 SDK 自带的功能来编写它们,但 boost 库在使代码更具可读性、异常安全和易用性方面做得非常出色,因此我决定使用它们。如果您不使用 boost(您真的应该使用!),本文介绍的电源管理概念仍然适用。
电源管理 API
Power Management API 允许我们的应用程序监控和更改任何电源管理设备(即驱动程序 声明了电源管理功能的设备)的电源状态。通常,这仅限于使用电源管理的 NDIS 微型端口驱动程序的 WiFi 802.11 适配器。
更改电源状态
Power Management API 提供了两个看似简单的调用来确定无线电的当前状态并更改状态:GetDevicePower()
和 SetDevicePower()
。我称它们为“看似简单”,因为虽然它们的三个参数中有两个是直接的,但第一个参数 pvDevice
却不是。
为了使用 Power Management API 设置或获取设备的电源状态,我们必须知道其电源类别和名称。NDIS 设备的电源类 GUID 默认是 {98C5250D-C29A-4985-AE5F-AFE5367E5006}。WiFi 无线电的名称则稍微复杂一些。
定位 WiFi 无线电
有许多 API 可以提供已连接网络设备的名称。我们将使用 IpHelperAPI
,因为与 IOCTL_NDISUIO_QUERY_BINDING
不同,它可以在无线电当前开启或关闭的情况下正常工作。
// Get the names of all NDIS adapters iin the device. We use the
// IPHelperAPI functions because IOCTL_NDISUIO_QUERY_BINDING won't
// work for adapters that aren't currently powered on.
DWORD buffer_size = 0;
if( ERROR_BUFFER_OVERFLOW == GetAdaptersInfo( NULL, &buffer_size ) )
{
std::vector< BYTE > adapter_buffer( buffer_size );
IP_ADAPTER_INFO* info =
reinterpret_cast< IP_ADAPTER_INFO* >( &adapter_buffer.front() );
DWORD res = GetAdaptersInfo( info, &buffer_size );
if( ERROR_SUCCESS == res )
{
// Which IP_ADAPTER_INFO is the WiFi adapter?
}
}
现在我们有了一个所有网络适配器的列表,但我们需要一种方法来确定哪个是 WiFi 适配器。一种好的方法是问一个只有 802.11 适配器才能回答的问题。在此示例中,我们将使用 OID_802_11_SSID
查询当前的 SSID。
查询 NDIS OID 有四个步骤
- 打开指向
NDISUIO_DEVICE_NAME
的句柄。 - 构建
IOCTL_NDISUIO_QUERY_OID_VALUE
查询结构。 - 通过
DeviceIoControl()
发出查询。 - 从查询缓冲区检索结果。
首先,我们使用 CreateFile()
打开指向 NDIS 协议驱动程序的句柄。
// Open a handle to the NDIS device. boost::shared_ptr<> provides us a
// method of ensuring our handle will be closed even if an exception is
// thrown.
boost::shared_ptr< void > ndis( ::CreateFile( NDISUIO_DEVICE_NAME,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
INVALID_HANDLE_VALUE ),
&::CloseHandle );
if( INVALID_HANDLE_VALUE != ndis.get() )
{
// use the NDIS protocol driver handle wisely
}
接下来,我们准备一个 NDIS_802_11_SSID
缓冲区来接收 SSID。我们将使用 GetAdaptersInfo()
提供的每个适配器名称。如果 WiFi 适配器已经开启,QueryOid()
将返回 ERROR_SUCCESS
。如果未开启,它将失败并返回 ERROR_GEN_FAILURE
。ERROR_INVALID_PARAMETER
错误表示我们提供了非 802.11 适配器的名称。
/// get the SSID of the specified NDIS adapter
DWORD GetSSID( HANDLE ndis,
const std::wstring& adapter_name,
std::wstring* ssid = NULL )
{
std::vector< BYTE > ssid_buffer( sizeof( NDIS_802_11_SSID ) );
NDIS_802_11_SSID* ssid_int =
reinterpret_cast< NDIS_802_11_SSID* >( &ssid_buffer.front() );
ssid_int->SsidLength = NDIS_802_11_LENGTH_SSID - 1;
DWORD res = QueryOid( ndis,
adapter_name.c_str(),
OID_802_11_SSID,
&ssid_buffer );
if( ERROR_SUCCESS == res && NULL != ssid )
*ssid = reinterpret_cast< wchar_t* >( ssid_int->Ssid );
return res;
}
第三,我们创建一个 NDISUIO_QUERY_OID
缓冲区并将其提供给 DeviceIoControl
。返回的缓冲区应包含 SSID。
/// Query an NDIS OID. The output buffer must be pre-sized to be large enough
/// for the expected result of the query.
DWORD QueryOid( HANDLE ndis,
const wchar_t* device_name,
ULONG oid,
std::vector< BYTE >* buffer )
{
DWORD bytes_returned = 0;
std::vector< BYTE > query_buffer( sizeof( NDISUIO_QUERY_OID ) + buffer->size() );
NDISUIO_QUERY_OID* query =
reinterpret_cast< NDISUIO_QUERY_OID* >( &query_buffer.front() );
query->Oid = oid;
query->ptcDeviceName = const_cast< wchar_t* >( device_name );
BOOL res = ::DeviceIoControl( ndis,
IOCTL_NDISUIO_QUERY_OID_VALUE,
reinterpret_cast< LPVOID >( &query_buffer.front() ),
query_buffer.size(),
reinterpret_cast< LPVOID >( &query_buffer.front() ),
query_buffer.size(),
&bytes_returned,
NULL );
if( res )
{
// extract the result from the query buffer
std::copy( query->Data, query_buffer.end(), buffer->begin() );
return ERROR_SUCCESS;
}
return GetLastError();
}
总而言之,我们现在可以确定哪个 IP_ADAPTER_INFO
结构代表我们的 WiFi 适配器。
// Check each adapter to see if it responds to the standard
// OID_802_11_SSID query. If it does, then it is an 802.11
// adapter.
for( IP_ADAPTER_INFO* i = info; NULL != i; i = i->Next )
{
// convert the adapter name from narrow to wide-character
std::wstringstream adapter_name;
adapter_name << i->AdapterName;
// If the WiFi adapter is already on, QueryOid() will return
// ERROR_SUCCCESS. If not, it will fail and return
// ERROR_GEN_FAILURE. If it weren't an 802.11 adapter, it
// would return ERROR_INVALID_PARAMETER. Therefore, we will
// consider ERROR_GEN_FAILURE a success.
DWORD res = GetSSID( ndis.get(), adapter_name.str() );
if( ERROR_SUCCESS == res || ERROR_GEN_FAILURE == res )
{
adapter_name.str().swap( *name );
return true;
}
}
启用和禁用无线电
要更改 NDIS WLAN 无线电的电源状态,我们将 NDIS 电源管理 GUID 与 WLAN 无线电的名称结合起来,并将其传递给 SetDevicePower()
。电源状态 D4
将关闭无线电,而 D0
将开启无线电。
void DoChangePowerState( const CEDEVICE_POWER_STATE& power_state )
{
std::wstring radio_name;
GetRadioName( &radio_name );
if( radio_name_.length() > 0 )
{
std::wstringstream power_command;
power_command << PMCLASS_NDIS_MINIPORT << L\\" << radio_name.c_str();
::SetDevicePower( const_cast< wchar_t* >( power_command.str().c_str() ),
POWER_NAME,
power_state );
}
}
void Enable()
{
DoChangePowerState( D0 );
}
void Disable()
{
DoChangePowerState( D4 );
}
监控电源状态的变化
除了更改 WLAN 无线电的电源状态外,Power Manager API 还可以用于在电源状态更改时通知我们。此过程有五个步骤:
- 打开指向 NDIS 协议驱动程序的句柄。
- 创建一个系统 消息队列。
- 请求 NDIS 电源通知,使用
IOCTL_NDISUIO_REQUEST_NOTIFICATION
。 - 等待 NDIS 电源通知消息到达。
- 从队列中读取电源通知消息,以确定无线电是关闭还是开启。
首先,我们打开 NDIS 协议驱动程序的句柄,这与我们 之前 在检索 SSID 时所做的完全相同。
// Open a handle to the NDIS device. boost::shared_ptr<> provides us a
// method of ensuring our handle will be closed even if an exception is
// thrown.
boost::shared_ptr< void > ndis( ::CreateFile( NDISUIO_DEVICE_NAME,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
INVALID_HANDLE_VALUE ),
&::CloseHandle );
if( INVALID_HANDLE_VALUE != ndis.get() )
{
// use the NDIS protocol driver handle wisely
}
接下来,我们创建一个只读消息队列来监听 NDIS 通知。
MSGQUEUEOPTIONS options = { 0 };
options.dwSize = sizeof( MSGQUEUEOPTIONS );
options.cbMaxMessage = sizeof( NDISUIO_DEVICE_NOTIFICATION );
options.bReadAccess = TRUE;
options.dwFlags = MSGQUEUE_NOPRECOMMIT;
boost::shared_ptr< void > radio_power_queue( ::CreateMsgQueue( NULL, &options ),
&::CloseMsgQueue );
if( NULL != radio_power_queue.get() )
{
// The message queue is open and ready for reading
}
我们使用 IOCTL_NDISUIO_REQUEST_NOTIFICATION
来请求向我们的 NDIS 消息队列发送电源状态更改通知。一个 struct
提供了一种方便的异常安全的方法,可以确保我们正确取消通知请求,无论将来发生什么。
/// encapsulates the registration and de-registration for ndis power-state
/// notifications
struct NdisRequestNotifications : private boost::noncopyable
{
NdisRequestNotifications( HANDLE ndis,
HANDLE queue,
DWORD dwNotificationTypes )
: ndis_( ndis )
{
NDISUIO_REQUEST_NOTIFICATION radio_power_notifications = { 0 };
radio_power_notifications.hMsgQueue = queue;
radio_power_notifications.dwNotificationTypes = dwNotificationTypes;
::DeviceIoControl( ndis_,
IOCTL_NDISUIO_REQUEST_NOTIFICATION,
&radio_power_notifications,
sizeof( NDISUIO_REQUEST_NOTIFICATION ),
NULL,
0,
NULL,
NULL );
};
~NdisRequestNotifications()
{
// stop receiving NDIS power notifications
::DeviceIoControl( ndis_,
IOCTL_NDISUIO_CANCEL_NOTIFICATION,
NULL,
NULL,
NULL,
0,
NULL,
NULL );
};
private:
/// handle to the NDIS instance we're listening for power notifications on
HANDLE ndis_;
}; // struct NdisRequestNotifications
// use the struct to request notifications whenever the device is turned on or off.
NdisRequestNotifications notifications( ndis.get(),
radio_power_queue.get(),
NDISUIO_NOTIFICATION_DEVICE_POWER_DOWN |
NDISUIO_NOTIFICATION_DEVICE_POWER_UP );
现在,我们必须等待消息队列通知我们是否有可用消息。第二个事件句柄可用于随时取消线程读取循环。
/// Event signaled when we should stop listening for power state change messages
HANDLE stop_notification_;
// wait for either a change in radio state or for the user to ask us
// to stop listening.
HANDLE wait_objects[] = { radio_power_queue.get(), stop_notification_ };
size_t object_count = _countof( wait_objects );
while( ::WaitForMultipleObjects( object_count,
wait_objects,
FALSE,
INFINITE ) == WAIT_OBJECT_0 )
{
// A message is available on the queue!
}
最后,当我们收到消息后,我们使用 ReadMsgQueue()
函数从队列中提取消息。无线电的电源状态将存储在 NDISUIO_DEVICE_NOTIFICATION
的 dwNotificationType
参数中。
// The radio has changed state. Read the change from the queue.
NDISUIO_DEVICE_NOTIFICATION notification = { 0 };
DWORD bytes_read = 0;
DWORD notification_flags = 0;
if( ::ReadMsgQueue( radio_power_queue.get(),
¬ification,
sizeof( NDISUIO_DEVICE_NOTIFICATION ),
&bytes_read,
0,
¬ification_flags ) &&
bytes_read > 0 )
{
// Is the radio enabled?
bool enabled =
( notification.dwNotificationType == NDISUIO_NOTIFICATION_DEVICE_POWER_UP );
}
电话 API
Microsoft Telephony Service Provider API (TAPI) 将允许我们管理 Wireless Wide-Area Network (WWAN) 无线电的电源状态。因为我们将用于此目的的函数是扩展 TAPI 接口的一部分,该接口完全处理蜂窝网络无线电,所以此方法不适用于任何其他类型的无线电。
调试说明
如果 Microsoft Visual Studio 2008 调试器在 TAPI 清理函数运行之前停止代码执行,您可能会收到类似以下的错误:
这很烦人,但似乎对 Visual Studio 会话或移动设备没有负面影响。
更改电源状态
首先,我们必须使用 lineInitializeEx()
初始化 TAPI 子系统。这不仅给我们一个句柄对象,还告诉我们系统中 TAPI 设备的数量以及 TAPI 在消息可用时发送信号的事件 HANDLE
。在此示例中,我们将 TAPI HLINEAPP
句柄包装在 boost::shared_ptr<>
中以管理对象的生命周期。
/// Manages the lifetime of the TAPI HLINEAPP
class TapiLineApp
{
public:
TapiLineApp()
: line_app_( TapiLineApp::Create( &device_count_,
&event_,
&error_code_ ),
&::lineShutdown )
{
};
/// Get the error code returned by lineInitializeEx()
DWORD GetErrorCode() const { return error_code_; };
/// Get the number of TAPI devices in the system
DWORD GetDeviceCount() const { return device_count_; };
/// Get the TAPI message event handle
HANDLE GetMessageEvent() const { return event_; };
operator HLINEAPP() const { return ( HLINEAPP )line_app_.get(); };
private:
///////////////////////////////////////////////////////////////////////
// Method private static TapiLineApp::Create
/// @brief Initialize the TAPI subsystem
///
/// @param DWORD * device_count - [out] number of TAPI devices
/// @param HANDLE * event_ - [out] event signaled when TAPI has a message.
/// @param DWORD * error_code - [out] 0 on success. TAPI error on failure.
/// @return HLINEAPP - TAPI handle
///////////////////////////////////////////////////////////////////////
static HLINEAPP Create( DWORD* device_count,
HANDLE* event_,
DWORD* error_code )
{
DWORD api_ver = TAPI_CURRENT_VERSION;
LINEINITIALIZEEXPARAMS init_params = { 0 };
init_params.dwTotalSize = sizeof( LINEINITIALIZEEXPARAMS );
init_params.dwOptions = LINEINITIALIZEEXOPTION_USEEVENT;
HLINEAPP line_app = NULL;
*error_code = ::lineInitializeEx( &line_app,
NULL,
NULL,
NULL,
device_count,
&api_ver,
&init_params );
if( *error_code == 0 )
{
*event_ = init_params.Handles.hEvent;
return line_app;
}
return NULL;
};
private:
// because we are using a static initializer function, the order of these
// members is important. Don't change it!
/// number of TAPI devices in the system
DWORD device_count_;
/// error code returned by lineInitializeEx()
DWORD error_code_;
/// TAPI message event handle
HANDLE event_;
/// TAPI line app instance
boost::shared_ptr< const HLINEAPP__ > line_app_;
}; // class TapiLineApp
接下来,我们使用 lineOpen()
通过蜂窝电话 TAPI 服务提供商 (TSP) 打开 WWAN 无线电的句柄。与上一个示例一样,我们使用 boost::shared_ptr<>
来管理 HLINE
句柄的生命周期。
/// Manages the lifetime of the TAPI HLINE
class TapiLine
{
public:
TapiLine( const TapiLineApp& line_app,
DWORD line_id,
DWORD version,
DWORD privilege ) :
line_app_( line_app ),
line_( TapiLine::Create( line_app,
line_id,
version,
privilege,
&error_code_ ),
&::lineClose )
{
};
/// Get the error code returned by lineOpen()
DWORD GetErrorCode() const { return error_code_; };
operator HLINE() const { return ( HLINE )line_.get(); };
private:
///////////////////////////////////////////////////////////////////////
// Method private static TapiLine::Create
/// @brief Open a TAPI line
///
/// @param HLINEAPP line_app - TAPI instance handle
/// @param DWORD line_id - line to open
/// @param DWORD version - TAPI version to use
/// @param DWORD privilege - TAPI LINECALLPRIVILEGE_*
/// @param DWORD * error_code - [out] error code returned by lineOpen.
/// @return HLINE - TAPI line handle
///////////////////////////////////////////////////////////////////////
static HLINE Create( HLINEAPP line_app,
DWORD line_id,
DWORD version,
DWORD privilege,
DWORD* error_code )
{
HLINE line = NULL;
*error_code = ::lineOpen( line_app,
line_id,
&line,
version,
0,
NULL,
privilege,
NULL,
NULL );
if( *error_code == 0 )
{
return line;
}
return NULL;
};
private:
// because we are using a static initializer function, the order of these
// members is important. Don't change it!
/// error returned by lineOpen()
DWORD error_code_;
/// handle to the TAPI line app
TapiLineApp line_app_;
/// handle to the TAPI line
boost::shared_ptr< const HLINE__ > line_;
}; // class TapiLine
现在,我们可以使用 lineGetEquipmentState()
轻松获取 WWAN 无线电的当前状态。
/// Open TAPI line handle
TapiLine line_;
/// Is the WWAN radio enabled?
bool IsRadioEnabled() const
{
DWORD equip_state = 0, radio_state = 0;
if( ::lineGetEquipmentState( line_, &equip_state, &radio_state ) == 0 )
{
return ( LINEEQUIPSTATE_MINIMUM != equip_state );
}
return false;
}
lineSetEquipmentState()
可用于启用 WWAN 无线电。
/// Open TAPI line handle
TapiLine line_;
/// enable the WWAN radio
void Enable() const
{
if( ::lineSetEquipmentState( line_, LINEEQUIPSTATE_FULL ) == 0 )
{
::lineRegister( line_, LINEREGMODE_AUTOMATIC, NULL, LINEOPFORMAT_NONE );
}
}
我们甚至可以将其置于飞行模式。
/// Open TAPI line handle
TapiLine line_;
/// disable the WWAN radio
void Disable() const
{
::lineSetEquipmentState( line_, LINEEQUIPSTATE_MINIMUM );
}
监控电源状态的变化
监听 WWAN 无线电状态的变化很容易。我们像 之前 所做的那样初始化 TAPI。然后,我们使用 lineGetMessage()
从 TAPI 消息队列读取消息。当扩展 TAPI LINE_DEVSPECIFIC
消息到达,并且 dwParam2
的值为 LINE_EQUIPSTATECHANGE
时,我们就知道电源状态已更改。新的电源状态存储在 LINEMESSAGE
的 dwParam2
中。
/// event signaled when we should stop listening for TAPI messages
HANDLE stop_notification_;
/// TAPI instance
TapiLineApp line_app_;
void MonitorRadioState()
{
HANDLE wait_objects[] = { line_app_.GetMessageEvent(),
stop_notification_ };
size_t object_count = _countof( wait_objects );
// wait for either a message from TAPI or for the user to ask us to stop
// listening.
while( ::WaitForMultipleObjects( object_count,
wait_objects,
FALSE,
INFINITE ) == WAIT_OBJECT_0 )
{
// A TAPI message has arrived, Read the message from the queue.
LINEMESSAGE message = { 0 };
if( ::lineGetMessage( line_app_, &message, 0 ) == 0 )
{
switch( message.dwMessageID )
{
case LINE_DEVSPECIFIC:
if( LINE_EQUIPSTATECHANGE == message.dwParam1 )
{
// Is the radio enabled?
bool enabled = ( message.dwParam2 != LINEEQUIPSTATE_MINIMUM );
}
break;
}
}
}
}
蓝牙应用程序开发 API
在我们检查的所有方法中,蓝牙应用程序开发 API 提供了最简单的无线电电源状态访问方式。
更改电源状态
检查和更改蓝牙无线电电源状态只需要两个命令:BthGetMode()
和 BthSetMode()
。它们不需要任何支持性句柄或结构,因此,正如您将看到的,它们非常易于使用。
蓝牙无线电是否已启用?
bool IsRadioEnabled() const
{
DWORD mode = 0;
if( ::BthGetMode( &mode ) == ERROR_SUCCESS )
return ( BTH_POWER_OFF != mode );
return false;
}
启用蓝牙无线电
void Enable() const { ::BthSetMode( BTH_DISCOVERABLE ); }
禁用蓝牙无线电
void Disable() const { ::BthSetMode( BTH_POWER_OFF ); }
监控电源状态的变化
我们将用于监控蓝牙无线电电源状态的技术与我们 之前 用于监控 WiFi 无线电状态的方法非常相似。
- 使用
CreateMsgQueue()
打开消息队列。 - 使用
RequestBluetoothNotifications()
请求在蓝牙堆栈启动或关闭时向我们的队列发送通知。 - 等待队列中的新消息或用户请求停止。
- 当新事件被触发时,读取
BTEVENT
消息。电源状态存储在dwEventId
参数中。
/// event signaled when we should stop listening for Bluetooth messages
HANDLE stop_notification_;
/// an exception safe handle encapsulation
typedef boost::shared_ptr< void > SafeHandle;
void MonitorRadioState()
{
// create a message queue to listen for Bluetooth power notifications
MSGQUEUEOPTIONS options = { 0 };
options.dwSize = sizeof( MSGQUEUEOPTIONS );
options.cbMaxMessage = sizeof( BTEVENT );
options.bReadAccess = TRUE;
options.dwFlags = MSGQUEUE_NOPRECOMMIT;
SafeHandle radio_power_queue( ::CreateMsgQueue( NULL, &options ),
&::CloseMsgQueue );
if( NULL != radio_power_queue.get() )
{
// request Bluetooth stack notifications
SafeHandle notifications(
::RequestBluetoothNotifications( BTE_CLASS_STACK, radio_power_queue.get() ),
&::StopBluetoothNotifications );
// wait for either a change in radio state or for the user to ask us
// to stop listening.
HANDLE wait_objects[] = { radio_power_queue.get(),
stop_notification_ };
size_t object_count = _countof( wait_objects );
while( ::WaitForMultipleObjects( object_count,
wait_objects,
FALSE,
INFINITE ) == WAIT_OBJECT_0 )
{
// The radio has changed state. Read the change from the queue.
BTEVENT notification = { 0 };
DWORD bytes_read = 0;
DWORD notification_flags = 0;
if( ::ReadMsgQueue( radio_power_queue.get(),
¬ification,
sizeof( BTEVENT ),
&bytes_read,
0,
¬ification_flags ) &&
bytes_read > 0 )
{
// Is the radio enabled?
bool enabled = ( notification.dwEventId == BTE_STACK_UP );
}
}
}
}
无线设备电源管理 API (OSSVCS.dll)
Wireless Power Management APIs 提供了管理 Windows Mobile 设备上可用无线电电源状态的函数。这包括蓝牙、电话和 WiFi 无线电。它通常用于 Wireless Manager(上图)等应用程序,这些应用程序由设备 OEM 或 Microsoft 创建。
注意
不幸的是,Microsoft 在其公开的 Windows Mobile SDK 中并未发布 Wireless Device Power Management API;它是 Platform Builder 的一部分。如果您无法访问 Platform Builder,您需要修改演示项目以 动态链接 到 ossvcs.dll。它的使用非常普遍,因此通过快速的互联网搜索可以轻松找到 wrlspwr.h 接口。
更改电源状态
GetWirelessDevices()
函数为我们提供了一个 RDD
结构体的链表,该链表定义了支持 Windows Embedded CE Power Management 功能的每个 NDIS 设备的当前状态(即蓝牙、Cellcore 和 WiFi 设备)。由于此结构必须通过调用 FreeDeviceList()
来释放,因此我们可以使用 boost::shared_ptr<>
来管理其生命周期。
/// encapsulate the lifetime management of the RDD structure.
class OssvcsRadioList
{
public:
typedef detail::OssvcsListIterator const_iterator;
OssvcsRadioList()
: radio_list_( OssvcsRadioList::Create(), &::FreeDeviceList )
{
};
explicit OssvcsRadioList( RDD* list )
: radio_list_( list, &::FreeDeviceList )
{
};
const_iterator begin() const
{
return const_iterator( radio_list_.get() );
};
const_iterator end() const
{
return const_iterator();
};
private:
static RDD* Create( DWORD flags = 0, HRESULT* error_code = NULL )
{
RDD* radio_list = NULL;
HRESULT hr = ::GetWirelessDevices( &radio_list, flags );
if( NULL != error_code )
*error_code = hr;
return radio_list;
};
boost::shared_ptr< RDD > radio_list_;
}; // class OssvcsRadioList
RDD
链表结构可以使用 boost::iterator_adaptor<>
轻松地与标准库函数一起使用。
/// Iterate over nodes in the RDD linked-list
class OssvcsListIterator
: public boost::iterator_facade< OssvcsListIterator,
const RDD*,
boost::forward_traversal_tag,
const RDD* >
{
public:
OssvcsListIterator() : node_( NULL ) {};
explicit OssvcsListIterator( const RDD* p ) : node_( p ) {};
private:
friend class boost::iterator_core_access;
void increment() { node_ = node_->pNext; };
bool equal( OssvcsListIterator const& other ) const { return other.node_ == node_; };
const RDD* dereference() const { return node_; };
const RDD* node_;
}; // class OssvcsListIterator
最后,可以使用 ChangeRadioState()
方法来开启或关闭特定无线电。
/// encapsulate power management functions
namespace OssvcsRadioPower {
/// Turn the specified radio device on
void Enable( const RDD* device )
{
::ChangeRadioState( const_cast< RDD* >( device ), 1, POWER_POST_SAVE );
};
/// Turn the specified radio device off
void Disable( const RDD* device )
{
::ChangeRadioState( const_cast< RDD* >( device ), 0, POWER_POST_SAVE );
};
/// Is the specified radio device on?
bool IsEnabled( const RDD* device )
{
return device->dwState != 0;
};
}; // namespace OssvcsRadioPower
现在,我们能够使用标准库函数(如 std::for_each()
)来实现出色的单行代码。
/// list of wireless devices
OssvcsRadioList radio_list_;
// Enable all radios
std::for_each( radio_list_.begin(), radio_list_.end(), OssvcsRadioPower::Enable );
监控电源状态的变化
OSSVCS API 确实提供了向程序发出无线电变化警报的机制:GetAndMonitorWirelessDevices()
。不幸的是,它有点问题。
- 监视器线程在 30 秒后退出,即使没有无线电改变状态。
- 无论使用什么标志,
GetAndMonitorWirelessDevices()
返回的设备列表都不包括 802.11 适配器。 - 任何进程都可以触发
WRLS_TERMINATE_EVENT
并取消监视器线程。 - 任何进程对
GetWirelessDevices()
的调用都将取消监视器线程。 - 回调函数在
GetAndMonitorWirelessDevices()
调用后立即执行,即使还没有设备改变状态。 - 监视器线程在无线电改变状态时退出,但不会先激活回调函数。
- 开启或关闭蓝牙或 WWAN 无线电不会触发回调;只有 WLAN 适配器才能触发它。
其中大部分问题都可以通过变通方法解决,但问题 7 意味着即使有变通方法,该 API 也只能用于监视 802.11 WLAN 设备的状态,而我们有 更简单的方法 来做到这一点。如果我在上述任何一点上犯了错误,或者您成功使用了这种方法,请添加评论描述您的方法!我很想看看。
解决方案是使用与 Wireless Manager 相同的方法:状态和通知代理。
状态和通知代理
Windows Mobile 状态和通知代理 将状态信息存储在 snapi.h 中定义的特定注册表位置(主要在 [HKLM]\System\State)。这允许我们使用标准的注册表查询来确定这些设备的当前状态,或使用注册表通知函数来接收状态变化的警报。不幸的是,它不能用于修改无线电的状态,这就是为什么我们将它与 OSSVCS 结合使用的原因。
RegistryNotifyCallback()
函数要求我们定义一个回调函数,该函数会在注册表值更改时收到通知。此回调函数必须符合 REGISTRYNOTIFYCALLBACK
定义。与大多数 Microsoft API 函数一样,此回调允许我们传递一个任意的 DWORD
参数。为了方便起见,我们的示例将传递一个 this
指针。
/// callback invoked by the notification broker when a registry value is changed.
/*static*/ void RadioNotification::Callback( HREGNOTIFY hNotify,
DWORD dwUserData,
const PBYTE /*pData*/,
const UINT /*cbData*/ )
{
RadioNotification* parent =
reinterpret_cast< RadioNotification* > ( dwUserData );
// The state of a radio has changed! Do something interesting.
// parent->DoSomethingInteresting();
}
一个小的辅助函数允许我们简化将注册表值与我们的回调函数关联起来。
/// Link a registry value to our callback function.
HREGNOTIFY RadioNotification::LinkToCallback( DWORD bitmask,
HKEY root,
LPCTSTR path,
LPCTSTR value ) const
{
NOTIFICATIONCONDITION condition = { REG_CT_ANYCHANGE, bitmask, 0 };
HREGNOTIFY notification = 0;
::RegistryNotifyCallback( root,
path,
value,
&RadioNotification::Callback,
( DWORD )this,
&condition,
¬ification );
return notification;
}
现在,我们可以提供一个应用程序可以使用的方法来开始监听 WiFi、蓝牙或蜂窝网络无线电状态的变化。与往常一样,boost::shared_ptr<>
在安全地管理我们的句柄生命周期方面非常有用。
/// manage the lifetime of HREGNOTIFY handles
typedef boost::shared_ptr< HREGNOTIFY__ > RegNotify;
/// handle to the WWAN phone power notification
RegNotify phone_notification_;
/// handle to the WLAN WiFi power notification
RegNotify wifi_notification_;
/// handle to the bluetooth power notification
RegNotify bluetooth_notification_;
/// start listening for changes to the radios' state
void RadioNotification::Start()
{
// get changes to the bluetooth radio power state
bluetooth_notification_.reset(
CreateCallback( SN_BLUETOOTHSTATEPOWERON_BITMASK,
SN_BLUETOOTHSTATEPOWERON_ROOT,
SN_BLUETOOTHSTATEPOWERON_PATH,
SN_BLUETOOTHSTATEPOWERON_VALUE ),
&::RegistryCloseNotification );
// get changes to the wifi radio power state
wifi_notification_.reset( CreateCallback( SN_WIFISTATEPOWERON_BITMASK,
SN_WIFISTATEPOWERON_ROOT,
SN_WIFISTATEPOWERON_PATH,
SN_WIFISTATEPOWERON_VALUE ),
&::RegistryCloseNotification );
// get changes to the phone power state
phone_notification_.reset( CreateCallback( SN_PHONERADIOOFF_BITMASK,
SN_PHONERADIOOFF_ROOT,
SN_PHONERADIOOFF_PATH,
SN_PHONERADIOOFF_VALUE ),
&::RegistryCloseNotification );
}
最后,我们定义了一种停止监听无线电状态变化的方法。此方法只需关闭注册表通知句柄即可。
/// stop listening for changes to the radios' state
void RadioNotification::Stop()
{
bluetooth_notification_.reset();
wifi_notification_.reset();
phone_notification_.reset();
}
结论
我们已经研究了我想到的所有用于管理和监控 Windows Mobile 设备无线电电源状态的有用方法。如果您使用不同的方法或对上述方法进行了修改,请在评论中进行描述,我将尝试将其添加到文章中。