Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
6/4/2007
Jim Wilson, JW Hedgehog, Inc.
May 2007
This month's column completes Jim's three-part State and Notifications Broker series. In this installment Jim covers a variety of State and Notifications Broker-related topics including over 40 new Windows Mobile 6 State and Notifications Broker values, how to create and handle custom notifications, multithreaded notification handling, the nuances of sharing persistent notifications across native and managed applications, and some unexpected details in the managed State and Notifications Broker class' bitmask handling implementation. Just as in the previous two installments, Jim covers both native and managed code throughout.
Microsoft® .NET Compact Framework 1.0
Microsoft .NET Compact Framework 2.0
Microsoft Visual Studio® 2005
Microsoft Windows Mobile® 5.0
Microsoft Windows Mobile 6
Introduction
New Windows Mobile 6 State and Notification Values
Custom State and Notifications Broker Values
Custom State and Notifications Broker Values in Native Code – Housekeeping
Custom State and Notifications Broker Values in Native Code – Improving Efficiency
Threading and Notification Handling
Persistent Notifications – Native and Managed Application Compatibility
One Last Thing: Bitmasks and Managed Applications
Conclusion
Welcome to part three of the State and Notifications Broker API series. We have just a few more things to cover and then you will know pretty much everything there is to know about using the State and Notifications Broker API.
If you haven't been following along with the series or if you need a refresher, both Part 1 and Part 2 of this series are still available. Part 1 focuses on the basics of using the State and Notifications Broker API to access state values and receive notification of changes. Part 2 focuses on notification specialization, things such as conditional notifications, batch notifications, and persistent notifications.
In this installment, we'll cover the new Windows Mobile 6 State and Notifications Broker values, custom notifications, multithreaded notification handling, sharing persistent notifications across native and managed applications, and the implementation of bitmask handling in the managed State and Notifications Broker classes. Like always, we cover both native and managed code throughout.
Windows Mobile 6 introduces over 40 new State and Notifications Broker values. The new values cover a number areas including Bluetooth status information, Wi-Fi status information, various lock states, cellular system availability, and more.
The addition of these new State and Notifications Broker values demonstrates the importance of the State and Notifications Broker API managed class, SystemState
, being part of the Windows Mobile platform rather than being tied to a particular version of the .NET Compact Framework library. If the SystemState
class were part of a .NET Compact Framework library, we'd have to wait for the next release of the .NET Compact Framework for an updated version of the SystemState
class that includes the static properties that expose the new state values. As part of the Windows Mobile platform, the SystemState
class doesn't have this problem because the assembly containing the SystemState
class, Microsoft.WindowsMobile.Status, is contained in the Windows Mobile device itself and is therefore updated with each new Windows Mobile release. Installing the Windows Mobile 6 SDK on your development computer installs an updated version of the design-time Microsoft.WindowsMobile.Status assembly so that the compilers and Visual Studio 2005 are aware of the new properties.
Rather than using a class-based abstraction to access the State and Notifications Broker state values, native applications read the values directly from the registry and therefore need the registry path information at compile time. The Windows Mobile 6 version of the snapi.h header file is updated to contain the macro definitions for the new state value registry paths.
Appendix A contains the list of the new Windows Mobile 6 State and Notifications Broker state values.
Note
The Windows Mobile 6 SDK includes an important fix that affects both native and managed code. In the Windows Mobile 5.0 SDK, the SystemState.KeyboardPresent
property and the corresponding macro SN_KEYBOARDPRESENT_ROOT
in snapi.h incorrectly referred to the HKEY_LOCAL_MACHINE
registry hive as containing the keyboard present value; this value is actually stored under the HKEY_CURRENT_USER
hive. The Windows Mobile 6 SDK corrects this issue in both the SystemState
class and in the snapi.h macros.
As you've seen throughout this series, the State and Notifications Broker provides a powerful and easy-to-use API that provides a central resource for all Windows Mobile–related information including a publish/subscribe model for applications wanting to receive notifications of changes in those state values. The State and Notifications Broker is not limited to exposing state values related to Windows Mobile itself; any application can expose state information through the State and Notifications Broker.
Just as with the Windows Mobile state values, applications can subscribe to these application-defined state values and the State and Notifications Broker will notify the subscribing application when the value changes. The State and Notifications Broker API provides all of the same features for application-defined state values at it does for the Windows Mobile state values, including conditional notifications, batch notifications, and persistent notifications.
Note
For a refresher on the details of conditional notifications, batch notifications, and persistent notifications, check out Part 2 of this series.
The documented guidelines indicate that you should create application-defined state values under either the HKEY_CURRENT_USER\Software\State
registry key or the HKEY_LOCAL_MACHINE\Software\State
registry key. Unless you're planning to sign your application as a privileged application, I recommend that you avoid the HKEY_LOCAL_MACHINE
hive and always store your application-defined state values under the HKEY_CURRENT_USER\Software\State
registry key.
Future releases of Windows Mobile will likely have greater security restrictions than Windows Mobile has today. As part of the increased security restrictions, there's a strong possibility that Windows Mobile will restrict which applications are allowed to modify system-related areas such as the registry keys and values under HKEY_LOCAL_MACHINE
. Keeping your application-defined state values under the HKEY_CURRENT_USER
hive helps you to reduce the likelihood that your application will encounter security-related compatibility issues with future versions of Windows Mobile.
In general, you should organize all application-defined state values for a particular application on a single registry key. Keeping the state values tied to a single registry key creates a logical relationship between common state values and makes working with the state values much easier during application development and debugging. A good example of grouping common state values on a single registry key is the registry key containing the Pocket Outlook® Task values, HKEY_CURRENT_USER\Software\State\Tasks
, as shown in Figure 1.
Figure 1. The Pocket Outlook Task state values in the registry
To minimize the likelihood that state values from different programs and different organizations collide, I suggest that you use a naming hierarchy similar to that used to store application configuration information. As you may recall, the Windows guidelines indicate that application-specific configuration values should be located under the HKEY_CURRENT_USER\Software
registry key, with each organization creating a key under the Software
key, and each application having a key under the organization key. For example, figure 2 shows the registry hierarchy that stores the Microsoft ActiveSync® configuration information.
Figure 2. The Microsoft ActiveSync configuration values in the registry
When creating the application-defined state values, we'll use this same naming hierarchy except that the application-defined state values are placed under the HKEY_CURRENT_USER\System\State
registry key rather than under the HKEY_CURRENT_USER\Software
registry key. Using this naming scheme, all state values for an application named ExampleApp from an organization named Proseware are located on the HKEY_CURRENT_USER\Software\State\Proseware\ExampleApp
registry key.
When working in native code, there is very little difference between working with application-defined state values and Windows Mobile state values. You still use the RegistryGetString
and RegistryGetDWORD
functions to retrieve the state values and you still use the RegistryNotifyWindow
and RegistryNotifyApp
functions to register for value change notifications.
The only special consideration is how an application creates and changes state values. For this, you use the RegistrySetString
and RegistrySetDWORD
functions. Like RegistryGetString
and RegistryGetDWORD
, the only difference between RegistrySetString
and RegistrySetDWORD
is the registry data type on which they operate. Like the other State and Notifications Broker API functions, RegistrySetString
and RegistrySetDWORD
require that you include the <regext.h>
header file.
The following code example demonstrates using RegistrySetString
to set a registry value named "Sales Region" on the HKEY_CURRENT_USER\System\State\Proseware\ExampleApp
registry key.
#define EXAMPLEAPP_SALESREGION_ROOT HKEY_CURRENT_USER
#define EXAMPLEAPP_SALESREGION_PATH _T("System\\State\\Proseware\\ExampleApp")
#define EXAMPLEAPP_SALESREGION_VALUE _T("Sales Region")
HRESULT hr = RegistrySetString(EXAMPLEAPP_SALESREGION_ROOT, EXAMPLEAPP_SALESREGION_PATH,
EXAMPLEAPP_SALESREGION_VALUE, "Northeast");
An application can now register to be notified of changes in the "Sales Region" state value using the RegistryNotifyWindow
function just as it would when registering for Windows Mobile state value change notifications. The following code demonstrates registering for "Sales Region" state value change notifications.
HRESULT hr = RegistryNotifyWindow(EXAMPLEAPP_SALESREGION_ROOT, EXAMPLEAPP_SALESREGION_PATH,
EXAMPLEAPP_SALESREGION_VALUE, g_hWnd, WM_SALESREGIONCHANGED, 0, NULL, &g_hNotifySalesRegion);
Note
If you need a refresher on registering for state value notifications, check out the Receiving Notifications section in Part 1 of this series. For a refresher in registering for persistent notifications, check out the Persistent Notifications section in Part 2 of this series.
The RegistrySetString
and RegistrySetDWORD
functions require that the key you specify exist. If the key does not exist, the function fails. One way to ensure that your application successfully sets the registry value is to check for the registry key before calling the RegistrySetXXX
functions. In my opinion, this solution is unnecessarily costly. The registry key likely exist the vast majority of the time that your application calls the RegistrySetXXX
functions. The only time the key doesn't exist is the very first time your application calls either of the RegistrySetXXX
functions to set a value on the application's registry key.
Because you can normally expect the key to exist, I prefer to simply call the RegistrySetXXX
function and check the return code. If the RegistrySetXXX
function fails, create the appropriate registry key and then try to set the value again. The following code example demonstrates this technique.
HRESULT SetSalesRegion(TCHAR *salesRegion)
{
HRESULT hr = RegistrySetString(EXAMPLEAPP_SALESREGION_ROOT, EXAMPLEAPP_SALESREGION_PATH,
EXAMPLEAPP_SALESREGION_VALUE, salesRegion);
if (FAILED(hr))
{
CreateRegistryKeyForExampleApp();
hr = RegistrySetString(EXAMPLEAPP_SALESREGION_ROOT, EXAMPLEAPP_SALESREGION_PATH,
EXAMPLEAPP_SALESREGION_VALUE, salesRegion);
}
return hr;
}
The preceding code example wraps the call to the RegistrySetString
function in an application function named SetSalesRegion
. The SetSalesRegion
function encapsulates all of the necessary logic to set the registry value and create the registry key if necessary. When the SetSalesRegion
function is called, the function immediately calls the RegistrySetString
function to store the salesRegion
parameter value in the registry. If the RegistrySetString
function executes successfully, there is no further work for the SetSalesRegion
function so the SetSalesRegion
function returns. If, on the other hand, the RegistrySetString
function should fail, the SetSalesRegion
function calls the CreateRegistryKeyForExampleApp
function that, as its name implies, creates the registry key that will contain all of the state values for the ExampleApp application. When the CreateRegistryKeyForExampleApp
function returns, the SetSalesRegion
function again calls the RegistrySetString
function to store the value of the salesRegion
parameter. With the registry key now created, the call to RegistrySetString
succeeds.
The following code example shows the implementation of the CreateRegistryKeyForExampleApp
function.
void CreateRegistryKeyForExampleApp()
{
HKEY hKey = NULL;
long errorCode = 0;
errorCode = RegCreateKeyEx(EXAMPLEAPP_SALESREGION_ROOT, EXAMPLEAPP_SALESREGION_PATH,
0, NULL, 0, NULL, NULL, &hKey, NULL);
if (errorCode == 0)
RegCloseKey(hKey);
}
As the preceding code example shows, the implementation of the CreateRegistryKeyForExampleApp
function is very simple. The CreateRegistryKeyForExampleApp
function creates the required registry key, HKEY_CURRENT_USER\Software\State\Proseware\ExampleApp
, by calling the RegCreateKeyEx
function. The returned registry key handle is then closed. The registry key handle is no longer needed because the call to the RegistrySetString
function in the SetSalesRegion
function accesses the registry key using a registry path string rather than using the registry key handle.
Note
Notice that the CreateRegistryKeyForExampleApp
function only creates the registry key; the function does not create the registry value. Although the RegistrySetXXX
functions require that the registry key exist, there is no such requirement for the registry value. The RegistrySetXXX
functions will automatically create the registry value if the value doesn't already exist.
By accepting the registry key path as a string, the RegistrySetString
and RegistrySetDWORD
functions make setting state values easy. You do not have to open, manage, or close a registry handle; instead you simply specify a string containing the registry key path. Although convenient, this approach has a cost. The RegistrySetString
and RegistrySetDWORD
functions must traverse the registry tree, open the registry key, set the value, and close the registry key each time you call either of these functions. The convenience of passing the registry key path as a string works well in situations where an application only occasionally changes a state value; however, for applications that make frequent changes to a state value requiring the RegistrySetString
and RegistrySetDWORD
functions to repeatedly open and close the registry handle each time the application sets the state value results in a great deal of unnecessary overhead.
You can avoid incurring this unnecessary overhead by having your application explicitly open the registry key and store the returned registry key handle. Each time your application needs to set the state value, pass the registry key handle to the RegistrySetString
or RegistrySetDWORD
function instead of passing the registry key path. With the registry key handle, the RegistrySetString
and RegistrySetDWORD
functions access the registry key directly; this makes the process of setting the state value very efficient.
Note
If you're monitoring a string state value that changes frequently, the same efficiency concerns exist. As you'll recall from Part 1, notification messages for DWORD
state values include the new value in the message; however, string values require your application to read the new value from the registry. To improve the efficiency of an application monitoring a frequently changing string value, you should follow the same efficiency guidelines discussed in this section when calling the RegistryGetString
function.
The following example code demonstrates explicitly opening the registry key and passing the registry key handle to the RegistrySetString
function.
static HKEY hRegExampleApp = NULL;
HRESULT SetSalesRegion(TCHAR *salesRegion)
{
if (hRegExampleApp == NULL)
RegCreateKeyEx(EXAMPLEAPP_SALESREGION_ROOT, EXAMPLEAPP_SALESREGION_PATH,
0, NULL, 0, NULL, NULL, &hRegExampleApp, NULL);
HRESULT hr = RegistrySetString(hRegExampleApp, NULL, EXAMPLEAPP_SALESREGION_VALUE, salesRegion);
return hr;
}
In the preceding code example, the SetSalesRegion
function starts by checking the hRegExampleApp
static field to see if the registry key is open. On the first call to this function, the registry key handle will be NULL, resulting in the SetSalesRegion
function calling the RegCreateKeyEx
function to retrieve a handle to the appropriate registry key. The returned handle is stored in the hRegExampleApp
static field.
It may seem strange to always call the RegCreateKeyEx
function rather than the RegOpenKeyEx
function; calling the RegCreateKeyEx
function may seem to imply that the application never expects the registry key to exist. This however is not the case. The reason the SetSalesRegion
function uses the RegCreateKeyEx
function is because the RegCreateKeyEx
function is architected to be flexible. If the specified key does not exist, the RegCreateKeyEx
function creates and opens the key; if the specified key already exists then the RegCreateKeyEx
function simply opens the key. In either case, the function returns a handle to the opened key.
Note
If your application needs to know whether the RegCreateKeyEx
function creates the key or simply opens it, you can pass a reference to a DWORD
variable as the last parameter to the RegCreateKeyEx
function. When the RegCreateKeyEx
function returns, the DWORD
variable contains a value indicating the specific action the RegCreateKeyEx
function performed. The two possible values are REG_CREATED_NEW_KEY
or REG_OPENED_EXISTING_KEY
.
With the key handle stored in the hRegExampleApp
static field, all successive calls to the SetSalesRegion
function utilize the open registry key's handle and efficiently update the state value without needing to perform tree traversal or incur the overhead of needing to open the key.
Note
Just a little note for any readers who may not have had the opportunity to use their C programming skills in a while: in the preceding code example the keyword static
is used in the C context rather than in the C++ context. This means that the hRegExampleApp
field will maintain its value between calls to the SetSalesRegion
function.
As you know from Part 1 of this series, you use the SystemState
class to interact with Windows Mobile state values. The SystemState
class encapsulates the registry details associated with the Windows Mobile state values and allows you to interact with these state values through static properties on the SystemState
class or by specifying the SystemProperty
enumeration to the SystemState
class constructor. The SystemState
class is actually a thin veneer over a class named RegistryState
, which is the class that takes care of all of the details of interacting with and monitoring the state values. The SystemState
class is provided as a convenience to simplify accessing the system state values.
Note
The implementation of the SystemState
class uses the RegistryState
class to do the actual work. The SystemState
class maintains a HashTable
that maps the SystemProperty
enumeration values to the appropriate registry keys and value names. When an instance of the SystemState
class is created, the SystemState
constructor looks up the registry information that corresponds to the SystemProperty
value passed to the SystemState
constructor; the SystemState
constructor then uses the registry information to create an internal instance of the RegistryState
class. Once constructed, most of the SystemState
class members are simply wrappers over the corresponding RegistryState
members.
Interacting with custom state values requires that you have a way to explicitly specify the registry key and value name. To do this, you bypass the SystemState
class and use the RegistryState
class directly.
The following code example demonstrates how to construct an instance of the RegistryState
class that is associated with the Sales Region value on the HKEY_CURRENT_USER\Software\State\Proseware\ExampleApp
registry key.
const string exampleAppSalesRegionKey = @"HKEY_CURRENT_USER\Software\State\Proseware\ExampleApp";
const string exampleAppSalesRegionValue = "Sales Region";
RegistryState salesRegionState = new RegistryState(exampleAppSalesRegionKey, exampleAppSalesRegionValue);
As you can see in the preceding code example, constructing the RegistryState
instance is as simple as specifying the registry key as the first constructor parameter and the value name as the second parameter. Once constructed, a RegistryState
instance provides basically the same features as an instance of the SystemState
class: the CurrentValue
property retrieves the current state value from the registry and the Changed
event fires each time the state value in the registry changes.
The following example code demonstrates accessing the Sales Region state value and monitoring the value for changes.
public partial class FormMain : Form
{
const string exampleAppSalesRegionKey = @"HKEY_CURRENT_USER\Software\State\Proseware\ExampleApp";
const string exampleAppSalesRegionValue = "Sales Region";
private void FormMain_Load(object sender, EventArgs e)
{
_salesRegionState =
new RegistryState(exampleAppSalesRegionKey, exampleAppSalesRegionValue);
tbCurrentRegion.Text = (string)_salesRegionState.CurrentValue;
_salesRegionState.Changed += new ChangeEventHandler(_salesRegionState_Changed);
}
RegistryState _salesRegionState;
// ...
// ...
}
In the preceding code example, the RegistryState
variable, _salesRegionState
, is declared as a class-level variable. When the application form loads, the RegistryState
instance is created and associated with the ExampleApp's Sales Region
state value. The application retrieves the current value for the Sales Region
state value and displays it in a textbox, tbCurrentRegion
. Finally, the application registers to receive notifications anytime that the Sales Region
state value changes. As you can see, other than the arguments passed to the constructor, working with the RegistryState
class is just like working with the SystemState
class.
In addition to providing support for reading the registry state value and monitoring the state value for changes, the RegistryState
class also allows you to set the registry state value. You use the same RegistryState
property to set the state value that you use when reading the state value, the CurrentValue
property. The following example code demonstrates updating the ExampleApp's Sales Region
state value.
private void SetSalesRegion(string newSalesRegion)
{
_salesRegionState.CurrentValue = newSalesRegion;
}
As the preceding code example shows, updating the registry state value really is as simple as assigning the new value to the CurrentValue
property.
Note
Both the RegistryState
and SystemState
classes inherit from a common base class, StateBase
. The StateBase
class is the class that actually defines the virtual property CurrentValue
. The StateBase
class defines the property to have both a getter and a setter, which is evident by the RegistryState
class' CurrentValue
property implementation. This implies that the SystemState
class must also support setting the CurrentValue
property; however, that's not quite true. The SystemState
class does provide a CurrentValue
property setter method but the SystemState
class implements the setter to throw a NotSupportedException
. This prevents applications from attempting to modify Windows Mobile system state values.
As you can see, the RegistryState
class provides one-stop shopping for interacting with custom state values. It provides support for consumers of a state value by making the value available through the CurrentValue
property and exposing change notifications through the Changed
event. For publishers of a state value, the RegistryState
class supports changing the state value by simply assigning the new value to the CurrentValue
property.
Just as when passing the registry path as a string to the RegistryGetString
, RegistrySetString
, RegistryGetDWORD
, and RegistrySetDWORD
C functions, the .NET Compact Framework RegistryState
class and SystemState
class must also incur the overhead of traversing the registry path string, opening the registry key, accessing the registry value, and closing the registry key each time your application accesses a registry state value. However, unlike the C functions, the RegistryState
class and SystemState
class do not provide support for accessing the registry keys directly with an existing registry handle; these classes only support specifying the registry location using a registry path string.
If you are developing an application that frequently updates a system state value and you find that the overhead of the RegistryState
class or SystemState
class are negatively affecting your application performance, you can update the registry directly using the registry-related classes in the Microsoft.Win32
namespace. In this case, you simply open the key containing the value and then modify the registry state value just as you would normally modify any registry value. Even though you're using the regular registry-related classes rather than the RegistryState
class, the State and Notifications Broker will still notify any applications that have subscribed to change notifications for the value.
The following code example demonstrates updating the registry value using the Microsoft.Win32
registry-related classes.
const string exampleAppSalesRegionSubKey = @"Software\State\Proseware\ExampleApp";
const string exampleAppSalesRegionValue = "Sales Region";
RegistryKey _salesRegionSubKey = null;
private void FormMain_Load(object sender, EventArgs e)
{
_salesRegionSubKey =
Registry.CurrentUser.CreateSubKey(exampleAppSalesRegionSubKey);
}
RegistryState _salesRegionState;
private void SetSalesRegion(string newSalesRegion)
{
_salesRegionSubKey.SetValue(exampleAppSalesRegionValue, newSalesRegion);
}
private void FormMain_Closing(object sender, CancelEventArgs e)
{
if (_salesRegionSubKey != null)
_salesRegionSubKey.Close();
}
// ...
// ... Rest of application logic will call SetSalesRegion
// ... as required to update the state value during
// ... application processing
// ...
In situations where your application frequently updates the Sales
Region
state value, the preceding code example provides significantly improved performance over using the RegistryState
class. In the preceding implementation, your application opens the registry key when the form loads and keeps the registry key open until the form is closing. By keeping the registry key open, each time the application calls the SetSalesRegion
method, the new value is directly stored into the registry without the additional overhead of reopening the registry key or traversing the registry hierarchy.
In many cases, when applications are monitoring a state value, the code that executes on each state value change is relatively short and requires a few milliseconds or less to complete. For example, a simple state value change handler might set a Boolean
flag or update the application display. There are, however, those cases where the state value change handler is more complex, requiring a more substantial period of time to complete. Handling such a request on the main application thread causes the user interface to momentarily freeze each time the event occurs. An application would also have similar issues if a monitored state value goes through periods of rapid change. In either case, the application user interface will appear sluggish or unresponsive because the state value change notifications are monopolizing the main application thread.
To avoid these and similar issues, you might consider architecting your application so that the code responsible for monitoring these state values executes on a thread other than the main application thread. Introducing this sort of multithreaded implementation can help your application provide a much more positive user experience because the main application thread remains available to respond to user events.
The implications of using the State and Notifications Broker API in a multithreaded application differ for native and managed developers. Native developers explicitly create the window that receives State and Notifications Broker notifications. With this being the case, native developers who want their application to handle state value change notifications on a thread separate from the main application thread can simply create a new thread and then have the new thread create the window that will handle the state value change notifications. This is no different than any other situation in which you want to handle a particular window's messages on a thread separate from the main application thread.
Note
If you're not familiar with the issues associated with creating windows in a multithreaded environment or if you would like a refresher on the subject, you can check out Using Messages and Message Queues and About Messages and Message Queues.
For managed developers, the details of window creation and message handling are abstracted by the RegistryState
class; therefore, the RegistryState
class needs to expose a facility for an application to request that the message handling occur on a separate thread. The RegistryState
class exposes such a facility through the constructor's useFormThread
parameter.
The RegistryState
class exposes several constructors, two of which expose a Boolean
parameter, useFormThread
. When you call the RegistyrState
constructor with the useFormThread
parameter set to false
, the RegistryState
class creates a new thread that then creates the window and performs the window's message handling. For simplicity, we'll call this new thread the RegistryState
thread. The newly created RegistryState
thread handles all the state value change notification processing for that RegistryState
class instance, which leaves the application's main thread free to handle user events. Keep in mind that the SystemState
class uses the RegistryState
class internally; therefore, the multithreading issues that apply to the RegistryState
class apply equally to the SystemState
class.
The following code example demonstrates using the SystemState
class to monitor for changes in network connectivity using a thread separate from the main application thread; the code example displays the text "Connected" or "Not Connected" in a StatusBar
each time the application gains or loses connectivity.
SystemState _connectionsCount = null;
private void FormMain_Load(object sender, EventArgs e)
{
_connectionsCount = new SystemState(SystemProperty.ConnectionsCount, false);
_connectionsCount.Changed += new ChangeEventHandler(ConnectionsCount_Changed);
}
void ConnectionsCount_Changed(object sender, ChangeEventArgs args)
{
int numberOfConnections = (int)args.NewValue;
string displayText = numberOfConnections > 0 ? "Connected" : "Not Connected";
_statusBar.BeginInvoke((MethodInvoker) delegate { _statusBar.Text = displayText; });
}
private void Form1_Closing(object sender, CancelEventArgs e)
{
if (_connectionsCount != null)
_connectionsCount.Dispose();
}
#if PocketPC || Smartphone
delegate void MethodInvoker();
#endif
The code constructs the _connectionsCount
class variable's instance to monitor for changes in the ConnectionsCount
state value using a newly created thread rather than the main application thread. We'll again call this new thread the RegistryState
thread. By monitoring for state value change notifications on the RegistryState
thread, the corresponding Changed
event handler, the ConnectionsCount_Changed
method, executes on that same thread, not the main application thread. Because the ConnectionsCount_Changed
method runs on the RegistryState
thread instead of the main application thread, you must use caution when interacting with other parts of your application. Your application is now a multithreaded application and requires that you use good multithreaded programming practices. One important consideration is ensuring that the event handler method safely interacts with application controls.
As you may know, Windows Forms controls can only be accessed from the thread on which they are created. Interacting with the controls from any other thread requires that the application marshal the call to the correct thread using either the control's Invoke
method or BeginInvoke
method. As you can see in the preceding code example, rather than attempting to directly update the _statusBar.Text
property, which would throw an exception due to the cross-thread access, the ConnectionsCount_Changed
method calls the _statusBar.BeginInvoke
method. As a result, the code that actually assigns the displayText
variable to the _statusBar.Text
property executes on the main application thread.
The syntax of the _statusBar.BeginInvoke
method call may seem a little strange if you're not familiar with C# anonymous methods. In the case of the preceding code example, rather than explicitly define a one-line named method to set the _statusBar.Text
property, I've defined an anonymous method within the BeginInvoke
method call that sets the _statusBar.Text
property. There are two key benefits to using an anonymous method in this case: first, the anonymous method requires less code than explicitly defining a named method; second, the anonymous method can access the ConnectionsCount_Changed
method's displayText
local variable, which eliminates the need to declare and pass a parameter containing the displayText
variable's value.
Note
If you would like to know more about anonymous methods, there are a number of good resources available. The following are a couple of resources that I like. A good article to get started with is Create Elegant Code With Anonymous Methods, Iterators, And Partial Classes. Also, Ian Griffith has an excellent blog post discussing delegates, which includes some helpful information on anonymous methods.
You'll notice that the preceding code example calls the _connectionsCount.Dispose
method when the form is closing. The Dispose
method plays a very important role in the RegistryState
class (and the SystemState
class) because the Dispose
method exits the RegistryState
class' notification monitoring thread. If you forget to call the Dispose
method on a RegistryState
class instance that was created with a useFormThread
value of false
, your application will never fully exit because the application's process waits indefinitely for the RegistryState
thread to exit. The problem is that the RegistryState
thread never exits because the RegistryState
thread remains running until the application calls the Dispose
method.
Note
If you would like to know more about how threads affect the application exit process, see Foreground and Background Threads in the .NET Framework Developers Guide.
One of the most telltale signs that you may have forgotten to call the RegistryState.Dispose
method is when you're debugging the application with Visual Studio 2005 and the application appears to exit on the device or emulator but Visual Studio 2005 remains in debug mode. Visual Studio 2005 remains in debug mode because the application is still running waiting for the RegistryState
thread to exit. If this happens, simply terminate the application using the Visual Studio 2005 Stop Debugging feature (Debug | Stop Debugging on the Visual Studio 2005 menu), which forcibly terminates the RegistryState
thread.
The final point regarding the preceding code example is the declaration of the MethodInvoker
delegate at the end of the code example, the MethodInvoker
delegate is a delegate defined in the full .NET Framework but not in the .NET Compact Framework. The MethodInvoker
delegate is defined as a delegate that has an empty parameter list and a void return value; I find that the MethodInvoker
delegate is often useful when using anonymous methods. I've placed the MethodInvoker
delegate declaration within the #if preprocessor block so that my declaration is only used when targeting Windows Mobile. This same code compiled for the desktop uses the .NET Framework defined MethodInvoker
delegate.
We've seen that moving the state value change notification processing off the main application thread prevents long-running change notification processing from interfering with the application user interface. The next issue that we need to look into is how long-running state value change notification handlers affect other notification handlers within the same application. By way of an example, the following code example monitors two state values: ConnectionsCount
and DisplayRotation
.
SystemState _connectionsCount = null;
SystemState _displayRotation = null;
private void FormMain_Load(object sender, EventArgs e)
{
_connectionsCount = new SystemState(SystemProperty.ConnectionsCount, false);
_connectionsCount.Changed += new ChangeEventHandler(ConnectionsCount_Changed);
_displayRotation = new SystemState(SystemProperty.DisplayRotation, false);
_displayRotation.Changed += new ChangeEventHandler(DisplayRotation_Changed);
}
void ConnectionsCount_Changed(object sender, ChangeEventArgs args)
{
int numberOfConnections = (int)args.NewValue;
if (numberOfConnections > 0)
UploadDataToServer();
}
void DisplayRotation_Changed(object sender, ChangeEventArgs args)
{
int displayOrientation = (int)args.NewValue;
CalculateNewDisplayPositions(displayOrientation);
this.BeginInvoke((MethodInvoker)UpdateApplicationDisplay);
}
private void FormMain_Closing(object sender, CancelEventArgs e)
{
if (_connectionsCount != null)
_connectionsCount.Dispose();
if (_displayRotation != null)
_displayRotation.Dispose();
}
When the ConnectionsCount
state value changes to a value greater than zero, indicating that the device has connected to a network, the application uploads data from the local device to the server. When the DisplayRotation
state value changes, indicating that the user has rotated the device display, the application recalculates the size and position of the user interface controls, and then updates the application display using the newly calculated user interface controls' sizes and positions.
Note
Be aware that in a real-life application, the simple fact that an application receives a notification that the device has connected to a network is not sufficient to begin the process of transferring data to a remote server. One also needs to verify that the connection makes the server of interest reachable. For an example of how to perform such a test, download the URLReachableDemo demo from this blog post.
You'll notice that each of the state values is monitored by a separate SystemState
class instance with each instance constructed with the bUseFormThread
parameter set to false
. Just as you expect, with the bUseFormThread
parameter set to false,
each of the state values are monitored on a thread separate from the main application thread; however, what you may not expect is that the state values are not monitored on threads separate from one another. All RegistryState
class instances created with a useFormThread
parameter value of false
use the same thread to monitor their respective state values.
Note
Just a reminder that the SystemState
class internally uses the RegistryState
class; therefore, from a threading perspective there is no difference from a SystemState
class instance or a RegistryState
class instance. The same thread is used for monitoring state values without regard for which of the classes you use in your application.
The decision of the RegistryState
class implementers to use one thread to monitor all of the state values is a reasonable one. Each additional thread your application creates adds overhead and consumes resources. In most cases, a particular state values changes only occasionally, therefore, using a common thread to monitor the state values is a judicious use of resources. Your responsibility as an application developer is to implement your state value change handlers such that one handler does not interfere with another. The preceding code example demonstrates how one state value change event handler might delay the execution of another state value change handler.
The issue of concern in the preceding code example is that the ConnectionsCount
state value change handler, the ConnectionsCount_Changed
method, can potentially run for a long period of time. Depending on the amount of data that the application must upload, the UploadDataToServer
method, and therefore the ConnectionsCount_Changed
method, may run for several seconds, minutes, or even possibly hours. The notification handlers for all RegistryState
class instances created with a useFormThread
parameter value of false
share a single thread; to avoid confusion, we'll continue to refer to this thread as the RegistryState
thread. This thread, the RegistryState
thread, cannot perform any other work until the ConnectionsCount_Changed
method returns. As a result, if the user were to rotate the device display while the ConnectionsCount_Changed
method is active, the application will not update the application display until the data upload completes because the DisplayRotation_Changed
method is not able to receive the DisplayRotation
state value change notification until the ConnectionsCount_Changed
method returns and makes the RegistryState
thread available to handle other notifications.
Resolving the problem of the two state value notification handlers competing for the same thread is easily resolved in the preceding code example. In this case, the DisplayRotation
state value is being monitored with the wrong thread. The processing to handle a change in the DisplayRotation
state value is tied directly to the user interface. With this being the case, the main application thread is the most reasonable choice to handle the DisplayRotation
state value change notification. With the DisplayRotation
state value change handling moved to the main application thread, the two state value change handlers, DisplayRotation_Changed
and ConnectionsCount_Changed
, no longer compete for the same thread and therefore do not interfere with one another.
Moving the DisplayRotation
state value notification handler to the main application thread resolves the issue of the ConnectionsCount
state value notification handler interfering with the DisplayRotation
state value notification handler but the ConnectionsCount
state value notification handler, ConnectionsCount_Changed
, still monopolizes the RegistryState
thread making the handling of any non-user interface oriented notifications difficult. For example, most applications that need to be notified when a connection becomes available also need to be notified when a connection is no longer available. Consider the following ConnectionsCount_Changed
method implementation.
void ConnectionsCount_Changed(object sender, ChangeEventArgs args)
{
if (numberOfConnections > 0)
UploadDataToServer();
else
PauseDataUpload();
}
This ConnectionsCount_Changed
method implementation uploads data from the device to the server when a connection becomes available and is supposed to pause the upload process when a connection is no longer available. Unfortunately, the application still has an issue with the ConnectionsCount_Changed
event handler monopolizing the RegistryState
notification thread when uploading the data. When a connection becomes available, the ConnectionsCount_Changed
event handler executes and calls the UploadDataToServer
method, which may still run for a long period of time. Until the UploadDataToServer
method returns, which then allows the ConnectionsCount_Changed
event handler to return, no other notifications on the RegistryState
notification thread can execute; therefore, when the network connection disconnects, the system is unable to execute the ConnectionsCount_Changed
event handler with the updated ConnectionsCount
state value of 0; therefore, the PauseDataUpload
method is never called. Instead, the UploadDataToServer
method continues executing until it throws an exception because there is no valid connection available.
The fundamental problem in both this case and the earlier DisplayRotation
state value case is that the ConnectionsCount
state value handler is allowed to monopolize the RegistryState
thread indefinitely. State value handlers should be designed to execute quickly and then return; otherwise, you'll always have the potential that your application will run into event handling problems due to the event handling thread being monopolized by one long-running state value notification handler.
Avoiding long-running event handlers doesn't mean that your application is limited to performing only simple tasks when a state value changes. The key is to separate the event notification handling from the actual task processing that the event controls. In cases where your application needs to perform some potentially long-running tasks based on state values, you should think of your state value notification handler as the manager of the task rather than the worker. The worker should instead execute on a thread other than the RegistryState
notification handler thread.
There is no one right way to manage threads from the state value event handlers. You can start a new thread to execute the required task using the Thread
class. Alternatively, you can use the ThreadPool
class to queue the task to execute on one of the Common Language Runtime (CLR) managed threads. Finally, you can use synchronization objects such as the AutoResetEvent
or ManualResetEvent
classes to start and stop the task-processing running on a separate thread.
Note
There are many caveats and situation-specific considerations when designing a multithreaded application. If you're new to working with threads, you should try to work with someone who has experience in multithreaded programming if you can; you'll also want to spend some time reading about the tools and techniques used in multithreaded programming. Here are a couple of resources that I think are a good for getting started: Developing Multithreaded Applications for the .NET Compact Framework and Multithreaded Programming with Visual Basic .NET. The Patterns and Practices group also provides a fairly comprehensive guide on .NET application scalability and multithreading: Improving .NET Application Performance and Scalability. It's a good resource but may be a bit much for someone who is just getting started in multithreaded programming.
The following code example demonstrates the technique of starting a new thread to perform the actual task.
Thread _uploadDataToServerThread;
void ConnectionsCount_Changed(object sender, ChangeEventArgs args)
{
int numberOfConnections = (int)args.NewValue;
if (numberOfConnections > 0)
{
ThreadStart threadStartMethod = new ThreadStart(UploadDataToServer);
_uploadDataToServerThread = new Thread(threadStartMethod) ;
_uploadDataToServerThread.Start();
}
else
PauseDataUpload();
}
The new implementation of the ConnectionsCount
state value change handler, ConnectionsCount_Changed
, now starts a new thread to perform the actual data upload and returns immediately. In this implementation, the ConnectionsCount_Changed
method executes and returns quickly, which makes the RegistryState
thread available to process other state value change notifications almost immediately. Notice that the PauseDataUpload
method is still run directly by the handler. This is because pausing the upload process generally involves little more than setting a flag or otherwise signaling that the upload process should stop; therefore, the PauseDataUpload
method is not likely to monopolize the RegistryState
thread.
Back in Part 2, we introduced persistent notifications. As you'll recall, persistent notifications allow you to provide notification handling that outlives a given application. Persistent notifications provide the important feature of automatically starting the associated application if necessary; persistent notifications also survive dead batteries and soft resets of the device.
When registering a persistent notification, an application can register itself as the target of a persistent notification or the application can register a different application as the notification target. In Part 2, we covered how a native application can use the RegisterNotificationApp
function to register another native application as the notification target; similarly, we covered how a managed application can use the EnableApplicationLauncher
method to register another managed application as the notification target. The issue that we put off until this month is how to register persistent notifications that cross the native/managed boundary: native applications that register a managed application or vice-versa.
A complete system often involves multiple applications and it is not uncommon for some of the applications to be native applications written in C/C++ and other applications in the system to be managed applications written in C# or VB.NET. With this being the case, it is reasonable to expect that there may be a need for a native application to register a persistent notification that starts a managed application; the opposite is also likely. The persistent notification infrastructure has no problem handling these two scenarios but there are some special issues that we need to address to make everything work.
Registering persistent notifications between native and managed applications does not involve the traditional concept of native/managed interoperability because the actual persistent notification registration occurs in the registry. In this case, interoperability simply means that a native application creates a persistent notification registry entry that is in the format required by a managed application; the same is true for a managed application registering a persistent notification for a native application.
Note
You can find the list of persistent notification registry entries in the Persistent Notifications section of Part 2.
For the most part, the persistent notification registry fields contain the same values for both managed and native applications. The fields that differ are the Class Name, Window Name, and Message fields; this is because of the difference in the notification handling implementation between the two environments. Native developers have direct control over the window and message id associated with a persistent notification; therefore, the registry Class Name, Window Name, and Message fields will contain the values defined by the native application.
Managed developers do not control these fields; the implementation of the RegistryState
class abstracts the window and message details from the application developer. A persistent notification registered for a managed application must specify the Class Name, Window Name, and Message fields that the RegistryState
class expects. Fortunately, these values are very predictable.
Two of the fields, Class Name and Message, are always the same. The RegistryState
class processes State and Notifications Broker notification messages with a class derived from the .NET Compact Framework MessageWindow
class. The .NET Compact Framework MessageWindow
class has a Windows class name of #NETCF_AGL_MSG_
; therefore, #NETCF_AGL_MSG_
is the value that you specify in the registry Class Name field. Internally the RegistryState's
MessageWindow
-derived class expects State and Notifications Broker notifications to have a message id of 0x8001, which is 32769 decimal; therefore, 32769 is the value you specify for the registry Message field. Although not always the same, the last registry value, Window Name, is easily determined. The RegistryState
class always names the notification-processing window as the concatenated value of the string "Notifications_" followed by the application id. Table 1 summarizes the Class Name, Window Name, and Message registry fields for managed applications.
Note
If you're wondering why the RegistryState
class implementers chose 0x8001 as the message id, the reason is that 0x8001 is the message id one gets when adding 1 to the C/C++ WM_APP
constant. In C/C++, application-specific message values are always defined as starting at the WM_APP
constant.
Table 1. Registry field values for persistent notifications targeting managed applications
Registry Field | Value |
---|---|
Class Name |
#NETCF_AGL_MSG_ |
Window Name |
Notifications_applicationidentifier |
Message |
32769 |
Now that we understand how the RegistryState
class implements notification message processing, we can easily write a native application to register a persistent notification that targets a managed application as shown in the following code example.
const TCHAR* appId = _T("CompanyA.MyManagedApp.MediaPlayerTrackTitle");
const TCHAR* targetApplication = _T("\\Program Files\\MyManagedApp\\MyManagedApp.exe");
const TCHAR* messageWindowClassName = _T("#NETCF_AGL_MSG_");
const UINT notificationMessage = WM_APP + 1;
HRESULT CreatePersistentNotificationForMgdApp()
{
TCHAR messageWindowWindowName[256];
_stprintf(messageWindowWindowName, _T("Notifications_%s"), appId);
HRESULT hResult = RegistryNotifyApp(SN_MEDIAPLAYERTRACKTITLE_ROOT,
SN_MEDIAPLAYERTRACKTITLE_PATH, SN_MEDIAPLAYERTRACKTITLE_VALUE,
appId, targetApplication, messageWindowClassName, messageWindowWindowName,
notificationMessage, RNAF_NONAMEONCMDLINE, NULL);
return hResult;
}
As shown in the preceding code, the native application still creates the persistent notification using the RegistryNotifyApp
function and uses the constants from snapi.h to associate the persistent notification with the Microsoft Windows Media® Player track title. Specifying the application id and application executable path is also the same as when targeting a native application. The only differences from targeting a native application are the three values discussed earlier. The managed application's windows class name and message id are declared as the constants messageWindowsClassName
and notificationMessage
respectively. To create the window's window name, the call to _stprintf
concatenates the string "Notifications_" with the appId
constant. That's all there is to it; the next time the Windows Media Player track title changes, the State and Notifications Broker will launch the MyMannagedApp application and send it the appropriate message for the SystemState
class or RegistryState
class to process the notification. When the track title changes again, the State and Notifications Broker will send the notification directly to the application just as it would had another managed application registered the persistent notification.
Because managed applications can import native functions, writing a managed application that creates a persistent notification targeting a native application is even easier. Simply DllImport
the native RegistryNotifyApp
function and call the function passing the same parameters that a native application does.
As you've seen throughout our discussion of the State and Notifications Broker, a single registry value may contain the bits that make up a number of different state values; therefore, extracting a state value from the registry may require a number of steps. You'll recall from Part 1 that the SystemState
class' static properties completely abstract these operations from the application developer. A great example is the SystemState.PowerBatteryStrength
property, which returns a BatteryLevel
enumeration value indicating whether the battery strength is VeryHigh
, High
, Medium
, Low
, or VeryLow
. Determining the PowerBatteryStrength
state value requires you to read the registry value, apply a bit-shift and a bitwise-and to the registry value that produces the integer representation of the PowerBatteryStrength
state value; the integer representation must then be cast to a BatteryLevel
enumeration.
The SystemState
static properties do such a great job of making the state values easily accessible, a managed developer may find himself lured into falsely believing that one does not need to understand the registry representation of the state values. Nothing could be further from the truth.
Remember that in addition to the SystemState
class' static properties; you also create SystemState
class instances using the SystemProperty
enumeration. The combination of the static properties and the class instance provide three ways that your application might access a state value: the SystemState
static property, the SystemState.CurrentValue
instance property, and the ChangeEventArgs
parameter in the Changed
event notification handler. Because the SystemState
static property, the SystemState.CurrentValue
instance property, and the ChangedEventArgs.NewValue
property all represent the same state value, one might expect each to return the same result; however, this is not the case because they each handle the issues of bitmasks and type conversion differently.
Consider the following code example.
SystemState _batteryStrength;
public FormMain()
{
InitializeComponent();
_batteryStrength = new SystemState(SystemProperty.PowerBatteryStrength);
_batteryStrength.Changed += new ChangeEventHandler(PowerBatteryStrength_Changed);
}
void PowerBatteryStrength_Changed(object sender, ChangeEventArgs args)
{
Debug.WriteLine("SystemState.PowerBatteryStrength = " + SystemState.PowerBatteryStrength);
Debug.WriteLine("PowerBatteryStrength args.NewValue = " + args.NewValue);
Debug.WriteLine("_batteryStrength.CurrentValue = " + _batteryStrength.CurrentValue);
}
The preceding code creates an instance of the SystemState
class, _batteryStrength
that is associated with the PowerBatteryStrength
state value; the application also handles the _batteryStrength.Changed
event. In the Changed
event handler, the application displays to the output window the SystemState.PowerBatteryStrength
static property, the _batteryStrength.Changed
event handler's ChangedEventArgs.NewValue
property, and the _batteryStrength.CurrentValue
instance property. Figure 3 shows the three values displayed from the PowerBatteryStrength_Changed
Changed
event handler.
Figure 3. The different values returned for the PowerBatteryStrength state value
As you can see, the values in each case are different. The static property has a value of Medium
, which is the appropriate BatteryLevel
enumeration value. The ChangedEventArgs.NewValue
property has a value of 41; the value 41 is the integer equivalent of the BatteryLevel.Medium
enumeration value. The CurrentValue
property on the SystemState
instance has a value of 2686980. The value 2686890 is not actually the PowerBatteryLevel
value but rather the registry value that contains the bits that make up the PowerBatteryLevel
value.
The reason for the differences in each property value is that each property performs a different level of conversion on the registry contents. The static property performs the most complete conversion: applying the bit-level work and converting the value to the appropriate data type. The ChangedEventArgs.NewValue
property applies the bit-level work but does not perform the data type conversion, which means that the contents of the ChangedEventArgs.NewValue
property are always the same data type as the registry value that contains the state value. The CurrentValue
instance property performs no conversion on the registry contents; the CurrentValue
property exposes the raw registry contents. Your application must perform any bit-oriented work and data type conversions required to extract the state value from the CurrentValue
property.
Even with these value discrepancies, the SystemState
class is still very useful at making state values easily accessible. As you work with the SystemState
class, just be sure that you understand the relationship between the state value and the corresponding registry contents so that you know what to expect from each of the three SystemState
-related properties. State values that are stored in the registry as strings or simple integers can be safely accessed through any of the three properties. You just need to use caution in those cases where a single registry value contains the bits that make up multiple state values.
Bitmasks also require caution when working with the RegistryState
class. The presence of the Bitmask
property on the RegistryState
class might lead one to believe that the RegistryState.CurrentValue
property and the corresponding RegistryState.Changed
event's ChangedEventArgs.NewValue
property take care of applying the bitmask to the registry value. This, however, is not the case; the RegistryState
class doesn't perform any bitmask handling.
You can assign a value to the BitMask
property and retrieve the bitmask value back from the property but the RegistryState
class never uses the bitmask; the RegistryState
class always works against the raw registry value. The primary benefit of the RegistryState.BitMask
property is that it allows your application to associate a bitmask with a particular RegistryState
instance so that your application doesn't need to track the bitmask separately. In other words, the RegistryState
class will store a bitmask value but your application must explicitly apply the bitmask to the value returned by the RegistryState.CurrentValue
property or the ChangedEventArgs.NewValue
property.
My goal in pointing out these bitmask-related issues with the SystemState
and RegistryState
classes is not to provide a negative finish to our series. On the contrary, the SystemState
and RegistryState
classes are incredibly powerful and useful. That doesn't mean that they are perfect. My hope is that the more you understand about the State and Notifications Broker and the more you know about what it can and cannot do, the more you'll take advantage of it and the more effectively you'll apply it in your applications.
The State and Notifications Broker continues to impress me as a well-designed and implemented feature of the Windows Mobile platform. The addition of the over 40 new state values by Windows Mobile 6 reinforces my belief that the State and Notifications Broker API is one of the most powerful tools we have access to on the Windows Mobile platform and is equally valuable to both native and managed developers. I've really enjoyed digging into the many capabilities of the State and Notifications Broker; I hope you've enjoyed reading about it.
This completes our three-part series on the State and Notifications Broker but that doesn't mean that there isn't a lot more great stuff to talk about. Join me again next month as we dig into some of the new capabilities provided by Windows Mobile 6.
You can always find the list of column topics under the You Can Take It with You node in the MSDN® library. If you have any questions about what we’ve covered, or if you have ideas for things that you would like to see me cover, please contact me through my blog.
What's New for Developers in Windows Mobile 6
What’s New for Developers in Windows Mobile 5.0
Using Messages and Message Queues
About Messages and Message Queues
Create Elegant Code With Anonymous Methods, Iterators, And Partial Classes
Foreground and Background Threads
C# v2.0 Delegate Syntax Blog Post
Jim Wilson is president of JW Hedgehog, Inc. (https://www.jwhh.com) a New Hampshire–based consulting firm specializing in solutions, content creation, and mentoring for the Windows Mobile platform. Jim has worked extensively with the .NET Framework and .NET Compact Framework since the original beta release of each; he has over 20 years experience in the software industry including more than 14 years experience with relational database programming including SQL Server™ and SQL Server Compact Edition. Jim writes frequently for MSDN and has developed mobility curriculums for two of the industry’s leading technology training organizations, DevelopMentor and PluralSight. Jim speaks regularly at Tech Ed, the Professional Developer's Conference (PDC), VSLive, and the Mobility & Embedded DevCon. You'll find Jim online at https://pluralsight.com/blogs/jimw.
This appendix provides the list of new State and Notifications Broker state values introduced with Windows Mobile 6. Each state value lists the managed SystemState
property and the C/C++ macro definitions from the snapi.h header file.
You'll recall from Part 1 that the snapi.h header file defines several macros for each state value with each macro relating to a particular state value having the same prefix: prefix_ROOT, prefix_KEY, prefix_VALUE, and prefix_BITMASK. This remains true for most of the macros but not all. In some cases where a single registry value represents multiple state values with each state value identified by a different bitmask, several bitmask macros share common root, key, and value macros.
In an attempt to make reading through the new state values a little easier, I've grouped the state values into separate tables based on the way the macros are defined. State values that follow the standard naming of using a common prefix for the root, key, value, and bitmask macros appear in the first table. For the remainder of the macros, each table lists the bitmask macros that all share common root, key, and value macros. I know it seems arbitrary but it's the easiest way I've found to display the values in a reasonably organized list.
Table 2 shows most of the new state values. The C/C++ macros for these values follow the standard naming practice of the root, key, value, and bitmask macros sharing a common prefix.
Table 2. Main list of New Windows Mobile 6 State and Notifications Broker state values
SystemState Property | C/C++ Macro Prefix | Description |
---|---|---|
BluetoothStateA2DPConnected |
SN_BLUETOOTHSTATEA2DPCONNECTED |
Gets a value indicating whether Bluetooth Advanced Audio Distribution Profile is connected |
BluetoothStateDiscoverable |
SN_BLUETOOTHSTATEDISCOVERABLE |
Gets a value indicating whether Bluetooth is discoverable |
BluetoothStateHandsFreeAudio |
SN_BLUETOOTHSTATEHANDSFREEAUDIO_ |
Gets a value indicating whether the device is under Bluetooth hands-free audio and control |
BluetoothStateHandsFreeControl |
SN_BLUETOOTHSTATEHANDSFERECONTROL |
Gets a value indicating whether device is under Bluetooth hands-free control - The macros for this value are spelled as shown here, the E and the R in the word FREE are transposed in the macros just as shown here |
BluetoothStateHardwarePresent |
SN_BLUETOOTHSTATEHARDWAREPRESENT |
Gets a value indicating whether Bluetooth hardware is present |
BluetoothStatePowerOn |
SN_BLUETOOTHSTATEPOWERON |
Gets a value indicating whether Bluetooth is powered on |
CameraEnabled |
SN_CAMERAENABLED |
Gets a value indicating whether a camera is enabled |
ClamshellClosed |
SN_CLAMSHELLCLOSED |
Gets a value indicating whether the clamshell is closed |
PhoneTalkingCallStartTime |
SN_PHONETALKINGCALLSTARTTIME |
Gets the time of the current active call in FILETIME format |
WiFiStateConnected |
SN_WIFISTATECONNECTED |
Gets a value indicating whether Wi-Fi is connected to a network |
WiFiStateConnecting |
SN_WIFISTATECONNECTING |
Gets a value indicating whether Wi-Fi is connecting to a network |
WiFiStateHardwarePresent |
SN_WIFISTATEHARDWAREPRESENT |
Gets a value indicating whether Wi-Fi hardware is present |
WiFiStateNetworksAvailable |
SN_WIFISTATENETWORKSAVAILABLE |
Gets a value indicating whether Wi-Fi networks are available |
WiFiStatePowerOn |
SN_WIFISTATEPOWERON |
Gets a value indicating whether Wi-Fi is powered on |
Table 3 shows the new state values related to cellular system availability. All of the state values in this table are contained in the Cellular System Available
registry value under the registry key HKLM\System\State\Phone
. All of the bitmask macros are used in conjunction with the SN_CELLSYSTEMAVAILABLE_ROOT,
SN_CELLSYSTEMAVAILABLE_PATH
, and SN_CELLSYSTEMAVAILABLE_VALUE
macros.
Table 3. New Windows Mobile 6 Cell System Availability Macros
SystemState Property | C/C++ Bitmask Macro Name | Cellular System |
---|---|---|
CellularSystemAvailable1xrtt |
SN_CELLSYSTEMAVAILABLE_1XRTT_BITMASK |
1XRTT |
CellularSystemAvailableEdge |
SN_CELLSYSTEMAVAILABLE_EDGE_BITMASK |
EDGE |
CellularSystemAvailableEvdo |
SN_CELLSYSTEMAVAILABLE_1XEVDO_BITMASK |
1XEVDO |
CellularSystemAvailableEvdv |
SN_CELLSYSTEMAVAILABLE_EVDV_BITMASK |
EVDV |
CellularSystemAvailableGprs |
SN_CELLSYSTEMAVAILABLE_GPRS_BITMASK |
GPRS |
CellularSystemAvailableHsdpa |
SN_CELLSYSTEMAVAILABLE_HSDPA_BITMASK |
HSDPA |
CellularSystemAvailableUmts |
SN_CELLSYSTEMAVAILABLE_UMTS_BITMASK |
UMTS |
Table 4 shows the new state values related to cellular system connectivity. All of the state values in this table are contained in the Cellular System Connected
registry value under the registry key HKLM\System\State\Phone
. All of the bitmask macros are used in conjunction with the SN_CELLSYSTEMCONNECTED_ROOT,
SN_CELLSYSTEMCONNECTED_PATH
, and SN_CELLSYSTEMCONNECTED_VALUE
macros.
Table 4. New Windows Mobile 6 Cell System Connectivity Macros
SystemState Property | C/C++ Bitmask Macro Name | Cellular System |
---|---|---|
CellularSystemConnected1xrtt |
SN_CELLSYSTEMCONNECTED_1XRTT_BITMASK |
1XRTT |
CellularSystemConnectedCsd |
SN_CELLSYSTEMCONNECTED_CSD_BITMASK |
CSD |
CellularSystemConnectedEdge |
SN_CELLSYSTEMCONNECTED_EDGE_BITMASK |
EDGE |
CellularSystemConnectedEvdo |
SN_CELLSYSTEMCONNECTED_1XEVDO_BITMASK |
1XEVDO |
CellularSystemConnectedEvdv |
SN_CELLSYSTEMCONNECTED_EVDV_BITMASK |
EVDV |
CellularSystemConnectedGprs |
SN_CELLSYSTEMCONNECTED_GPRS_BITMASK |
GPRS |
CellularSystemConnectedHsdpa |
SN_CELLSYSTEMCONNECTED_HSDPA_BITMASK |
HSDPA |
CellularSystemConnectedUmts |
SN_CELLSYSTEMCONNECTED_UMTS_BITMASK |
UMTS |
Table 5 shows the new state values related to device lock states. All of the state values in this table are contained in the Lock
registry value under the registry key HKLM\System\State
. All of the bitmask macros are used in conjunction with the SN_LOCK_ROOT,
SN_LOCK_PATH
, and SN_LOCK_VALUE
macros. In addition to exposing properties for each of the individual lock-related state values, the SystemState
class also exposes the LockStates
property, which returns a LockStates
enumeration value for the various device lock states.
Table 5. New Windows Mobile 6 Device Lock States Macros
SystemState Property | C/C++ Bitmask Macro Name | Description |
---|---|---|
DeviceLocked |
SN_LOCK_BITMASK_DEVICELOCKED |
Gets a value indicating whether the device is locked |
KeyLocked |
SN_LOCK_BITMASK_KEYLOCKED |
Gets a value indicating whether the keys are locked |
LockStates |
Use DeviceLocked, KeyLocked, and SimLocked macros |
Returns a managed enumeration identifying the Device, Key, and SIM locked states |
SimLocked |
SN_LOCK_BITMASK_SIMLOCKED |
Gets a value indicating whether the SIM is locked |
Table 6 shows the new state values related to Internet Sharing. All of the state values in this table are contained in the InternetSharing
registry value under the registry key HKLM\System\State\Connectivity
. All of the bitmask macros are used in conjunction with the SN_INTERNETSHAREING_ROOT,
SN_ INTERNETSHAREING _PATH
, and SN_ INTERNETSHAREING _VALUE
macros.
Managed developers use the RegistryState
class to access the registry value for the Internet Sharing state values because the SystemState
class does not expose properties for the Internet Sharing state values. The following code example shows the appropriate RegistryState
class constructor call.
RegistryState _internetSharingState;
_internetSharingState = new RegistryState(@"HKEY_LOCAL_MACHINE\System\State\Connectivity", "Value");
To access the individual Internet Sharing state values, convert the RegistryState.CurrentValue
property from an object
to a uint
, and then perform a bitwise-and against the bitmask that corresponds to the state value of interest. The bitmask for each state value is listed in the first column of the table. The following code example demonstrates how to test if Internet Sharing is running.
uint internetSharingRegistryValue = (uint) _internetSharingState.CurrentValue;
bool isRunning = (internetSharingRegistryValue & 0x00000001) > 0;
Note
Unlike the other bitmask macros, the bitmask macros for Internet Sharing state values do not contain the word BITMASK in the macro name.
Table 6 New Windows Mobile 6 Internet Sharing State Macros
Bitmask Value | C/C++ Bitmask Macro Name | Description |
---|---|---|
0x00000001 |
SN_INTERNETSHARING_PROCESS_RUNNING |
InternetSharing has successfully been loaded and is ready for use |
0x00000002 |
SN_INTERNETSHARING_ENABLED |
InternetSharing data session is currently enabled—this means InternetSharing is either connecting or connected |
0x00000004 |
SN_INTERNETSHARING_DATA_CONNECTED |
InternetSharing has a valid cellular data connection |
0x00000008 |
SN_INTERNETSHARING_HOST_CONNECTED |
InternetSharing has a valid connection with PC |
0x00000010 |
SN_INTERNETSHARING_HOST_USB |
Connection with PC is over USB |
0x00000020 |
SN_INTERNETSHARING_HOST_BLUETOOTH |
Connection with PC is over Bluetooth |