Mobile Ink Jots 4: Writing Solid Tablet PC Applications

 

Shawn A. Van Ness
Leszynski Group, Inc.

September 2004

Applies to:
   Microsoft Windows XP Tablet PC Edition 2005

Summary: Now that Microsoft Windows XP Tablet PC Edition 2005 has arrived, with a fresh new user experience and a fresh new SDK, it's a good time to pause and take stock of your coding practices, to ensure you're getting the most out of your Tablet PC. This month, Shawn runs down a list of common bugs and other coding mistakes that commonly appear in the course of developing Tablet PC applications, and demonstrates how to avoid them. (12 printed pages)

Contents

Introduction
Tip #1: Mind Your Current Thread ID
Tip #2: Don't Let Exceptions Escape Unnoticed
Tip #3: Always Clean Your Plate
Tip #4: Don't Make Assumptions About Security
Tip #5: Learn to Recognize the Local Dialect
Conclusion

Introduction

As I sit down to write this month's Mobile Ink Jots column, it occurs to me that I just celebrated an anniversary: It was roughly two years ago that I began developing Tablet PC applications, full-time. It's been a very fun and rewarding experience—but also a challenging one. I came into the job with plenty of experience developing for the Microsoft Windows platform, steeped in C/C++, COM and .NET technologies. Even so, my experience of developing Tablet PC applications has come with quite a steep learning curve.

My goal this month is simple: to help everyone avoid some of the same mistakes that my team and I have made over the past two years and, in so doing, to help everyone write better application code for the Microsoft Tablet PC platform. Forewarned is forearmed.

This is not a grab-bag of tips and tricks—I will not be demonstrating how to implement inverted-pen erase or how to detect screen rotation (those articles have already been written). Rather, this will be a more general tour of techniques that can improve the robustness, efficiency, and security of almost any Tablet PC application, written in any language. The topics of the day are threading, exception handling, resource management, security, and localization.

Truth be told, most of these topics have already been written about, too. The information, however, is scattered across a variety of books, magazine articles, Knowledge Base entries, and SDK documentation—often in areas not specific to Tablet PC programming. If these topics have anything in common, it's that they're all especially relevant to Tablet PC programming. They deserve to be collected and analyzed, in detail, together. So, without further ado, let's jump in and talk about threading.

Tip #1: Mind Your Current Thread ID

Is your Tablet PC application multithreaded? That's a trick question—before you answer "no," think about it carefully. Many events from the Tablet PC runtime fire on background threads (see "Threads on Which an Event Can Fire" in the SDK documentation, for a comprehensive list). This means that, even if your own code is not explicitly creating any threads, your application may still be multithreaded.

Let's put aside, for the moment, all the usual difficulties inherent in multithreaded programming—volumes have been written on that topic. The crux, for Tablet PC developers, is that the Windows user interface has very specific rules about threading. Put simply, the rules are:

  1. Don't touch any window object from any thread other than the one that created it.
  2. Any thread that creates a window must regularly service its message queue.

The first rule comes about because window objects (any object represented by an HWND, in the Win32 API) are generally not thread-safe. If the operating system tries to manipulate a window at the same time as your application code, you can count on unexpected results.

If the OS detects that a window's UI thread is currently executing (responding to a message from its queue), it knows to keep its hands off that window. Conversely, it's your application's responsibility to keep its hands off the window, whenever it's not responding to a message (for example, when running code on a background thread). It's also your application's responsibility to promptly respond to any messages in the queue—hence the second rule.

It sounds simple enough, but in practice it's fairly difficult to police which functions your code should be allowed to call, at such a fine level of granularity. This is especially true in an event-based, multithreaded framework such as the Tablet PC runtime, where it's not always obvious if a given line of code is going to be running on the UI thread, a background thread, or both.

I have good news and bad news. First, the bad news: If you neglect the first rule by making calls to UI properties and methods from a background thread, your application will likely still appear to work just fine most of the time. Yes, that's bad news—your code may be indeed contain bugs without your realizing it—and the bugs will be the nastiest, ugliest kind imaginable. They'll seldom appear, they'll be terribly difficult to reproduce, and they will likely produce different visible side-effects on different hardware and software configurations. These are the classic symptoms of a race-condition bug.

