Wednesday, May 30, 2007
Pay it Forward: Part 1
When writing C++ code, do your compiler a favor and use forward declarations whenever possible: it will pay you back later... and more.
Forward declarations will...
Here are some rules of thumb that will help you determine whether a full definition is necessary for a certain type:
Pay it forward... and wait to be paid back.
Forward declarations will...
- ... decouple classes that depend on each other.
- ... improve incremental build times considerably when changing the header file declaring the class where the forward declaration was used.
- ... increase the portability of your code.
AddressBook.h
//#include "Contact.h" // NO! Do this in the .cpp file instead. Unless youIf the user of this class doesn't use the member functions that require the undefined type, he will not be required to include the header that defines it. The user will only need to do this if he uses those member functions.
// really need the definition of the class.
// Forward type declaration
class Contact; // YES!
class AddressBook
{
public:
// ...
/** Ways you can use the declaration without having the definition **/
// Member functions input parameters.
void addContact(unsigned int id, Contact const& newContact);
void addContact(unsigned int id, Contact const* newContact);
// Member functions output parameters.
void getContact(unsigned int id, Contact& contact);
void getContact(unsigned int id, Contact* contact);
// These cases are a bit more obscure than the others, but it is legal and
// still works.
void addContact(unsigned int id, Contact newContact);
Contact getContact(unsigned int id);
private:
// Member variables
Contact* m_contactPointer;
Contact& m_contactRef;
/** These some cases where the compiler needs the definition. **/
Contact::Address m_address;
static const size_t ms_contactSize = sizeof(Contact);
Contact m_contact;
Contact m_contactArray[10];
/** Here are some cases that often work with different STL implementations and
compilers, but are illegal. **/
std::vector<Contact> m_contacts;
std::auto_ptr<Contact> m_contactAutoPtr;
};
Here are some rules of thumb that will help you determine whether a full definition is necessary for a certain type:
- Enums and typedefs types cannot be forward-declared.
- You will be able to use your forward-declared type as a template parameter if and only if the templated type doesn't need the full definition.
- You will need the full definition to get the size of a type, use any constructors or detructors, or access members (types scoped at class level).
Pay it forward... and wait to be paid back.
Labels: c++, compiler, optimization, pay it forward
Tuesday, May 15, 2007
Bluetooth HandsFree Profile Support for Windows Mobile 5 - The Series (part 3)
In the last two articles, we talked about the different ways to interact with Microsoft's Audio Gateway (AG). We then focused on how to start writing your own light-weight Audio Gateway, since some versions of the PPC and Smartphone SDK do not include mechanisms to support control of the AG. In the second article, we took a system level view of the AG core and gave enough detail to create a Parser and Interface Module.
Today, we'll get to the real guts, and this is what heroes are made of: the Handler Module. However, be warned that this is not for the timid, nor the faint of heart. We'll dive into the deep end right away!
This third and last installment will discuss how to create the Handler Module, which includes processing the services and network commands from the Interface module; processing the AT Commands from the Parser Module, starting the service/control level socket connection with the Bluetooth Device; and routing audio to the Bluetooth chip.
Yeah, that's right--what you've been waiting for! With this information, you can do loads of cool things like streaming mp3 music from your mobile device to your bluetooth headset. Wow! Imagine what else you can do with a little imagination!
The guts of the Handler Module contains four small sub-modules, the connection processing, the AT command processing, network notification processing and the services processing. We'll break down these four sub-modules in this article.
First, we assume that the Bluetooth device has already been paired with WM5 device, if not then do so right now! Start->Settings->Connections->Bluetooth... you know the rest, if not consult your WM5 Device User's Manual on how to pair with a Bluetooth device.
Once the pair has been completed and Microsoft's AG has been turned off (See the previous article), we're ready to create a service level connection with the Bluetooth device.
The pairing of the Bluetooth device will create an entry in the registry, under [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Bluetooth\\AudioGateway\\Devices]. In this registry folder will be numbered folders for the different Bluetooth devices that have been paired with the WM5 device. These folders consist of a socket address and the associated service value which the connection offers, in our case, the Handsfree profile service. To create a service/control-level socket connection, first iterate through the numbered folders to get the right address and service that corresponds to the device that you want to create a connection with. Then, simply connect with the Bluetooth device by using the standard winsock socket connection scheme as shown in the code below:
So, we've created a service/control level connection with the Bluetooth device, now what? Well, once the Bluetooth device has detected a connection, it will send initialization codes as AT Commands. It's the Parser's job to parse these initialization codes and send them to us to send to the Handler Module for processing.
To receive these commands, we'll need to receive, from the socket, i.e.
And subsequently send out responses through the same socket the AT Command responses, i.e.
The initialization hand-shaking is outlined below. However, for a detailed overview of this call flow go to the Bluetooth website .
Note that the OK command send string as follows: "\r\nOK\r\n"
1. The Headset first sends the AG the features that it supports and in turn the AG sends back features that are supported.
Listed below are constants to define the end of the AT command (after the "=" sign)
These values can be OR'ed together to create the support value, e.g. if we only want to support three-way calling and reject a call features the value would be 0x21. Subsequently, if the headset only supports voice recognition activation and remote volume control, it would send to the AG 0x18. Hence, the string which would be sent out from the AG to the headset would be "\r\n+BRSF:%d\r\n", where %d is equal to the features supported.
Note the new line and carriage return before and after the command. These are classic AT Command delimiters. See the Bluetooth Spec for details.
2. The next message sent from the Headset to the AG is a request by the Headset to the AG to indicate which indicators are supported by the AG. An example of the response string is as follows: "\r\n+CIND:
(\"service\",(0,1)),(\"call\",(0,1)),(\"callsetup\",(0-3))\r\n"
3. The headset then requests the status of those indicators. In our example we respond with the string "\r\n+CIND: %d,%d,%d\r\n", where the %d are defined:
first %d indicates that we have cellular service (from the listed values this could be 0-1)
second %d indicates that we're in a call: 0 for false (from the listed values this could be 0-1)
third %d indicates the current call set-up: 0 for idle (from the listed values this could be 0-3)
4. Finally, the headset sends a request to enable the indicator status update in the AG.
NOTE: From time to time, in my experience, what follows with some devices that the microphone volume information is sent after step 4.
ALSO NOTE: In the last article, we discussed the different AT commands which can be expected to be received by the AG, the additional commands not mentioned in this article are out of scope, i.e. "AT+BLDN" - last number dialed, which requires the developer to have some grasp of the RIL layer. However, from a purely simple view, sending back an "\r\nOK\r\n" to the headset for these commands will be sufficient. But, I've given enough information that if the developer does understand the RIL and has implemented telephony functionality, they will be able to piece together how processing of these AT commands can be done. Keep a look out for additional articles about RIL if interested.
OK, we're done with the initialization! WOW! that was a lot of information. Let's move on to network signaling.
As mentioned in the second article from the Interface layer, there are a few messages which will be signaled by the Interface Module to the Handler module that need to be supported: Incoming Call, Outgoing Call, Call Connected, Call Disconnected, Call Rejected, Call Busy, Call Waiting and Call Ringing. Listed below are the AT Commands sent to the headset for the corresponding Network event to signal the Headset into a different state
Incoming Call
Outgoing Call
Call Connected
Call Disconnected and Call Rejected
Call Waiting
Call Ringing
Almost done! Let’s move to the final section--voice routing.
In the last article, we talked about the Interface Module and how the Handler Module needs to support method calls from the Interface Layer. In this section, we'll go deeper into these calls to see how we can accurately process them. The commands are listed as:
Opening Audio
Closing audio
Opening a control channel
Closing a control channel
Getting/setting the mic volume
Getting/setting the speaker volume
Getting/setting the power mode
I leave it up to the reader to be able to handle the start and stop of your own AG service and also the mic/speaker and power getting and setting methods, since they are trivial.
Open/Close Control Channel: from the previous section, we learned how to connect to the Bluetooth socket to create a service level connection with the remote Bluetooth device. Hence, to handle the Open Control Channel command, we'll need to create some reliability and error checking around this socket connection. To close the control channel, all that is needed is to close the connected socket with a Winsock call. I leave it up to the reader to choose an appropriate method to shut down the socket connection.
Finally, the last piece is to open and close the audio, and this is not a trivial task.
First, we need to create a synchronous connection with the Bluetooth device. If you're lucky enough, you'll already have the libraries for creating this connection. Unfortunately, this is an article that describes a work-around if you don't have this library.
In the Windows directory, you'll find the DLL "btdrt.dll." You'll need to dynamically link to a few methods to get the audio connection. Listed below is example code to call the create synchronous connection and shut down the synchronous connection (which we'll need to route audio):
Once we have a service level connection, a SCO connection with the Bluetooth device,
we can call the audio driver to route to the Bluetooth chip.
To close the audio channel, first signal the audio driver to stop routing to the Bluetooth chip and close the SCO connection.
And that's it!
I hope that this has been an adventure. Play around with what has been shown; contact me if there is anything missing. I've omitted the getting and setting of the registry values with regards to different call states, volume settings and feature supports. However, if you're interested, take a look on the MSDN websites for registry settings related to the Audio Gateway and apply them into your architecture (and AT Command processing).
Best of luck in your Bluetooth Handsfree Adventures!
If you've read this series in its entirety drop me a line, I would love to hear what you've thought about the article.
Look for more articles related to Bluetooth and Telephony in the near feature by me!
Today, we'll get to the real guts, and this is what heroes are made of: the Handler Module. However, be warned that this is not for the timid, nor the faint of heart. We'll dive into the deep end right away!
This third and last installment will discuss how to create the Handler Module, which includes processing the services and network commands from the Interface module; processing the AT Commands from the Parser Module, starting the service/control level socket connection with the Bluetooth Device; and routing audio to the Bluetooth chip.
Yeah, that's right--what you've been waiting for! With this information, you can do loads of cool things like streaming mp3 music from your mobile device to your bluetooth headset. Wow! Imagine what else you can do with a little imagination!
The Guts of the Handler Module
The guts of the Handler Module contains four small sub-modules, the connection processing, the AT command processing, network notification processing and the services processing. We'll break down these four sub-modules in this article.
Creating a Socket Connection with the Bluetooth HandsFree device:
First, we assume that the Bluetooth device has already been paired with WM5 device, if not then do so right now! Start->Settings->Connections->Bluetooth... you know the rest, if not consult your WM5 Device User's Manual on how to pair with a Bluetooth device.
Once the pair has been completed and Microsoft's AG has been turned off (See the previous article), we're ready to create a service level connection with the Bluetooth device.
The pairing of the Bluetooth device will create an entry in the registry, under [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Bluetooth\\AudioGateway\\Devices]. In this registry folder will be numbered folders for the different Bluetooth devices that have been paired with the WM5 device. These folders consist of a socket address and the associated service value which the connection offers, in our case, the Handsfree profile service. To create a service/control-level socket connection, first iterate through the numbered folders to get the right address and service that corresponds to the device that you want to create a connection with. Then, simply connect with the Bluetooth device by using the standard winsock socket connection scheme as shown in the code below:
// make sure to include theseSimply put, get the registry value, create the socket, fill the SOCKADDR_BTH structure with the registry value and try to connect to the Bluetooth device.
#include <Winsock2.h>
#include <ws2bth.h>
// in a function to create the connection, do this
// create a Socket
m_socket = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
if (INVALID_SOCKET == m_socket)
{
// Failed to create socket: Log something here with GetLastError();
return;
}
// Fill the socket addr structure with the bluetooth device information
SOCKADDR_BTH socketAddr = {0};
socketAddr.serviceClassId = /*Service value from the registry goes here*/;
socketAddr.addressFamily = AF_BTH;
socketAddr.btAddr = /*Address from the registry goes here*/;
// connect to the device
if( SOCKET_ERROR == connect(m_socket, (SOCKADDR *)&m_addr, sizeof(m_addr)) )
{
// Failed to connect: Log something here with GetLastError();
return;
}
Parse AT Command Processing and Initialization Hand-shaking
So, we've created a service/control level connection with the Bluetooth device, now what? Well, once the Bluetooth device has detected a connection, it will send initialization codes as AT Commands. It's the Parser's job to parse these initialization codes and send them to us to send to the Handler Module for processing.
To receive these commands, we'll need to receive, from the socket, i.e.
// create an array of char with a size of RECEIVE_BUFFER_SIZE (that's your define)
// with s(the socket that was just connected)
int result = recv(s, buffer, RECEIVE_BUFFER_SIZE, 0);
And subsequently send out responses through the same socket the AT Command responses, i.e.
// "command" is a std::string and "s" is the socket
// that has the connection to the Bluetooth device
::send(s, command.c_str(), command.size(), 0);
The initialization hand-shaking is outlined below. However, for a detailed overview of this call flow go to the Bluetooth website .
Headset AG
| |
| AT+BRSF=<supported> |
|--------------------->| 1.
| |
| +BRSF:<AG supported> |
|<---------------------|
| OK |
|<---------------------|
| |
| AT+CIND=? |
|--------------------->| 2.
| |
| +CIND:... |
|<---------------------|
| OK |
|<---------------------|
| |
| AT+CIND |
|--------------------->| 3.
| |
| +CIND:... |
|<---------------------|
| OK |
|<---------------------|
| |
| AT+CMER= |
|--------------------->| 4.
| OK |
|<---------------------|
| |
| |
Note that the OK command send string as follows: "\r\nOK\r\n"
1. The Headset first sends the AG the features that it supports and in turn the AG sends back features that are supported.
Listed below are constants to define the end of the AT command (after the "=" sign)
#define HF_FEATURE_EC_ANDOR_NR 0x01
#define HF_FEATURE_CALL_WAITING_THREEWAY_CALLING 0x02
#define HF_FEATURE_CLI_PRESENTATION 0x04
#define HF_FEATURE_VOICE_RECOGNITION_ACTIVATION 0x08
#define HF_FEATURE_REMOTE_VOLUME_CONTROL 0x10
#define AG_FEATURE_THREE_WAY_CALLING 0x01
#define AG_FEATURE_EC_ANDOR_NR 0x02
#define AG_FEATURE_VOICE_RECOGNITION 0x04
#define AG_FEATURE_INBAND_RINGTONE 0x08
#define AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG 0x10
#define AG_FEATURE_REJECT_A_CALL 0x20
These values can be OR'ed together to create the support value, e.g. if we only want to support three-way calling and reject a call features the value would be 0x21. Subsequently, if the headset only supports voice recognition activation and remote volume control, it would send to the AG 0x18. Hence, the string which would be sent out from the AG to the headset would be "\r\n+BRSF:%d\r\n", where %d is equal to the features supported.
Note the new line and carriage return before and after the command. These are classic AT Command delimiters. See the Bluetooth Spec for details.
2. The next message sent from the Headset to the AG is a request by the Headset to the AG to indicate which indicators are supported by the AG. An example of the response string is as follows: "\r\n+CIND:
(\"service\",(0,1)),(\"call\",(0,1)),(\"callsetup\",(0-3))\r\n"
3. The headset then requests the status of those indicators. In our example we respond with the string "\r\n+CIND: %d,%d,%d\r\n", where the %d are defined:
first %d indicates that we have cellular service (from the listed values this could be 0-1)
second %d indicates that we're in a call: 0 for false (from the listed values this could be 0-1)
third %d indicates the current call set-up: 0 for idle (from the listed values this could be 0-3)
4. Finally, the headset sends a request to enable the indicator status update in the AG.
NOTE: From time to time, in my experience, what follows with some devices that the microphone volume information is sent after step 4.
ALSO NOTE: In the last article, we discussed the different AT commands which can be expected to be received by the AG, the additional commands not mentioned in this article are out of scope, i.e. "AT+BLDN" - last number dialed, which requires the developer to have some grasp of the RIL layer. However, from a purely simple view, sending back an "\r\nOK\r\n" to the headset for these commands will be sufficient. But, I've given enough information that if the developer does understand the RIL and has implemented telephony functionality, they will be able to piece together how processing of these AT commands can be done. Keep a look out for additional articles about RIL if interested.
OK, we're done with the initialization! WOW! that was a lot of information. Let's move on to network signaling.
Network Signaling of In-call States:
As mentioned in the second article from the Interface layer, there are a few messages which will be signaled by the Interface Module to the Handler module that need to be supported: Incoming Call, Outgoing Call, Call Connected, Call Disconnected, Call Rejected, Call Busy, Call Waiting and Call Ringing. Listed below are the AT Commands sent to the headset for the corresponding Network event to signal the Headset into a different state
Incoming Call
"\r\n+BSIR:0\r\n"
"\r\n+CIEV:3,1\r\n"
Outgoing Call
"\r\n+CIEV:3,3\r\n"
Call Connected
"\r\n+CIEV:2,1\r\n"
"\r\n+CIEV:3,0\r\n"
Call Disconnected and Call Rejected
"\r\n+CIEV:2,0\r\n"
Call Waiting
"\r\n+CCWA:\"0\",0,1\r\n"
Call Ringing
"\r\nRING\r\n"
Almost done! Let’s move to the final section--voice routing.
Services Processing:
In the last article, we talked about the Interface Module and how the Handler Module needs to support method calls from the Interface Layer. In this section, we'll go deeper into these calls to see how we can accurately process them. The commands are listed as:
Opening Audio
Closing audio
Opening a control channel
Closing a control channel
Getting/setting the mic volume
Getting/setting the speaker volume
Getting/setting the power mode
I leave it up to the reader to be able to handle the start and stop of your own AG service and also the mic/speaker and power getting and setting methods, since they are trivial.
Open/Close Control Channel: from the previous section, we learned how to connect to the Bluetooth socket to create a service level connection with the remote Bluetooth device. Hence, to handle the Open Control Channel command, we'll need to create some reliability and error checking around this socket connection. To close the control channel, all that is needed is to close the connected socket with a Winsock call. I leave it up to the reader to choose an appropriate method to shut down the socket connection.
Finally, the last piece is to open and close the audio, and this is not a trivial task.
First, we need to create a synchronous connection with the Bluetooth device. If you're lucky enough, you'll already have the libraries for creating this connection. Unfortunately, this is an article that describes a work-around if you don't have this library.
In the Windows directory, you'll find the DLL "btdrt.dll." You'll need to dynamically link to a few methods to get the audio connection. Listed below is example code to call the create synchronous connection and shut down the synchronous connection (which we'll need to route audio):
//******Note that the developer needs to call
//before these calls call ::LoadLibrary(...)
//once finished with these calls call ::FreeLibrary(...)
// Code for creating a SCO connection
typedef int (*BthCreateSCOConnectionWrapper)( BT_ADDR*, unsigned short* );
BthCreateSCOConnectionWrapper proc;
HMODULE hMod;
if(NULL == (hMod = ::GetModuleHandle(L"\\Windows\\btdrt.dll")))
{
// Log something since we can't find the module
return;
}
if(NULL == (proc = (BthCreateSCOConnectionWrapper)::GetProcAddress(hMod,
L"BthCreateSCOConnection")))
{
// Log something since we can't find the method
return;
}
//ptrBTAddr is a pointer to the Bluetooth Address--the same address we created the service level connection with
//ptrHandle is a pointer to an unsigned short which will be filled with the handle to the SCO connection
proc(ptrBTAddr, ptrHandle);
// Code for closing the SCO connection
typedef int (*BthCloseConnectionWrapper)( unsigned short );
BthCloseConnectionWrapper proc;
HMODULE hMod;
if(NULL == (hMod = ::GetModuleHandle(L"\\Windows\\btdrt.dll")))
{
// Log something since we can't find the module
return;
}
if(NULL == (proc = (BthCloseConnectionWrapper)::GetProcAddress(hMod,
L"BthCloseConnection")))
{
// Log something since we can't find the method
return;
}
// handle is the pointer to the unsigned short value that was passed in to create the SCO connection
return proc( handle );
Once we have a service level connection, a SCO connection with the Bluetooth device,
we can call the audio driver to route to the Bluetooth chip.
#define WODM_BT_SCO_AUDIO_CONTROL 500
waveOutMessage(0, WODM_BT_SCO_AUDIO_CONTROL, 0, TRUE);
To close the audio channel, first signal the audio driver to stop routing to the Bluetooth chip and close the SCO connection.
waveOutMessage(0, WODM_BT_SCO_AUDIO_CONTROL, 0, FALSE);
And that's it!
I hope that this has been an adventure. Play around with what has been shown; contact me if there is anything missing. I've omitted the getting and setting of the registry values with regards to different call states, volume settings and feature supports. However, if you're interested, take a look on the MSDN websites for registry settings related to the Audio Gateway and apply them into your architecture (and AT Command processing).
Best of luck in your Bluetooth Handsfree Adventures!
If you've read this series in its entirety drop me a line, I would love to hear what you've thought about the article.
Look for more articles related to Bluetooth and Telephony in the near feature by me!
Labels: audio gateway, Bluetooth, c++
Thursday, May 10, 2007
Why, that's just SNAPI!
Windows Mobile 5 is the new version of Windows CE 5.0 that is targeted at the Windows mobile market. It is the underpinnings of both the Pocket PC and Smartphone editions.
Windows Mobile 5 comes packed with some new (native) features and APIs such as:
SNAPI is a great improvement to Windows Mobile, long overdue. Basically SNAPI is a registry notification system. You receive events when keys or values you are watching are changed in the registry. You can use SNAPI on any sub-key or value in the registry. However there are some pre-defined helpers specifically related to the state of the device. For a full list see snapi.h.
In my last Windows Mobile project, I used SNAPI for many things, some of which I will get into in later posts, but perhaps the easiest use was to register for the battery state notifications.
Some devices show the battery strength indicator in the shell notification area of the screen and some show it only on the "Today" screen; we had a requirement to show the battery strength on the application's main screen.
This is a common requirement, in past non-WM5 projects, we had to set up a thread and to poll the value, update the screen, then repeat. Not hard, just tedious.
In WM5 however, all we needed to do was to register to that notification. Registering is easy:
RegistryNotifyCallback registers a transient notification request that will be used to notify the caller via the specified callback when the value of the sub-key changes. Transient simply means that the registration to the registry notification will not survive a device reset.
It's important to note that when notified of changes via the callback, the callback is executed on a private thread separate from the one called RegistryNotifyCallback.
If the value or the key does not exist at time of registration, the caller will be notified when the value or key is added.
The callback has to have the following signature:
Now that we're registered, the supplied callback will be called whenever the battery strength changes. A sample implementation of the call back could be as follows:
See, very simple. When don't want to get the notifications anymore, just call:
This API has to be called to stop receiving the notification and to close the handle.
There are however, some caveats with using this API: how often the value changes in the registry is dependent on the device. Some devices only update the value when the actual strength hits a predefined boundary, such as 75%, 50%, or 25%. The value also gets changed when you plug in the device to be charged, and then unplugged. On some devices I see the strength go briefly from 100% to 0% when un-cradled.
As you can see SNAPI provides alot of functionality, and is a pretty flexible API. In future posts I hope to explore this API in further detail.
Windows Mobile 5 comes packed with some new (native) features and APIs such as:
- Direct X APIs
- GPS Intermediate Driver
- An API to get the unique id of a device
- Some APIs dealing with DRM
- A couple of APIs to facilitate ActiveSync integration
- Picture selection
- Camera capture
- State Notification API (SNAPI)
SNAPI is a great improvement to Windows Mobile, long overdue. Basically SNAPI is a registry notification system. You receive events when keys or values you are watching are changed in the registry. You can use SNAPI on any sub-key or value in the registry. However there are some pre-defined helpers specifically related to the state of the device. For a full list see snapi.h.
In my last Windows Mobile project, I used SNAPI for many things, some of which I will get into in later posts, but perhaps the easiest use was to register for the battery state notifications.
Some devices show the battery strength indicator in the shell notification area of the screen and some show it only on the "Today" screen; we had a requirement to show the battery strength on the application's main screen.
This is a common requirement, in past non-WM5 projects, we had to set up a thread and to poll the value, update the screen, then repeat. Not hard, just tedious.
In WM5 however, all we needed to do was to register to that notification. Registering is easy:
// Battery strength
NOTIFICATIONCONDITION nc = { 0 };
nc.ctComparisonType = REG_CT_ANYCHANGE;
nc.dwMask = SN_POWERBATTERYSTRENGTH_BITMASK;
nc.TargetValue.dw = 0; // ignored for REG_CT_ANYCHANGE
HREGNOTIFY regNotify;
HRESULT hr = ::RegistryNotifyCallback(
SN_POWERBATTERYSTRENGTH_ROOT,
SN_POWERBATTERYSTRENGTH_PATH,
SN_POWERBATTERYSTRENGTH_VALUE,
onBatteryStrengthChangedCallback,
0,
&nc,
®Notify );
// save hr if successful somewhere for later use
RegistryNotifyCallback registers a transient notification request that will be used to notify the caller via the specified callback when the value of the sub-key changes. Transient simply means that the registration to the registry notification will not survive a device reset.
It's important to note that when notified of changes via the callback, the callback is executed on a private thread separate from the one called RegistryNotifyCallback.
If the value or the key does not exist at time of registration, the caller will be notified when the value or key is added.
The callback has to have the following signature:
void onBatteryStrengthChangedCallback(
HREGNOTIFY hNotify,
DWORD dwUserData,
const PBYTE pData,
const UINT cbData )
Now that we're registered, the supplied callback will be called whenever the battery strength changes. A sample implementation of the call back could be as follows:
// pData contains the new value for SN_POWERBATTERYSTRENGTH_VALUE.
DWORD batteryStrength;
batteryStrength = (*(DWORD*) pData);
batteryStrength = ( batteryStrength & SN_POWERBATTERYSTRENGTH_BITMASK ) >> 16;
// update the screen
...
See, very simple. When don't want to get the notifications anymore, just call:
// hr was what was returned from registering successfully to the notification.
::RegistryCloseNotification( regNotify );
This API has to be called to stop receiving the notification and to close the handle.
There are however, some caveats with using this API: how often the value changes in the registry is dependent on the device. Some devices only update the value when the actual strength hits a predefined boundary, such as 75%, 50%, or 25%. The value also gets changed when you plug in the device to be charged, and then unplugged. On some devices I see the strength go briefly from 100% to 0% when un-cradled.
As you can see SNAPI provides alot of functionality, and is a pretty flexible API. In future posts I hope to explore this API in further detail.
Labels: c++, PocketPC, SNAPI, WM5