Now the good news: The Windows Forms classes in the Microsoft .NET Framework make it very easy to detect this situation and make the jump from a background thread to a given window's UI thread, wherever necessary. The base class Control (from which all window objects ultimately derive) offers a simple, Boolean property named InvokeRequired. We can query this property at any time to determine if our current thread is the owning UI thread for the specified Control object. If it's not, we can then call the Control object's Invoke method to execute a block of code on the correct UI thread.

The signature of the Invoke method specifies a delegate and a collection of arguments—this matches nicely with the way we subscribe to events in C#. This means that the requisite code used to both detect and avoid the potential race-condition can often be elegantly expressed with just a few lines of code, at the top of any event handler method. For example, consider the following handler for the Recognition event, on a Control-derived Windows Forms class:

private void recoctx_Recognition(object sender, RecognizerContextRecognitionEventArgs e)
{
  // This event is often fired on a background thread, which makes access 
  // to UI objects racey.  Jump back to our UI thread, in these cases.
  if (InvokeRequired)
  {
    Invoke( new RecognizerContextRecognitionEventHandler(recoctx_Recognition), 
      new object[] { sender, e });
    return;
  }

  // Ok, now we're back on the main UI thread.
  ...
}

The above code simply calls itself using the Invoke method, effectively rescheduling the call on the Control-derived class's UI thread, if it ever detects that it's running on a background thread.

As elegant and powerful as this technique may be, it's probably a little too unwieldy to include in every single event handler you write. I do, however, recommend performing a simple assertion at the top of every event handler in your Windows Forms applications (and not just for events specific to the Tablet PC—many events and callbacks from the .NET runtime arrive on background threads) to verify that your code is, in fact, running on the correct UI thread. An all too common case for this is found in event handlers for the various Timer classes in the .NET Framework. There are three "Timer" classes (System.Timers.Timer, System.Threading.Timer, and System.Windows.Forms.Timer) and although their callback signatures are all very similar in syntax, each Timer class has completely different semantics with respect to what thread(s) they fire on.

private void timer_Tick(object sender, EventArgs e)
{
  // Ensure we're on the correct UI thread.
  System.Diagnostics.Debug.Assert(!InvokeRequired);

  // Ok, we know it's safe to modify UI objects.
  ...
}

The above Assert statement will have no effect in a release-mode build, but it will allow you to detect any unexpected background-thread events early in your development and debugging cycle. This is much better than worrying, in the final days and weeks before you ship your software, about a spate of bugs that can't be reproduced.

Tip #2: Don't Let Exceptions Escape Unnoticed

Let's continue the discussion of how to write solid handlers for events fired from the Tablet PC runtime. Threading bugs may be public enemy number one, but exception-handling bugs can also be quite frustrating to locate.

The exception-handling features of the .NET Framework represent a quantum leap in simplicity and usefulness when compared to anything that came before. With power, however, comes responsibility. Remember, the bulk of the Tablet PC runtime library is implemented as unmanaged COM code, which will have no idea what to do with an exception thrown from managed code. Regardless of which thread an event fires on, if you fail to catch an exception from the handler, the exception will be silently discarded by the COM Interop layer that resides between the Tablet PC runtime and your application code.

Making matters worse, any subsequent subscribers to the event will not be called. That is not a problem unique to COM Interop, but rather a limitation of the .NET event model itself—the MulticastDelegate invocation code is not hardened against exceptions.

To see what I'm talking about, let's write a few lines of code that contains bugs and attempt to investigate it. Create a new Windows Forms application, attach an InkCollector to the Form, and handle the Stroke event with the following code—pretend you don't see the glaring bug:

private void inkCollector_Stroke(object sender, InkCollectorStrokeEventArgs e)
{
  string bug = null;
  bug = bug.ToLower(); // obvious NullReferenceException
  this.Text = bug;
}

What happens when you compile and run the program? Nothing. Nothing overtly bad, that is—no error messages appear, and no debugger is invoked. The bug is clearly real, however: the Form's caption text is not modified.

Now imagine that this bug is buried in the middle of a code path that's several hundred lines long, or that it's caused by a much more subtle code defect. You'll never see or hear the NullReferenceException, unless you've written code to catch it—your application will simply be left in an awkward, possibly inconsistent state.

My advice: Guard the boundaries of all your event handlers with a try/catch block so as to prevent exceptions from propagating back up the call stack to the event's source. Even if you have no recourse for handling the exception, other than logging a message to the debugger's output stream for future investigation, you should do so. Only bad things can come from letting the exception slip away unnoticed. Combining this advice with Tip number one above, a solidly written handler for the background Recognition event, on a Control-derived C# class, might look something like this:

private void recoctx_Recognition(object sender, RecognizerContextRecognitionEventArgs e)
{
  // Ensure we're on the correct UI thread.
  if (InvokeRequired)
  {
    Invoke( new RecognizerContextRecognitionEventHandler(recoctx_Recognition), 
      new object[] { sender, e });
    return;
  }

  // Prevent exceptions from propagating 
  // back to the Ink runtime (they'll be swallowed 
  // by the COM Interop layer, and prevent other subscribers 
  // from being called).
  try
  {
    // Ok, now we've guarded against unexpected exceptions - let's get to work.
    this.Text = e.Text;
    ...
  }
  catch (Exception ex)
  {
    // Log the exception, for further investigation.
    System.Diagnostics.Debug.WriteLine(ex.ToString());
  }
}

Entering and leaving a 'try' block is not an expensive operation, at run-time—if no exceptions occur, the previous code adds almost no performance overhead to a method. Still, it may seem a bit cumbersome to write a try/catch block around every event handler's method body—but trust me, the time and frustration you'll save is worth it. The next version of Microsoft Visual Studio .NET may offer statement-completion for C#, making this chore a bit easier. Until then, I recommend creating a macro in Visual Studio .NET to fill out this pattern for all your event handlers.

Finally, be sure to investigate any exceptions that show up in the catch-all block, to ensure that you're not inadvertently leaving your application in an inconsistent state. (Hint: Perhaps a call to Debug.Assert would be more helpful than Debug.WriteLine, in the above example?)

Tip #3: Always Clean Your Plate

Last month, while discussing Ink on the Web, I attempted to make the case for deterministic cleanup of Ink resources. It's a very important subject, so it bears repeating here—Tablet PCs are not nearly as resource-challenged as Pocket PCs, but neither are they infinite wells of memory and threads.

Why can't we simply rely on garbage collection? Again, remember that the underlying implementation of the Tablet PC runtime is COM-based. In COM, there is no garbage collection; instead, objects maintain their own reference-counts, and destroy themselves when they're no longer in use. Readers experienced with COM (or any other reference-counted programming environment, for that matter) will recall that whenever "reference loops" are formed among a set of objects, it becomes impossible for any of the objects' reference-counts to go to zero. Some explicit notification is required in order to "break the cycle," before any such mutually-referencing set of objects can be destroyed.

In the managed Tablet PC runtime library, these reference loops manifest themselves in the form of event subscriptions. If your application code subscribes to one or more event handlers on a class from the Tablet PC runtime, a reference-loop is formed. At that point the managed wrapper can never be finalized by the garbage collector, because an outstanding COM event subscription is keeping it alive, from below.

This is the reason why IDisposable implementations were added to the classes of the Microsoft.Ink assembly (see the following list) and why we must remember to call them, instead of passively relying on the managed finalizer. The Dispose method serves to explicitly break the underlying reference-loop that occurs as a result of an event subscription, and releases the reference to the managed wrappers so that they become subject to garbage collection. Disposable types in the Tablet PC SDK include:

In the .NET Framework, any class containing a member that implements IDisposable is obligated to implement IDisposable, itself. This is no big problem—the Windows Forms classes we typically use to contain Tablet PC objects like InkCollector already implement IDisposable. The Component base class provides an overridable method named Dispose, with a Boolean parameter indicating if the call to Dispose was explicit (by means of the IDisposable interface: true) or implicit (by means of the finalizer: false).

If the argument is true, that's our cue to cleanup our Ink resources (and any other objects we own that happen to implement IDisposable). As an aside, I find the naming of this argument, disposing, to be utterly confusing. Don't you? (The method is named Dispose, after all—how could it not be disposing?) I prefer to rename this method parameter deterministic, in my own code. Your taste may differ, of course, but I find the code's purpose is much clearer this way, and I suspect that it is confusion about the semantics of overriding Dispose that's the cause for much of its neglect. The following code demonstrates how I like to release unmanaged resources (Ink or otherwise) in a Windows Forms component's override of Dispose:

protected override void Dispose(bool deterministic)
{
  if (deterministic)
  {
    // Cleanup unmanaged resources.
    if (components != null) 
      components.Dispose();

    if (inkCollector != null) 
      inkCollector.Dispose();
  }

  // Forward the call to the base class.
  base.Dispose(deterministic);
}

Of course, if your class contains any other unmanaged members outside of the 'components' collection, you should Dispose of them, too.

Tip #4: Don't Make Assumptions About Security

Last month, I demonstrated the new support for Ink on the Web in Microsoft Windows XP Tablet PC Edition 2005. We saw that, in this context, the word "Web" really means "an application or component obtained from a partially-trusted source." We discussed the topic of code access security at some length, but one subject I neglected was the use of declarative security attributes and imperative permission requests.

Those of you familiar with FxCop (a code-analysis tool for the .NET Framework) may recognize what I'm talking about: the "Assemblies specify permission requests" rule shows up as a Critical Error in most new projects aimed at the .NET Framework.

The idea behind this rule is that your assemblies should declare, as early as possible (before they're loaded, even), any special permissions that they require, or might require. This is much better than just letting the security exceptions happen naturally in the course of using your application. Why? By way of example, let's imagine that you've placed an Ink control in a Web page to collect users' signatures on a form. Perhaps you require the ability to access the current username environment variable and to transmit it back to the server along with the signature, for auditing purposes.

If you attempt to read the Environment.UserName property without the proper care, your code could fail (in the current version of the .NET Framework, it would succeed by default if run from the Intranet zone, but fail if run from the Internet zone). What's more, the failure would occur at the worst possible time: after the user has spent quite a bit of time and effort filling out the form—but before the data is saved.

On the other hand, if you'd specified a declarative security attribute on the control's assembly, your control would simply fail to load. While not ideal, that's certainly better than losing the user's work:

[assembly: EnvironmentPermission(SecurityAction.RequestMinimum, Read="USERNAME")]

In this particular case, an even better approach might be to apply the declarative security attribute at a more fine-grained level, or perhaps perform an imperative security check instead, in order to catch the SecurityException and gracefully downgrade (or display a helpful error message). The goal is to detect any missing permissions early in the users' workflow, not at the end, when they try to save their work. The following code demonstrates how to demand permission to read the current username in a Windows Forms control's constructor:

public MyControl()
{
  // Required for Windows Form Designer support
  InitializeComponent();

  // Verify we have permission to read the current username.
  try
  {
    EnvironmentPermission readUsername = new EnvironmentPermission(
      EnvironmentPermissionAccess.Read, "USERNAME");

    readUsername.Demand();
  }
  catch (SecurityException ex)
  {
    // Degrade the control's functionality, and show a helpful error message.
    this.Enabled = false;

    MessageBox.Show(this,ex.Message,"Unable to obtain required permission",
      MessageBoxButtons.OK, MessageBoxIcon.Error);
  }
}

The bottom line: if you intend to support your application in a partially-trusted context, then it's your responsibility to regularly review your application's permission requirements and employ whatever means necessary to fail gracefully in the absence of those permissions. Bear in mind that the default permission sets applied to the various security zones have been known to change, from version to version of the .NET Framework—from service pack to service pack, even. If you decide to perform imperative security checks as demonstrated above, you can still appease FxCop by applying an assembly-level permission request to indicate that you'd like permission to read the current username, but you won't crash without it. This is done with the SecurityAction.RequestOptional flag:

[assembly: EnvironmentPermission(SecurityAction.RequestOptional, Read="USERNAME")]

Finally, if you do not intend to support your code in a partial-trust environment at all, you should prevent this sort of misuse by simply leaving off the AllowPartiallyTrustedCallersAttribute and applying a strong-name signature. There is no need to explicitly perform a demand for full-trust—the .NET Framework will happily do it for you.

Note   Assemblies without strong-name signatures may still be downloaded and executed in a partial-trust context, even though they lack the AllowPartiallyTrustedCallersAttribute.

Tip #5: Learn to Recognize the Local Dialect

Writing localizable software is hard. Anyone who's gone through the process of localizing commercial software knows that it's not just a matter of swapping out the static text strings appearing in your user interface. Some languages read right-to-left, and some languages are much more verbose than English, requiring different dialog sizes and form layouts.

Developing localizable software for the Tablet PC is no different, but handwriting recognition adds an extra facet of complexity to the problem—RecognizerContext objects created from different languages' recognizers (say, English and Japanese) have very different capabilities and behaviors. For example, Japanese and other East Asian language recognizers support character Autocomplete, while English and other Latin-based language recognizers support word lists—but not vice versa.

The problem is that the vast majority of sample code out there naively calls Recognizers.GetDefaultRecognizer(), without specifying a locale id, but then proceeds to set properties, to call methods, and to handle events as if the recognizer is the US English recognizer. This can result in a horrible experience for users of non-English Tablet PC systems—poor recognition, at best, or an unhandled exception, at worst.

Let's not perpetuate this practice. If you don't intend to localize your application's resources, that's fine—simply call the overload of GetDefaultRecognizer that takes a locale ID (LCID) as an argument, and specify the locale ID for the language your application's resources are written in. (The locale ID for US English is '1033,' and that recognizer is available on all Tablet PC operating systems, regardless of language.)

Recognizers recognizers = new Recognizers();
Recognizer english = recognizers.GetDefaultRecognizer(1033);

using (RecognizerContext recoctx = english.CreateRecognizerContext())
{
  ...

On the other hand, if you do intend to localize your application's resources and you wish to provide the best recognition experience possible for as wide a variety of languages as possible, then some extra work is required. You may call the default overload of GetDefaultRecognizer() to get a recognizer for the current thread's language, but you'll need to query its Capabilities property to safely determine what features it supports, and what sort of input it expects. And that's just for starters. The RecognizerCapabilities flags don't provide any information about which factoids are supported by the current locale, or whether word lists are supported—you'll need to build that knowledge into your applications, and apply liberal exception-handling. Indeed, even the call to GetDefaultRecognizer() may fail if there is no recognizer available for the current locale (Russian, for example). In such cases, the best course of action is probably to ask the user to select an alternate language for recognition, or else to simply fall back to the recognizer for US English (1033).

Recognizers recognizers = new Recognizers();
Recognizer recognizer;
try
{
  // Attempt to load recognizer for current thread locale.
  recognizer = recognizers.GetDefaultRecognizer();
}
catch (Exception)
{
  // Fall back to US English reco.
  recognizer = recognizers.GetDefaultRecognizer(1033);
}

using (RecognizerContext recoctx = recognizer.CreateRecognizerContext())
{
  ...

Writing fully international-aware Tablet PC applications is a topic worthy of its own book. At a minimum, we should strive to ensure that our applications don't completely fail when they find themselves running in an exotic locale.

Conclusion

Microsoft Windows XP Tablet PC Edition 2005 has done a lot to improve the experience for Tablet PC users—we ISVs owe it to our users to do the same. One way we can achieve that is to deliver rock-solid implementations of our Tablet PC components and applications. This month, I ran down a list of the top five bugs I look for when reviewing my team's code: threading, exception handling, resource management, security, and localization.

Although many of these lessons can be applied to both Tablet PC and ordinary desktop development, they're all especially relevant to development on the Tablet PC platform. Hopefully, by sharing these suggestions I'll have saved some of you from suffering the same slings and arrows of outrageous bug reports, and by better coding, end them.

Show: