 MSDN Magazine April 2006

MSDN Magazine April 2006

Managed Spy: Deliver The Power Of Spy++ To Windows Forms With Our New Tool

Spy++ displays Win32 information such as window classes, styles, and messages. Now you can get that same functionality for managed code using our ManagedSpy. Get it here.

Benjamin Wulfe No More Hangs: Advanced Techniques To Avoid And Detect Deadlocks In .NET Apps

You can combat deadlock using a combination of disciplined locking practices which Joe Duffy aptly explains in this article.

Joe Duffy Mutant Power: Create A Simple Mutation Testing System With The .NET Framework

With mutation testing, the system under test is changed to create a faulty version called a mutant. Here James McCaffrey explains how to do this in .NET.

James McCaffrey Built For Speed: Develop Turbocharged Apps For Windows Compute Cluster Server

This article explores the services provided by Compute Cluster Server 2003 and the tools provided by Visual Studio 2005 that will help you develop High-Perfomance Computing applications.

Rich Ciapala .NET Profiling: Write Profilers With Ease Using High-Level Wrapper Classes

Here Joachim H. Fröhlich and Reinhard Wolfinger show you how to get all the great functionality of the .NET Profiling API the easy way, with custom wrappers.

Joachim H. Fröhlich and Reinhard Wolfinger Editor's Note:

The returns are in. MSDN Magazine—or, we should say, the award-winning MSDN Magazine—has added a couple of trophies to the shelf. And in the spirit of sharing, we are also officially the award-giving MSDN Magazine.

Toolbox: Analyze HTTP Traffic, Synchronize Databases, and More

Debugging Web applications can be a difficult process due to the logical, physical, and temporal differences between the mishmash of technologies that comprise such an application. For bugs that arise from the HTML and script received by the browser or in the transfer or request of a page's markup, developers often adopt archaic debugging techniques, such as using View Source and Notepad to scrutinize the contents received by the browser.

Scott Mitchell Advanced Basics: IntelliSense Code Snippets

IntelliSense code snippets are one of the coolest new features in Visual Studio® 2005. Code snippets are highly customizable code fragments intended to accomplish simple to intermediate tasks quickly; they can be inserted into your code with just a few keystrokes.

Lorenzo Minore CLR Inside Out: Extending System.Diagnostics

The System. Diagnostics namespace in the Microsoft® . NET Framework contains powerful tracing capabilities. This includes the main tracing API: TraceSource. As you will see, the tracing APIs in System.

Krzysztof Cwalina Cutting Edge: Windows Workflow Foundation, Part 2

In last month's column, I presented a helpdesk workflow sample that focused on Windows® Forms client applications. This month I'll discuss ASP. NET workflow applications and the ability to expose a workflow as a Web service and invoke a Web service from a workflow.

Dino Esposito Extreme ASP.NET: A New Solution to an Old State Storage Problem

State management in Web applications is a contentious issue. Should you store user data per session or should you persist it across sessions? You can easily store information temporarily while someone navigates your site by using session state.

Fritz Onion Service Station: Migrating to WSE 3.0

You've probably heard that the new version of Web Services Enhancements (WSE) for the Microsoft® . NET Framework simplifies the process of building secure Web services. What you may not know is that most of these improvements derive from some core architectural changes made in WSE 3.

Aaron Skonnard Security Briefs: A First Look at InfoCard

The Web can be annoying at times. I'm certain that I'm not alone in my frustration with filling out the same old forms on every Web site I visit. Like most other techies, I've acquired many tools over the years to help combat this repetition, and I even wrote my own password manager for my hundreds of different identities on the Web.

Keith Brown Pure C++: How Templates and Generics Work Together

I'm closing out this series of columns on generic programming in the Microsoft® . NET Framework by showing how templates and generics do and do not work together, and pointing out a pitfall with the current implementation of templates under Visual C++® 2005.

Stanley B. Lippman C++ at Work: Implement Triple-Click, Subclass the Main Window

Paul DiLascia { End Bracket }: The World-Wide Telescope

Someday all scientific literature and data will be online and accessible to everyone everywhere. The astronomy community has made unusually good progress toward this vision of online science in addition to addressing the associated challenges of data publication.

Jim Gray and Alexander Szalay


Managed Spy: Deliver The Power Of Spy++ To Windows Forms With Our New Tool

Managed Spy

Deliver The Power Of Spy++ To Windows Forms With Our New Tool

Benjamin Wulfe
This article discusses:
  • The new ManagedSpy utility
  • Understanding how ManagedSpy works and helps in debugging
  • A look at the inner workings of ManagedSpyLib
  • Using ManagedSpyLib for unit testing
This article uses the following technologies:
.NET Framework 2.0
Code download available at: ManagedSpy.exe (284KB)
Contents
Spy on Your UI
Inside ManagedSpy
Using ManagedSpyLib
Accessing Underlying Control Properties
Other ControlProxy Methods
Using Window Hooks
Using Memory-Mapped Files
Creating a ControlProxy and Handle Recreation
ManagedSpyLib for Unit Testing
Conclusion

M
any developers use the Spy++ tool provided with Visual Studio®. With Spy++, you can understand the window layout of a running application or identify a certain window message that causes a bug. However, when you create a Microsoft® .NET Framework-based application, Spy++ becomes less useful because the window messages and classes intercepted by Spy++ don't correspond to anything a developer uses or even sees. What a developer really wants to see are managed events and property values.

This article describes how to use a new utility called ManagedSpy and its associated library ManagedSpyLib, both of which are available for download from the MSDN®Magazine Web site. Similar to how Spy++ displays Win32® information such as window classes, styles, and messages, ManagedSpy displays managed controls, properties, and events. ManagedSpyLib allows you to programmatically access Windows® Forms controls in another process. You can get and set properties and sync on events in your own code. ManagedSpyLib can also help you build test harnesses and can perform window, message, and event logging.

Spy on Your UI

When writing client applications, there are many cases where a traditional debugger isn't useful. For example, if your bug involves focus or other UI aspects, it's difficult to debug because the debugger modifies this state whenever you hit a breakpoint. Another problem that's difficult to debug is layout. If your form has a complex and dynamic layout, it's not always obvious whether your layout logic is invoked multiple times. To debug these problems, you usually must resort to event or message logging to get an idea of what input is being fed to your UI.

With complex UI, it is useful to have a view of windows and associated states. For example, it can be difficult to locate the relevant control objects in the debugger. Most of the time you must guess that a debugger variable is the one you are looking at in the UI.

Figure 1 shows a dialog box with a few nested controls. This app has a bug in the top-right textbox, though for the purposes of this example it doesn't really matter what the bug is. It would be useful to identify not only which member is the red textbox, but also the parent hierarchy and layout of related controls.

Figure 1 Problem Dialog Box
Figure 1 Problem Dialog Box

ManagedSpy can help with this scenario and others. It displays a treeview of controls in your .NET-based client application. You can select any control and get or set any property on it. You can also log a filtered set of events that the control raises. While great for debugging, this can also help in compatibility testing of your control. You can use real applications and log events to ensure that event ordering is preserved for the next version of your control.

When you first run ManagedSpy, it displays a list of processes in a treeview in the left side of the window and a PropertyGrid on the right side. You can expand the process to see top-level windows in that process.

When you select a control, the PropertyGrid shows properties on that control. You can examine or change property values here. You should note that custom types are supported as long as they are binary serializable (see Basic Serialization).

The toolbar contains commands to select which events get logged to the event pane, to refresh the TreeView when new windows have been created, to start or stop logging of events to the event pane, and to clear the event pane.

For the dialog box shown in Figure 1, ManagedSpy displays the information shown in Figure 2. According to ManagedSpy, textBox1 is parented to a SplitContainer (SplitContainer2), which in turn is parented to a TableLayoutPanel (tableLayoutPanel1). The parent of the TableLayoutPanel is a TabControl, which is in yet another SplitContainer. Note also that ManagedSpy tells me that the BackColor is Red.

Figure 2 Debugging a Control in ManagedSpy
Figure 2 Debugging a Control in ManagedSpy

Clicking on the Events tab will display events such as MouseMove on the currently selected control in the treeview. To begin logging events, click the Start Logging button. The output will appear as shown in Figure 3.

Figure 3 Logging Events
Figure 3 Logging Events

There are usually many mouse events. You can filter these or other events from being logged by clicking the Filter Events button, which displays a dialog box that lets you specify which events are to be logged. The event filter dialog lists all events from the Type control. Any event that is declared in a derived class is controlled through the Custom Events selection.

Inside ManagedSpy

The main method in ManagedSpy is called RefreshWindows. Its job is to fill the TreeView with all of the processes and windows running on the desktop. The first thing it does is clear the TreeView and requery all of the top-level windows in the system:

   private void RefreshWindows() {
       this.treeWindow.BeginUpdate();
       this.treeWindow.Nodes.Clear();

       ControlProxy[] topWindows =
          Microsoft.ManagedSpy.
             ControlProxy.TopLevelWindows;
    ...
Once it has a collection of the top-level windows, ManagedSpy enumerates each window and, if it is a managed window, adds it to the treeview:
if (topWindows != null && topWindows.Length > 0) {
    foreach (ControlProxy cproxy in topWindows) {
        TreeNode procnode;

        //only showing managed windows
        if (cproxy.IsManaged) {
Here, ManagedSpy is using the ControlProxy class defined in ManagedSpyLib. A ControlProxy represents a window running in another process. If the window is actually a System.Windows.Forms.Control, then IsManaged will be true. Since ManagedSpy can only show information on .NET Framework-based controls, it doesn't display other window types.

Now, for each top-level ControlProxy that is managed, ManagedSpy can find its owning process. Once the process has a node in the TreeView, ManagedSpy uses that as the parent TreeNode of the new ControlProxy entry:

Process proc = cproxy.OwningProcess;
if (proc.Id != Process.GetCurrentProcess().Id) {
    procnode = treeWindow.Nodes[proc.Id.ToString()];
    if (procnode == null) {
        procnode = treeWindow.Nodes.Add(proc.Id.ToString(),
            proc.ProcessName + "  " +
            proc.MainWindowTitle +
            " [" + proc.Id.ToString() + "]");
        procnode.Tag = proc;
    }
    ...
At this point, procnode is a TreeNode of the owning process. Its title is generated using information from System.Diagnostics.Process. The only other interesting point here is that ManagedSpy avoids showing windows from itself.

Finally, ManagedSpy adds another TreeNode under procnode to represent the Window (see Figure 4). ManagedSpy uses ControlProxy.GetComponentName and ControlProxy.GetClassName as the title of the TreeNode. GetClassName refers to the System.Type of the remote control—not the window class that Spy++ displays.

Figure 4 Adding a Window Node (Close Figure 4)
string name = String.IsNullOrEmpty(cproxy.GetComponentName()) ?
    "<noname>" : cproxy.GetComponentName();
TreeNode node = procnode.Nodes.Add(cproxy.Handle.ToString(),
    name + "     [" + cproxy.GetClassName() + "]");
node.Tag = cproxy;
...
if (treeWindow.Nodes.Count == 0)
{
    treeWindow.Nodes.Add("No managed processes running.");
    treeWindow.Nodes.Add("Select View->Refresh.");
}
this.treeWindow.EndUpdate();

Whenever you select a TreeNode, ManagedSpy places that TreeNode's Tag in the PropertyGrid displayed on the right side. This is how properties are displayed for the remote control. The following code shows how ManagedSpy displays its TreeView and all of its properties:

private void treeWindow_AfterSelect(object sender, TreeViewEventArgs e)
{
    this.propertyGrid.SelectedObject = this.treeWindow.SelectedNode.Tag;
    this.toolStripStatusLabel1.Text = treeWindow.SelectedNode.Text;
    StopLogging();
    this.eventGrid.Rows.Clear();
    StartLogging();
}

I won't step through how events are logged, but it is no more complicated than displaying properties. ManagedSpy subscribes on the EventFired event of the selected ControlProxy. When this event fires, a new row is added to a DataGridView control to display the data (the DataGridView control is new to the .NET Framework 2.0).

Using ManagedSpyLib

ManagedSpy is written on top of a managed C++ library called ManagedSpyLib. The purpose of ManagedSpyLib is to allow programmatic access to .NET Framework-based windows in another process. ManagedSpyLib exposes a class called ControlProxy that represents a control in another process. Although it's not an actual control, you can access all of the properties and events of the control it represents.

ManagedSpyLib works by transferring data between the spying and spied processes using a memory-mapped file. For this to work, all data transferred between the processes must be binary serializable. The main mechanism used to communicate between processes is custom window messages and SetWindowsHookEx. This ensures that the destination code runs on the thread that owns the window you need to query. This is important because there are many operations that only work when called from the owning thread of a window.

There are two ways to create a ControlProxy. The first is by using ControlProxy.FromHandle, passing into the method the IntPtr representing the HWND of the target control. This returns to you a ControlProxy for the target. The HWND of a window can usually be found using Win32 methods such as EnumWindows or by using applications like Spy++. You can also obtain the HWND by accessing a Control's Handle property.

The second way is by using ControlProxy.TopLevelWindows. You call this static method to get an array of ControlProxy classes. You will get a ControlProxy for every top-level window on your desktop. Not all of these windows are represented by managed controls, however. To determine this, examine the properties of the ControlProxy to see if it is indeed a managed window. See the Properties section that follows for more information on what you can retrieve. Figure 5 provides an example that lists the number of top-level windows per process.

Figure 5 Listing Windows by Process (Close Figure 5)
using System;
using System.Text;
using System.Diagnostics;
using System.Windows.Forms;
using System.Collections.Generic;
using Microsoft.ManagedSpy;

class Program
{
    static void Main(string[] args)
    {
        Dictionary<int, int> topWindowCounts =
            new Dictionary<int, int>();

        foreach (ControlProxy proxy in ControlProxy.TopLevelWindows)
        {
            if (!topWindowCounts.ContainsKey(proxy.OwningProcess.Id))
            {
                topWindowCounts.Add(proxy.OwningProcess.Id, 0);
            }
            topWindowCounts[proxy.OwningProcess.Id]++;
        }

        foreach (int pid in topWindowCounts.Keys)
        {
            Process p = Process.GetProcessById(pid);
            Console.WriteLine("Process: " + p.ProcessName +
                " has " + topWindowCount[pid].ToString() +
                " top level windows");
        }
    }
}

Accessing Underlying Control Properties

One of the main reasons to use ControlProxy is to access properties from a control in another process. (The properties are described in Figure 6.) To access these properties, you simply create a ControlProxy using ControlProxy.FromHandle or ControlProxy.TopLevelWindows, then call two methods to access the values. Call GetValue to obtain a property value from the underlying control in the spied process. For example, you can call GetValue with this code to obtain the Size property:

controlproxy.GetValue("Size")
Call SetValue to change a property value in the underlying control in the process you're watching. For example, the following sets the background color to blue:
controlproxy.SetValue("BackColor", "Color.Blue")

Figure 6 ControlProxy Properties (Close Figure 6)

Property | Description
IntPtr Handle | Returns the underlying HWND of the Control. This would be equivalent to accessing the Handle property on the Control.
array<ControlProxy^>^ Children | Returns an array of ControlProxy classes that represent the child windows of the Control.
Process^ OwningProcess | Returns a Process object that the Control is running in.
bool IsManaged | If the window that the ControlProxy is examining represents a managed System.Windows.Forms.Control, then the value returned is true. Otherwise it is false. Most ControlProxy methods only work if the window is a System.Windows.Forms.Control.
Type^ ComponentType | Returns the Type of the Control. Note that this Type is originally from an Assembly loaded in the process being spied. The Assembly and Type are reloaded in the spying process when the ControlProxy is first created.
ControlProxy^ Parent | Returns the parent window as a ControlProxy. Use Parent and Children to navigate the window chain of the process.

To demonstrate the usefulness of ManagedSpyLib for editing properties across processes, I'll create a simple C# application. I add a textbox called textBox1 and a button called button1. I then double-click the button to create the button1_Click handler and add some code that includes the excerpt shown in Figure 7.

Figure 7 Modifying Other Instances of an Application (Close Figure 7)
private void button1_Click(object sender, EventArgs e)
{
    foreach (Process p in
        Process.GetProcessesByName("WindowsApplication1"))
    {
        if (p.Id != Process.GetCurrentProcess().Id)
        {
            ControlProxy proxy =
                ControlProxy.FromHandle(p.MainWindowHandle);
            string val = (string)proxy.GetValue("MyStringValue");
            MessageBox.Show("Changing " + val + " to " + MyStringValue);
            proxy.SetValue("MyStringValue", (object)MyStringValue);
        }
    }
}

public string MyStringValue
{
    get { return this.textBox1.Text; }
    set { this.textBox1.Text = value; }
}

If I run two instances of the application, type some text into textBox1 of one instance, and then click button1, it will find all other running instances of this app and change their textbox strings to match, as shown in Figure 8.

Figure 8 Instances
Figure 8 Instances

You can subscribe to events such as Click or MouseMove on a control in another process. Subscribing to events is a two-step process. You must first call SubscribeEvent with the event name to have the ControlProxy listen on that event. You then subscribe on the ControlProxy event called EventFired:

private void SubscribeMainWindowClick(ControlProxy proxy)
{
    proxy.SubscribeEvent("Click");
    proxy.EventFired += new ControlProxyEventHandler(
        Program.ProxyEventFired);
}

void ProxyEventFired(object sender, ProxyEventArgs args)
{
    System.Windows.Forms.MessageBox.Show(args.eventDescriptor.Name
        + " event fired!");
}
Note that when you are finished with a ControlProxy, you should unsubscribe from all previously subscribed events.

ManagedSpy itself uses the ControlProxy class to retrieve property values. For example, FlashCurrentWindow highlights the selected window for a few seconds. It also subscribes to events for its logging functionality.

Other ControlProxy Methods

There are a few additional methods worth taking a look at in ControlProxy. Call the SendMessage method to send a window message to the control. This is useful if you want to create a test harness. For example, you can send WM_CLICK or WM_KEYDOWN messages to simulate input. If you want to use ManagedSpyLib in this way, you can modify it so that the window hook procedure is always on and have it filter every window message except those you have programmed. This creates an automation driver that disables other input.

PointToClient and PointToScreen transform screen coordinates into client coordinates. The SetEventWindow and RaiseEvent methods are not intended to be used from user code. They are used internally to manage events cross process. ICustomTypeDescriptor allows an object to dynamically specify properties and events. ControlProxy implements this interface for PropertyGrid support. You can call these methods directly from user code, but it's usually not necessary. To access properties, use the GetValue and SetValue methods.

Using Window Hooks

As mentioned previously, ManagedSpyLib works by transferring data between processes. A window hook is a way to intercept window messages such as WM_SETTEXT. There are two methods of creating a window hook. SetWindowLong allows you to intercept window messages on a specific window in the same process. SetWindowsHookEx allows a wide range of message hooking, including the ability to hook messages for all windows for all processes in the current desktop.

Most developers who use native code will recognize SetWindowLong as the Win32 function that subclasses a window. After you have subclassed a window, Windows will send your callback method all of the Win32 messages destined for the window handle that you specified. This allows you to modify or just examine the message.

Note that SetWindowLong requires you to be in the same process as the window you are subclassing. If you want to do this type of subclassing, the .NET Framework makes it very simple by providing a class called System.Windows.Forms.NativeWindow. You may be asking two questions at this point.

  1. What if I want to see window messages and I am not in the same process as the target window?
  2. How does hooking window messages relate to ManagedSpyLib if it ends up showing managed information anyway?
If you want to see window messages and you are not running in the same process as the target window, you cannot use SetWindowLong. You can use SetWindowsHookEx with one caveat: for most types of hooks, your callback method must be exposed as a dllexport. This means you must write the callback in a native DLL or a mixed mode C++ DLL. ManagedSpyLib was written using managed C++ for this very reason. It uses the C++/CLI support in Visual Studio 2005.

There are two reasons why ManagedSpyLib uses window message hooking. To receive requests in a target process, it must be able to execute code in that process. SetWindowsHookEx allows you to do this. ManagedSpyLib also uses custom window messages to send and receive data between processes. This means its window hook must be activated when it sends a request (such as retrieving the BackColor of a Control in another process).

Using Memory-Mapped Files

But how exactly does ManagedSpyLib transfer data across processes? Sure, it can send a custom window message such as WM_SETMGDPROPERTY to set a value of a property. But if the property is BackColor, for example, how does it send BackColor.Red? Window messages only have two DWORDs as parameters.

The answer is that it uses a memory-mapped file. This is not actually a file on disk. It is an area of memory that is shareable among multiple processes. You map that memory into your own process address space. The consequence of this, however, is that the shared area has a different starting address. Therefore, you have to be careful storing data in it—no pointers! Also, you cannot have any managed objects in the memory-mapped file because the common language runtime (CLR) cannot manage that memory. This means you can only store raw byte data.

For this reason, ManagedSpyLib stores only binary serialized data. This is why properties (and EventArgs) must be serializable to be supported by ManagedSpyLib. ManagedSpyLib uses CAtlFileMapping to create a memory-mapped file for every transaction.

ManagedSpyLib calculates the size of the binary stream, creates a memory-mapped file of the right size, and then copies the data to it. Now that you have a general idea of how ManagedSpyLib uses window hooks to install itself and memory-mapped files to send data, let's take a closer look at how the ControlProxy class is created and maintained.

Creating a ControlProxy and Handle Recreation

Figure 9 illustrates how a ControlProxy is created (red arrows) and how it is maintained when its handle changes (blue arrows). The user initially calls either ControlProxy.FromHandle or ControlProxy.TopLevelWindows. TopLevelWindows will call EnumWindows and then FromHandle on each window enumerated. So you can think of TopLevelWindows as just a more complex FromHandle call.

Figure 9 Creating a ControlProxy
Figure 9 Creating a ControlProxy

ManagedSpyLib turns on a windows hook for the thread that owns the destination window. Then ManagedSpyLib sends a WM_GETPROXY message to the destination window (the windows hook will be turned off once this message is handled). On the receiving side, the message is received and the command library calls Control.FromHandle to get the managed control running in the spied process. Using the control, ManagedSpyLib creates a new ControlProxy. This ControlProxy stores the Type.FullName of the control as well as the Assembly.Location of all loaded assemblies in the current AppDomain.

The ControlProxy subscribes to the Control's HandleCreated and HandleDestroyed events. It uses this later to maintain the proper window handle state. The ControlProxy is stored in the ProxyCache of the spied process and sent back to the spying process using binary serialization. The spying process deserializes the ControlProxy and adds it to its local ProxyCache. It then returns the ControlProxy to the user.

When the spied process recreates the handle for a control, ManagedSpyLib maintains the proper state. HandleDestroyed is received from the ControlProxy in the spied process. The ControlProxy checks Control.RecreatingHandle to see if the control is just performing a handle recreate. If the handle is being recreated, the ControlProxy waits for the corresponding HandleCreated. It updates the local ProxyCache, then sends a WM_HANDLECHANGED to the EventWindow of the spying process. The spying process locates the correct ControlProxy from the ProxyCache by looking up with the old window handle. It then updates the ControlProxy and the spying process's ProxyCache.

Figure 10 shows how ControlProxy gets properties (red arrows) and receives events (blue arrows). ManagedSpyLib performs the following sequence when you get a property value via ControlProxy.GetValue(propertyName). First, the spying process calls into ControlProxy.GetValue with the name of the property. ManagedSpyLib turns on a window hook for the thread that owns the destination window. This will be turned off once the message is handled. ManagedSpyLib stores the name of the property to get in a memory-mapped file (the Parameters section of the calling process's memory store). It uses binary serialization to do this.

Figure 10 Getting Proxies and Receiving Events
Figure 10 Getting Proxies and Receiving Events

ManagedSpyLib sends a WM_SETMGDPROPERTY message to the destination window. The windows hook procedure (MessageHookProc) will be called inside the spied process to handle the window message. MessageHookProc will then process the command and uses reflection to get the return value. It stores the return value in the calling process's memory store. When SendMessage completes, the spying process deserializes the return value from its memory store. It sends a WM_RELEASEMEM to the same destination window to notify it that it can release its reference on the mapped file. Finally, it returns the value.

To subscribe and get events is similar. The spying process calls into SubscribeEvent, which stores the following in the Parameters section of the spying process' memory store: the EventWindow handle, the name of the event to subscribe to, and an event code unique to this event within this window (usually an index to the event in the event list).

SubscribeEvent sends WM_SUBSCRIBEEVENT to the destination control. Upon receiving WM_SUBSCRIBEEVENT in the spied process, ManagedSpyLib creates an EventRegister object that subscribes on the event and keeps track of what event it has subscribed on. When an event is fired, the EventRegister sends a WM_EVENTFIRED message to the Event window with the source window, event code and the EventArgs stored in the spied process' memory store.

The spying process handles WM_EVENTFIRED, parses the source window, event code and EventArgs, and calls RaiseEvent on the correct ControlProxy with the correct event and EventArg information. RaiseEvent raises the EventFired event on ControlProxy.

ManagedSpyLib for Unit Testing

With ManagedSpyLib, you can do testing without having to expose hooks from your application. To illustrate this, I created a new C# Windows Forms-based application called Multiply. I added three textboxes and one button, then double-clicked the button and added the following code for its Click event:

private void button1_Click(object sender, EventArgs e)
{
    int n1 = Convert.ToInt32(this.textBox1.Text);
    int n2 = Convert.ToInt32(this.textBox2.Text);
    this.textBox3.Text = (n1 * n2).ToString();
}
All this application does is calculate two textboxes and show the result in the third textbox. The real point is creating a unit test app that works with this simple sample.

For the next step, I added a new C# Windows-based application to the solution and name it UnitTest. There's just a single button on the form along with the code shown in Figure 11.

Figure 11 Testing Code (Close Figure 11)
private void button1_Click(object sender, EventArgs e)
{
    Process[] procs = Process.GetProcessesByName("Multiply");
    if (procs.Length != 1) return;
    ControlProxy proxy =
        ControlProxy.FromHandle(procs[0].MainWindowHandle);
    if (proxy == null) return;

    //find the controls we are interested in...
    if (cbutton1 == null)
    {
        foreach (ControlProxy child in proxy.Children)
        {
            if (child.GetComponentName() == "textBox1") {
                textBox1 = child;
            }
            else if (child.GetComponentName() == "textBox2") {
                textBox2 = child;
            }
            else if (child.GetComponentName() == "textBox3") {
                textBox3 = child;
            }
            else if (child.GetComponentName() == "button1") {
                cbutton1 = child;
            }
        }

        //sync testchanged on textbox3 so we can tell if it has changed.
        textBox1.SetValue("Text", "5");
        textBox2.SetValue("Text", "7");
        textBox3.SetValue("Text", "");
        textBox3.EventFired +=
            new ControlProxyEventHandler(textBox3_EventFired);
        textBox3.SubscribeEvent("TextChanged");
    }
    else textBox3.SetValue("Text", "");

    //now click on the button to start the test...
    if (cbutton1 != null)
    {
        cbutton1.SendMessage(WM_LBUTTONDOWN, IntPtr.Zero, IntPtr.Zero);
        cbutton1.SendMessage(WM_LBUTTONUP, IntPtr.Zero, IntPtr.Zero);
        Application.DoEvents();
    }

    if (result == 35) MessageBox.Show("Passed!");
    else MessageBox.Show("Fail!");
}

void textBox3_EventFired(object sender, ProxyEventArgs ed)
{
    int val;
    if (int.TryParse((string)textBox3.GetValue("Text"), out val)
    {
        result = val;
    }
}

When you run the unit test application, it will change the first textbox to 5 and the second to 7. It then sends a click (via a mousedown and mouseup) to the button and looks at the final result (which is set in the event callback).

Conclusion

ManagedSpy is a diagnostic tool, similar to Spy++. It shows managed properties, allows you to log events, and is a good example of using ManagedSpyLib. ManagedSpyLib introduces a class called ControlProxy. A ControlProxy is a representation of a System.Windows.Forms.Control in another process. ControlProxy allows you to get or set properties and subscribe to events as if you were running inside the destination process. Use ManagedSpyLib for automation testing, event logging for compatibility, cross process communication, or whitebox testing.

Benjamin Wulfe has been at Microsoft for six years working on Visual Studio, the Windows Forms designer for both the .NET Framework and .NET Compact Framework, and several Framework classes such as ComboBox and NativeWindow.

© 2007 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.


No More Hangs: Advanced Techniques To Avoid And Detect Deadlocks In .NET Apps

No More Hangs

Advanced Techniques To Avoid And Detect Deadlocks In .NET Apps

Joe Duffy
This article discusses:
  • Understanding how deadlocks occur
  • Avoiding deadlocks with lock leveling
  • Detecting and breaking deadlocks
  • Examining a custom CLR host for deadlock detection
This article uses the following technologies:
.NET Framework, C#, C++
Code download available at: Deadlocks.exe (188KB)
Contents
Deadlocks 101
Other Subtle Deadlock Examples
Avoiding Deadlocks with Lock Leveling
Detecting and Breaking Deadlocks
The Algorithms
Spelunking Through the Hosting APIs
Wait Graph Construction and Traversal
Custom Deadlock Host in Action
Wrap-Up

A
pplication hangs are one of the most frustrating situations a user can experience. They're terribly difficult to find before shipping, and even more difficult to debug after an application has been deployed. Unlike a crash, an application hang may not produce a crash dump or trigger custom failure logic. Users frequently shut down a frozen application before such information can be captured, meaning there's no tell-tale stack trace to help you find the source of the problem.

Hangs can range from temporary to permanent. They can result from slow I/O, a computationally expensive algorithm, or mutually exclusive access to resources, in any case reducing the overall responsiveness of an application. Code that blocks a GUI thread's execution can prevent a window from processing incoming user input and system-generated messages, resulting in an app that is "Not Responding." Even non-GUI programs can suffer from responsiveness problems when using shared resources or when performing interthread, interprocess, or intermachine communication. Clearly the worst type of a hang is one that never comes back—in other words, a deadlock.

Engineering large concurrent applications using the standard Windows® shared-memory toolkit of threads and locks is significantly more involved than it might appear at first glance. Forgetting to lock memory in motion can lead to race conditions that lead to crashes at best and corrupt data at worst. There's a trade-off between correctness and sequential performance (coarse granularity of locks) and parallel scalability (fine granularity of locks). Disparate segments of code, each acquiring and releasing locks at varying granularities, tend to interact with each other in unforeseen ways. Assuming your system does nothing to mitigate deadlocks, a subtle misstep here is apt to gum up the complex machinery of your program.

Today's shared memory programming models require that the programmer think about the possible interleaving of instructions throughout program execution. When the unexpected occurs, it can be extremely difficult to replay the steps leading up to the incident. Deadlocks, while more obvious than races (in the simplest case, the program just stops dead in its tracks), are still difficult to locate and eliminate. You may find a small number of them by accident in your ordinary functional test suites, but it's more likely that many will lie dormant in your shipping product, waiting to spring onto an unsuspecting customer. And even when you find the cause, you may have already painted yourself into a corner.

You can combat deadlock using a combination of disciplined locking practices and aggressive stress testing on a range of hardware platforms. The former of these disciplines is the primary topic of this article. But first, let's review the basics of deadlocks.

Deadlocks 101

A deadlock occurs when a set of concurrent workers are waiting on each other to make forward progress before any of them can make forward progress. If this sounds like a paradox, it is. There are four general properties that must hold to produce a deadlock:

Mutual Exclusion When one thread owns some resource, another cannot acquire it. This is the case with most critical sections, but is also the case with GUIs in Windows. Each window is owned by a single thread, which is solely responsible for processing incoming messages; failure to do so leads to lost responsiveness at best, and deadlock in the extreme.

A Thread Holding a Resource is Able to Perform an Unbounded Wait For example, when a thread has entered a critical section, code is ordinarily free to attempt acquisition of additional critical sections while it is held. This typically results in blocking if the target critical section is already held by another thread.

Resources Cannot be Forcibly Taken Away From Their Current Owners In some situations, it is possible to steal resources when contention is noticed, such as in complex database management systems (DBMSs). This is generally not the case for the locking primitives available to managed code on the Windows platform.

A Circular Wait Condition A circular wait occurs if chain of two or more threads is waiting for a resource held by the next member in the chain. Note that for non-reentrant locks, a single thread can cause a deadlock with itself. Most locks are reentrant, eliminating this possibility.

Any programmer who has worked with pessimistic algorithms should already understand how a deadlock occurs. A pessimistic algorithm detects contention when attempting to acquire a shared resource, usually responding by waiting until it becomes available (for example, blocking). Compare this to optimistic algorithms, which will attempt forward progress with the risk that contention will be detected later on, such as when a transaction attempts to commit. Pessimism is generally easier to program and reason about—and is substantially more common on the platform compared to optimistic techniques, usually taking the form of a monitor (a C# lock block or a Visual Basic® SyncLock block), a mutex, or a Win32® CRITICAL_SECTION.

Lock-free or interlocked-based algorithms that can detect and respond to contention are relatively common for systems-level software; these algorithms often avoid entering a critical section altogether in the fast path, choosing to deal with livelock instead of deadlock. Livelock presents a challenge to parallel code too, however, and is caused by fine-grained contention. The result stalls forward progress much like a deadlock. To illustrate the effect of livelock, imagine two people trying to pass each other in a hallway; both move side to side in an attempt to get out of the other's way, but do so in such a way that neither can move forward.

To further illustrate how a deadlock might occur, imagine the following sequence of events:

  1. Thread 1 acquires lock A.
  2. Thread 2 acquires lock B.
  3. Thread 1 attempts to acquire lock B, but it is already held by Thread 2 and thus Thread 1 blocks until B is released.
  4. Thread 2 attempts to acquire lock A, but it is held by Thread 1 and thus Thread 2 blocks until A is released.

At this point, both threads are blocked and will never wake up. The C# code in Figure 1 demonstrates this situation.

Figure 1 Classic Deadlock (Close Figure 1)
object lockA = new object();
object lockB = new object();

// Thread 1
void t1() {
  lock (lockA) {
    lock (lockB) {
      /* ... */
    }
  }
}

// Thread 2
void t2() {
  lock (lockB) {
    lock (lockA) {
      /* ... */
    }
  }
}

Figure 2 outlines the six possible outcomes should t1 and t2 enter a race condition with each other. In all but two cases (1 and 4), a deadlock occurs. While tempting, you can't conclude that a deadlock will occur 2 out of 3 times (since 4 of the 6 outcomes lead to deadlock), because the tiny windows of time needed to produce 2, 3, 5, or 6 will not occur statistically as frequently as 1 and 4.

Figure 2 Figure 2 (Close Figure 2)

Case | Results
1 | t1 locks A
 | t1 locks B
 | t2 locks B
 | t2 locks A
2 | t1 locks A
 | t2 locks B
 | t2 locks A
 | t1 locks B
3 | t1 locks A
 | t2 locks B
 | t1 locks B
 | t2 locks A
4 | t2 locks B
 | t2 locks A
 | t1 locks A
 | t1 locks B
5 | t2 locks B
 | t1 locks A
 | t2 locks A
 | t1 locks B
6 | t2 locks B
 | t1 locks A
 | t1 locks B
 | t2 locks A

Regardless of the statistics, if you write code like this clearly you're balancing your program on a knife's edge. Sooner or later your program is apt to hit one of these four deadlock cases—perhaps due to new hardware your customer has purchased that you never used to test your software, or a context switch between two specific instructions resulting from a high-priority thread becoming runnable—stopping the program in its tracks.

The example just shown is easy to identify, and reasonably easy to fix: Just rewrite t1 and t2 such that they acquire and release locks in a consistent order. That is, write the code such that both t1 and t2 acquire either A followed by B, or vice versa, eliminating the so-called deadly embrace. But consider a more subtle case of a deadlock-prone locking protocol:

void Transfer(Account a, Account b, decimal amount) {
  lock (a) {
    lock (b) {
      if (a.Balance < amount)
        throw new InsufficientFundsException();
      a.Balance -= amount;
      b.Balance += amount;
    }
  }
}

If somebody tried transferring, say, $500 from account #1234 to account #5678 at the same time somebody else tried transferring $1,000 from account #5678 to account #1234, a deadlock becomes highly probable. Reference aliasing such as this, where multiple threads refer to the same object using different variables, can cause headaches and is an extraordinarily common practice. Calls to virtual methods implemented in user-code can likewise lead you into a call-stack that dynamically acquires certain locks in unpredictable orders, causing unforeseen combinations of locks held simultaneously by a thread and a real risk of deadlock.

Other Subtle Deadlock Examples

The risk of a deadlock is not limited to mutually exclusive critical sections. There are even subtler ways in which a real deadlock might occur in your program. Soft deadlocks—where an application appears to have deadlocked, but has really just gotten stuck performing a high-latency or contentious operation—are problematic, too. Computation-intensive algorithms that execute on the GUI thread, for example, can lead to a complete loss of responsiveness. In this case, scheduling the work to occur using ThreadPool would have been a better choice (preferably using the new BackgroundWorker component in the .NET Framework 2.0).

Understanding the performance characteristics of each method your program calls is important, but very difficult in practice. An operation that invokes another whose performance characteristics have a wide range can lead to unpredictable latency, blocking, and CPU-intensiveness. The situation gets worse if the caller holds an exclusive lock on a resource when it does so, serializing access to shared resources while some arbitrarily expensive operation takes place. Parts of your application might have great performance in common scenarios, but might change dramatically should some environmental condition arise. Network I/O is a great example: it has moderate-to-high latency on average, but deteriorates significantly when a network connection becomes saturated or unavailable. If you've embedded lots of network I/O in your application, hopefully you've carefully tested how your software works (or doesn't) when you unplug the network cable from your computer.

When your code is executing on a single-threaded apartment (STA) thread, the equivalent of an exclusive lock occurs. Only one thread can update a GUI window or run code inside an Apartment-threaded COM component inside an STA at once. Such threads own a message queue into which to-be-processed information is placed by the system and other parts of the application. GUIs use this queue for information such as repaint requests, device input to be processed, and window close requests. COM proxies use the message queue to transitioning cross-Apartment method calls into the apartment for which a component has affinity. All code running on an STA is responsible for pumping the message queue—looking for and processing new messages using the message loop—otherwise the queue can become clogged, leading to lost responsiveness. In Win32 terms, this means using the MsgWaitForSingleObject, MsgWaitForMultipleObjects (and their Ex counterparts), or CoWaitForMultipleHandles APIs. A non-pumping wait such as WaitForSingleObject or WaitForMultipleObjects (and their Ex counterparts) won't pump incoming messages.

In other words, the STA "lock" can only be released by pumping the message queue. Applications that perform operations whose performance characteristics vary greatly on the GUI thread without pumping for messages, like those noted earlier, can easily deadlock. Well-written programs either schedule such long-running work to occur elsewhere, or pump for messages each time they block to avoid this problem. Thankfully, the CLR pumps for you whenever you block in managed code (via a call to a contentious Monitor.Enter, WaitHandle.WaitOne, FileStream.EndRead, Thread.Join, and so forth), helping to mitigate this problem. But plenty of code—and even some fraction of the .NET Framework itself—ends up blocking in unmanaged code, in which case a pumping wait may or may not have been added by the author of the blocking code.

Here's a classic example of an STA-induced deadlock. A thread running in an STA generates a large quantity of Apartment threaded COM component instances and, implicitly, their corresponding Runtime Callable Wrappers (RCWs). Of course, these RCWs must be finalized by the CLR when they become unreachable, or they will leak. But the CLR's finalizer thread always joins the process's Multithreaded Apartment (MTA), meaning it must use a proxy that transitions to the STA in order to call Release on the RCWs. If the STA isn't pumping to receive the finalizer's attempt to invoke the Finalize method on a given RCW—perhaps because it has chosen to block using a non-pumping wait—the finalizer thread will be stuck. It is blocked until the STA unblocks and pumps. If the STA never pumps, the finalizer thread will never make any progress, and a slow, silent build-up of all finalizable resources will occur over time. This can, in turn, lead to a subsequent out-of-memory crash or a process-recycle in ASP.NET. Clearly, both outcomes are unsatisfactory.

High-level frameworks like Windows Forms, Windows Presentation Foundation, and COM hide much of the complexity of STAs, but they can still fail in unpredictable ways, including deadlocking. COM synchronization contexts introduce similar, but subtly different, challenges. And furthermore, many of these failures will only occur in a small fraction of test runs and often only under high stress.

Different types of deadlocks unfortunately require different techniques to combat. The remainder of the article will focus on critical-section deadlocks exclusively. The CLR 2.0 comes with useful tools for catching and debugging STA transitioning problems. A new Managed Debugging Assistant (MDA), ContextSwitchDeadlock, was created to monitor for deadlocks induced by cross-Apartment transitions. If a transition takes longer than 60 seconds to complete, the CLR assumes the receiving STA is deadlocked and fires this MDA. For further information on enabling this MDA, see the MSDN® documentation.

Two general strategies are useful for dealing with critical-section-based deadlocks.

Avoid Deadlocks Altogether Eliminate one of the aforementioned four conditions. For example, you can enable multiple resources to share the same resource (usually not possible due to thread safety), avoid blocking altogether when you hold locks, or eliminate circular waits, for example. This does require some structured discipline, which can unfortunately add noticeable overhead to authoring concurrent software.

Detect and Mitigate Deadlocks Most database systems employ this technique for user transactions. Detecting a deadlock is straightforward, but responding to them is more difficult. Generally speaking, deadlock detection systems choose a victim and break the deadlock by forcing it to abort and release its locks. Such techiques in arbitrary managed code could lead to instability, so these techniques must be employed with care.

Many developers are aware of the possibility of deadlocks at a theoretical level, but few know how to combat deadlocks in a serious way. We'll take a look at some solutions that fall into both of these categories.

Avoiding Deadlocks with Lock Leveling

A common approach to combating deadlocks in large software systems is a technique called lock leveling (also known as lock hierarchy or lock ordering). This strategy factors all locks into numeric levels, permitting components at specific architectural layers in the system to acquire locks only at lower levels. For example, in the original example, I might assign lock A a level of 10 and lock B a level of 5. It is then legal for a component to acquire A and then B because B's level is lower than A's, but the reverse is strictly illegal. This eliminates the possibility of deadlock. Cleanly factoring your software's architectural dependencies is a broadly accepted engineering discipline.

There are, of course, variants on the fundamental lock leveling idea. I've implemented one such variant in C#, available in the code download for this article. An instance of LeveledLock corresponds to a single shared lock instance, much like an object on which you Enter and Exit via the System.Threading.Monitor class. At instantiation time, a lock is assigned an int-based level:

LeveledLock lockA = new LeveledLock (10);
LeveledLock lockB = new LeveledLock (5);
A program would normally declare all of its locks in a central location, for example, using static fields that are accessed by the rest of the program.

The Enter and Exit method implementations call through to an underlying private monitor object. The type also tracks the most recent lock acquisition in Thread Local Storage (TLS) so that it can ensure no locks are procured that would violate the lock hierarchy. You can thus eliminate entirely the chance that somebody will accidentally acquire B and then A (in the wrong order), removing the circular wait possibility and therefore any chance of a deadlock:

// Thread 1
void t1() {
  using (lockA.Enter()) {
    using (lockB.Enter()) {
      /* ... *
} } }

// Thread 2
void t2() {
  using (lockB.Enter()) {
    using (lockA.Enter()) {
      /* ... */
} } }

Attempting to execute t2 will throw a LockLevelException originating from the lockA.Enter call, indicating that the lock hierarchy was violated. Clearly you should use aggressive testing to find and fix all violations of your lock hierarchy prior to shipping your code. An unhandled exception is still an unsatisfactory experience for users.

Notice that the Enter method returns an IDisposable object, enabling you to use a C# using statement (much like a lock block); it implicitly calls Exit when you leave the scope. There are a couple other options when instantiating a new lock, available through constructor parameters: the reentrant parameter indicates whether the same lock can be reacquired when it is already held, true by default; the name parameter is optional, and enables you to give your lock a name for better debugging.

By default, intra-level locks cannot be acquired; in other words, if you hold lock A at level 10 and attempt to acquire lock C at level 10, the attempt will fail. Permitting this would directly violate the lock hierarchy, because two threads could try to acquire locks in the opposite order (A and the C, versus C and then A). You can override this policy by specifying true for an Enter overload that takes the permitIntraLevel parameter. But once you do this, a deadlock once again becomes possible. Carefully review your code to ensure this explicit violation of the lock hierarchy won't result in a deadlock. But no matter how much work you do here, it will never be as foolproof as the strict lock leveling protocol. Use this feature with extreme caution.

While lock leveling works, it does not come without challenges. Dynamic composition of software components can lead to unexpected runtime failures. If a low-level component holds a lock and makes a virtual method call against a user-supplied object, and that user object then attempts to acquire a lock at a higher level, a lock hierarchy-violation exception will be generated. A deadlock won't occur, but a run-time failure will. This is one reason that making virtual method calls while holding a lock is generally considered a bad practice. Although this is still better than running the risk of a deadlock, this is a primary reason databases do not employ this technique: they must enable entirely dynamic composition of user transactions.

In practice, many software systems end up with clumps of locks at the same level, and you'll find several calls using permitIntraLevel. This rarely occurs, not because the developers have been extremely clever or careful, but rather because lock leveling is a difficult and a relatively onerous practice to follow. It's much easier to knowingly accept the risk of a deadlock or pretend it isn't there—assuming the odds are that it likely won't come up in testing or even in the wild—than it is to spend hours cleanly refactoring code dependencies to make a single lock acquisition work. But disciplined locking, such as minimizing the time a lock is held, only invoking code over which you have static control, and making defensive copies of data when necessary, can be used to make living within the confines of your lock hierarchy simpler.

Many lock-leveling systems are turned off in non-debug builds to avoid the performance penalty of maintaining and inspecting lock levels at run time. But this means you must test your application to uncover all violations of the locking protocol. Dynamic composition makes this incredibly difficult. If you turn off lock leveling when you ship, one missed case can result in a real deadlock. An ideal lock-leveling system would detect and prevent violation of the hierarchy statically through either compiler or static analysis support. There is research available that uses such techniques, but unfortunately there are no mainstream products that can be used for managed applications.Instead of deadlock avoidance, a second technique, deadlock detection, can sometimes be used.

Detecting and Breaking Deadlocks

The techniques discussed so far are useful for avoiding deadlocks entirely. But lots of code is already written that might not lend itself to wholesale adoption of lock leveling. Normally when a deadlock occurs, it will manifest as a hang, which may or may not result in failure information being captured and reported. But ideally, if the system can't avoid them altogether, it would detect a deadlock when it occurred and report it promptly so that the bug can get fixed. This is possible even without adopting lock leveling in your applications.

While all modern databases use deadlock detection, employing the same techniques in arbitrary managed applications is more difficult in practice. When competing database transactions, try to acquire locks in a way that would lead to deadlock, the DBMS can kill the one with the least time and resources invested thus far, enabling all others involved to proceed as though nothing happened. The deadlock victim's operations are automatically rolled back by the DBMS. The application invoking the database call can then respond by either retrying the transaction or giving the user an option for retry, cancellation, or modification of their action. For a ticketing system, for example, a deadlock might mean that a reserved ticket was given to another customer to maintain forward progress, in which case the user might have to restart their ticket reservation transaction.

But a run-of-the-mill managed application that has not been written to deal with deadlock errors resulting from lock acquisitions will likely not be prepared to respond intelligently to an exception originating from an attempted lock acquisition. Crashing the process is just about the only thing you can do.

Managed code hosted in SQL Server™ 2005 enjoys deadlock detection similar to what I'll describe here. Since all managed code is implicitly wrapped in a transaction, this works very well in conjunction with SQL Server's ordinary data-based deadlock detection.

Sophisticated managed code can be written to notice deadlocks, and attempt one-by-one back-off of the locks that it has accumulated. For example, if code holds locks A and B, and receives a deadlock failure when attempting acquisition of C, it can roll back changes introduced since B was acquired, release B, yield execution so other threads can make progress, reacquire B, perform changes previously rolled back, and then attempt the acquisition of C. If that fails again due to deadlock, it can roll all the way back to the acquisition of A and retry.

The CLR's hosting interface can be used to inject custom logic for acquisition and release of monitors. I have used this capability to construct a new sample host to perform deadlock detection and mitigation (which is included in the code download). Now let's take a tour to review this host and demonstrate how deadlock detection might work in practice.

The Algorithms

There are two broadly accepted techniques for deadlock detection: timeout-based detection and graph-based detection.

With timeout-based detection, acquisition of a lock or resource is given a timeout that is much larger than the expected non-deadlock case. If that timeout is exceeded, it indicates failure to the caller. Sometimes failure causes an exception, while other times the function just returns false (like Monitor.TryEnter), which enables the user program to do something about it. The System.Transactions namespace uses this model for TransactionScopes, by default aborting any transaction that has run for 60 seconds or more.

The main drawback with this approach is that a deadlock will typically go undetected for longer than it should, or legit but lengthy transactions will be killed because they appear to be deadlocked. In addition, functions that leave it to the programmer to decide how to respond are error prone; the poor application developer seldom knows the right response, and might just attempt to reacquire the lock (without contemplating whether that might lead to a deadlock-induced infinite loop).

With graph-based detection, if you have a list of workers that are actively waiting and a list of which worker owns what resource, you can construct a graph of who is waiting for whom. If you do this and detect a cycle in the graph, a deadlock has occurred (unless somebody in the cycle is on the timeout plan and might break the deadlock willingly). This is a foolproof strategy, but can be costly in terms of run-time impact to lock acquisitions.

In many cases, a combination of techniques can lead to the best results. If a timeout occurs, you can perform graph-based detection. This avoids expensive detection on the common case, where the acquisition succeeds before the timeout is exceeded. This is what I have implemented.

Before diving into the implementation of the custom host, let's review the algorithm for wait graph construction and deadlock detection. Figure 3 describes this process.

Figure 3 Wait Graph Construction and Deadlock Detection (Close Figure 3)

1. | Add the current thread t1 to the list of threads you have seen, called waitGraph.
2. | Set current equal to the lock that t1 is about to acquire.
3. | Set owner equal to the thread that currently owns current.
4. | If owner is null (meaning current is unowned), there is no cycle. Stop looking and exit the procedure.
5. | If owner is in waitGraph, you've seen it already, and thus have encountered a cycle. This is a deadlock. The graph begins at the index of owner in waitGraph and extends to the end. Return information to the caller about waitGraph.
6. | Set current equal to the lock that owner is currently blocked on.
7. | If current is null (meaning current is not blocked waiting on a lock acquisition), there is no cycle; stop looking and exit the procedure.
8. | Otherwise, if you fell through to here, go to Step 3.

The policy I will use for deadlock breakage (for example, if Step 5 finds a deadlock) is to kill the offending task whose acquisition would have caused the deadlock. This is by far the simplest and least expensive strategy, but isn't as clever as most DBMSs, which attempt to optimize overall system throughput by killing the transaction in the graph with the least amount of work invested. Because measuring work is more difficult for arbitrary managed programs than it is in transaction systems, I have chosen the simpler strategy. (An interesting alternative strategy would be to change the host to use an alternative technique—for instance, using lock-acquisition timestamps to simulate work, terminating the worker that acquired its first lock most recently.)

Let's see how this might work. Imagine three threads, 1, 2, and 3, and three locks, A, B, and C, that are involved in some form of shared-memory coordination. Thread 1 holds lock A and is blocked on acquiring lock B; thread 2 holds lock B and is blocked on acquiring lock C; thread 3 holds lock C. If thread 3 then attempts to acquire lock A, the algorithm kicks in and constructs a wait graph like that depicted in Figure 4. I will then detect a cycle and respond by terminating thread 3. This frees up lock C, which enables thread 2 to unblock, acquire C, execute, and release B. This unblocks thread 1, which is then able to acquire B and execute to completion. Thread 3 is probably not very happy, but threads 1 and 2 are (assuming the program survived the exception).

Figure 4 Sample Wait Graph
Figure 4 Sample Wait Graph

Naturally, this discussion has been simplified to help illustrate the core elements of the algorithm. But now let's take a look at how you actually extend the CLR to perform this logic. This will require a few tangents related to harnessing the power of the CLR's hosting APIs.

Spelunking Through the Hosting APIs

The CLR hosting APIs provide ways to plug in your own manager objects to control assembly loading, memory management, thread-pooling, and startup and shutdown, among other things. In this sample, I've used the task and synchronization management extensions. The new host is a simple executable written in Visual C++®, deadhost.exe. When run, you pass to it the path of the managed executable to run with deadlock detection turned on, along with any arguments to that executable. It then starts the CLR in-process using a set of APIs exposed from mscoree.dll, loads the target program, and runs it. Any deadlocks that occur are reported immediately.

In the interest of brevity, I've omitted coverage of many fundamentals. For a complete overview of the CLR's hosting capabilities, I recommend picking up a copy of Steven Pratschner's book, Customizing the Microsoft .NET Framework Common Language Runtime (Microsoft Press®, 2005).

The two host controls implemented are DHTaskManager for the IHostTaskManager interface and DHSyncManager for the IHostSyncManager interface. Instances of these types are returned to the CLR on demand, at which point the CLR will make callbacks into my code for certain events. Of course, I've implemented all of the related interfaces, too: IHostCrst, IHostAutoEvent, IHostManualEvent, IHostSemaphore, and IHostTask. These interfaces are rather large, but most functions are simple translations to Win32 counterparts. Those functions that aren't can be safely ignored (thread-affinity, critical-regions, delay-aborts), and just a few require specialized logic for deadlock detection. I'll review only the latter.

The CLR implements the basic functionality of Monitor.Enter and Exit. For noncontentious lock acquisitions (for monitors without owners), the CLR just does a quick compare-and-swap (InterlockedCompareExchange) on an internal data structure. If this attempt fails, it means contention was detected and a Windows autoreset event is used. This event is then used to wait for lock availability. Whenever somebody releases the lock, they set the event, which has the effect of releasing one waiting thread into the monitor. It is lazily created the first time a specific monitor exhibits contention. The hosting APIs enable you to control the event creation and any calls to wait on or set it. The hosting APIs also support reader-writer locks (System.Threading.ReaderWriterLock), but for space considerations I will omit discussion of them in this article.

Here's precisely what the host needs to do: any time managed code tries to acquire a monitor that is already held by somebody, the CLR calls out to IHostSyncManager::CreateMonitorEvent. This returns an object whose type implements a simple event wait-handle-like interface (IHostAutoEvent, with Wait and Set methods) in which I can place custom detection logic to fire whenever anybody blocks on or releases a monitor. Attempting to acquire a blocked monitor results in a call to Wait, while releasing a monitor calls Set.

Each time the Wait method is called on an event returned by CreateMonitorEvent, I will insert a wait-record into a process-wide list to indicate that the thread is about to block on acquisition of a monitor. This is then used for deadlock detection further on.

I then perform timed waits, starting at 100 ms and backing off exponentially, executing the deadlock detection algorithm each time. Using a timeout first optimizes for the common case where locks are held for short periods of time, avoiding the costly process of wait graph construction and traversal. If a deadlock is ever discovered, I return HOST_E_DEADLOCK (0x80131020) to the CLR, which responds by generating a new COMException with the message "The current thread has been chosen as a deadlock victim." This is new functionality in CLR 2.0, facilitating deadlock detection and breakage in SQL Server 2005. I run the managed program in its own thread so that any such exceptions are able to go entirely unhandled, tripping the attached debugger (if any) on the first pass, crashing the process, and then giving the end user a second chance to attach the debugger. If I successfully acquire the monitor, I just remove the wait-record and return execution to the managed program.

The detection algorithm has been written to impose as little overhead as possible (most of it is lock-free), but there is still some synchronization necessary while accessing shared data structures, such as management of the wait-records. The fast-path occurs when the program doesn't have to execute this logic at all. The best case happens when the CLR can acquire the monitor without having to create an event and call Wait, when the monitor isn't contended, because both operations require costly kernel-mode transitions.

Something else is worth a brief mention. The Windows internal KTHREAD per-thread data structure contains a list of dispatcher objects a given thread is currently waiting on. The data is stored in a _KWAIT_BLOCK data structure, whose definition can be found in Ntddk.h. In Windows XP, the Driver Verifier program uses this to perform detection of deadlocks in device drivers. You too could use this data rather than inserting wait-records, and this would work for both managed and unmanaged code. But doing that requires some messy memory manipulation with internal data structures and an alternate strategy for obtaining current ownership information. It was much simpler for this sample to maintain a custom list of wait-records entirely in user mode. Because of this, I explicitly ignore deadlocks resulting from CLR internal locks, unmanaged and Windows locks, and the OS loader lock, however.

Wait Graph Construction and Traversal

When the CLR hooks our host for anything related to a monitor, it deals in terms of cookies (of type SIZE_T), which are unique identifiers for each monitor. I've created a type called DHContext that is generally responsible for implementing all deadlock-detection-related functionality. An instance of the host shares a processwide context across all managed threads. A context owns a list of wait-records, which associates tasks with the monitor cookies on which they are waiting:

map<IHostTask*, SIZE_T> *m_pLockWaits

The context also offers a set of methods for invoking the deadlock detection itself. TryEnter inserts a wait-record and checks for deadlock, returning a pointer to a DHDetails object if one was found or NULL otherwise; and EndEnter does some maintenance after a monitor has been acquired, such as removing the wait-record. This architecture is depicted in Figure 5.

Figure 5 Host Architecture
Figure 5 Host Architecture

When the host needs an event for a contentious monitor, I construct and supply one from the DHSyncManager::CreateMonitorEvent function. I return an object of type DHAutoEvent, a custom implementation of the IHostAutoEvent interface that makes targeted function calls into the context through its field m_pContext of type DHContext. The Wait method (see Figure 6) is the most interesting, executing the first four steps outlined in Figure 3.

Figure 6 Wait Method (Close Figure 6)
STDMETHODIMP Wait(DWORD dwMilliseconds, DWORD option)
{
    DHContext *pctx = m_pContext;

    if (!pctx || option & WAIT_NOTINDEADLOCK)
    {
        return DHContext::HostWait(m_hEvent, dwMilliseconds, option);
    }
    else
    {
        DWORD dwCurrentTimeout = 100;
        DWORD dwTotalWait = 0;

        HRESULT hr;
        while (TRUE)
        {
            if (HOST_E_TIMEOUT != (hr = DHContext::HostWait(m_hEvent,
                min(dwCurrentTimeout, dwMilliseconds), option)))
                break;

            DHDetails *pDetails = pctx->TryEnter(m_cookie);
            if (pDetails)
            {
                pctx->PrintDeadlock(pDetails, m_cookie);
                hr = HOST_E_DEADLOCK;
            }
            else
            {
                dwTotalWait += dwCurrentTimeout;
                if (dwTotalWait >= dwMilliseconds)
                {
                    hr = HOST_E_TIMEOUT;
                }
                else
                {
                    if (dwTotalWait >= dwMilliseconds / 2)
                    {
                        if (dwMilliseconds != INFINITE)
                            dwCurrentTimeout = dwMilliseconds –
                                dwTotalWait;
                    }
                    else
                    {
                        dwCurrentTimeout *= 2;
                    }
                    continue;
                }
            }
            pctx->EndEnter(m_cookie);
            break;
        }

        return hr;
    }
}

DHContext::HostWait performs a wait based on the flags specified by the CLR in Wait's option argument. This bypasses deadlock detection entirely if WAIT_NOTINDEADLOCK was supplied, and employs a pumping style and/or alertable wait as needed if WAIT_MSGPUMP and /orWAIT_ALERTABLE was specified.

Notice that a large portion of this code is dedicated to managing the exponential back-off timeout. The most interesting part is the call to TryEnter. After inserting the wait-record, it delegates to the internal method DHContext::DetectDeadlock to perform the actual detection logic. If TryEnter returns a non-null value, you've encountered a deadlock. You can respond by calling a custom PrintDetails function to print the deadlock information to the standard error stream (a more robust solution might write this to the Windows Event Log), and then return HOST_E_DEADLOCK so the CLR may react as discussed earlier (by throwing a COMException).

The deadlock detection algorithm outlined earlier in this article is implemented inside of the DetectDeadlock function shown in the code in Figure 7.

Figure 7 DetectDeadlock Method (Close Figure 7)
STDMETHODIMP_(DHDetails*)
DHContext::DetectDeadlock(SIZE_T cookie)
{
    ICLRSyncManager *pClrSyncManager =
        m_pSyncManager->GetCLRSyncManager();

    IHostTask* pOwner;
    m_pTaskManager->GetCurrentTask((IHostTask**)&pOwner);

    vector<IHostTask*> waitGraph;
    waitGraph.push_back(pOwner);

    SIZE_T current = cookie;
    while (TRUE)
    {
        pClrSyncManager->GetMonitorOwner(current, (IHostTask**)&pOwner);
        if (!pOwner)
            return FALSE;

        BOOL bCycleFound = FALSE;
        vector<IHostTask*>::iterator walker = waitGraph.begin();
        while (walker != waitGraph.end())
        {
            if (*walker == pOwner)
            {
                bCycleFound = TRUE;
                break;
            }
            walker++;
        }

        if (bCycleFound)
        {
            DHDetails *pCycle = new DHDetails();
            CrstLock lock(m_pCrst);
            while (walker != waitGraph.end())
            {
                map<IHostTask*, SIZE_T>::iterator waitMatch =
                    m_pLockWaits->find(*walker);
                waitMatch->first->AddRef();
                pCycle->insert(DHDetails::value_type(
                    dynamic_cast<DHTask*>(waitMatch->first),
                    waitMatch->second));
                walker++;
            }
            lock.Exit();

            return pCycle;
        }

        waitGraph.push_back(pOwner);

        CrstLock lock(m_pCrst);
        map<IHostTask*, SIZE_T>::iterator waitMatch =
            m_pLockWaits->find(pOwner);
        if (waitMatch == m_pLockWaits->end())
            break;
        current = waitMatch->second;
        lock.Exit();
    }

    return NULL;
}

This code uses the IHostTaskManager::GetCurrentTask method to obtain the currently executing IHostTask, ICLRSyncManager::GetMonitorOwner to retrieve the owning IHostTask of a given monitor cookie, and the context's m_pLockWaits list to find what monitor on which a given task is waiting for acquisition. With this information in hand, you can fully implement the logic discussed earlier. Any code run through this host will correctly identify and break deadlocks. And that's it—a fully functional deadlock-detecting CLR host.

I've omitted over 1,000 lines of code, of course, so if this gave you the impression that writing a custom CLR host is straightforward, it's not. But the number of possibilities, deadlocks aside, that arise from the powerful CLR hosting interfaces is amazing, ranging from failure injection to testing reliability robustness in the face of rude AppDomain unloads. Deployment of custom hosts to your end users is obviously difficult, but a host can easily be used to perform deadlock detection in your test suites, for example.

Custom Deadlock Host in Action

I developed some simple tests while I was writing deadhost.exe, available in the \tests\ directory in the code download. For example, the program test1.cs shown in Figure 8 will deadlock close to 100 percent of the time.

Figure 8 Trying to Deadlock (Close Figure 8)
static void Main() {
    Console.WriteLine("Main: locking a");
    lock (a) {
        Console.WriteLine("Main: got a");
        ThreadPool.QueueUserWorkItem(delegate {
            Console.WriteLine("TP : locking b");
            lock (b) {
                Console.WriteLine("TP : got b");
                Console.WriteLine("TP : locking a");
                lock (a) {
                    Console.WriteLine("TP : got a");
                }
            }
        });
        Thread.Sleep(100);
        Console.WriteLine("Main: locking b");
        lock (b) {
            Console.WriteLine("Main: got b");
        }
    }
}

When run under the host by using the following command line, you will receive an unhandled exception:

deadhost.exe tests\test1.exe
If you break into the debugger (if it's already attached or if you choose to attach it in response to the error), you will be right at the line whose execution would have caused the deadlock, at which point you can inspect all of the threads involved and the locks held, and get a head start on debugging the problem. If left entirely unhandled, you will see a message similar to Figure 9 printed to standard output.

Figure 9 Resulting Output from Unhandled Deadlock (Close Figure 9)
Main: locking a
Main: got a
TP : locking b
TP : got b
TP : locking a
Main: locking b
A deadlock has occurred, terminating the program. Details below.
 600 was attempting to acquire 18a27c, which created the cycle
 600 waits on lock 18a27c (owned by 5b4)
 5b4 waits on lock 18a24c (owned by 600)

Unhandled Exception: System.Runtime.InteropServices.COMException (0x80131020):
 The current thread has been chosen as a deadlock victim.

 at Program.Main() in c:\...\tests\test1.exe:line 23
 at ...

The message printed to the console isn't directly useful in understanding and debugging the error in depth. It gives you the raw thread handles that are waiting and the monitor cookies on which they are waiting. With more time and effort, the host could be extended to gather more data during lock acquisitions and releases, printing details about the object used for the lock and source-code location of acquisitions, for example. But with just the information shown in Figure 8, you can easily see that thread 600 attempted to acquire 18a27c; 5b4 owned 18a27c and was waiting on lock 18a24c; but 18a24c was owned by 600, creating the deadlock cycle. And you can see the stack trace for the guilty deadlock victim. The best part is the ease with which you can enter the debugger and troubleshoot the problem right then and there.

Wrap-Up

In this article I discussed some basic information about deadlocks and presented an overview of strategies for avoiding them and dealing with them. Generally, the finer the granularity of your locks, the more of them that can be held at once—and the longer you hold them for, the higher your risk of deadlock. Increased parallelism on server and desktop computers will only exacerbate and expose this risk as instructions become interleaved in new and interesting ways.

Deadlock avoidance is widely recognized as the best practice for carefully engineered concurrent software, while deadlock detection can be useful for dynamically composed, complex systems, especially those equipped to deal with lock failures (such transacted systems and hosted or plug-in software). Doing something to ensure deadlocks don't plague your applications is better than doing nothing. Hopefully this article will help you to write easier-to-maintain deadlock-free code, improve the responsiveness of your programs, and ultimately save you a fair amount time when debugging concurrency-based failures.

Joe Duffy is a technical program manager on the Microsoft common language runtime (CLR) team, where he works on concurrency programming models for managed code. Joe blogs regularly at www.bluebytesoftware.com/blog and has written an upcoming book, Professional .NET Framework 2.0 (Wrox, 2006).


Mutant Power: Create A Simple Mutation Testing System With The .NET Framework

Mutant Power

Create A Simple Mutation Testing System With The .NET Framework

James McCaffrey
This article discusses:
  • Understanding mutation testing and its relevance to code coverage
  • Preparing and mutating the DLL under test
  • Analyzing test results
This article uses the following technologies:
.NET Framework, C#
Code download available at: MutationTesting.exe (353KB)
Contents
The DLL and Test Harness
The Mutation Testing System
Preparing for Mutation Analysis
The Main Mutation Loop
Mutation Analysis Results
Conclusion

M
utation testing is the least common and most misunderstood type of testing analysis. With mutation testing, the system/program under test is changed to create a faulty version called a mutant. You then run the mutant program through a suite of test cases, which should produce new test case failures. If no new failures appear, the test suite most likely does not exercise the code path containing the mutated code, which means the program isn't fully tested. You can then create new test cases that do exercise the mutant code.

Mutation testing is rarely used because the technical issues have been considered difficult and resource-intensive. One benefit of the Microsoft® .NET Framework is that it makes mutation testing dramatically easier to perform. In this article, I will show you a simple yet complete mutation testing technique implemented in the C# language.

Let's take a look at a lightweight .NET Framework mutation analysis. Figure 1 shows that I am analyzing a library named LibUnderTest.dll. It's just a demo library containing a single Max method. In the first part of my mutation analysis, an existing test suite named TestHarness.exe runs on the DLL. For simplicity, the test suite has only 10 test cases. Notice that all 10 test cases pass. As I'll explain later, in general you'll want to begin mutation analysis with a test suite containing only test cases that pass when run against your program.

Figure 1 Mutation Analysis Output (Close Figure 1)

After establishing baseline test results, the DLL under test is examined for potential mutations. This is one of the tasks that you'll find dramatically easier to do with the .NET Framework. I'll explain how to examine a system or program under test for possible mutations later; for now it's enough to say that I disassemble the DLL under test and examine the resulting .il file for branching instructions. In this example the DLL has three ble (branch on less than or equal) instructions. The mutation analysis then enters a main processing loop. On each pass through the loop, the original DLL is mutated, creating a new DLL. The new mutant DLL is built and then the test suite runs against the mutant.

In the example here, the first mutant did not produce any test failures. This is not good—my test suite probably misses the code path containing the mutated code. The second and third mutants do produce test case failures (good) and contribute to the beta power of the test suite. The mutation analysis finishes by printing the relative mutation alpha and beta powers. The alpha power (0.67 in my example) is just the proportion of mutations that produce test case failures. I'll explain the beta power later.

In this article, I will briefly describe the DLL under test, and then I will present the simple mutation analysis code that produced the results shown in Figure 1. You will be able to modify and extend the mutation system to meet your own needs. I will conclude with a very brief discussion of how and when to use mutation testing and its relationship to other types of testing, code coverage in particular.

The DLL and Test Harness

To keep things simple, my DLL under test contains a single Utility class with an instance method Max:

public int Max(int a, int b, int c)
{
    if (a > b && a > c) return a;
    else if (b > c) return b;
    else return c;
}

Method Max is implemented correctly, but in the real world you could not make this assumption—otherwise you wouldn't need to be testing at all!

For this article I've created a demo test harness designed for maximum simplicity (see Figure 2). Notice that the test harness has 10 hardcoded test cases. Each test case has a test case ID, a field to instruct the harness which method to test, three input arguments for Max, and an expected result. You'll notice that the cases do not thoroughly test the Max method. In all 10 cases, the maximum value is the third input argument. This is easy to see in a tiny example, but in a production environment with thousands of test cases it will not be obvious if your suite of test cases thoroughly tests the code. This is exactly what mutation testing is designed to determine.

Figure 2 Test Harness (Close Figure 2)
static void Main(string[] args)
{
  try
  {
    ArrayList testCases = new ArrayList();

    testCases.Add("0001:Max:1:7:9:9");
    testCases.Add("0002:Max:7:1:9:9");
    testCases.Add("0003:Max:2:9:9:9");
    testCases.Add("0004:Max:9:7:9:9");
    testCases.Add("0005:Max:9:9:9:9");

    testCases.Add("0006:Max:-3:3:5:5");
    testCases.Add("0007:Max:3:-3:5:5");
    testCases.Add("0008:Max:-2:5:5:5");
    testCases.Add("0009:Max:5:3:5:5");
    testCases.Add("0010:Max:5:5:5:5");

    Console.WriteLine("Begin test run");
    int numPass = 0, numFail = 0;

    for (int i = 0; i < testCases.Count; ++i)
    {
      string raw = (string)testCases[i];
      string[] tokens = raw.Split(':');
      string id = tokens[0];
      string method = tokens[1];
      if (method == "Max")
      {
        int arg1 = int.Parse(tokens[2]);
        int arg2 = int.Parse(tokens[3]);
        int arg3 = int.Parse(tokens[4]);
        int expected = int.Parse(tokens[5]);

        LibUnderTest.Utility u = new LibUnderTest.Utility();
        int actual = u.Max(arg1, arg2, arg3);
        if (actual == expected)
          ++numPass;
        else
          ++numFail;
      }
    } // end test loop

    Console.WriteLine("Num pass = " + numPass);
    Console.WriteLine("Num fail = " + numFail);

    string finishTime = DateTime.Now.ToShortTimeString();
    using(FileStream ofs = new FileStream(
      "..\\..\\..\\TestHarness\\Results\\Results.txt",
      FileMode.Create))
    using(StreamWriter sw = new StreamWriter(ofs))
    {
      sw.Write(numPass + ":");
      sw.Write(numFail  + ":");
      sw.WriteLine(finishTime);
    }

    Console.WriteLine("End test run");
  }

  ...

} // Main()

The test harness iterates through each test case, parses out each test case field, creates an instance of a LibUnderTest object, calls method Max with the test case inputs, and compares the actual result with the expected result to determine whether the test case passes or fails. The test harness emits results to an external file named Results.txt in a single-line, comma-delimited format with the number of cases that pass, the number of cases that fail, and the time the test run finished. A results file might look like this:

8,2,9:55 AM

When you are going to perform mutation testing, you will likely inherit existing test suites and test harnesses. As long as you can programmatically execute the harness and retrieve the test run results, you will be able to conduct mutation testing. You will run into trouble only if the test harness has been designed so that you cannot programmatically retrieve results (a pure GUI test framework, for example). If you are designing a test harness, remember that the more automation-friendly you can make the harness, the easier it will be to create the mutation testing system.

The Mutation Testing System

Now I'll walk through the mutation program that produced the output shown in Figure 1. A high-level skeleton of the program is presented in Figure 3. You can find the entire program in the code download that accompanies this article.

Figure 3 Mutation Testing Program Structure (Close Figure 3)
static string[] brInstructions = { "beq", "bge", "bge.un", "bgt",
  "bgt.un", "ble", "ble.un", "blt", "blt.un", "bne.un" };
static string[] brComplements  = { "bne.un", "blt", "blt.un", "ble",
  "ble.un", "bgt", "bgt.un", "bge", "bge.un", "beq"    };

[STAThread]
static void Main(string[] args)
{
    Console.WriteLine(
        "\nBeginning mutation analysis of: LibUnderTest.dll");

    // Copy original DLL into default location.
    // Run test harness against original, unmutated DLL under test.
    // Use ildasm to produce file LibUnderTest.il.
    // Read file LibUnderTest.il into an ArrayList in memory.
    // Find all potential lines to mutate and save that info in an
    // ArrayList of Info objects.
    // Verify the listInfo list.

    for (int i = 0; i < listInfo.Count; ++i) // main loop
    {
        // Create mutated, in-memory, asm version of the DLL under test,
        // then build a new DLL from the mutation.
        // Run the test harness and read test results from mutated DLL.
    }

    // Display results of mutation testing.
    Console.WriteLine("\nDone");
 }

The mutation analysis program starts by declaring two key string arrays named brInstructions and brComplements. The first problem you'll face when writing a mutation testing system is deciding exactly what you want to mutate. Remember that you want the mutant code under test to fail if the test suite exercises the mutated code.

You can mutate just about anything—text strings, arithmetic operators like + and -, and so forth. There is quite a bit of research available on the topic of exactly what you should mutate, but a reasonable compromise choice from both a theoretical and practical point of view is to mutate Boolean comparison operators such as >, <, and ==.

After you've decided what to mutate, you need to decide how to perform the mutation. Before the .NET Framework, often the only choice was to write a routine that parsed the code under test, looking for the Boolean operators. This is extremely difficult. Fortunately, the .NET Framework makes it much easier to write mutation tests. Because .NET-based programs compile to an intermediate language (IL), instead of parsing C# or Visual Basic® source code, you can parse the IL, which is much easier.

I'll illustrate with a tiny example. Suppose your system under test contains a statement like this:

if (x <= y) ++z;
Parsing source code for <= is much tougher than you might expect because you have to worry about whether you're in a comment, a string literal or not, and so on. But the IL that corresponds to that deceptively simple source code looks something like this:
IL_0006:  ldloc.0 // x
IL_0007:  ldloc.1 // y
IL_0008:  bgt.s      IL_000e
IL_000a:  ldloc.2 // z
IL_000b:  ldc.i4.1
IL_000c:  add
IL_000d:  stloc.2 // z = z+1
IL_000e:  ...

The <= Boolean operator in the source code translates to the bgt instruction in the IL (meaning branch on greater). You might have expected <= to translate to ble (branch on less than or equal), but it turns out that the compiler will usually optimize control flow by translating a Boolean operator into its IL complement branching instruction. If you can get the IL for your system under test—and you can—then parsing for Boolean operators reduces to finding IL branching instructions, which is relatively easy.

The last issue is to determine exactly what IL branching instructions you want to mutate, and what to mutate them into. The IL reference guide lists 12 branching instructions, as shown in Figure 4.

Figure 4 Common IL Branching Instructions (Close Figure 4)

IL Branching Instruction | Instruction Title | Complement
beq | branch on equal | bne.un
bge | branch on greater than or equal | blt
bge.un | branch on greater than or equal (unsigned) | blt.un
bgt | branch on greater than | ble
bgt.un | branch on greater than (unsigned) | ble.un
ble | branch on less than or equal | bgt
ble.un | branch on less than or equal (unsigned) | bgt.un
blt | branch on less than | bge
blt.un | branch on less than (unsigned) | bge.un
bne.un | branch on not equal | beq
brfalse | branch on false, null, or zero | brtue
brtrue | branch on non-false, or non-null | brfalse

Because you want to cause a test case failure, it makes sense to mutate each IL branching instruction into its logical complement. So a bgt will mutate into a ble and so forth.

I'll point out now that using this approach does not absolutely guarantee that a mutation will produce a test case failure. Suppose your system under test contains source code like this:

if (s > t) i = i;

Mutating the IL counterpart to the > operator will not have any effect on your test case results.

With this knowledge in hand, you can declare the brInstructions and brComplements arrays and write the following tiny substitution method that accepts a branching instruction and returns its complement/mutation:

static string mutatedInstruction(string instruction)
{
  for (int i = 0; i < brInstructions.Length; ++i)
    if (brInstructions[i] == instruction)
      return brComplements[i];
  return null;
}

Like all the samples in this article I have removed most of the error-checking code to keep the main points as clear as possible. But in a production environment you'll want to liberally add error-checking code. Here I just traverse the brInstructions array and return the counterpart in the brComplements array. Notice that I arbitrarily decided to leave out the brfalse and brtrue instructions. The design of the mutation testing system I present here allows you to easily modify what you want to mutate and how you want to mutate it.

Preparing for Mutation Analysis

In the first phase of the preparation, run your test suite against the original, unmutated DLL under test so you can establish the baseline results for later comparison. Start by grabbing the unmutated DLL under test from its drop point and copying it into the same folder as the test harness executable:

Console.WriteLine(
  "\nCopying original DLL under test into test harness folder");
File.Copy(
  "..\\..\\..\\LibUnderTest\\bin\\Debug\\LibUnderTest.dll",
  "..\\..\\..\\TestHarness\\bin\\Debug\\LibUnderTest.dll", true);

You have to be a bit careful here. I make the important assumption that the test harness references the DLL under test in the test harness executable folder. If your test harness was created with Visual Studio®, as is usually the case, you will have a project reference to the DLL under test. Project references bind by file name and will physically copy the DLL they point to into the test harness executable folder. If your test harness references a folder other than the one containing the harness executable, then when your mutation analysis copies a mutated DLL into the test harness executable folder, it could be overwritten by the original, unmutated DLL. The true argument in the static File.Copy method causes the code to overwrite any existing DLL from previous mutation analyses.

The next step in the mutation analysis preparation is to execute the test harness, as shown here:

Console.WriteLine(
  "\nRunning test suite on original DLL under test");
using(Process test = Process.Start(
  "..\\..\\..\\TestHarness\\bin\\Debug\\TestHarness.exe"))
{
    test.WaitForExit();
}

Here I run the harness simply by calling the static Process.Start method. As I described earlier, the design of your mutation analysis will be influenced by the design of the existing test harness. As you'll recall, in this case the test harness just writes its results to a text file. After running the test harness against the original DLL, I fetch the test run results as shown in Figure 5.

Figure 5 Retrieving Test Results (Close Figure 5)
string result;
using(StreamReader sr = new StreamReader(
    "..\\..\\..\\TestHarness\\Results\\Results.txt");
{
    result = sr.ReadLine();
}
int basePass = int.Parse(result.Split(',')[0]);
int baseFail = int.Parse(result.Split(',')[1]);

Console.WriteLine(
  "Baseline number pass, fail = " + basePass + " " + baseFail);
if (baseFail > 0) Console.WriteLine(
    "** Warning: baseline should not contain test case failures **");

There is nothing fancy going on here. I just use a StreamReader object to read the single line in the test case results file. Recall that the results output format looks like this:

8,2,9:55 AM

So if I call String.Split with a comma character argument, the number of test cases that passed will be in index location [0] of the resulting string array, and the number of test cases that failed will be in index location [1]. A realistic test harness will produce more complicated output, possibly in an XML file or a SQL Server™ database.

Throughout this example, I use hardcoded paths, which you'll most likely want to parameterize. I also have avoided using helper methods to keep the explanation as simple as possible. You may want to recast the system presented here into helper methods. For example, you could refactor the previous lines of code into a GetBaselineResults method that accepts a string representing the file name of the test harness results, and returns an int array where [0] holds the number of cases which pass and [1] holds the number of failed test cases. The call might then look like:

int[] baseResults = GetBaselineResults(testResultFile);

The next step in the mutation analysis preparation is to produce the IL version of the DLL under test. Fortunately, you can use the ildasm.exe tool that ships with Visual Studio to do this:

using(Process p = Process.Start("ildasm.exe",
  "..\\..\\..\\TestHarness\\bin\\Debug\\LibUnderTest.dll " +
  "/OUT=..\\..\\LibUnderTest.il"))
{
    p.WaitForExit();
}

I use the Process.Start method to launch ildasm.exe. I point ildasm.exe to the original unmutated DLL under test and specify an /OUT switch. This instructs ildasm.exe to emit output to a text file rather than use the default graphical interface. I save the IL version of the original DLL as LibUnderTest.il in the root folder of the mutation analysis system. The following shows a small snippet of the resulting text file:

.maxstack  2
.locals init (int32 V_0)
IL_0000:  ldarg.1
IL_0001:  ldarg.2
IL_0002:  ble.s      IL_000c

Apart from the technical details of writing mutation analysis, there are also a lot of files and folders to juggle. Do not underestimate the difficulty of keeping track of all the files and folders being created! Each of the three systems involved—the code under test, the test harness, and the mutation system—will have source files, executables, input and output files, configuration files, and directory structures.

After you have created and saved the IL disassembly of the original DLL, you can read the IL instructions into memory:

using(StreamReader sr = new StreamReader("..\\..\\LibUnderTest.il"))
{
    string line;
    ArrayList listLibUnderTestOrig = new ArrayList();
    while ((line = sr.ReadLine()) != null)
    {
        listLibUnderTestOrig.Add(line);
    }
}

I use a StreamReader object, read the IL file into memory line by line, and save it into an ArrayList object named listLibUnderTestOrig. The idea is that I will use this in-memory data store as the basis for all mutations (with larger projects, you could also process and mutate each line individually, rather than reading in and storing everything). Using an ArrayList object is simple and effective, but depending on your needs you may want to store the original IL code of the DLL under test into some other type of data structure. Notice that this mutation testing technique has the huge advantage of being language independent. Working with IL lets you avoid all the source code differences between C#, Visual Basic, and other .NET-compliant languages.

The next step in the mutation analysis preparation is to examine the IL code of the original DLL, looking for lines containing potential IL instructions to mutate. I store this potential mutation information into an ArrayList object named listInfo as shown in the code in Figure 6.

Figure 6 Analyze IL for Potential Mutations (Close Figure 6)
ArrayList listInfo = new ArrayList();
string currMethod = "unknown";
for (int i = 0; i < listLibUnderTestOrig.Count; ++i)
{
  string s = (string)listLibUnderTestOrig[i];

  if (s.IndexOf(".method") >= 0)
    currMethod = (string)listLibUnderTestOrig[i+1].ToString().Trim();

  for (int j = 0; j < brInstructions.Length; ++j)
  {
    if (s.IndexOf(":  " + brInstructions[j]) >= 0)
    {
      Info info = new Info(i, currMethod, brInstructions[j]);
      listInfo.Add(info); // lineNum, method, branch instruction
      break;
    }
  }
}

Because I saved the IL instructions into the ArrayList named listLibUnderTestOrig, I just iterate through that collection one object (a string) at a time. Although not absolutely necessary, in addition to keeping track of which line the mutable instruction occurs on, it will be very useful to keep track of which method the instruction occurs in. Within IL code produced by ildasm.exe, all methods are preceded by a .method tag:

.method public hidebysig instance int32
        Max(int32 a, int32 b, int32 c) cil managed
{
  // Code size       26 (0x1a)
  .maxstack  2
  .locals init (int32 V_0)
   IL_0000:  ldarg.1
...
   IL_0019:  ret
} // end of method Utility::Max

Notice that the method name typically (but not always) comes on the line following the line containing the .method string. So, every time I hit a line with .method, if I grab the next line, I will usually get the first part of the current method signature. This is a very lightweight approach; a robust solution would have more advanced string parsing logic in place.

If you need better method name information, you can take advantage of the fact that ildasm.exe conveniently finishes each method with a comment beginning with "// end of method" that contains the name of the method, but not the parameter list or return type. You can perform a first pass through the IL code to determine the starting and ending line numbers for each method. Then, on a second pass through the IL code, when you hit a .method string you'll know what line you are on and can look up which method you are in.

When analyzing the IL code of the DLL under test, you can look for any of the branching instructions in the brInstructions string array. If you find one of these instructions, capture the instruction found, the line number on which it occurs, and the method name in which it occurs. Save this information for later when you actually perform the mutations. The easiest approach is to create a very lightweight object that has fields for IL instruction, line number, and method, and save that object into an ArrayList. I define such a lightweight class as follows:

class Info
{
  public int lineNum;
  public string method instruction;

  public Info(int lineNum, string method, string instruction)
  {
    this.lineNum = lineNum;
    this.method = method;
    this.instruction = instruction;
  }
}

I consider this the least amount of information you need to capture. You may want to add additional information such as the current IL offset, the parent class name, or the operand associated with the branching instruction. Because each Info object holds one IL branching instruction, the listInfo.Count property will return the number of potential mutations. In the example here, I execute every possible mutation. In a large-scale project with a huge test suite, this may not be feasible. There, the approach to take is to mutate randomly selected branching instructions instead of every one. I'll explain this idea in more detail in the next section.

The last step in the mutation analysis preparation process is to display information about the potential mutations:

for (int i = 0; i < listInfo.Count; ++i)
{
  Info info = (Info)listInfo[i];
  Console.WriteLine(
    "On line " + info.lineNum + " inside method " +
    info.method + " found mutatable instruction '" +
    info.instruction + "'");
}
This step is optional, but is really helpful when you are tracking down problems with the mutation analysis. One option to consider is adding a verbose switch to your analysis program that controls whether to print diagnostic information like this.

The Main Mutation Loop

At this point in the mutation analysis program, all the preparation work is done. I copied the original, unmutated DLL under test into the test harness executable folder. I ran the test harness against the original DLL and grabbed those baseline test results. I created an in-memory data store (listLibUnderTestOrig) of the IL code image of the DLL under test. And I analyzed the IL code image for branching instructions, which are possible mutations, and stored line number, instruction, and containing method name into memory as listInfo. See Figure 7 for a high-level overview of the main mutation loop.

Figure 7 Main Mutation Loop Overview
Figure 7 Main Mutation Loop Overview

Now comes the main mutation loop where I create an in-memory version of mutated IL code of the tested DLL, build the mutant, and execute the test harness against the mutant. If I'm going to mutate all branching instructions in the original DLL under test, I can iterate through the listInfo object:

for (int i = 0; i < listInfo.Count; ++i)
Inside this loop I first make a copy of the unmutated IL code:
listLibUnderTestMutated = (ArrayList)listLibUnderTestOrig.Clone();
Info info = (Info)listInfo[i];
int lineNum = info.lineNum;
string instruction = info.instruction;

I create an ArrayList named listLibUnderTestMutated, then I copy all the lines from listLibUnderTestOrig in. Next I fetch just the one line that contains the mutable branching instruction, replace that instruction with its Boolean complement using the mutatedInstruction helper method I described earlier, and replace the original line with the line containing the mutated instruction:

string originalLine = (string)listLibUnderTestOrig[lineNum];
string mutatedLine = originalLine.Replace(instruction,
    mutatedInstruction(instruction));
listLibUnderTestMutated[lineNum] = mutatedLine;

Next I write the information in listLibUnderTestMutated to an external text file:

using(StreamWriter sw =
    new StreamWriter("..\\..\\LibUnderTestMutated.il"))
for (int j = 0; j < listLibUnderTestMutated.Count; ++j)
{
    sw.WriteLine((string)listLibUnderTestMutated[j]);
}
}

There's nothing tricky here except making sure you know where you are saving this new file. I named it LibUnderTestMutated.il and saved it in the root mutation analysis program folder. Next I build a mutated DLL from the mutated IL code using the ilasm.exe program, as shown here:

using(Process assemble = Process.Start(
  "ilasm.exe", "..\\..\\LibUnderTestMutated.il " +
  "/OUTPUT=..\\..\\..\\TestHarness\\bin\\Debug\\LibUnderTest.dll /DLL"))
{
    assembly.WaitForExit();
}

Here's where another big advantage of the .NET Framework comes in handy. Instead of recompiling source code, I can assemble IL code, which is generally much faster and easier. The ilasm.exe Intermediate Language Assembler program ships with the .NET Framework SDK. I specify an /OUTPUT switch to control where my resulting DLL goes, placing it directly into the test harness executable folder. I need the /DLL switch because the default output is an .exe file. Finally, I name the mutant DLL as LibUnderTest.dll rather than giving it a new name, because the test harness binds by file name and will be looking for the original DLL name.

Now that I've created a mutated DLL, I can run the test harness against the mutant:

using(Process test = Process.Start(
  "..\\..\\..\\TestHarness\\bin\\Debug\\TestHarness.exe"))
{
    test.WaitForExit();
}

Notice I don't have to do anything special. Because the test harness binds to the DLL it runs against by name, the test harness will use the new mutant DLL when it runs. After the test harness runs, I can grab the test suite results exactly as before.

Now comes a key concept of the whole mutation process: I check to see whether the test suite has yielded any test case failures when run against the mutated DLL under test:

Console.WriteLine(
  "Mutated number pass, fail = " + mutatedPass + " " + mutatedFail);
if (mutatedPass == basePass)
{
  Console.WriteLine(
    "\n** ALERT: Mutation did not creates failure(s) **");
  Console.WriteLine(
    "** Likely that test suite misses this code path **");
}

The results are much easier to interpret if the original test suite has a 100 percent pass rate. If that is true, any failures must have been due to the mutation. But if the original DLL has test failures, you can't really be sure what test results against the mutant mean. If you get test case failures, you should create a subset test suite containing only test cases that pass and use that subset for the mutation analysis.

To track summary results, I record the cumulative number of test cases that pass and fail and the number of mutations that have produced one or more test case failures:

cumPass += mutatedPass;
cumFail += mutatedFail;

if (mutatedFail > baseFail)
  ++numMutationsWhichIncreaseFailures;

I'll discuss how to interpret these values in the next section. I mentioned that it might not be feasible to run your system against every potential mutation. In this case, for the main loop, instead of iterating through every object in the listInfo collection, you can decide how many mutations you want to create and select random mutations. The code could look like this:

int numMutations = 100;
Random r = new Random(0);
for (int i = 0; I < numMutations; ++i)
{
  int whichMutant = r.Next(listInfo.Count);
  // etc.

You can create a Random object and seed it so you can get reproducible results. Calling Random.Next(5) for example will return an integer between 0 and 4 inclusive, so calling r.Next(listInfo.Count) will return a random index that points to one of the Info objects. You might consider adding a bit of additional logic to prevent testing the same mutation twice.

Mutation Analysis Results

The last part of the mutation analysis is to display and interpret results. Here's the code that produced the output in Figure 1:

double alphaPower = (
  numMutationsWhichIncreaseFailures * 1.0) / listInfo.Count;
double betaPower = (cumFail * 1.0) / (cumFail + cumPass);

There are three ways to use the results of a mutation analysis. First, whenever you have a mutant that does not create test case failures, it is likely that your test suite does not test the code path containing the mutant. This was shown in Figure 1. You need to investigate and create new test cases.

The second way to use mutation results is as a measure of your test suite effectiveness relative to mutation testing. I call this the test suite relative alpha power. It is the ratio of the number of mutants that do create failures to the total number of mutants. For example, if you create 100 mutations and 90 of them yield test case failures, the alpha power is 90/100 = 0.90. Alpha will range between 0 and 1, with larger numbers being better. If you track alpha power as you add test cases to your test suite over time during product development, you can gauge the progress of your test suite effectiveness.

A third way to use mutation results is to compare the overall number of test case failures produced by mutants to the total number of test cases. This test suite mutation beta power correlates highly with the test suite alpha power. Suppose you have a test suite with 1,000 test cases. You run against 20 mutants and produce a total of 80 test case failures. The beta power would be:

80 / (1,000 * 20) = 0.004

The more failures you create, the higher the beta power will be. So beta power is also a number between 0 and 1, with larger values indicating a more effective test suite. Beta power is less meaningful as an indicator of test suite effectiveness than alpha power, but is still useful as a way to validate alpha power metrics.

Conclusion

The term "mutation testing" is somewhat of a misnomer. The technique might better be called mutation analysis because the result is a measure of test suite effectiveness rather than a pass or fail result. Mutation testing/analysis is a close cousin to code coverage analysis. Code coverage is generally easier than mutation testing in unmanaged development environments.

Code coverage tells you which parts (which can mean methods, blocks, or individual statements) of your code under test are touched by your test suite, and mutation testing is one approach to obtain similar results. Because the system I've presented in this article is easy to deploy, it may make mutation analysis just as easy as code coverage. With Visual Studio 2005, which has comprehensive support for code coverage analysis, along with some mutation analysis tools, you can integrate all of the methods discussed here into your testing suite.

James McCaffrey works for Volt Information Sciences Inc., where he manages technical training for software engineers working at Microsoft. He has worked on several Microsoft products including Internet Explorer and MSN Search. James can be reached at jmccaffrey@volt.com or v-jammc@microsoft.com.


Built For Speed: Develop Turbocharged Apps For Windows Compute Cluster Server

Built For Speed

Develop Turbocharged Apps For Windows Compute Cluster Server

Rich Ciapala
This article is based on a prerelease verison of Windows Compute Cluster Server 2003. All information herein is subject to change.
This article discusses:
  • Windows-based high-performance compute clusters
  • Scheduling and executing serial tasks
  • Using MPI to implement parallel execution
  • Debugging MPI tasks
This article uses the following technologies:
Windows Server 2003, Visual Studio 2005
Code download available at: ClusterComputing.exe (128KB)
Contents
High-Performance Compute Clusters
Windows Cluster Solution
Serial Tasks
Scheduling Jobs
Executing Tasks
Multiple Serial Tasks
MPI and Parallel Tasks
Debugging MPI Tasks
Compute Cluster Availability
Conclusion

H
igh Performance Computing (HPC) involves the execution of long-running, computation-intensive workloads. Examples of such workloads include equity market analysis, weather prediction, stress analysis, and air flow simulations. Historically these workloads have been handled by expensive supercomputers. But with the increase of desktop computer processing power and the introduction of fast and affordable server interconnects, the clustering of large numbers of PCs has been realized as a viable, cost-effective alternative.

Though HPC applications are currently available for the Windows® platform, Microsoft is releasing a version of Windows, called Windows Compute Cluster Server 2003 (CCS), that provides a common execution and management environment for HPC applications. Visual Studio® 2005 also provides tools to accelerate the development of HPC applications. This article explores the services provided by CCS and the tools provided by Visual Studio 2005 for use in developing HPC applications.

High-Performance Compute Clusters

Like any cluster, a compute cluster aggregates multiple physical nodes and exposes them to clients as a single resource pool. Compute clusters are unique in the workloads they can handle. They execute requests (called jobs) that may complete in minutes, hours, or even days. As a result, jobs are developed to leverage resources across multiple machines in order to decrease their execution times. This requires the cluster to monitor the resource utilization of all of its nodes and to match incoming jobs with available resources across those nodes.

If a job executes across multiple nodes, its individual processes may need to share data frequently. As a result, network bandwidth may often be a job's performance bottleneck. To compensate, compute clusters often connect their nodes via fast, high-bandwidth interconnects such as Gigabit Ethernet (GigE), InfiniBand, or Myrinet. There are a number of different usage scenarios for compute clusters, including multiuser compute clusters, personal compute clusters, and cycle scavenging clusters.

Multiuser compute clusters are often owned and managed by an organization's IT department and are shared across multiple users. Users submit jobs to the cluster for execution. The IT department's support personnel control job priority, node requirements and execution context through policy services provided by the cluster.

Personal compute clusters are assigned to a single user, giving that user complete control and isolation.

Cycle scavenging clusters are comprised of nodes that execute some other primary workload, but participate in the compute cluster when normally idle. Desktop scavenging is one type of cycle scavenging, where the resources from client workstations are used after hours for compute cluster workloads. The SETI@home project is one example.

Windows Cluster Solution

CCS focuses on the submission of jobs to multiuser and personal compute clusters (future versions are expected to support other scenarios such as cycle scavenging). Figure 1 illustrates the physical topology of a typical CCS cluster. CCS provides a command-line interface as well as a client user interface for submitting jobs to the cluster. CCS also provides DCOM-based programming interface for managing jobs.

Figure 1 Compute Cluster Topology
Figure 1 Compute Cluster Topology

Clients submit work to a compute cluster as a job. A job contains a description of the resources necessary to execute it and a collection of tasks that implement the job. A task can be implemented as a serial task or a parallel task. A serial task executes as a single process, while a parallel task executes as multiple tightly coupled processes or threads.

The head node is the entry point into the cluster and receives all jobs submitted to the compute cluster. The head node hosts the cluster's scheduler service, whose role is to match the resources requested by the submitted jobs to resources available across the cluster (such as processors). Once a job receives its resources, its tasks are dispatched for execution to the compute nodes allocated to the job. The head node can also serve as a compute node.

The compute node joins a compute cluster by finding its head node through a local registry setting or the head node's service connection point registration in Active Directory®. When the compute node contacts the head node, the head node checks whether the compute node is running a CCS-compatible operating system. If it is, the head node and the compute node perform mutual authentication. If the authentication succeeds, the node's addition to the cluster then awaits approval by the cluster administrator.

The administrator can approve the addition of nodes via the Compute Cluster Administrator console. The compute nodes' resource utilization statistics and overall health can also be monitored there. The compute nodes send their statistics to the head node every minute. If the head node misses three updates from a compute node, it considers the compute node down. Both the update period and number of missed updates allowed are configurable. CCS also enables rapid deployment of compute nodes through Remote Installation Services (RIS).

The data server, which is often as simple as a file server, is used to share data across the nodes as jobs execute.

The management client hosts the Compute Cluster Administrator and Compute Cluster Job Submission and Monitoring consoles. Some functionality provided by these consoles includes:

  • Identifying a cluster's head node and its compute nodes
  • Approving, enabling, and disabling compute nodes
  • Creating and deploying server images for compute nodes
  • Creating, importing, and exporting compute cluster job definitions
  • Submitting new jobs as well as querying, altering, and cancelling executing jobs
  • Enabling policies for accepting jobs for execution and charging back for the use of cluster resources
  • Submitting compute cluster software updates as low- or high-priority jobs

The Message Passing Interface (MPI) is a standard programming model for developing explicitly parallel applications that frequently share data across processes. MPI defines a language and platform independent API for efficiently sharing data across multiple processes and other related functionality. Individual implementations of the MPI are provided for a variety of interconnects on a variety of operating systems. MSMPI provides a standard messaging implementation for the Windows platform based on MPICH2, an open source implementation of MPI 2.0 started by the Argonne National Laboratory. MSMPI supports language bindings for Fortran77, Fortran90, C, and C++. CCS does not provide a Microsoft® .NET Framework class library for MPI. However, jobs implemented with serial tasks can be developed in .NET Framework-compliant languages, and jobs that do rely on MPI can use many of the MPI functions through P/Invoke. Future versions of the CCS are expected to provide a .NET Framework interface for MPI.

MSMPI also allows third-party hardware interconnect vendors to plug in their products via the existing WinSock Direct driver framework. This eliminates the need for each vendor to build an entire MPI stack. WinSock Direct has been available since Windows 2000 Service Pack 2. It allows faster network I/O by allowing applications to bypass the kernel networking layers and communicate directly to the interconnect driver.

CCS will only ship with support for x64 processors, which includes the AMD Operton (and compatible) and Intel EM64T (and compatible) chipsets. This means CCS requires the use of x64 hardware and Windows Server™ 2003 SP1 x64, but permits both 32-bit and 64-bit applications. CCS will provide 32-bit and 64-bit version of MSMPI, but the WinSock Direct drivers need to be 64-bit.

Serial Tasks

Let's jump into some code to see how to develop and submit a CCS job. Reaching back in my memory to my high school algebra classes, I vaguely recollect the notion of matrix multiplication. I will use this as the basis for the samples in this article to demonstrate three distinct ways to build a CCS job. First let's take at a quick refresher in matrix multiplication. Figure 2 shows two matrixes, A and B, and the product of the two. Each cell in Matrix C is calculated individually. For example, cell C1,1 is calculated as follows:

C1,1 = (A1,1 x B1,1) + (A1,2 x B2,1) + (A1,3 x B3,1)
In real terms this is calculated as:
72 = (6 x 0) + (5 x 0) + (8 x 9)

Figure 2 Matrix Example
Figure 2 Matrix Example

The MatrixMultipler sample provided with this article is a Win32® console application that implements matrix multiplication. The sample loads two matrixes from individual files, multiplies them together, and saves the results to a third file. The following command line runs the sample:

MatrixMultipler matrix1.mtx matrix2.mtx matrix3.mtx
The heart of the sample is the MultiplyMatrixes function, which multiplies the matrixes as follows:
for(int i=0;i<nMatrixDim;i++)
{
  for(int j=0;j<nMatrixDim;j++)
  {
    int* pnOutputCell = GetCellPtr(j, i);

    for(int k=0;k<nMatrixDim;k++)
    {
      *pnOutputCell = *pnOutputCell + (inputMatrix1.GetCellVal(k,i) *
          inputMatrix2.GetCellVal(j,k));
    }
  }
}

The MatrixUtil utility, also available in the code download that accompanies this article, creates the input matrix files. These command lines create the input matrixes of random numbers with dimensions of 5,000 by 5,000:

MatrixUtil matrix1.mtx 1 5000
MatrixUtil matrix2.mtx 1 5000

Though MatrixMultipler is implemented as a Win32 console app, it can still be transformed into a CCS job and submitted to a CCS compute cluster. A CCS job is defined by its tasks, the terms in which to schedule the job, and the terms in which to execute its tasks. CCS jobs can be defined programmatically or as strongly typed XML. Figure 3 shows the job definition for the MatrixMultiplierSerial job using XML. Information about the XML schema for job definitions is available with the beta of CCS.

Figure 3 XML Job Definition (Close Figure 3)
<?xml version="1.0" encoding="utf-8" ?>
<Job xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    SoftwareLicense=""
    MaximumNumberOfProcessors="1"
    MinimumNumberOfProcessors="1"
    Runtime="00:00:50"
    IsExclusive="false"
    Priority="Normal"
    Name="MatrixMultiplierSerial"
    Project="MSDN Samples"
    RunUntilCanceled="false">
    <Tasks xmlns="http://www.microsoft.com/ComputeCluster/">
        <Task MaximumNumberOfProcessors="1"
            MinimumNumberOfProcessors="1"
            WorkDirectory="\\DataServer\Public"
            Stdout="MatrixMultiplier.out"
            Name="MatrixMultliplier"
            CommandLine=
                "MatrixMultiplier matrix1.mtx matrix2.mtx matrix3.mtx"
            IsCheckpointable="false"
            IsExclusive="false"
            IsRerunnable="false"
            Runtime="00:00:50">
            <EnvironmentVariables />
        </Task>
    </Tasks>
</Job>

Now that the job has been defined, it can be submitted to the cluster. CCS provides a script called job.vbs that provides a command-line interface for submitting jobs to the CCS head node. This command line will submit the MatrixMultiplierSerial job:

job.vbs submit /f:MatrixMultiplierSerial.xml
If job.vbs succeeds in submitting the job, it will output the job's ID assigned by the compute cluster. The job ID can then be used to query, alter, and cancel the job.
Job created, ID: 55

Scheduling Jobs

Job scheduling entails the matching of jobs with available resources on the cluster while ensuring those resources are used within the context of the submitting user. The Compute Cluster Scheduler Service runs on the cluster's head node. It receives all incoming jobs and begins scheduling them. When a client first submits a job, the user is prompted for credentials to execute the job with. After providing the credentials, the user has the option of storing the credentials locally in an encrypted form. The job is then passed over an encrypted channel to the job scheduler with the specified credentials. When the scheduler receives the job, it encrypts the credentials by using the Windows Data Protection API (DPAPI) and stores the job and the encrypted credentials in a local Microsoft SQL Server™ Desktop Engine (MSDE) database. If the user asked that the credentials also be stored locally, the encrypted credentials are included in the response to the job submission and stored on the client. Subsequent job submissions simply pass the encrypted form of the credentials back to the cluster. Optionally, a Job Submission Filter can be written to ensure that the job includes all the required terms and excludes all disallowed terms as defined by the cluster's administrator. Common examples of policies include restricting which users can submit jobs, enforcing user quotas, and implementing charge backs for using a shared compute cluster.

At this point the job is pending within a logical queue in the MSDE database. At a specified interval, the scheduler starts a scheduling session to process jobs that are currently in the job queue. Job policies are implemented by Job Activation Filters and allow administrators to define rules for jobs submission. If a job is not filtered out by a policy, it will remain in the queue to be scheduled.

The job queue is divided into five sections for low, below normal, normal, above normal, and high priority jobs. Jobs within each section are processed in first-come, first-served order. When a job is first enqueued it is added as the last job in the section that corresponds to its priority. Administrators can change the priority of any message already in the queue, causing the message to be moved to the end of another section. The scheduler will start matching resources to the oldest job in the highest-priority section of the queue.

The most common compute cluster resource to be allocated is a processor, and allocating processors is tied to allocating nodes. By default, the compute nodes are allocated in order based on their amount of physical memory followed by the speed of their processor or processors. If a job explicitly specifies that its nodes are to be allocated through the AskedNodes term, the scheduler instead allocates the nodes specified in the term in the order specified there. If the IsExclusive job term is set to true, even if a job is allocated only some of the processors on a node, it is exclusively allocated the entire node.

CCS does not support the preemption of executing jobs in order to execute higher-priority jobs. In other words, if a higher-priority job is enqueued, it will execute before lower-priority jobs pending in the queue, but it will need to wait for any executing jobs (regardless of their priority) to complete if the executing jobs have resources it needs. Preemption of executing jobs will be implemented in a future version of the scheduler service. Jobs whose resource requirements could not be completely met remain in the queue as pending jobs. These jobs are still allocated those resources that are available and become candidates for backfill.

Backfill allows jobs—usually shorter jobs—that are later in the queue to potentially execute while larger jobs earlier in the queue are waiting for all of their necessary resources. Let's walk through a scenario to see how it works.

A hypothetical compute cluster has two nodes, A and B, both of which have two processors (see Figure 4). Three jobs are submitted to the cluster: X, Y, and Z. Job X has a run-time limit of two hours and is executing on node A. Job Y requires four processors, but only the two processors from node B are available. As a result job Y will remain pending in the queue with the two available processors on node B allocated to it. Job Z is after job Y in the queue and requires one processor for thirty minutes.

Figure 4 Compute Cluster
Figure 4 Compute Cluster

The scheduler identifies a backfill window made up of the two unused processors on node B currently allocated to job Y, which is waiting for the two processors on node A to become available. The scheduler checks to see whether any jobs after Y in the queue can leverage this window without affecting job Y. Job Z could execute on node B without affecting when job Y is scheduled. As a result, the scheduler schedules job Z on node B ahead of job Y.

Backfill is enabled by default and can be disabled. In order to work optimally, backfill relies on jobs to have accurate run-time limit values. A job's run-time limit is specified by the run-time job term. If a job does not specify a run-time term, the scheduler assumes that its run-time limit is infinite. Jobs whose run time is set to infinite are not candidates for backfill since it is impossible for the scheduler to know if the job's execution can fit into any available backfill window.

The scheduler also allows end users and support personnel to query the status of a queued or executing job, cancel a job, and alter some properties of a queued or executing job (such as increasing resource requirements). The following command line uses Job.vbs to query the status of a submitted job:

job.vbs view 55
The results returned would look something like this:
JOB_ID: 55
SubmittedBy:    TestComputeCluster\Administrator
NAME:   MatrixMultiplierSerial
STATUS: Running
CPUS:   1,1
ALLOCATED_NODES:        TestNode1
SUBMIT_TIME  :  11/22/2005 9:49:00 AM
NUM_TASKS:      1
  Queued:       0
  Running:      1
  Finished:     0
  Failed:       0
  Cancelled:    0

Executing Tasks

After the job scheduler allocates all of a job's required resources, it dispatches the job's tasks to the allocated nodes. The tasks are dispatched in the same order as they are defined in their job, along with the end user's credentials, over an encrypted channel. If the number of tasks exceeds the number of processors requested and allocated to the job, the scheduler will dispatch n tasks at at time where n is the number of processors allocated to the job. As tasks complete, another n tasks (or the remaining tasks) are dispatched. Backfilling does not occur at the task level.

When the compute node receives a task to execute, it is handled by the Compute Cluster Node Manager service. The Node Manager receives the task's command line, environment variables, submitting user's credentials, and a list of processors to use. The submitting user's credentials are used to create a Windows security token and then immediately erased. A Windows process is then created in suspended mode based on the task's command line and environment variables and is assigned the submitting user's security token. The new process is then associated with a Windows job object, which assigns the processors to be used and monitors that task's resource utilization. Once the process is associated with the job object, its execution resumes.

As the process executes, the local Node Manager monitors the process and communicates its status to the scheduler. If the process terminates abnormally, the Node Manager will communicate the error back to the scheduler and, optionally, invoke a Dr. Watson crash dump. After the task completes, the scheduler is notified of the completion and updates the status of the job in the job queue to Completed. Completed jobs remain in the job queue for a specified period of time to allow users and administrators to query for their status.

Multiple Serial Tasks

The MatrixMultiplierSerial job only leveraged a single processor of the compute cluster and as a result had a fairly long run time. Luckily there are ways to parallelize matrix multiplication. One simple approach is to have each unit of execution receive both input matrixes, but only compute a designated submatrix of the output matrix, as illustrated in Figure 5. The MatrixMultiplier sample supports four more command-line arguments that specify the submatrix of the output matrix to calculate. This allows the job to execute multiple instances of MatrixMultiplier.exe on different processors, each calculating different output submatrixes. This design pattern is known as a Parametric Sweep, whereby the scheduler provides parallelism to the job by launching multiple instances of a serial task.

Figure 5 MatrixMultiplierMultiSerial Parallel Execution
Figure 5 MatrixMultiplierMultiSerial Parallel Execution

The CCS job definition for MatrixMultiplierMultiSerial is shown in Figure 6. The first difference in comparing this job to the MatrixMultiplierSerial job is that four separate serial tasks have been defined to calculate a different submatrix of the output matrix in parallel. This increases the processor requirement for the job to four—one for each of the serial tasks. The submatrix that each task calculates is specified as command-line arguments along with the input and output files. A fifth task is added to the job that merges the output of the four previous tasks. Since this task requires the output of the four previous tasks, it specifies those tasks as dependencies, causing it not to execute until those tasks complete. The handy MatrixUtil sample is used to merge the matrixes. The sixth and final task cleans up the individual output matrixes once the merged output matrix is created and, as a result, is dependent on the fifth task. The dependencies of these two tasks demonstrate some of the basic task flow capabilities of the scheduler service. After testing the job, an overall run time of 12 minutes is estimated for the job's definition. The tasks that calculate the submatrixes are estimated at 10 minutes each with 1 minute for the merging of the output matrixes and 1 minute for the cleanup task. The following command line submits the MatrixMultiplierMultiSerial job to the compute cluster.

job.vbs submit /f:MatrixMultiplierMultiSerial.xml

Figure 6 MatrixMultiplierMultiSerial Job Definition (Close Figure 6)
<?xml version="1.0" encoding="utf-8" ?>
<Job xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    SoftwareLicense=""
    MaximumNumberOfProcessors="4"
    MinimumNumberOfProcessors="4"
    Runtime="00:00:12"
    IsExclusive="false"
    Priority="Normal"
    Name="MatrixMultiplierMultiSerial"
    Project="MSDN Samples"
    RunUntilCanceled="false">
    <Tasks xmlns="http://www.microsoft.com/ComputeCluster/">
        <Task MaximumNumberOfProcessors="1"
            MinimumNumberOfProcessors="1"
            WorkDirectory="\\DataServer\Public"
            Stdout="MatrixMultiplierMultiSerial.out"
            Name="Task1"
            CommandLine="MatrixMultiplier.exe matrix1.mtx matrix2.mtx
                         matrix_out1.mtx 0 0 2500"
            IsCheckpointable="false"
            IsExclusive="false"
            IsRerunnable="false"
            Runtime="00:00:10">
            <EnvironmentVariables />
        </Task>

        <!—- Task2, Task3 and Task4 are defined similarly -->
        <Task MaximumNumberOfProcessors="1"
            MinimumNumberOfProcessors="1"
            Depend="Task1,Task2,Task3,Task4"
            WorkDirectory="\\DataServer\Public"
            Stdout="MatrixMutltiplierMultiSerial.out"
            Name="Task5"
            CommandLine="MatrixUtil.exe MatrixMerged.mtx 2 matrix_out1.mtx
                         matrix_out2.mtx matrix_out3.mtx matrix_out4.mtx"
            IsCheckpointable="false"
            IsExclusive="false"
            IsRerunnable="false" Runtime="00:00:01">
            <EnvironmentVariables />
        </Task>
        <Task MaximumNumberOfProcessors="1"
            MinimumNumberOfProcessors="1"
            Depend="Task5"
            WorkDirectory="\\DataServer\Public"
            Stdout="MatrixMutltiplierMultiSerial.out"
            Name="Cleanup"
            CommandLine="del \\DataServer\Public\matrix_out*.mtx"
            IsCheckpointable="false"
            IsExclusive="false"
            IsRerunnable="false"
            Runtime="00:00:01">
            <EnvironmentVariables />
        </Task>
    </Tasks>
</Job>

MPI and Parallel Tasks

The MatrixMultiplierMPI sample is another parallel implementation of matrix multiplication that dissects a job's work in a slightly different manner. Like MatrixMultiplierSerial, multiple instances of MatrixMultiplierMPI.exe execute across multiple nodes and each still calculates a designated submatrix of the output matrix. However, instead of having all the processes read both input matrixes in their entirety from a file share, one process loads the input matrixes and the other processes are fed just the input submatrixes that they need to calculate their output submatrixes. This lowers the amount of memory that each task requires and the amount of remote file I/O.

Figure 7 MatrixMultiplierMPI Parallel Execution
Figure 7 MatrixMultiplierMPI Parallel Execution

When the job is first run, each process receives its initial two input submatrixes. This is illustrated with the blue and yellow lines in Figure 7. Each process then multiplies its two input submatrixes together and applies the product to its output submatrix as illustrated by the green lines in Figure 7. Next, each process gives its input submatrixes to its neighbors to the top and the left and in turn receives new input submatrixes from its neighbors from below and to the right of it (as illustrated with the black lines in Figure 8). The process multiplies the new input submatrixes together and applies the product to its previous output. The shifting and multiplying of new input submatrixes continue until all the processes are given the input submatrixes that they need.

Figure 8 Communication Between Processes
Figure 8 Communication Between Processes

Since this algorithm requires processes to communicate more often, MPI (instead of a file share) will be used to communicate between the processes. As mentioned at the beginning of this article, MPI is a programming interface for developing parallel applications that need to frequently share data across processes and nodes. The MPI programming model is tailored for efficient I/O and to take advantage of shared memory and fast interconnect technologies.

To use the MPI library the sample must initialize itself with MPI. This is done by calling the MPI_Init API:

   MPI_Init(NULL, NULL);
Each time this job is run, multiple instances of MatrixMultiplierMPI are started across the compute cluster. MPI groups these processes in what is referred to as a collective and provides a data type called a communicator, which is a handle to a collective. MPI also defines a constant called MPI_COMM_WORLD, which is defined as a communicator and is set to the current process's default collective. A process can use MPI_COMM_WORLD to query for more information about its collective. For instance, passing MPI_COMM_WORLD to MPI_Comm_size returns the number of processes currently running within the caller's collective. Similarly, passing MPI_COMM_WORLD to MPI_Comm_rank returns the calling process's rank within its collective. Each process is assigned a rank starting at 0 to serve as its unique identifier and to represent its order within the collective. Each process of MatrixMultiplierMPI uses its rank to select the submatrix of the output matrix it will calculate. The following code shows how a process retrieves the number of processes in its collective and its rank:
MPI_Comm_size(MPI_COMM_WORLD, &g_nProcessCount);
MPI_Comm_rank(MPI_COMM_WORLD, &g_nProcessRank);

Logically the process's rank gives it an idea as to where it is positioned linearly relative to the other processes in is collective. However, many parallel algorithms require the units of execution (the processes) to be arranged in a specific topology such as a ring or Cartesian plane. With this parallel matrix multiplication algorithm, the processes need to be logically organized into a Cartesian plane so that input submatrixes can correctly be transferred from one process to the other. Implementing this algorithm with the default linear view of the collective forces the developer to do more work.

MPI provides the ability to create a topological view of the physical processes, which for purposes of this sample is a Cartesian plane. Through this view, each process can be referenced with X and Y coordinates and each process can easily reference its surrounding neighbors:

g_nClusterDim = (int) sqrt((double) g_nProcessCount);
int rgnDims[] = {g_nClusterDim, g_nClusterDim};
int rgnPeriods[] = {true, true};
MPI_Cart_create(MPI_COMM_WORLD, 2, rgnDims, rgnPeriods, false,
    &g_commCartesian);
MPI_Cart_create creates a logical view of a Cartesian plane across the physical processes of the collective. The first parameter takes a communicator to the collective of processes to create the view upon. As discussed earlier, MPI_COMM_WORLD includes all of the processes in its default collective. The next parameter specifies the number of dimensions. Since this sample only calculates two-dimensional matrixes, I only need to organize the application's processes as such. The rgnDims parameter specifies the number of processes in each dimension. MatrixMutlplierMPI will create an even-dimensioned Cartesian plane with however many processes are assigned to the job. (Note that the sample requires the number of processes to be an even square for simplicity.) The rgnPeriods parameter specifies whether each dimension is continuous. This essentially means that if the application requests the next process after the last process in a dimension, the first process in the dimension is returned as opposed to an error. The next parameter specifies whether the ranks of the processes can be rearranged in the new collective, and the final parameter returns a communicator to the new collective.

Besides calculating a submatrix of the output matrix, the root instance of MatrixMultiplierMPI is also responsible for loading the input matrixes, divvying up the initial input matrixes across all the processes in the default collective, and later consolidating the results of the other instances. The other instances, which I will refer to as leaf processes, initially wait for their submatrixes from the root process and later send their completed output submatrixes back to the root process (the process with a rank of 0).

First the root process loads the input matrixes from the input files specified by the command line. The matrixes are loaded into submatrixes designated to each process in the collective. (Note that another limitation of this sample is that the dimensions of the input matrixes must be the same to maintain simplicity.) Before sending each process its initial input submatrixes, the root process broadcasts the dimensions of the submatrixes to each process in the collective. MPI_BCast is used to broadcast data to all the processes in a specified collective in one operation:

MPI_Bcast(&nSubMatrixDim, 1, MPI_INT, g_nProcessRank, MPI_COMM_WORLD);

MPI consistently describes data involved in transmissions with three parameters: a pointer to the transmission buffer, the number of data elements transmitted, and the type of the data elements. In the sample, the first three parameters of MPI_Bcast show the dimensions of the submatrixes being broadcast as a single integer—since the height and width of a submatrix is always the same, only one dimension needs to actually be sent. MPI also consistently describes a specific process with two parameters: the rank of the process within its collective and a communicator to its collective. The final two parameters of MPI_Bcast use this pattern to specify the process from which the broadcast is sent.

A quick digression: MPI defines a set of constants that represent primitive data types and the bindings between these data types and their corresponding data types in C, C++, and Fortran. However, MPI does not define a standard wire format for these types. Therefore the behavior of an MPI process developed in C receiving a message from an MPI process developed in Fortran is undefined by the MPI specifications. With standards such as Web Services Architecture (WSA) in developers' minds, this may seem like a significant issue, but keep in mind that the main use for MPI is to segment a tightly coupled task across nodes to gain use of more resources. In most cases the componentization of tasks will occur vertically, not horizontally, and as a result interoperability between tasks developed on heterogeneous platforms is less important. On the other hand, exposing an interoperable interface to submit, cancel, and query jobs is a common scenario, and CCS exposes a WSA-compliant interface for doing just that.

After sending the submatrixes' dimensions, the root process sends the initial input submatrixes to the leaf processes. MPI_Scatter allows the root to send a different submatrix to each process in a single operation:

MPI_Scatter(pnSubMatrixes, nSubMatrixCells, MPI_INT, inputSubMatrix1.GetBuffer(),
            nSubMatrixCells, MPI_INT, g_nSubMatrixTag, MPI_COMM_WORLD);
The first parameter is a pointer to a buffer containing all the submatrixes to send to the processes of the collective. The submatrixes are laid out contiguously in the buffer one right after the other. The second and third parameters specify the number of cells in each submatrix and the type of the cells (which are of type integer), respectively. MPI_Scatter is also used to receive the data that was sent via MPI_Scatter to itself as a member of the collective. Since the root process is also responsible for calculating a submatrix of the output matrix, it will receive back from MPI_Scatter the submatrix that is designated to it. The fourth, fifth, and sixth parameters are used to receive the submatrix. The seventh parameter is used to tag the data that is sent so that it is distinguishable by the receivers. The eighth and final parameter specifies the communicator to the collective to send the submatrixes. If the collective has four processes (including the root), the buffer passed in the first parameter must contain four contiguous submatrixes.

Let's take a look at how the leaf processes receive the data sent via BCast and MPI_Scatter. First the leaf process needs to receive the dimensions of the submatrix. MPI_BCast was used to broadcast the dimensions of the submatrixes and is also used to receive them. The function knows that it's being used to receive broadcast data when its fourth parameter takes a rank of a process that is not its own. As a result the buffer passed in the first parameter is used to receive the data instead of providing the data to be sent.

Once the leaf processes know the dimensions of the submatrixes they are about to receive, the buffer size of each submatrix can be calculated and the submatrixes themselves can be received. MPI_Scatter is also used to receive data sent via MPI_Scatter. When used to receive data, the first parameter takes a NULL value and the next two parameters are ignored. The fourth, fifth, and sixth parameters are used to receive the submatrix in the same way the sending process receives back its submatrix. The seventh parameter specifies the same tag the sender used when sending the data and the final parameter is again the communicator to the collective containing the sending process.

Now that all the processes have their initial submatrixes, each begins to calculate the submatrixes that of the output matrix designated to them. As explained, the calculation is done by multiplying the two input submatrixes that the processes currently have and saving the results in the output submatrix. Each process then shifts the input submatrixes, sending one input submatrix to the node above it and the other input submatrix to the node to its left. The process then receives the submatrixes being shifted to it from the nodes below it and to the right. After the process receives the next set of submatrixes, it multiplies them together and adds the results to the results from the previous multiplication. The processes continue shifting their current input submatrixes and calculating the newly acquired input submatrixes until all the necessary submatrixes are multiplied:

do
{
    outputSubMatrix.Multiply(inputSubMatrix1, inputSubMatrix2);
    nSubMatrixesReceived++;
}
while(ShiftSubMatrixes(inputSubMatrix1, inputSubMatrix2,
                       nSubMatrixesReceived));
The ShiftSubMatrixes function implements shifting of the submatrixes. Each process sends its input1 submatrix to the process to its left and receives its next input1 submatrix from the process to its right. Similarly each process also sends its input2 submatrix to the process above it and receives its next input2 from the process below it. Doing so requires that each process have the rank of its surrounding processes; this is where the Cartesian plane view created earlier can be leveraged. MPI_Cart_shift will return the ranks of the processes that that would be involved in the specified shift:
MPI_Cart_shift(g_commCartesianView, 0, -1, &nAboveProcessID,
               &nBelowProcessID);
The first parameter is the communicator that references the Cartesian view. The second parameter specifies the dimension of the plane that is shifting (0 is the vertical plane). The third parameter's numerical value represents how many coordinates should take place in the shift, and its sign represents the direction of the shift. The final two parameters receive the ranks of the processes to send to and the process to receive from, respectively.

To perform the actual shift, the MPI function MPI_Sendrecv_replace is used. In one operation the current input submatrix is sent and the incoming input submatrix is received. The process ranks from MPI_Cart_shift are used to specify the external processes involved in the shift. ShiftQuadrants returns false when the process has received all the submatrixes it needs to calculate its output submatrix.

As each process completes the calculation of its output, its output submatrix is sent to the root process. MPI_Gather allows leaf processes to easily send their output submatrixes to the root process and allows the root process to gather all the submatrixes in a single operation. Like MPI_BCast and MPI_Scatter, MPI_Gather is used on the sending and receiving side of this operation:

MPI_Gather(outputSubMatrix.GetBuffer(), nSubMatrixCells, MPI_INT,
           NULL, 0, 0, g_nRootProcess, MPI_COMM_WORLD);

The first three parameters to MPI_Gather specify the data to send. The fourth, fifth, and sixth parameters are ignored on the sending side. The last two parameters specify the process that performs the gathering, which is the root process:

MPI_Gather(pnSubMatrix, nSubMatrixCells, MPI_INT, pnGatheredMatrixes,
           nSubMatrixCells, MPI_INT, 0, MPI_COMM_WORLD);

The root process also uses MPI_Gather to receive the submatrixes. The first three parameters specify the root process's output submatrix, which is simply copied to the correct location in the receive buffer specified next. The fourth, fifth, and sixth parameters specify the buffer that will receive the output submatrixes of each process in the collective. If there are four processes in the collective, the buffer must be large enough to accept four submatrixes.

Finally, MatrixMultiplierMPI saves the consolidated output matrix to disk and then terminates the MPI library via MPI_Finalize. Now let's take a look at the job definition to execute MatrixMultiplierMPI as on a compute cluster (see Figure 9).

Figure 9 MatrixMultiplierMPI Job Definition (Close Figure 9)
<?xml version="1.0" encoding="utf-8" ?>
<Job xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    SoftwareLicense=""
    MaximumNumberOfProcessors="4"
    MinimumNumberOfProcessors="4"
    Runtime="00:00:12"
    IsExclusive="false"
    Priority="Normal"
    Name="MatrixMultiplierMPI"
    Project="MSDN Samples"
    RunUntilCanceled="false">
    <Tasks xmlns="http://www.microsoft.com/ComputeCluster/">
        <Task MaximumNumberOfProcessors="4"
            MinimumNumberOfProcessors="4"
            WorkDirectory="\\DataServer\MatrixMultiplier"
            Stdout="MatrixMultiplierMPI.out" Name="Task1"
            CommandLine="mpiexec.exe -hosts %CCP_NODES%
                         MatrixMultiplierMPI.exe matrix1.mtx
                         matrix2.mtx matrix_mpi.mtx"
            IsCheckpointable="false"
            IsExclusive="false"
            IsRerunnable="false"
            Runtime="00:00:12">
            <EnvironmentVariables />
        </Task>
    </Tasks>
</Job>

The first variation in this job definition is that there is only one task even though multiple processes will be spawned. When executing parallel tasks, the job scheduler leaves it up to the MPI implementation to spawn the multiple processes on nodes and processors that the scheduler allocated to the job. The second version of the MPI standard (MPI2) recommends a standardized startup for all MPI applications so that scripts, schedulers, and other launching programs can expect to start MPI applications in a similar fashion. More specifically, MPI2 recommends that all MPI implementations provide a launcher, mpiexec.exe, that supports a minimum set of parameters. For the purposes of MatrixMultiplierMPI, its command line would look like this:

mpiexec –hosts %CCP_NODES% MatrixMultiplierMPI.exe

The hosts argument is an MPI standard argument. It takes the nodes on which to launch the processes and it also takes the processors on those nodes that the processes should be bound to. The CPP_NODES environment variable includes the nodes and the processors on each node allocated to the job by the scheduler. This binding allows mpiexec.exe to launch the MatrixMultiplierMPI processes across the cluster based on the resources allocated to the job by the scheduler service. By passing the allocated nodes and processors as an environment variable (in a format defined by MPI), third-party MPI implementations can be integrated with the scheduler service. The following command line submits the MatrixMultiplierMPI job to the compute cluster:

job.vbs submit /f:MatrixMultiplierMPI.xml

Debugging MPI Tasks

MPI applications can be challenging to debug due to chatty conversations between multiple processes executing in parallel. To simplify debugging, Visual Studio 2005 allows debugging of MPI applications in the development environment. Figure 10 shows the Debugging section of the MatrixMultiplierMPI project property sheet.

Figure 10 MatrixMultiplierMPI Debugging Properties
Figure 10 MatrixMultiplierMPI Debugging Properties

Notice that the debugger to launch is set to MPI Cluster Debugger. This presents a list of options that specify how the MPI application is launched from the Visual Studio debugger. The MPIRun Command is set to the MPI utility that launches the project as an MPI application. Here MatrixMultiplierMPI uses mpiexec.exe that ships with CCS as the launcher implemented by MSMPI. The MPIRun Arguments specify the arguments to mpiexec.exe, and in this case specifies that four instances of MPIMultiplierMPI.exe will be launched locally by the debugger. Alternatively, the hosts argument could also be specified, allowing the processes to be launched remotely across multiple nodes and still be debugged as if they were local. Application Command specifies the path to MatrixMultiplierMPI.exe and Application Arguments specifies the paths to the two input matrixes and output matrix. MPIShim- Location specifies where mpishim.exe can be found. If the MPI app is debugged across multiple nodes, mpishim.exe must be at the location specified by MPIShim Location on each node. The MPI network security mode is set to allow connections from any machine in the local subnet to connect to the launched processes. You can be more restrictive by specifying specific IP addresses from which connections will be allowed in the MPI network filter field.

After closing the property sheet, you can start the MPI application. As with debugging most applications from Visual Studio, F5 causes the application to launch with the Visual Studio debugger attached. Depending on the debugger settings that you have specified in the previous property page, the processes that make up the MPI application are launched on the local machine, on remote nodes, or a combination of both. Either way, however, you can set breakpoints, step through code, view the values of variables and leverage all the debugging features found in the Visual C++® debugger.

Figure 11 shows MatrixMultiplierMPI stopped at a breakpoint after the process identifies its rank within its collective. In the Processes windows, you can specify which process to debug and then view the call stack for that process. This is very similar to how you would select a thread in the Threads window and view its current call stack.

Figure 11 Debugging MatrixMultiplierMPI
Figure 11 Debugging MatrixMultiplierMPI

By default, when the debugger hits a breakpoint in any process, all other processes in the MPI application will also break. This behavior can be changed at a global level across all projects in the global Options property page. This property page can be opened by selecting the Options menu in the Tools top-level menu. When the property page dialog appears, select the Debugging branch and the General leaf in left-hand tree control. In the list view on the right, deselecting "Break all processes when one process breaks" will cause only the process that triggered the breakpoint to actually break while the other processes continue to execute.

Visual Studio 2005 provides other features that are useful when building CCS applications. These include x64 C++ and .NET-based compilers, Open MP C++ extensions, and floating point improvements to the C++ compiler that allow developers to make trade-offs between speed and accuracy.

Compute Cluster Availability

As I mentioned earlier, the scheduler maintains all of its state in a persisted form in an MSDE database on the head node. This allows the scheduler to recover a job's execution as a result of a node failure. If the head node fails, the cluster cannot accept new jobs or schedule queued jobs until the head node is brought back online, but jobs executing on compute nodes continue to execute. If a compute node shuts down when executing a serial job, the scheduler decides whether the job's tasks running on that node can be run on other nodes already allocated for that job. If they can be, then the job's resource requirements will be decreased and the tasks that were supposed to run on the failed nodes will be rescheduled on the other allocated nodes.

If one or more of the tasks that were executing on the failed node cannot be run on the other nodes allocated to the job, all the executing tasks for that job are killed, their resources are deallocated and the entire job is placed back into the scheduler's queue in the pending state. If a compute node shuts down when executing a parallel job, the entire job is placed back in the queue.

CCS does not support multiple head nodes per compute cluster for redundancy or scalability purposes. Only one head node can be configured per cluster. If the head node fails, it must be restarted or rebuilt before new jobs can be submitted. Again, jobs already executing on separate compute nodes will still execute. Also, other versions of SQL Server cannot be used for the scheduler's database in this version of CCS. Only the MSDE instance on the head node is supported. Optionally the head node could provide RAID drives to improve its overall availability. Future versions of CCS will allow multiple head nodes per cluster.

Conclusion

A common HPC platform provides multiple benefits to a variety of parties. HPC application developers can develop to a common scheduler and MPI implementation while leveraging a wide variety of development environments available on Windows. Hardware vendors that develop interconnect technologies can focus on developing WinSock Direct drivers as opposed to an entire MPI stack. System administrators can manage large compute clusters that host a variety of internal and external applications in a consistent manner. Furthermore, administrators can utilize their existing investments in Windows infrastructure to manage user authentication and to set policies for user authorization, user quotas, and charge backs with services such as Group Policy.

Historically the Windows platform has simplified the development, deployment, and maintainability of a long list of application types. Windows Compute Cluster Server now extends that list to include high-performance computing applications. Now that you're ready to dive into CCS, you should visit Windows Compute Cluster Server 2003 for more information and to download the beta software.

Rich Ciapala has worked in Microsoft Consulting Services and Microsoft product groups for the last 15 years. He is currently a member of the Windows Server Customer Advisory Team focusing on the design of mission-critical applications.


.NET Profiling: Write Profilers With Ease Using High-Level Wrapper Classes

.NET Profiling

Write Profilers With Ease Using High-Level Wrapper Classes

Joachim H. Fröhlich and Reinhard Wolfinger
This article discusses:
  • Inside the low-level CLR Profiling API
  • Overview of a sample profiler target app
  • Creating a high-level profiling API
  • Bridging the high-level and low-level APIs
This article uses the following technologies:
.NET Framework, COM, C++
Code download available at: NETProfiling.exe (304KB)
Contents
The Low-Level CLR Profiling API
A Sample Profiler Target
A High-Level Profiling API
Bridging the APIs
Conclusion

T
he Profiling API of the common language runtime (CLR) provides services that exceed the needs of typical function and memory profilers. The latest extensions of the Profiling API for the Microsoft® .NET Framework 2.0 increase this functionality even further. These extensions enable tools to trace not only method calls at the class level, but also data flow across assembly boundaries without the need to instrument Common Intermediate Language (CIL) code for that purpose.

However, the powerful functionality and technical details of this API compromise its simplicity and usefulness. Programming with the CLR Profiling API can be laborious and error-prone because the numerous services are organized in an interface that applies a primarily functional design at a low level of abstraction.

In this article we will present a high-level, easy-to-grasp, and easy-to-apply profiling API, based on the CLR Profiling API, that conceals the mechanisms necessary to filter elementary profiling data and to rejoin them into semantically coherent concepts. Programming with the high-level API should considerably reduce the development effort required to construct various types of tools that profile a program, as it affords an abstraction level close to that of high-level languages within the .NET Framework.

A classic example of such a tool is a profiler that reconstructs method-call trees and aggregates execution times at the various levels of a call tree. Another demanding example is a profiler that generates test driver frames, such as for stochastic tests on the basis of real usage scenarios of assemblies under test. We'll use a sample tracer, a profiler of the latter type, to illustrate the high-level API. This profiler's job is to trace control and data flow across component interfaces.

We will start with a short review of the design of the low-level CLR Profiling API. For a detailed presentation of the API's functions, see the information in the "Profiling API Resources" sidebar.

Profiling API Resources

The following articles and books provide further information on the CLR Profiling API.

"CLR Profiler: No Code Can Hide from the Profiling API in the .NET Framework 2.0," by Jay Hilyard, MSDN Magazine, January 2005

"Under the Hood: The .NET Profiling API and the DNProfiler Tool" by Matt Pietrek, MSDN Magazine, December 2001

Debugging Applications for Microsoft .NET and Microsoft Windows by John Robbins (Microsoft Press, 2003) Chapter 10, "Managed Exception Monitoring," and Chapter 11, "Flow Tracing"

We will illustrate the design from a profiler's point of view with an example application that reveals the intricacy of the low-level API. This app focuses on the functions recently added to the API for inspecting data structures and their interplay with API functions for tracing control and data flow. The intrusiveness of technical details required for the low-level API in this case leads directly to a proposal for a high-level API and a preview of a profiler implementation that employs this API.

Finally we'll sketch an implementation that bridges both the high- and low-level APIs. The complete source code of the high-level profiler, along with a sample application to be profiled—a simple bank simulation—and some example profiles, are available in the code download for this issue.

The Low-Level CLR Profiling API

The CLR provides an easy-to-use, object-oriented platform for high-level programs. The list of CLR services includes management of assemblies, objects, method activation records, threads, exceptions, and metadata. These services concern the basic units of all .NET Framework-based programs regardless of the programming language in which they are written.

With the low-level CLR Profiling API, the exact behavior of a program is expressed as state changes in these CLR services. To receive notification about these state changes and handle them in some useful way, a profiler must be implemented as an in-process COM server. This is because the low-level CLR Profiling API spans three COM interfaces: ICorProfilerCallback2, ICorProfilerInfo2, and IMetaDataImport2 (IMetaDataImport2 is actually part of the Metadata API, and is used by unmanaged tools that target the CLR, including disassemblers, compilers, obfuscators, debuggers, and profilers). Figure 1 provides an explanation of these interfaces.

Figure 1 Profiling API Interfaces (Close Figure 1)

Interface | Description
ICorProfilerCallback2 | A callback interface that a profiler implements and the CLR uses to inform a profiler about state changes of the profiled application.
ICorProfilerInfo2 | An interface that the CLR implements and a profiler uses for navigating the current state of the profilee in terms of CLR structures.
IMetaDataImport2 | An interface that the CLR implements and a profiler uses for interpreting CLR structures.

A closer look at the low-level CLR Profiling API underscores the conceptual distance to high-level concepts in the .NET Framework. For example, these three COM interfaces comprise 171 functions with 718 parameters. High-order concepts like assemblies, classes, and methods are addressed through tokens (UINT pointers). Structured method parameters of a profiled app require a complex interpretation procedure. Error checking of items passed to the COM interfaces is minimal, which can lead to spectacular crashes if a profiler provides wrong tokens. In addition, the CLR provides method input parameters and the addresses of output parameters only on method entry. To evaluate output parameters, a profiler needs to manage a separate shadow runtime stack.

Object addresses in the CLR Profiling API context do not reference actual objects because the heap management of the CLR moves objects during garbage collection and reuses addresses. A profiler must track movements in physical object memory. Future releases of the CLR could also use fibers. Running in fiber mode amounts to the CLR disassociating runtime threads from specific operating system threads. This impedes thread-local storage for implementing freethreaded profilers. These points go back to variable address mappings, which distort the recovery of execution models that high-level .NET-targeted languages apply.

Why are functions and UINT pointers used instead of cohesive COM interfaces for accessing constructs like assemblies, classes, and methods? One reason could be efficiency. However, a method call across an interface of an in-process COM server does not cost more than a method call through a vtbl in C++, and every call into the CLR goes across a COM interface anyway. So efficiency isn't likely to be the reason why the low-level API works with UINT pointers instead of COM interfaces. Another reason might be COM's approach to cooperative lifetime control of objects by means of the AddRef and Release members of IUnknown, and this can cause subtle errors in highly dynamic scenarios. A profiler violating the COM rules could easily confuse the CLR.

A Sample Profiler Target

To illustrate how profilers mix COM statements with functional C-style application logic and thus produce a heavy piece of code, we'll introduce an example bank application. Written in C#, this program simulates a simplistic bank with branches, accounts, and customers. Figure 2 shows the architecture of the bank app and indicates where the CLR and the profiler become involved. Standard assemblies like mscorlib.dll are omitted for clarity.

Figure 2 Bank App Architecture
Figure 2 Bank App Architecture

There are a few details of the client-side code worth looking at more closely. For example, the following code sets up a bank branch and a target account:

IBank bank = CBank.Get();
IBankBranch bankBranch;
bank.Provide(out bankBranch);
IAccount target = bankBranch.SetupAccount(); // initial balance 0.00 USD

The code in Figure 3 sets up several account holders (customers) where each customer can have a second account. In this code, all customers transfer various amounts of money to the target account at once (this is a multithreaded application):

foreach (CCustomer client in customers)
    client.StartTransaction();
Figure 4 shows a customer that can take money from several source accounts and transfer it to a target account in another thread.

Figure 4 CCustomer Class (Close Figure 4)
using System.Threading;

internal class CCustomer
{
  private IBankBranch _bankBranch;
  private IAccount _account1;
  private IAccount _account2;
  private IAccount _target;
  private IAmount _amountToTransfer;
  private Thread _thread;

  internal CCustomer(string name, IBankBranch bankBranch)
  {
    _bankBanch = bankBranch;
    _thread = new Thread(new ThreadStart(Transfer));
    _thread.Name = name;
  }

  internal string Name { get { return _thread.Name; } }

  internal IAccount BankAccount1
  {
    set { _account1 = value; account.Owner = this.Name; }
    get { return _account1; }
  }

  internal IAccount TargetAccount
  {
    set { _target = value; }
    get { return _target; }
  }

  internal IAmount AmountToTransfer
  {
    set { _amountToTransfer = value; }
    get { return _amountToTransfer; }
  }

  internal void StartTransaction() { _thread.Start(); }

  private void Transfer()
  {
    _bankBranch.Transfer(_amountToTransfer,
      _account1, _account2, _target);
  }

  ...
}
Figure 3 Setting Up Accounts (Close Figure 3)
ArrayList customers = new ArrayList();

// Set up accounts for Smith
CCustomer client = new CCustomer("Smith", bankBranch);
client.BankAccount1 = bankBranch.SetupAccount(
    bank.GetAmount(100.00, "USD"));
client.BankAccount2 = bankBranch.SetupAccount(
    bank.GetAmount(200.00, "USD"));
client.TargetAccount = target;
client.AmountToTransfer = bank.GetAmount(280.00, "USD");
customers.Add(client);

// Set up accounts for Jones
client = new CCustomer("Jones", bankBranch);
client.BankAccount1 = bankBranch.SetupAccount(
    bank.GetAmount(800.00, "USD"));
client.BankAccount2 = bankBranch.SetupAccount(
    bank.GetAmount(400.00, "USD"));
client.TargetAccount = target;
client.AmountToTransfer = bank.GetAmount(1100.00, "USD");
customers.Add(client);
...

The profiler needs to record the application's control flow as well as the data crossing method boundaries. One interesting new feature of the low-level CLR Profiling API is interpretation of method parameters. To extract the names, types, and values of both unstructured and structured parameters, the profiler must connect several low-level API functions during a callback from the CLR, which signals normal or exceptional control flow crossing a method boundary on either method activation (entering) or method deactivation (leaving).

The amount of code in the source file LowLevel.cpp in the download accompanying this article gives an impression of what must be done at the low-level API to dump the names, types, and values of a list of unstructured and structured method parameters. For example, we represent a sum of money as a structured parameter. In the sample app, the object comprises a currency denomination and a floating-point currency amount.

Technical details, like the guarding statements necessary for handling HRESULTs of COM functions and dummy values for parameters not needed when calling a function with many parameters, illustrate some frustrating elements of using the low-level Profiling API. Furthermore, at this implementation level, several services are simply not available, like the mapping of physical object addresses managed by the CLR's garbage collector back to object identifiers. For that to work, a profiler must track memory addresses for each object during its lifetime.

A High-Level Profiling API

Different types of profiling tools, like monitors, visualizers, protocol checkers, and generators of test drivers all apply the low-level CLR Profiling API in a similar way to reconstruct the behavior of a .NET Framework-based program in terms of components and objects. All these tools combine run-time data that reflect the behavior of a program written within .NET (using the ICorProfilerInfo2 interface) with data that enables interpretation of this data (through IMetaDataImport2). In general, profiling starts with tracing threads and functions or methods. Therefore threads and methods together constitute the root of the structured profiling interface, as shown in Figure 5. Figure 6 shows this interface in the overall profiling architecture.

Figure 5 High-Level Profiling Interface
Figure 5 High-Level Profiling Interface
Figure 6 Low-Level and High-Level Profiling Interfaces
Figure 6 Low-Level and High-Level Profiling Interfaces

The high-level interface is designed according to how profilers will likely use it. The interface comprises several subinterfaces like IEvents and IMethod. IEvents is the high-level equivalent of ICorProfilerCallBack2 for signaling state changes of the profiled app, such as the CLR creating a thread, normal control flow entering a method, and exceptional control flow entering a method. IMethod is a high-level condensing of selected parts of ICorProfilerInfo2 and IMetaDataImport2 for obtaining the executing thread, the execution context (an object), all parameters, and the result, if any. Each of these interfaces is implemented as a pure abstract class, which in fact is a struct just to save access labels (public), as demonstrated by means of IEvents. A client of the high-level API must implement IEvents to be notified about state changes in the profiled app:

interface IEvents // #define interface struct
{
 virtual void ThreadCreated(const IThread *thread) = 0; //"tc" for short
 virtual void ThreadDestroyed(const IThread *thread) = 0;    //td
 virtual void NormalFlowEntered(const IMethod *method) = 0;  //nfe
 virtual void NormalFlowLeft(const IMethod *method) = 0;     //nfl
 virtual void ExceptionThrown(const IObject *exception) = 0; //et
 virtual void ExceptionalFlowEntered(
   const IMethod *method, const IObject *exception) = 0;     //efe
 virtual void ExceptionalFlowLeft(
   const IMethod *method, const IObject *exception) = 0;     //efl
 ...
};
As this code suggests, programming with the high-level profiling API should be more appropriate to the needs of a profiling application and less error-prone than with the low-level CLR Profiling API. One reason for this convenience is the semantically rich structure of the high-level API. It has several high-level interfaces replacing low-level handles and tokens in a type-safe and expressive manner. Consider the excerpt of a profiler application shown in Figure 7. With significantly fewer statements, it does much more than the profiler implemented with the low-level API in LowLevel.cpp. Like the low-level variant, the high-level API dumps strings, doubles, and objects—as well as structs and arrays of one of the former types. Moreover, while the low-level profiler is capable only of dumping addresses of an object (which can change during the execution of a method of the profiled app due to garbage collection), the high-level profiler identifies objects with numbers unique to an application session. Object identifiers more directly reflect how objects are identified at the level of a .NET-targeted programming language. To ease comparison, the function call graph of the high-level profiler corresponds to that of the low-level profiler, albeit with one important difference: calls of the low-level API are completely hidden. Thus a profiler application can focus on its business.

Figure 7 High-Level Profiler API (Close Figure 7)
void MsdnTracer::NormalFlowEntered(const IMethod *method)
{
  TraceParameterList(method);
}

void MsdnTracer::TraceParameterList(const IMethod *method)
{
  for(int i=0; i < method->GetArgumentCount(); i++)
    TraceParameter(method->GetArgument(i).get());
  if(method->GetArgumentCount() > 0) _tprintf(_T("\n"));
}

void MsdnTracer::TraceParameter(const IArgument *arg)
{
  IMemoryPtr value = arg->GetValue();
    // typedef std::auto_ptr<IMemory> IMemoryPtr;
    CString direction;

  if(arg->IsIn()) direction = _T("in");
  else if(arg->IsOut()) direction = _T("out");
  else direction = _T("ref");
  _tprintf(
    _T("High-level API: [ %s]  %s %s  %s="),
    direction, arg->GetTypeName(),
    arg->IsArray() ? _T("[]") : _T(""), arg->GetName());
  TraceValue(arg->GetValue().get());
}

void MsdnTracer::TraceValue(const IMemory *value)
{
  const IPrimitive *pri;
  const IString *str;
  const IStructured *obj;
  if((pri = dynamic_cast<const IPrimitive*>(value)) != NULL
    && pri->GetDatatype() == typeDouble)
    TraceDouble(pri);
  else if((str = dynamic_cast<const IString*>(value)) != NULL)
    TraceString(str);
  else if((obj = dynamic_cast<const IStructured*>(value)) != NULL)
    TraceStructured(obj);
  else
    _tprintf(_T("$Unsupported"));
}

void MsdnTracer::TraceDouble(const IPrimitive *value)
{
  _tprintf(_T(" %f"), value->GetDouble());
}

void MsdnTracer::TraceString(const IString *value)
{
  _tprintf(_T("# %d \" %s\""), value->GetLogicalId(),
    value->GetText());
}

void MsdnTracer::TraceStructured(const IStructured *value)
{
  _tprintf(_T("# %d {"), value->GetLogicalId());
  for(int i=0; i < value->GetSize(); i++)
  {
    IMemoryPtr field = value->GetAt(i);
    if(i > 0) _tprintf(_T(", "));
    _tprintf(_T(" %s %s  %s="), field->GetTypeName(),
      field->IsArray() ? _T("[]") : _T(""), field->GetName());
    TraceValue(field.get());
  }
  _tprintf(_T("}"));
}

The high-level profiler, HighLevel.cpp (shown in Figure 7), works essentially as follows. Class MsdnTracer is the entry point into the profiling application. Therefore MsdnTracer implements the NormalFlowEntered method of the callback interface IEvents in order to react to the control flow entering a method:

NormalFlowEntered(IMethod *method)
In case of an elementary parameter (for example, IPrimitive or IString), MsdnTracer dumps it and moves on to the next parameter. If a parameter consists of several components (for example, IStructured), then MsdnTracer loops over the components and dumps the type, name, and value of each:
for(int i=0; i < value->GetSize(); i++)
{
  IMemoryPtr field = value->GetAt(i);
  if(i > 0) _tprintf(_T(", "));
  _tprintf(_T(" %s %s  %s="), field->GetTypeName(),
    field->IsArray() ? _T("[]") : _T(""), field->GetName());
  TraceValue(field.get());
}
The loop is generically implemented because it covers all kinds of structures, namely arrays (IArray), structs (IRecord), and objects (IObject), in a uniform and type-safe way through the common supertype IStructured. This includes objects whose components (such as member variables) are defined by several classes along an inheritance path. Components belonging to one of the structured subtypes of IStructured are dumped during the recursive call of method TraceValue. Moreover, MsdnTracer dumps logical identifiers of strings (as they are objects) and of user-defined structures by calling the appropriate service of the profiler, a service that is not available at the low-level API:
_tprintf(_T("# %d \" %s\""), value->GetLogicalId(),
  value->GetText());
...
_tprintf(_T("# %d {"), value->GetLogicalId());

Note that the high-level profiler application is shorter, more functional, more readable, more robust, and easier to maintain than the profiler application directly implemented with the low-level CLR Profiling API. A variant of a high-level profiling application that formats the output in a more sophisticated manner is available in App.Tracer\IndentedTextFormatter.cpp in the download. The variant produces logs as contained in directory Bank.log in the download.

Figure 8 Stack Flow Control
Figure 8 Stack Flow Control

On examining the high-level profiling API in Figure 5, you may have some questions about the design of the run-time model, including the modeling of interfaces and namespaces, exception handling, and so on. Designers of expressive logs or test drivers for real-world applications cannot avoid such questions, so we provide answers regarding design decisions for our profiling API. (Furthermore, the reader might not concur with all of our design decisions.)

We modeled namespaces—which, strictly speaking, include classes and interfaces—as properties of IMethod. This facilitates filtering of relevant method activations on the basis of namespaces. This decision is appropriate due to the central role that methods play in the profiling of program behavior.

We modeled an exception entering a method simply as a reverse control flow that optionally carries an object that provides information about the exception. Figure 8 illustrates this by means of events describing an exceptional control flow across several stack layers (ET1 to EFE2).

Bridging the APIs

The expressiveness of the high-level API does not come for free, of course. This is simply because, at the high-level API, our profiler offers services necessary for reconstructing different views on programs at the object-oriented abstraction level, including:

  • Filtering events for objects of relevant assemblies, classes, and interfaces
  • Cutting call stacks beyond a certain depth
  • Tracking different memory positions of a .NET-based object during its lifetime
  • Remapping operating system threads onto threads in the .NET Framework
  • Managing method parameters on a stack to access them equally on method entry and on method exit
  • Providing the machinery for easy interpretation of elementary and structured method parameters

This is a lot of work, so you might wonder how all the pieces fit together. To configure our profiler we have to set some environment variables as shown in Config.cmd (see Figure 9). Recall that the profiler discussed here records usage scenarios of a concrete bank implementation. In this configuration, the profiler produces the logs in Directory Bank.log in the download. These logs support testing and debugging of profiled apps.

Figure 9 Section Profiler Configuration (Close Figure 9)
@REM *** Section 1: configure the CLR profiler
@set COR_ENABLE_PROFILING=1
@regsvr32 /s Spyder.dll
@set COR_PROFILER={B98BC3F3-D630-4001-B214-8CEF909E7BB2}
@REM *** Section 2: configure the profiler core
@set SPYDER_TARGET_PROCESS=BankApplication.exe
@set SPYDER_STACK_DEPTH=1    & REM trace only direct method calls
@set SPYDER_FILTER=C:{Bank.Interface}.* ^| I:{Bank.Interface}.*
  & REM ‚or' in the sense of set union
@set SPYDER_TARGET_ASSEMBLY=BankInterface;Bank
@set SPYDER_APPLICATION=Tracer
@REM *** Section 3: configure the profiler application (here: Tracer.dll)
@set SPYDER_INSTANCE_DATA=1   & REM dump objects crossing boundaries
@set SPYDER_SHOW_NAMESPACE=0  & REM do not dump namespace names
@set SPYDER_FILE_PER_THREAD=0  & REM all data go to one file

First, we tell the CLR which profiler to load. In our case the profiler actually resides in Spyder.dll, which we refer to as the profiler core:

@set COR_ENABLE_PROFILING=1
@regsvr32 /s Spyder.dll
@set COR_PROFILER={B98BC3F3-D630-4001-B214-8CEF909E7BB2}
In the second section, we tell the application-independent part of our profiler to hook only onto our profiled app of interest, BankApplication.exe. Herein the profiler will record all top-level activations of methods declared in classes or interfaces of namespace Bank.Interface:
@set SPYDER_STACK_DEPTH=1    & REM trace only direct method calls
@set SPYDER_FILTER=C:{Bank.Interface}.* ^| I:{Bank.Interface}.*
  & REM ‚or' in the sense of set union

As you might guess from the code, namespace Bank.Interface is restricted to the assembly BankInterface.dll, which completely separates a bank application from a concrete bank implementation (see Figure 2). In order to accelerate the profiler, we restrict its attention to exactly these two assemblies (see line 9) because we know that they together constitute the parts relevant for our profiling goal:

@set SPYDER_TARGET_ASSEMBLY=BankInterface;Bank
Next we tell the profiler core to load the application-dependent part of our profiler from Tracer.dll, a possible profiler application, which is configured in the third section:
@set SPYDER_APPLICATION=Tracer
In this case the tracer is set up to dump the structure of objects flowing between a bank application and the bank implementation. Note that here dumping object structures is just for the sake of demonstration:
@set SPYDER_INSTANCE_DATA=1 & REM dump objects crossing boundaries

Usually it should suffice to dump object identifiers (set SPYDER_INSTANCE_DATA=0) because their inner structure is completely hidden from clients, as is the case in the bank example. Furthermore the profiler application shall dump names of classes and interfaces without leading namespaces to compact logs:

@set SPYDER_SHOW_NAMESPACE=0 & REM do not dump namespace names
Because we want to obtain a global view of the program behavior during one specific session, we tell the profiler to dump data about all threads into a single log file:
@set SPYDER_FILE_PER_THREAD=0 & REM all data go to one file
The last point suggests an efficiency bottleneck. You might propose shortening the response time of the tracer and replacing our simple variant with a more clever solution. Such a tracer could, for instance, be split into two parts and work in two phases. The first part is attached to the profiler core:
set SPYDER_APPLICATION=YourCleverProfiler.dll

In the first phase, it tags each control flow event of each thread with a sequential number or time stamp and then dumps the data into separate files on a per-thread basis. In the second phase, an autonomous, externally running part of the tracer merges these separate files to retrieve the global view. This discussion brings us directly to the inner workings of the profiler.

Profilers must be implemented efficiently to minimize the impact on profiled apps. For a typical profiler that determines the distribution of run-time cycles among functions or methods, this essentially means subtracting the overhead due to the measuring procedure from the total run time. For a profiler that captures program sessions as traces for testing or debugging purposes, this in particular means avoiding timeouts. Timeouts triggered by a profiler would change a profiled app's behavior in irregular ways. Therefore, the profiler must be optimized primarily for run- time performance. The memory needs of a profiler can increase the profiled app's working set as long as it remains below a certain threshold and does not stress the memory manager of the operating system, thus again avoiding timeouts.

The profiler core is primarily designed to fulfill two requirements: it must be as lean as possible and it must operate only on demand. Both requirements aim primarily to save running time. While generally treating both time and space as efficiently as possible, in a conflict situation we decided in favor of minimal impact on time.

The profiler core implements the first requirement by using the CLR as a data container instead of managing separate copies of data items. Only in some cases does the profiler core convert CLR data and cache the conversion results in order to accelerate future access. Obvious examples are names of methods and namespaces. Once converted on method entry, these names are maintained because a profiler application most likely will access them again on method exit. In the majority of cases the profiler core simply remembers positions within CLR data structures to avoid repeated iteration.

The profiler encapsulates this processing state information in objects that a profiler application can access only through parts of the high-level profiling interface, such as IPrimitive or IObject. The code in Figure 10 illustrates the resulting implementation by means of an excerpt of the Method class implementing the high-level interface IMethod.

Figure 10 Method Class (Close Figure 10)
IArgumentPtr Method::InternalGetArgument(int index)
{
  PCCOR_SIGNATURE sigBlob = NULL;
  int argumentCount = this->GetArgumentCount();

  // reuse position in the argument's part of the signature blob
  int argIndex = 0;

  if(m_argSig.GetSize() == 0)
  {
    this->Seek(_Arguments); // go to the argument's part
    sigBlob = m_sigBlob[_Arguments];
  }
  else
  {
    // argument has already been read; use nearest position
    argIndex = min(index, m_argSig.GetSize()-1);
    sigBlob = m_argSig[argIndex]; // postion of the nearest argument
  }

  for(; sigBlob != NULL && argIndex < argumentCount; argIndex++)
  {
    // remember where the part describing an argument
    // starts in the sigBlob
    if((argIndex+1) > m_argSig.GetSize())
      m_argSig.Add(sigBlob);

    bool isByRef, isArray;

    CorElementType type =
      TypeInterpreter::GetType(sigBlob, isByRef, isArray);
    mdTypeDef typeDef = TypeInterpreter::GetTypeToken(sigBlob, type);

    if(argIndex == index)
      return IArgumentPtr(DEBUG_NEW Argument(
        this, argIndex, type, isByRef, isArray, typeDef));
  }

  return IArgumentPtr(NULL);
}

The code illustrates what runs behind the high-level profiling API when a profiler application accesses an argument for a parameter of a profiled app's method by its position in the parameter list. This code block results in an object of type IArgument, which the profiler application can then use for querying name, type, and value. Note that the profiling interface of the CLR forces a profiler to iterate through the CLR data structure describing the argument list (sigBlob in Figure 10) until it arrives at the designated argument. The profiler core indexes this data structure while iterating. Thus recurrent parameter access requires constant time instead of linear time as during initial access.

The on-demand requirement reveals the profiler core as lazy. The profiler core merely wraps method arguments in access objects like those of class Method and converts parameter values when a profiler application requires access to them. The profiler core executes autonomously only those tasks absolutely necessary for tracking the behavior of a profiled app at the object-oriented .NET abstraction level. One of these tasks is managing shadow stacks with method activations of components that the profiler user selected for recording.

Another task concerns the inversion of mappings for entities like threads and objects. In the case of .NET Framework-based objects, CLR memory management can map them to different physical addresses, and one physical address might be assigned to different objects. In the case of threads, the CLR can back them with different threads of the OS. To undo these mappings, the profiler core uses two tables: one for maintaining relations between low-level OS threads and high-level .NET-based threads and one for maintaining relations between low-level memory addresses and high-level object identifiers. Using tables for mapping ensures constant run-time usage. Note that high-level profiler applications that record usage scenarios of selected components, as test-driver generators do, lead to much smaller shadow stacks because they only observe top-level calls of methods on the interface of selected assemblies. If a profiler application traces each method call of any assembly at any call level, then the real stacks and their respective shadow stacks manage the same number of method activations.

We must stress another consequence of the profiler core's laziness, even if it seems to be rather obvious. A developer of a profiler application could be tempted to save an activation record, actually a reference to an object of type IMethod, in order to check protocol-dependent constraints on parameter values across method activations. After deactivation of a method—actually after the profiler core has raised one of the events NormalFlowLeft or ExceptionalFlowLeft for an activated method—activation-specific data is no longer available. An extended profiler core could offer an additional service that, on demand by a profiler application, clones activation records and saves them in a compact form for future access.

The design of the high-level profiling API profits from the expressiveness of C++. We have already mentioned pure abstract structs as a means for describing interfaces with only public method declarations. From a conceptual point of view, another example is the fact that all parts of the runtime model are constant because a profiler must not change the usual course of actions, the logical behavior, of a profiled app. However, declaring a function at the high-level API as constant contradicts the requirement for the profiler's core to operate only on demand, at the latest possible time.

Remember that in C++ you can slack the tension between the demand for security on the one side and efficiency on the other side through lazy initialization simply by making a const object temporarily modifiable with a const-cast. Service objects running inside the profiler core wait and change state only when a profiler application forces them to do so. Now we can close the gap between the abstract, high-level interface exemplified by method IMethod.GetArgument and the implementation Method.InternalGetArgument in the profiler core:

interface IMethod // #define interface struct
{
  ...
  virtual IArgumentPtr GetArgument(int index) const = 0;
  ...
};
A const-cast in this case simply removes the const part of the object's type for an entire method:
class Method : public IMethod
{
  public:
    ...
    IArgumentPtr GetArgument(int index) const
    {
      return (const_cast<Method*>(this))->InternalGetArgument(index);
    }
    ...
};

Conclusion

This article can be seen as a discussion of the relationship between efficiency and abstraction. COM, with its focus on binary interfaces, and C++, with its comprehensive language features, both provide ideal means to reconcile abstraction and efficiency in a harmonic way. We maintain that this will hold for some time to come, even as managed programs written in one of the amazingly potent .NET-targeted languages gain in importance.

A final question remains to be answered: what does the overall performance of a profiler application amount to? This obviously depends on the profiler's tasks and the profiled app's structure.

All in all, the underlying problem has several dimensions. For instance, a profiler that only dumps method signatures obviously needs less time than a profiler that also recursively dumps method arguments. With minimal runtime overhead, the profiler core provides services not available at the low-level CLR Profiling API as well as a method for diminishing the multidimensional problem by means of additional data filters. Data filters enable the extraction of focused profiles—and generating test drivers for selected assemblies is exactly this. Developers and testing staff should always know the structure of their programs in terms of assemblies, components, and publicly available classes and interfaces of interest and their relations; these interfaces should be concise and minimal. If they are not, either the data filter is too complicated, which increases the cost of applying the filter, or the data filter is too narrow or too wide, which leads to less useful profiles.

Experts agree that sharp interfaces of highly cohesive components characterize well-designed and testable programs. Testable programs enable the use of the power of both profiling APIs to ensure the quality of your .NET-based program. Programming with the low-level CLR Profiling API is less structured and thus more flexible, but it is also harder to use. Programming with our high-level profiling API is less flexible, but it is often simpler to use, and it is tailored to the needs of profilers operating close to the abstraction level of high-level .NET-targeted languages.

Joachim H. Fröhlich (joachim.froehlich@acm.org) is a member of the Software Engineering Department of the Johannes Kepler University of Linz, Austria. He works on .NET component architectures with a focus on testability and configurability.

Reinhard Wolfinger (wolfinger@ase.jku.at) works as a researcher at the Christian Doppler Laboratory for Automated Software Engineering in Linz, Austria. He focuses on .NET component architectures and reuse.


Editor's Note

The returns are in. MSDN Magazine—or, we should say, the award-winning MSDN Magazine—has added a couple of trophies to the shelf. And in the spirit of sharing, we are also officially the award-giving MSDN Magazine. It may not be as fancy as winning the Campbell's Chunky Academic Achievement Award or being named to the 2003 Scrapbook Hall of Fame, but we've had the opportunity to recognize some of the brightest and most innovative new projects that put to full use the technologies we discuss in our magazine each month.

In conjunction with the big launch event last November, MSDN Magazine co-sponsored the Connected Systems Developer competition. The rules were simple: show us your most creative programming efforts using SQL Server 2005, Visual Studio 2005, and BizTalk 2004 or 2006. We were expecting a handful of interesting entries, maybe 50 or so. But entries started to flood in at an amazing rate. Hundreds and hundreds of people took the opportunity to submit incredible, innovative work. It was really a Herculean challenge to properly judge each entry.

Each of the entries was top-notch, but the grand prize (in the form of an oversized novelty check) went to John Arnold of Datalan. His tool focused on executive performance management, integrating the SQL Server business intelligence stack, OLAP, a Key Performance Indicators (KPI) framework, and a SharePoint front end.

Although John won the highest honor, there were more truly outstanding winners and runners-up as well. Here is the list:

John Arnold of Datalan won the big check for "Best tool to provide additional functionality to SQL Server 2005."

Stephen Abdo of TREKg won "Best extension of a SQL Server 2005 database using the SQLCLR."

Marc-Donald Gagne of Intranet System for Business Web Log Management took "Best application using the Business Intelligence features of SQL Server 2005."

Wolfgang Portugaller created Persistor.NET, the "Best Visual Studio 2005 PowerToy utilizing SQL Server Express."

Shaun Tonstad created the Suva3D MDX Game Engine and Block Realm MMORPG, which placed as the "Best use of Managed DirectX, Visual Studio 2005, and SQL Server Express."

Michael Voigt was behind the Event Management for Hotel Guests [EMHG] project, which won in the category "Best integrated/SOA application utilizing SQL Server 2005, Visual Studio 2005, and BizTalk."

Jesus Rodriguez created some SQL Server Service Broker enhancements, which turned out to be the "Best BizTalk adapter not already supplied by Microsoft or other adapter vendors."

Rod da Silva , mastermind of the Transacted Parallel Splitter BizTalk Pattern, took "Best implementation of a generic pattern on the BizTalk Server platform."

Stephen Thomas , an MCP from Dallas, demonstrated key functionality that was not included in the BizTalk Server SDK with his Untyped Messages and Dynamic Maps BizTalk sample.

As we mentioned, however, we don't just give awards—we get them, too. MSDN Magazine recently took home two awards from the Society for Technical Communication, and our sister publication TechNet Magazine also snared some recognition. MSDN Magazine was presented with a Merit award for technical publications. In addition, we were presented with an Excellence award for a recent article by Vance Morrison, "Concurrency: What Every Dev Must Know About Multithreaded Apps." TechNet Magazine also received a Merit award for a highly popular article in our premiere issue, "Anatomy of a Hack: How a Criminal Might Infiltrate Your Network" by Jesper Johansson.

These accomplishments are great, but now we feel the pressure. This year, we have to outdo our coverage in 2005. We've got a lot of exciting topics in store, and we're confident that we've got a shot at that Chunky Achievement Award this time around.

—J.T.

Thanks to the following Microsoft technical experts for their help with this issue: Nathan Anderson, Fiona Fung, Mark Fussell, Jonathan Keljo, Ronald Laeremans, Serge Lidin, Vance Morrison, Stefan Schackow, Dharma Shukla, Ryan Waite, and Ming Xu.

Active Directory, ActiveX, IntelliSense, Microsoft, MSDN, Microsoft Press, Outlook, Visual Basic, Visual C++, Visual Studio, Windows, Win32, Windows Server, and Windows Vista are either registered trademarks or trademarks of Microsoft Corporation. Other trademarks or tradenames mentioned herein are the property of their respective owners..

MSDN Magazine does not make any representation or warranty, express or implied with respect to any code or other information herein. MSDN Magazine disclaims any liability whatsoever for any use of such code or other information.


Toolbox: Analyze HTTP Traffic, Synchronize Databases, and More

Toolbox
Analyze HTTP Traffic, Synchronize Databases, and More
Scott Mitchell

Contents
Dissect Your Browser's HTTP Traffic
Compare and Synchronize SQL Server Databases
Generate Help Files for Visual Basic Projects
The Bookshelf

Dissect Your Browser's HTTP Traffic

Debugging Web applications can be a difficult process due to the logical, physical, and temporal differences between the mishmash of technologies that comprise such an application. For bugs that arise from the HTML and script received by the browser or in the transfer or request of a page's markup, developers often adopt archaic debugging techniques, such as using View Source and Notepad to scrutinize the contents received by the browser. With increasingly complex Web pages carrying dozens of inline and external style sheet and script definitions, the markup in most Web pages have become unwieldy.

In such debugging scenarios developers need to know what requests have been made by the browser, what HTTP headers and post data was sent, what response headers were received, the content received, and how this content may have differed from previous requests with different inputs.

IEWatch, by IEWatch Software LLC, aids developers by providing all this information and more through an easy-to-use interface directly from Microsoft® Internet Explorer. After installation, IEWatch launches as a plug-in directly in Internet Explorer. When enabled, IEWatch records all incoming and outgoing HTTP traffic, saving the request headers, post data, response headers, and received content for each request. This log can be saved to a file for later examination or to include in a bug report.

In my opinion, IEWatch's two most impressive features are its filters and its HTML analysis. By default, IEWatch records all HTTP traffic, including requests for image files, external style sheets and script files, Flash files, and so on. To help reduce the sheer volume of requests, IEWatch allows the results to be filtered by URL, request type, content type, request and response headers, status codes, and response content. The HTML analysis breaks down the received HTML into various categories: links, images, both inline and external scripts and style sheets, forms, frames, ActiveX® controls, and more.

What's more, the HTML analysis pane provides two modes of visual highlighting. When you drill down into those categories that have visual representations on screen (such as images, links, and forms), clicking on one of the items in the list highlights its location in the browser. Similarly, when enabling the HTML spotlight feature, clicking on a region in the HTML document highlights the corresponding HTML markup displayed in IEWatch.

IEWatch makes investigating HTTP requests and responses and exploring the resulting content a walk in the park. With its seamless integration into Internet Explorer, filtering capabilities, HTML analysis, and affordable price, you should consider adding IEWatch to your toolbox today.

Price: $89 for a single-user license.

www.iewatch.com

Compare and Synchronize SQL Server Databases

In a perfect world a customer's requirements would be well-defined and frozen before beginning any development work. However, for most projects the requirements are expanded or refined as the development cycle progresses. Similarly, for internal or custom applications, the development cycle really never ends—it's just a continual process of adding new features and finetuning implemented ones.

For data-driven applications, developers frequently use multiple databases: one for development, perhaps one for staging, and one for production. For customers with changing requirements, these databases's schemas often vary from one another. The production server's schema is designed from the feature set of the last stable release, whereas the development server's schema has been modified to incorporate the functionality currently being designed. These differences present challenges when pushing a build from development to staging (or from staging to production).

SQL Server™ does not ship with any tools for easily comparing and synchronizing the schemas of disparate databases; however, there are a number of third-party tools that make this task a breeze. One such tool is SQL Examiner, by TulaSoft, LLC. SQL Examiner allows you to choose two SQL Server databases—which can be on the same or different servers—and compares the table structures, views, stored procedures, user-defined functions, and so on.

You can then decide how to synchronize the schemas by specifying which database will act as the source and which will be the destination, as well as which database objects to synchronize. After making these selections, SQL Examine will generate the T-SQL to propagate the schema changes from the source to the destination.

One of SQL Examiner's strongest attributes is its ease of use and straightforward user interface. After starting the program, you are only a few mouse clicks away from synchronizing two databases. In addition to its user-friendly interface, with the click of a button SQL Examiner will create a Database Variance Report, which is an HTML document that lists the objects in the two databases and shows precisely how the objects in one database differ from the other. SQL Examiner also includes a snapshot feature, which will save your database's schema in an XML-formatted file. You can later use this saved snapshot to compare a database's current schema with its past schemas.

Keep in mind that SQL Examiner is designed to compare and synchronize database schemas. As of the time of this writing, SQL Examiner does not allow for comparing and synchronizing actual data. For most scenarios, though, developers are more interested in working with just the schemas and they aren't interested in performing deep copies of the database.

Price: $199.95 for a single-user license.

www.sqlaccessories.com

Generate Help Files for Visual Basic Projects

Documentation is a lot like exercise—it's a vital part of any large software application, but only a handful of people really enjoy doing it. Fortunately, the Microsoft .NET Framework simplifies documenting classes, methods, properties, and events with XML comments.

XML comments are special XML tags embedded within comments that provide metadata about classes and their members. During compilation, this metadata is converted into a standalone XML file by Visual Studio®, and can then be converted into help files in a variety of formats using Visual Studio .NET, NDoc (ndoc.sourceforge.net), or other programs. With Visual Studio .NET 2003, XML comments are only compiled into the standalone XML file for C# projects, although there are third-party solutions that handle this process for Visual Basic® projects; Visual Studio 2005 supports XML comments for Visual Basic, C#, and C++ projects.

Visual Studio inserts the common XML comment elements and provides IntelliSense® support for a handful of valid elements, but developers are still on the hook for properly escaping their XML comment markup, knowing the purpose and rendered display for each element, and recalling the names of those elements for which Visual Studio does not provide IntelliSense.

If you find yourself frustrated at providing XML comments, consider VBdocman .NET from Helixoft. VBdocman .NET parses the files in a Visual Basic Project or Solution, reading the XML comments and generating documentation in one of a variety of formats, including HTML Help, Microsoft Help 2, HTML, and RTF. Implemented as an add-in in Visual Studio, adding new XML comments or compiling the Project or Solution's comments into a help file is just a click away. For those still building Visual Basic Web- or desktop-based applications using Visual Studio .NET 2002 or Visual Studio .NET 2003, VBdocman .NET provides the XML comment support that Visual Studio .NET lacks.

VBdocman .NET has value even for those using Visual Studio 2005. While XML comment support for Visual Basic is now baked into Visual Studio, VBdocman .NET greatly simplifies the process of generating XML comments thanks to its WYSIWYG XML comment editor. Memorizing XML comment element names is a thing of the past, as the rich comment editor provides an interface that shows what the comments will look like in the resulting help file. You simply type text into this editor, generating the documentation as desired.

VBdocman .NET's main draw, in my opinion, is its slick comment editor, which I've found to be much easier, faster, and satisfying than entering the XML comments markup by hand. This feature alone will likely be worth the price for those who religiously document their code with XML comments.

Price: $229 for a single-user license.

www.vbdocman.com

The Bookshelf

Most developers rely on IntelliSense and statement completion to relieve the burden of memorizing the vast array of classes, methods, properties, and events that make up the .NET Framework and any third-party components or controls used. Unfortunately, for SQL we must still use the old fashioned techniques—memorization, documentation, online resources, and books.

While there are plenty of books that provide a thorough discussion of database theory, or the intricacies of a particular database platform, often I'm in need of just a nugget of information, such as the syntax for iterating through a cursor, the syntax for RAISERROR, or how to retrieve database metadata through T-SQL syntax. These tidbits of information are just a glance away with SQL Server 2005 T-SQL Recipes: A Problem-Solution Approach (Apress, 2005) by Joseph Sack. Like a cookbook, SQL Server 2005 T-SQL Recipes contains a bounty of short and to the point sections, each one detailing a feature of the SQL Server 2005 T-SQL syntax.

The introduction notes that the book is "index-friendly, [and] does not need to be read front to back." The index is certainly a mine of information, but I also found the table of contents to be as good a starting place as any, where each of the recipes in the book's 22 chapters are listed.

The book includes recipes for all skill levels, from recipes that cover the basics of SELECT, INSERT, UPDATE, and DELETE statements, to recipes that detail managing deadlock priority in transactions. The breadth of the material ranges from the everyday SQL, to database tuning and profiling, through higher-level concepts, like working with XML data, Web services, error handling, encryption, and more.

Price: $59.99.

www.apress.com

Send your questions and comments for Scott to toolsmm@microsoft.com.

All prices were confirmed at press time and are subject to change. The opinions expressed in this column are solely those of the author and do not necessarily reflect the opinions of Microsoft.

Scott Mitchell, author of numerous books and founder of 4GuysFromRolla.com, is an MVP who has been working with Microsoft Web technologies since 1998. Scott is an independent consultant, trainer, and writer. Reach him at Mitchell@4guysfromrolla.com or via his blog at ScottOnWriting.NET.


Advanced Basics: IntelliSense Code Snippets

Advanced Basics
IntelliSense Code Snippets
Lorenzo Minore

Code download available at: AdvancedBasics0604.exe (117KB)
Contents
Inserting Code Snippets into Code
Code Snippet Customization
Managing Code Snippets
The Visual Basic Snippet Editor
Sharing Code Snippets
The Power of Snippets

IntelliSense code snippets are one of the coolest new features in Visual Studio® 2005. Code snippets are highly customizable code fragments intended to accomplish simple to intermediate tasks quickly; they can be inserted into your code with just a few keystrokes.

In this column I'll describe how to use the code snippets installed with Visual Studio 2005, how to edit the existing set or create your own, how to share your snippets with other users, and where to look for more samples.

Inserting Code Snippets into Code

Code snippets can be inserted in a variety of ways. If you're in the editor browsing for a code snippet, use the Code Snippet Inserter. You can invoke it by right-clicking in the editor and selecting the Insert Snippet... command from the context menu. In Visual Basic® you can also type "?", then hit the Tab key to obtain the same result. The Code Snippet Inserter has some features in common with the IntelliSense® UI; it lets you navigate the snippet directory structure to select the snippet you want to use (see Figure 1). Tooltips will appear beside the currently selected snippet to give you a description and the shortcut for it.

Figure 1 Code Snippet Inserter
Figure 1 Code Snippet Inserter

For snippets that you use more frequently or for which you want quicker access, you can just type the shortcut in the editor, followed by the Tab key to insert them. To learn a snippet's shortcut, you can either select it in the Code Snippet Inserter and look at the tooltip, or view it in the Code Snippets Manager, which can be accessed via the Tools menu.

The shortcut+Tab insertion method also produces a neat effect for snippets that use keywords as their shortcuts. Type Select followed by Tab in your Visual Basic code and the Select-Case code snippet will be inserted (see Figure 2).

Figure 2 Select-Case Snippet
Figure 2 Select-Case Snippet

If you only remember a part of the snippet's shortcut, in Visual Basic, you can type the beginning of the shortcut, type "?" followed by the Tab key, and a shortcut completion list will appear (see Figure 3). For example, to insert a Visual Basic snippet from the Application directory, you could type "app" (the shortcut prefix for that directory), or just "a" followed by "?", then hit the Tab key in order to invoke the list. When the shortcut list appears, the title of the snippet corresponding to the currently selected shortcut will be displayed in the tooltip. You can navigate the list, then double-click or press Enter to insert the selected snippet into your code.

Figure 3 Snippet Shortcut List
Figure 3 Snippet Shortcut List

Visual Basic snippets can also be inserted in code through a simple drag and drop operation. If you know the location of a particular snippet file on disk, you can simply drag and drop it from Windows® Explorer directly into your Visual Basic code!

You can undo a snippet insertion; if you inserted the wrong snippet or invoked a shortcut by accident, just execute the undo command (Ctrl+Z) and your code will return to its previous state.

Code Snippet Customization

Code snippets are different from simple fragments that you can copy and paste into your editor.

When you insert a Visual Basic code snippet it will automatically add the referenced assemblies to the project and the required imports to the code file, so that your code will build properly (this feature, however, is not supported in C# code snippets).

What really makes code snippets so convenient to use is the replacement fields, green highlighted areas that identify the values you may commonly want to replace to fit the snippet to your specific task. You can use Tab and Shift+Tab to move back and forth through replacement fields. Each one has a tooltip associated with it that describes what it represents and how it should be replaced.

In Visual Basic, replacement fields also have IntelliSense support to help you make the right substitution; if you hit Ctrl+Space while in a replacement, a list of the local variables of the same type as the replacement will pop up, and you can pick one as your substitution. In C#, this key combination will bring up the same IntelliSense completion list you would see when typing ordinary code (see Figure 4).

Figure 4 Replacement Fields with IntelliSense
Figure 4 Replacement Fields with IntelliSense

While you type in a replacement field, it will extend itself; if you want to keep typing without further extending that field, you can hit the Esc key, which lets you transition outside of the replacement.

Replacements can be linked; there may be multiple occurrences of the same one, and when you replace one, all the linked ones will be updated automatically as well.

In Visual Basic the replacement fields will stay highlighted as long as you don't close the code file and all associated files (that is, a Form's design view needs to be closed as well). In C#, once you press Enter, the replacement fields will be gone and your changes will be committed. Of course you can always make more changes later on; as the snippet becomes part of your code, though, you won't have the visual help of the highlighting or the other replacement field features.

Managing Code Snippets

Code snippets installed with Visual Studio are organized in a directory structure by the subject or task they accomplish. To see all directories and the snippets they contain, you can open the Code Snippets Manager. Simply select Tools | Code Snippets Manager (see Figure 5). This dialog lets you browse the snippet folders and displays useful information. When you select a snippet, you'll be able to see its physical location on disk, a short description of what it does, the shortcut, and the snippet author (which is Microsoft Corporation for all preinstalled snippets).

Figure 5 Code Snippets Manager
Figure 5 Code Snippets Manager

The Add button in the Code Snippets Manager lets you add a whole folder to the root of the directory structure shown. This is a good way to add new snippets that you have stored in a directory on disk to your collection. The folder you're adding may contain some files that are not code snippets, but when you look at it in the snippet manager you'll see only subfolders and code snippets. Your current selection in the tree view doesn't affect where the new folder is added; it will always be a direct child of the snippet folders root.

The Import button lets you add one or more code snippets to the directory structure shown by the snippet manager. Once you select the snippets you want to import, you'll be able to choose the folders where you want to place these snippets.

The snippet directory structure contains a folder called My Code Snippets, where you should install your own snippets or the ones obtained from other users. If you want to make changes to some of the code snippets installed with Visual Studio 2005, it's a good practice to create a copy of the snippet in the My Code Snippets folder and modify it; otherwise it may be hard to revert.

To change the code snippets installed with Visual Studio without first copying them, you also need write privileges to the Program Files folder, which are normally granted only to administrators.

The Remove button will simply remove the currently selected folder from the directory structure shown by the Code Snippets Manager. The folder you remove won't be deleted from the file system, it will just be removed from the Snippets Manager's view. You can always use the Add command to get it back. The Snippets Manager view represents the snippets you'll be able to insert in your code editor through the snippet inserter or the other techniques described here, so if you remove a folder from this view, you won't have access to the snippets it contains in the IDE.

The Search Online command will bring you to the MSDN® search dialog, where you can enter keywords and search for code snippets. Unfortunately, this feature doesn't always work quite as expected, and you may end up with results matching your keywords but not related to code snippets. If you're searching online, I suggest using a search engine and entering your keywords together with "code snippet" or "IntelliSense Code Snippet" as this should give you a more fitting set of results.

The Visual Basic Snippet Editor

You're not limited to the code snippets that come with Visual Studio 2005; you can write your own or obtain them from other people.

Code snippets are actually XML files that follow a specific format. To see what they look like, open a code snippet in an XML editor. If you want, one way to edit code snippets is to work on the XML directly. If you want to learn more about the XML format of code snippets, take a look at the MSDN Code Snippet Schema Reference.

An easier way to write or modify code snippets is using the Visual Basic Code Snippet Editor tool.

The Visual Basic Snippet Editor is a shared source project developed by the Visual Basic user community in conjunction with the Visual Basic team. One of the Visual Basic MVPs, Bill McCarthy, did most of the development and bug fixing for the editor and gave it the slick look it has today (see Figure 6).

Figure 6 Snippet Editor
Figure 6 Snippet Editor

When you open the editor, you'll see the left-hand pane containing a tree view similar to the one in the Code Snippets Manager. A textbox above it also lets you filter for particular keywords. When you select a node in the tree view and right-click on it, you'll be presented with a context menu and different options to manage your snippets and directories.

Both the Snippet Editor and the Code Snippets Manager share the same information regarding the code snippets folders, so if you make a change in one of the two tools, it will be reflected in the other one.

The Snippet Editor gives you the option to remove a single snippet when you select it in the left pane. In this case, the snippet will be deleted from the file system, so be very careful with this command. You can either add a new empty snippet to one of the folders or open an existing one.

If you plan to modify one of the snippets installed with Visual Studio, I suggest you create a copy in the My Code Snippets folder and work on that one. Depending on the version of the snippet editor you have, the My Code Snippets folder may not be displayed by default in the tree view, but you can easily add this directory yourself. The snippet code will be shown in the editor view, and there is also a preview pane that shows you how the snippet is going to look when you insert it in code.

Below the editor window there are five different panes that let you further customize your snippet. In the Properties pane you can modify the snippet's Title, Shortcut, Description, Author, Language, Scope, and Help URL. Most of these are self-explanatory. The Scope property refers to the code construct represented by the snippet—whether it's a class declaration, a member declaration, or part of a class member body. This can be a bit misleading because you may think that the scope is where the snippet lives instead of what the snippet represents. The Help Url property isn't enabled; it will be saved in the snippet file, but currently it won't be shown anywhere in the Visual Studio IDE. Other users will be able to see it if they open your snippet with the editor.

In the Replacements pane you can browse through replacement fields and modify their properties, or you can create new ones. Some replacement properties are not supported in all languages; the Function and Editable fields are supported in C#, but not in Visual Basic.

To create a new replacement, you can simply select the code in the editor and click on the + button in this pane. The Snippet Editor also allows you to create linked replacements; it will do so automatically if it finds expressions matching the one you selected in the snippet code. The Replacement Kind and Type properties are used in Visual Basic to provide IntelliSense for that field, so make sure to set them correctly if you want to enable this feature.

The References and Imports panes let you specify the referenced assemblies and Imports statements associated with the snippet you're authoring. These will be automatically added to your project and code file when you insert the snippet. This feature is currently supported only in Visual Basic. The Test pane lets you compile the code snippet inserted in the right scope and check for errors.

The Options dialog in the Snippet Editor can be used to customize the editor font and appearance and to select which languages you want to use. The language choice affects the snippets shown in the tree view and the keyword colorization in the code editor. Another interesting feature is the Save as .vsi command. This option lets you save a code snippet as a VSI (Visual Studio Installer) file. This is the recommended format for sharing community content, and makes installing new code snippets very easy.

For further information on how to use the editor, you can download the documentation from the Snippet Editor workspace. Here you can also file bugs or request new features for the editor and download the source code. You should also check out Snippy, another tool for creating code snippets, created by Gus Perez.

Sharing Code Snippets

Two Web sites that host code snippets are DotNetJunkies and GotCodeSnippets. Both sites currently have a limited number of code snippets available, but I expect these to increase significantly over the next few months.

The code snippets you download from there are in .vsi format. Once you have downloaded the .vsi package, double-click it and a wizard will guide you through the installation process, asking you, among other things, in which folder you want to place the snippets (see Figure 7). Again, I suggest separating snippets you download from the Web or obtain from other developers from the ones created by Microsoft; you can do so by placing them in the My Code Snippets folder.

Figure 7 Screen from the Visual Studio Content Installer
Figure 7 Screen from the Visual Studio Content Installer

If you're planning to publish or share your own code snippets, I'd recommend using the .vsi format as well; this will make installation for your target audience easiest. To create a VSI with multiple snippets, the easiest way is probably to use Craig Skibo's Content Installer Powertoys, available on GotDotNet. The Content Installer Tool will walk you through the process of creating a Visual Studio Installer Package with multiple code snippets. This tool actually supports creating packages for other content types as well.

The Power of Snippets

Give code snippets a try. The ones included in Visual Studio provide a wide variety of productivity-enhancing solutions. However, the real power of this feature is its extensibility. The snippet editor, the code snippet hosting sites, and the VSI format and tools provide a framework for the user community to create a diversified code snippet library accessible to everyone.

Send your questions and comments to basics@microsoft.com.

Lorenzo Minore is a Software Design Engineer for Microsoft on the Visual Basic team, working on the data design time experience in Visual Studio. He also coordinates a team to promote IntelliSense code snippets in the Visual Basic user community. Reach Lorenzo at lorenzom@microsoft.com.


CLR Inside Out: Extending System.Diagnostics

New information has been added to this article since publication.
Refer to the Editor's Update below.

CLR Inside Out
Extending System.Diagnostics
Krzysztof Cwalina

Code download available at: CLRInsideOut0604.exe (128KB)
Contents
Hello TraceSource
Creating a Custom TraceListener
Creating a Custom Switch
Extending Switch
Extending SourceSwitch
Generic SourceSwitch
Creating a Custom TraceSource
Containment
Configuration System Extensibility
Wrapping It Up

The System.Diagnostics namespace in the Microsoft® .NET Framework contains powerful tracing capabilities. This includes the main tracing API: TraceSource. As you will see, the tracing APIs in System.Diagnostics support powerful extensibility. In this column I'll describe advanced extensibility scenarios and various ways to customize the tracing APIs. I'll also discuss API design principles, in particular designing for extensibility with inheritance, containment, and generic types.

Hello TraceSource

Before I dive into customization, let's write a short "Hello World" program instrumented with the TraceSource API.

class Program {
    static TraceSource source1 = new TraceSource("MyProgram.Source1");

    static void Main() {
        source1.TraceInformation("Main enters");
        Console.WriteLine("Hello World");
        source1.TraceInformation("Main exists");
    }
}

Each trace source contains an instance of a SourceSwitch, which decides whether a trace message should be output or not. Each source also contains a set of TraceListener instances that determine to where messages should be output. By default, source switches are not set to pass informational messages, nor do they contain listeners that would pass the messages to the console, so this program just prints "Hello World". To enable tracing, the configuration file in Figure 1 must be added. This configuration file instructs the source switch to output informational messages and adds a trace listener that directs messages to the console. Now the program not only outputs "Hello World", but also traces simple messages when the main method enters and exits. Here's the result:

MyProgram.Source1 Information: 0 : Main enters
Hello World
MyProgram.Source1 Information: 0 : Main exists

Figure 1 Tracing Configuration (Close Figure 1)
<configuration>
  <system.diagnostics>
    <sources>
      <source name="MyProgram.Source1" switchName="MyProgram.Switch1">
        <listeners>
          <add name="Console"
              type="System.Diagnostics.ConsoleTraceListener"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="MyProgram.Switch1" value="Information"/>
    </switches>
  </system.diagnostics>
</configuration>

Now that you're familiar with some of the basics, let's start customizing this code.

Creating a Custom TraceListener

I will begin by developing a custom trace listener. Instead of directing trace messages to the console, my trace listener will send some important messages to an e-mail recipient.

Custom trace listeners like this can be implemented by inheriting from the TraceListener class directly. But for historical reasons, this is not very easy. TraceListener contains many interdependent virtual members that can be overridden. And after working with these APIs for several years, I still forget which members need to be overridden in which scenarios. For this reason, I created a helper type called TraceListener2, which makes it much easier to create custom listeners in common scenarios.

I spent some time thinking about a more meaningful name, but settled on TraceListener2. This adheres to a guideline that will appear in an update of the .NET Framework General Reference Design Guidelines for Class Library Developers on MSDN®online. It states that you should "Use a numeric suffix to indicate a new version of an existing API, if the existing name of the API is the only name that makes sense."

In general, however, I find it's better to use a good name that describes the difference between the old type and the new type. Consider using a brand new but meaningful identifier, instead of simply adding a suffix or a prefix.

The code in Figure 2 shows the main members of TraceListener2. [ Editor's Update - 4/26/2006: Figure 2 has been updated.] The full source of TraceListener2 is in the TraceListener2.cs file in the code download.

Figure 2 TraceListener2 (Close Figure 2)
public abstract class TraceListener2 : TraceListener {
    protected TraceListener2(string name): base(name) {}

    protected abstract void TraceEventCore(
        TraceEventCache eventCache, string source,
        TraceEventType eventType, int id, string message);

    protected virtual string FormatData(object[] data) {
        StringBuilder strData = new StringBuilder();
        for (int i = 0; i < data.Length; i++) {
            if (i >= 1) strData.Append("|");
            strData.Append(data[i].ToString());
        }
        return strData.ToString();
    }

    protected void TraceDataCore(TraceEventCache eventCache,
            string source, TraceEventType eventType,
            int id, params object[] data) {
        if (Filter != null &&
            !Filter.ShouldTrace(eventCache, source, eventType,
                id, null, null, null, data)) return;
        TraceEventCore(eventCache, source, eventType,
            id, FormatData(data));
    }

    public sealed override void TraceEvent(TraceEventCache eventCache,
            string source, TraceEventType eventType,
            int id, string message) {
        if (Filter != null &&
            !Filter.ShouldTrace(eventCache, source, eventType, id,
            message,null,null,null)) return;
        TraceEventCore(eventCache, source, eventType, id, message);
    }

    public sealed override void Write(string message) {
        if (Filter != null &&
            !Filter.ShouldTrace(null, "Trace", TraceEventType.Information,
            0, message, null, null, null)) return;
        TraceEventCore(null, "Trace", TraceEventType.Information,
                       0, message);
    }

    public sealed override int GetHashCode() {
        return base.GetHashCode();
    }

    public sealed override string Name {
        get { return base.Name; }
        set { base.Name = value; }
    }

    ...
}

As you can see, this class has only one abstract member, making it very clear what must be overridden. Also, most of the virtual members of the TraceListener base class are overridden and sealed to indicate that they don't need to be overridden. (Note that they don't need to be overridden in common scenarios. There are still less common scenarios in which it might be useful, but as I mentioned before, TraceListener2 is designed as a helper base class for common scenarios.)

There are several design guidelines that I took into account when trying to improve TraceListener. First, define top usage scenarios for each major feature area. Make only the longest overload virtual if extensibility is required (shorter overloads should simply call through to a longer overload). And, finally, favor protected accessibility over public accessibility for virtual members (public members should provide extensibility, if required, by calling into a protected virtual member).

So I defined the top scenario as implementing a custom listener to write simple events to a simple event store. I limited the number of virtual members. And I made all the remaining extensibility points protected.

Creating a custom listener for simple scenarios is now trivial. The only thing required is to implement the TraceEventCore abstract method. Note that Visual Studio® 2005 provides a great new feature to make this even easier. Just create an empty class that inherits from an abstract class, then right-click on the name of the abstract class and select "Implement Abstract Class", as you see in Figure 3.

Figure 3 Implement Abstract Class
Figure 3 Implement Abstract Class

The editor will generate default implementations for all abstract members, like this:

public class EmailListener : TraceListener2 {
    protected override void TraceEventCore (TraceEventCache eventCache,
             string source, TraceEventType eventType,
            int id, params object[] data)
        throw new System.Exception ("The method or operation is not...")
    }
}

The only task remaining is to add constructors and fill in the body of the autogenerated (overridden) method. The example that is shown in Figure 4 represents a custom listener responsible for sending all critical events via e-mail.

Figure 4 Sending Events by E-Mail (Close Figure 4)
public class EmailListener1 : TraceListener2 {
    public EmailListener1(string name) : base(name) {}
    public EMailListener1() : this("EMailListener") { }

    protected override void TraceEventCore(TraceEventCache eventCache,
            string source, TraceEventType eventType,
            int id, string message) {
        if (eventType != TraceEventType.Critical) return;
        MailMessage emailMessage =
            new MailMessage("kcwalina@microsoft.com",
                            "kcwalina@microsoft.com");
        emailMessage.Body = message;
        emailMessage.Subject = "Critical Error";
        SmtpClient client = new SmtpClient();
        client.Send(emailMessage);
    }
}

To use the listener, you need to configure the SMTP settings in the configuration file. Something like the following will do the trick; just fill in the values for host, username, and password:

<configuration>
<system.net>
    <mailSettings>
        <smtp deliveryMethod="network">
            <network host="..." defaultCredentials="false"
                userName="..." password="..." />
        </smtp>
    </mailSettings>
</system.net>
</configuration>
You can now add the listener to your trace source, as follows:
<listeners>
  <add name="Email"
    type="Microsoft.Samples.Tracing.EMailListener,Extensions"/>
  <add name="Console" type="System.Diagnostics.ConsoleTraceListener"/>
</listeners>
Next, try to output some critical trace messages, like so:
static void Main() {
    source1.TraceInformation("Main enters");
    Console.WriteLine("Hello World");
    source1.TraceEvent(TraceEventType.Critical, 0, "Main exists");
}

Creating a Custom Switch

Creating custom listeners is a relatively common scenario and is well documented. The same is true for custom switches, but to a lesser degree. The Framework has a base switch class called System.Diagnostics.Switch and three subclasses, BooleanSwitch, TraceSwitch, and SourceSwitch.

The first two concrete switches are intended to be used as standalone types, and creating a custom standalone switch is relatively straightforward. SourceSwitch is intended to be used with TraceSource classes, and implementing a custom SourceSwitch is a bit trickier. I'll discuss this a little later.

Extending Switch

TraceSource uses SourceSwitch, which was added to the .NET Framework 2.0, to decide which messages should be traced. The Trace and TraceSwitch classes, which were present in the first version of the Framework, are still available. Since they are simpler, I will use them first to illustrate how to create custom switches.

Let's get started by creating a custom switch that is better suited than TraceSwitch to trace execution flow (method entry and exit). For example, if I used the Trace and TraceSwitch classes to trace execution flow I would have to write something like this:

static TraceSwitch trace = new TraceSwitch("flowSwitch");
...
void Main(){
    if(trace.TraceInformation){
        Trace.WriteLine("Entering method Foo");
    }
    ...
    if(trace.TraceInformation){
        Trace.WriteLine("Exiting method Foo");
    }
}
But this leaves some limitations. What if I want to enable either of these messages independently? In other words, what if I want to output only the exit message?

I would probably want a custom switch with custom levels corresponding to exit and enter events. It would look like this:

static FlowSwitch trace = new FlowSwitch("flowSwitch");
...
void Main(){
    if(trace.Entering){
        Trace.WriteLine("Entering method Foo");
    }
    ...
    if(trace.Exiting){
        Trace.WriteLine("Exiting method Foo");
    }
}
Such a FlowSwitch can actually have four states: one indicates that events should be traced when a method enters, another when a method exists, and the third and fourth are combinations of the core values to trace both or none of the event types. The four states of a FlowSwitch are represented by the FlowSwitchSettings enumeration, as shown here:
[Flags]
public enum FlowSwitchSettings {
    None = 0,
    Entering = 32,
    Exiting = 64,
    Both = Entering | Exiting
}
Values representing combinations of flags are very convenient and providing such values is recommended by the design guidelines.

Note that the enum is named using a plural noun phrase and has the FlagsAttribute applied. This follows two fundamental flags enum design guidelines: apply the System.FlagsAttribute to flag enums but not to simple enums, and use a plural type name for an enumeration with bit fields as values (also called a flags enum).

The FlowSwitch uses this enum for the Level property, which is set and changed by the OnValueChanged method. The method is called after the protected Value property is set to the value stored in the configuration file, which typically happens when the switch is instantiated. The value can be refreshed later during execution of the program by calling the Trace.Refresh method (new in the .NET Framework 2.0), which re-reads the configuration file. I also added two helper properties (Entering and Exiting), to avoid having to do complex bitwise operations in common usage scenarios (see Figure 5).

Figure 5 FlowSwitch Class (Close Figure 5)
public class FlowSwitch : Switch {
    public FlowSwitch(string name) : base(name,"") {}

    public FlowSwitchSettings Level {
        get { return (FlowSwitchSettings)this.SwitchSetting; }
        set { this.SwitchSetting = (int)value; }
    }

    public bool Entering {
        get { return (Level & FlowSwitchSettings.Entering) ==
              FlowSwitchSettings.Entering;  }
    }

    public bool Exiting {
        get { return (Level & FlowSwitchSettings.Exiting) ==
              FlowSwitchSettings.Exiting; }
    }

    protected sealed override void OnValueChanged() {
        SwitchSetting =
            (int)Enum.Parse(typeof(FlowSwitchSettings), Value);
    }
}

TraceSwitch and BooleanSwitch (which are included in the .NET Framework) are two other examples of concrete implementations of the Switch class.

Extending SourceSwitch

As you can see, extending the Switch class is relatively simple. Extending the SourceSwitch is both more interesting and more challenging. As I mentioned before, the switch is used with the main tracing entry point called TraceSource. TraceSource relies on two enums: one is used to specify the type of the event being traced and the second specifies the types of events that the given source should trace. The challenge in implementing custom source switches stems from the fact that the enums need to be specially designed to interoperate properly when used inside a TraceSource.

For example, in the .NET Framework, TraceSource uses a SourceLevels flags enum to represent the switch setting and a TraceEventType simple (non-flag) enum to represent the type of the event, as shown in the following:

TraceSource diagnostics = new TraceSource("MyApplication");
// diagnostics.Switch.Level = SourceLevels.Error;
diagnostics.TraceEvent(TraceEventType.Error, 0, "File not found");

For the custom flow source switch, I will reuse the FlowSwitchSettings enum from the previous section to represent the switch setting and add a new enum, FlowEventType, to represent the type of an event. This flow switch can control two types of events: Entering and Exiting. Therefore, the enum will have two values. Note that this enum is not a flags enum. Each event can be either an entering or an exiting event, but not both:

public enum FlowEventType {
    Entering = 32,
    Exiting = 64
}
As you might have noticed, this enum's name (FlowEventType) is a singular noun phrase so as to conform to the basic enum naming guidelines: use a singular type name for an enumeration unless its values are bit fields.

Unlike FlowSwitch, FlowSourceSwitch (and all SourceSwitches) does not need helper properties (Entering and Exiting). Instead, it contains a ShouldTrace method that will compare the event type to the switch setting to determine whether or not the event should be traced (see Figure 6).

Figure 6 To Trace or Not to Trace (Close Figure 6)
public class FlowSourceSwitch1 : SourceSwitch {
    public FlowSourceSwitch1(string name) : base(name) {}

    public new FlowSwitchSettings Level {
        get { return (FlowSwitchSettings)base.Level; }
        set { base.Level = (SourceLevels)value;  }
    }

    public bool ShouldTrace(FlowEventType eventType) {
        return ((int)eventType & (int)Level) != 0;
    }

    protected sealed override void OnValueChanged() {
        SwitchSetting =
            (int)Enum.Parse(typeof(FlowSwitchSettings), Value);
    }
}

The ShouldTrace method is probably the most interesting member of a source switch. As you might recall, I mentioned that the integer values of the two enums used by trace sources need to be chosen carefully. The reason is the implementation of the ShouldTrace method. It performs a bitwise comparison of the integer values to determine whether an event should be traced. Because the SourceSwitch.ShouldTrace method is nonvirtual, you cannot change how it compares the values. You can only change, or rather select, the values carefully to customize how the method works. Note that the method is nonvirtual to maximize performance (it allows for inlining). Performance of this method is critical to ensure the overhead of tracing can approach zero when a switch is turned off and the switch check is hoisted out. For example, the following if check can get inlined, and if the Boolean expression evaluates to false, the cost of the whole statement will be negligible:

if(source.Switch.ShouldTrace(TraceEvent.Information))
source.TraceEvent(TraceEvent.Information,...);

In fact, you don't absolutely have to add the ShouldTrace method to a custom switch—after all, the ShouldTrace method is just an overload and is not called by trace sources. TraceSource will call the base type's ShouldTrace(TraceEventType) method anyway. The overload is useful for scenarios in which the caller wants to move the switch check out of the TraceEvent call to improve performance.

Later in this article, I will show how to hook up such a custom SourceSwitch to a TraceSource. But first let's see if we can do better than the painstakingly handcrafted FlowSourceSwitch.

Generic SourceSwitch

Customization, or extensibility, can be provided through several mechanisms including abstractions, events, and callbacks. The .NET Framework 2.0 added a feature called Generics, which is a very powerful extensibility mechanism. Figure 7 shows how generics can be used to provide a switch that can be tweaked to support custom settings.

Figure 7 Switch that Supports Custom Settings (Close Figure 7)
public class SourceSwitch<TEventType,TSetting> : SourceSwitch {
    TSetting enumLevel;
    static Type settingEnumType = typeof(TSetting);

    public SourceSwitch(string name) : base(name) {}

    public new TSetting Level {
        get { return enumLevel; }
        set {
            enumLevel = value;
            SwitchSetting = Convert.ToInt32(enumLevel);
        }
    }

    public bool ShouldTrace(TEventType eventType) {
        int eventTypeValue = Convert.ToInt32(eventType);
        return (eventTypeValue & SwitchSetting) != 0;
    }

    protected override void OnValueChanged() {
        Level = (TSetting)Enum.Parse(settingEnumType, this.Value);
    }
}

This generic type has two type parameters, TEventType and TSetting, that are used in order to specify the enums representing the event type and the switch setting. This switch setting can be used in the following manner, which makes it so much simpler to create custom switches:

public class FlowSourceSwitch :
        SourceSwitch<FlowEventType,FlowSwitchSetting> {
    public FlowSourceSwitch(string name) : base(name) {}
}

Generics support a feature called constraints. The feature allows type parameters to be constrained to only certain types. For example, it's possible to specify that the type argument passed to TSetting needs to be derived from, for example, IComparable. In this particular example of the Switch<TEventType,TSetting>, it would be nice to constrain both of the type parameters to types that inherit from System.Enum. Unfortunately, such constraint is not supported by the .NET Framework 2.0.

This restriction can be partially mitigated by choosing descriptive type parameter names. In naming the type parameters I followed the guideline to use descriptive names, unless a single-letter name is completely self-explanatory and a descriptive name would not add value.

Creating a Custom TraceSource

So how do you integrate the new switch with a TraceSource? Let's look at two approaches. In the first one, I will inherit from TraceSource and in the second, I will use containment. First, inherit from TraceSource to customize the default switch type:

public class FlowTraceSource1 : TraceSource {
    public FlowTraceSource1(string name) : base(name) {
        base.Switch = new FlowSourceSwitch(name);
    }

    public new FlowSourceSwitch Switch {
        get { return (FlowSourceSwitch)base.Switch; }
        set { base.Switch = value; }
    }
}
If the source is configured using the configuration system, the switch type needs to be specified in the source configuration file:
<source
  name="FlowTracing"
  switchName="FlowTracing"
  switchType="Microsoft.Samples.Tracing.FlowSourceSwitch,Extensions"
>
  <listeners>
    ...
  </listeners>
</source>
Now the source can be used as shown in this example:
static readonly FlowTraceSource1 flow1 =
    new FlowTraceSource1("FlowTracing");
flow1.TraceEvent((TraceEventType)FlowEventType.Entering, 0, "Method1");
flow1.TraceEvent((TraceEventType)FlowEventType.Exiting, 0, "Method1");

Casting the event type argument is very inconvenient. The problem can be addressed by adding (to the TraceEvent methods) overloads that take FlowEventType, as shown here:

public class FlowTraceSource2 : TraceSource {
    ...

    public void TraceEvent(FlowEventType eventType, int id,
            string format, params object[] args) {
        int eventTypeValue = Convert.ToInt32(eventType);
        base.TraceEvent((TraceEventType)eventTypeValue, id, format,
                                 args);
    }

    public void TraceEvent(FlowEventType eventType, int id,
                                string format)
    {
        int eventTypeValue = Convert.ToInt32(eventType);
        base.TraceEvent((TraceEventType)eventTypeValue, id, format);
    }
}

But there is a problem with this design. FlowTraceSource pretends to be just an extended TraceSource. FlowTraceSource contains all the members inherited from TraceSource and in fact can be cast to TraceSource and used in place of it.

The problem is that FlowTraceSource is not really a TraceSource. For one thing, it's not designed to trace regular TraceSource events, like TraceEvent.Information, as the FlowSourceSwitch is not really designed to control such events. In cases where a subclass cannot be really used in place of its base class, inheritance should not be used. Containment may be a better approach.

Containment

Extensibility through containment (wrapping) means that a custom type uses another type in its implementation instead of inheriting from the type. In this example, FlowTraceSource3 uses TraceSource in its implementation (see Figure 8).

Figure 8 Wrapping a Type (Close Figure 8)
public class FlowTraceSource3 {
    TraceSource source;
    public FlowTraceSource3(string name) {
        source = new TraceSource(name);
        source.Switch = new FlowSourceSwitch(name);
    }

    public FlowSourceSwitch Switch {
        get { return (FlowSourceSwitch)source.Switch; }
        set { source.Switch = value; }
    }

    public void TraceEvent(FlowEventType eventType, int id,
            string format, params object[] args) {
        int eventTypeValue = Convert.ToInt32(eventType);
        source.TraceEvent((TraceEventType)eventTypeValue, id,
            format, args);
    }

    public void TraceEvent(FlowEventType eventType, int id,
        string format)
    {
        int eventTypeValue = Convert.ToInt32(eventType);
        source.TraceEvent((TraceEventType)eventTypeValue, id, format);
    }

    public TraceListenerCollection Listeners {
        get { return source.Listeners; }
    }

    public void Flush() { source.Flush(); }

    public void Close() { source.Close(); }
}

This design seems much simpler and cleaner. FlowTraceSource3 does not pretend to be a substitute for TraceSource, but by using TraceSource internally, FlowTraceSource3 gets many of the useful properties of TraceSource. For example, FlowTraceSource3 can be configured using the configuration system—it reuses TraceSource's listeners collection with its synchronization facilities, and many others.

Like the generic Switch we talked about before, it's possible to create a generic trace source. The type has two type parameters used to specify the type of the switch and the type of the event type enum. Again, the event type cannot be constrained, but the switch can be (see Figure 9). This generic type can be used as follows:

public class FlowTraceSource :
        TraceSource<FlowSourceSwitch, FlowEventType> {
    public FlowTraceSource(string name) : base(name) { }
}

Figure 9 Generic TraceSource (Close Figure 9)
public class TraceSource<TSwitch, TEventType> where TSwitch : SourceSwitch {
    TraceSource source;
    public TraceSource(string name) {
        source = new TraceSource(name);
        Type st = typeof(TSwitch);
        object s = st.GetConstructor(
            new Type[] { typeof(string) }).Invoke(new object[] { name });
        this.Switch = (TSwitch)s;
    }

    public TSwitch Switch {
        get { return (TSwitch)source.Switch; }
        set { source.Switch = value; }
    }

    public void TraceEvent(TEventType eventType, int id,
            string format, params object[] args) {
        int eventTypeValue = Convert.ToInt32(eventType);
        source.TraceEvent((TraceEventType)eventTypeValue,
            id, format, args);
    }

    public void TraceEvent(TEventType eventType, int id, string format) {
        int eventTypeValue = Convert.ToInt32(eventType);
        source.TraceEvent((TraceEventType)eventTypeValue, id, format);
    }

    public void Flush() { source.Flush(); }

    public void Close() { source.Close(); }
}

Configuration System Extensibility

Let's go back to the e-mail listener. If you remember, I hardcoded the recipient e-mail address, subject, and so forth. But there is a better way to do this. You can enable the listener to be configurable using the configuration system and it can be configured using the System.Diagnostics section of the configuration file. To do this, you need to override the GetSupportedAttributes method and then use the Attributes dictionary instead of the hardcoded values when sending e-mail messages. Figure 10 shows the code used to do this. Now the listener can be configured the following way:

<sharedListeners>
  <add name="Email"
       type="Microsoft.Samples.Tracing.CriticalMailListener,Extensions"
       Recipients="kcwalina@microsoft.com"
       Subject="Critical Error from TraceListener"
       From="kcwalina@microsoft.com "
  />
</sharedListeners>

Figure 10 Configurable E-Mail Listener (Close Figure 10)
public class CriticalMailListener : TraceListener2 {

    public CriticalMailListener() : base("CriticalMailListener") { }
    public CriticalMailListener(string name) : base(name) { }

    protected override string[] GetSupportedAttributes() {
        return new string[] { "Receipients", "Subject", "From" };
    }

    protected override void TraceEventCore(
        TraceEventCache eventCache, string source,
        TraceEventType eventType, int id, string message)
    {
        if (eventType != TraceEventType.Critical) return;
        MailMessage emailMessage = new MailMessage(
            Attributes["From"], Attributes["Recipients"]);
        emailMessage.Body = message;
        emailMessage.Subject = Attributes["Subject"];
        SmtpClient client = new SmtpClient();
        client.Send(emailMessage);
    }
}

Wrapping It Up

As you can see, the tracing APIs in System.Diagnostics support powerful extensibility. I hope you will find some time to experiment with these APIs on your own. If you create some interesting extensions, I would love to hear about them. Remember, you can read more about the Tracing APIs in my blog at Tracing APIs in .NET Framework 2.0. You can also read the design guidelines now in Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries by Krzysztof Cwalina, Brad Abrams (Addison-Wesley, 2005).

Send your questions and comments to clrinout@microsoft.com.

Krzysztof Cwalina is a program manager on the CLR team at Microsoft. He started his career at Microsoft designing APIs for the first release of the .NET Framework. Currently, he is leading the effort to develop, promote, and apply the design guidelines to the .NET Framework and WinFx. He is coauthor of Framework Design Guidelines (Addison-Wesley, 2005). Reach him at his blog at blogs.msdn.com/kcwalina.


Cutting Edge: Windows Workflow Foundation, Part 2

Cutting Edge
Windows Workflow Foundation, Part 2
Dino Esposito

Code download available at: CuttingEdge0604.exe (153KB)
Contents
ASP.NET and Workflows
Consuming a Workflow in ASP.NET
Managing the Execution of the Workflow
Configuring ASP.NET Apps for Workflow
Calling Workflow Components from ASP.NET
Exposing a Workflow as a Web Service
Conclusion

In last month's column, I presented a helpdesk workflow sample that focused on Windows® Forms client applications. This month I'll discuss ASP.NET workflow applications and the ability to expose a workflow as a Web service and invoke a Web service from a workflow.

ASP.NET and Workflows

A workflow built on Windows Workflow Foundation is a component that requires an ad hoc runtime environment to function. The workflow runtime environment is represented by the WorkflowRuntime class. To host a workflow library, you create and configure an instance of the WorkflowRuntime class to operate on a particular workflow type. For performance reasons, you normally create the runtime environment only once in the application lifetime and make it serve all incoming requests. In a Windows Forms application, you initialize the workflow runtime in the form's constructor and destroy it with the form when the application shuts down. So how does this work if you're using ASP.NET?

Once you have a workflow component up and running, calling it from within a Web app or Windows Forms shouldn't be an issue. As far as Windows Workflow Foundation-based workflows are concerned, ASP.NET developers have only a few small issues to face that are mostly related to the nature of Web applications.

Just like in Windows Forms, with ASP.NET you need to have just one instance of the workflow runtime created when the application starts. Unlike Windows Forms applications, though, a Web application works by accepting and processing requests. Requests are treated individually and don't know anything about each other. That leads to issues related to workflow persistence and threading.

Here's the persistence problem: the instance of the workflow type you're working with must be persisted across successive requests. You process a request, create an instance of the workflow, and get some results to generate the response for the user. When a successive request arrives, you probably need to resume the workflow from its old state and continue. In Windows, two consecutive user actions occur in the same operational context; in ASP.NET, two user actions take two distinct requests with no state being automatically maintained. In order for a workflow to work properly in the context of ASP.NET applications, you need to be able to persist its state across requests.

Here's the threading problem: by default, the workflow runtime functions asynchronously. When the runtime receives the order to spawn an instance of a given workflow type, the Start method allocates a new thread on which to initiate the workflow. The WorkflowCompleted event gives the caller a chance to detect when the workflow completes so it can refresh the user interface. As you can see, this model raises a structural problem in ASP.NET. Imagine a Web page that places a call to Start in a postback event. The page proceeds as soon as the Start method on the WorkflowRuntime class returns. Since it is asynchronous, the Start method starts a new thread to process the workflow, and then returns immediately to ASP.NET. The page will likely reach its completion before even the world's simplest workflow has completed. As a result, the results of the workflow process can't be incorporated into the page that is sent to the final user. To render the page only after the workflow interaction has completed, you need to synchronize the page rendering with the workflow execution.

Both the persistence and the threading synchronization issues are solved through workflow services. When hosting a workflow in an ASP.NET application, you must employ a richer runtime environment that supports both a synchronous dispatch model for requests and automatic data serialization when the workflow is idle. I'll tackle that requirement here.

Consuming a Workflow in ASP.NET

Let's review all the steps required to set up an ASP.NET page that consumes a workflow. I'll begin by building a simple workflow that includes only a Code activity, a workflow item that lets you add .NET-based code into the workflow. The example will be a workflow that implements an authentication service and validates credentials.

While in most cases using a workflow for such a task is overkill, I have worked on a couple of projects where the authentication process was quite complex, justifying the use of a workflow. Unfortunately there was no Windows Workflow Foundation around at the time. Be that as it may, what matters is that you end up with a workflow like the one in Figure 1. The workflow works by receiving a couple of parameters from its host—typically a user name and password—and returns a Boolean value that indicates whether the user has been authenticated. Figure 2 shows the list of parameters defined on the workflow.

Figure 2 Workflow Input and Output Parameters (Close Figure 2)

Parameter | Description
UserName | Input parameter indicating the name of the user
Password | Input parameter indicating the password for the user
IsAuthenticated | Output parameter indicating whether the user has been authenticated
Figure 1 Sample Authentication Workflow
Figure 1 Sample Authentication Workflow

You add parameters to a workflow by exposing public properties from the workflow class, typically backed by local members. In Figure 2, I have defined two input parameters to bring in the user name and password to authenticate. I have also defined a Boolean output parameter to return the result of the authentication process. In code, you pass input parameters to a workflow through a name/value dictionary where each entry matches the name of a public property on the workflow class. You pass the dictionary to the CreateWorkflow method that creates and runs an instance of the workflow.

Dictionary<string, object> parameters = new
   Dictionary<string, object>();
parameters.Add("UserName", UserName.Text);
parameters.Add("Password", Password.Text);

You receive the output of a workflow through the OutputParameters collection exposed by the WorkflowCompletedEventArgs instance provided to handlers for the WorkflowCompleted event. More precisely, the OutputParameters collection contains values from all public properties on the workflow class and doesn't distinguish between read/write and read-only properties. Figure 3 shows the codebehind file of the LoginWorkflow. The event handler associated with the CheckCredentials activity calls an internal function that performs the task of authenticating the provided credentials. The result is stored in the isAuthenticated private member variable. As you can see in Figure 3, this value acts as the data repository for the IsAuthenticated output parameter.

Figure 3 LoginWorkflow Class Codebehind (Close Figure 3)
namespace Samples
{
   public sealed partial class LoginWorkflow : SequentialWorkflowActivity
   {
      private bool isAuthenticated;
      private string userName;
      private string passWord;

      public string UserName
      {
         get { return userName; } set { userName = value; }
      }

      public string Password
      {
         get { return passWord; } set { passWord = value; }
      }

      public bool IsAuthenticated { get { return isAuthenticated; } }

      public LoginWorkflow() { InitializeComponent(); }

      private void CheckCredentials_ExecuteCode(
          object sender, EventArgs e)
      {
         isAuthenticated = Authenticate(userName, passWord);
      }

      private bool Authenticate(string user, string password)
      {
         return true; // Do something more serious here :-)
      }
   }
}

So much for the login workflow. Let's take a look at the ASP.NET application, its pages, and configuration file.The key points to address while building an ASP.NET page that uses workflows can be seen with a relatively simple page that contains user name and password boxes, a Login button, and a message welcoming the user. When the user clicks on the Login button, you need to retrieve or create the workflow runtime and start a new instance of the workflow type you're using.

Managing the Execution of the Workflow

Last month, I created a workflow runtime object using the new operator and the WorkflowRuntime class constructor, like so:

WorkflowRuntime wr = new WorkflowRuntime();
Using this code in ASP.NET is fine as long as you place it in Application_Start and then find a way to make the object that is created reachable from within your page code. Instantiating the workflow runtime for each request results in an exception because the workflow runtime can be loaded more than once in the same AppDomain. Two ASP.NET requests have distinct state and context, but run in the same AppDomain. As a result, the first request works while the second throws an exception.

Windows Workflow Foundation provides a built-in infrastructure to guarantee that there's exactly one workflow runtime object per AppDomain and that you don't have to worry about its creation and instantiation. In ASP.NET, in fact, you can use the WorkflowWebRequestContext class to access the current and correct instance of the workflow runtime. This code shows how to get a reference to the workflow runtime:

   WorkflowRuntime wr =
      WorkflowWebRequestContext.Current
         .WorkflowRuntime;

Internally, the get accessor of the Current property calls into a public member of a static class that caches the workflow runtime instance, creating a new instance if one cannot be found. Once you've got a reference to the runtime object, you can register the handler for the WorkflowCompleted event and start the actual workflow. This code shows how:

wr.WorkflowCompleted +=
   new EventHandler<WorkflowCompletedEventArgs>(LoginCompleted);
Type t = typeof(Samples.LoginWorkflow);
WorkflowInstance instance = wr.CreateWorkflow(t, parameters);
instance.Start();

When the workflow returns, the code of LoginCompleted runs (see Figure 4) and checks the IsAuthenticated entry in the OutputParameters collection. OutputParameters is one of the event data arguments you get through the WorkflowCompletedEventArgs class. The contents of OutputParameters is determined by the runtime when the workflow completes.

Figure 4 ASP.NET Code Calling into a Workflow (Close Figure 4)
using System;
using System.Web;
using System.Collections.Generic;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting.Web;
using System.Workflow.Runtime.Hosting;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        SetFocus("UserName");
    }
    protected void LoginButton_Click(object sender, EventArgs e)
    {
        // Define the parametes for the workflow
        Dictionary<string, object> parameters =
            new Dictionary<string, object>();
        parameters.Add("UserName", UserName.Text);
        parameters.Add("Password", Password.Text);

        // Get a reference to the Workflow runtime
        WorkflowRuntime wr =
            WorkflowWebRequestContext.Current.WorkflowRuntime;

        // Attach to the WorkflowCompleted event
        wr.WorkflowCompleted +=
            new EventHandler<WorkflowCompletedEventArgs>(LoginCompleted);

        // Start the Workflow1
        WorkflowInstance workflowInstance =
            wr.CreateWorkflow(typeof(Samples.LoginWorkflow), parameters);
            workflowInstance.Start();
    }

    void LoginCompleted(object sender, WorkflowCompletedEventArgs e)
    {
        bool isAuth = (bool)e.OutputParameters["IsAuthenticated"];
        CurrentUser.Text = isAuth ?
            String.Format("Welcome, {0}", UserName.Text) :
            String.Format("Sorry {0}. You can't be authenticated." +
                          " Try again.", UserName.Text);
    }
}

The code in Figure 4 is not enough for the workflow to work correctly and for the ASP.NET page to serve users pages that incorporate workflow results. As I mentioned, you need to add a couple of services to the runtime—one for state persistence and one for synchronous request dispatching. The first idea that springs to mind is to bind services once you've got the workflow runtime instance. Here's some sample code (note that wr denotes the workflow runtime instance):

String connString = "...";
SqlWorkflowPersistenceService servState =
    new SqlWorkflowPersistenceService(connString);
wr.AddService(servState);
When you run this code, you get an exception stating that you can't further configure the runtime once it has started. But who really started the runtime anyway, since there is no code in the page that would?

The culprit is the WorkflowWebRequestContext class I mentioned earlier. This class creates an instance of the workflow runtime from within an ASP.NET application. Then it attempts to read a section in the Web.config file named WorkflowRuntime, which is supposed to list all the services you want to bind to the runtime. Can you guess what happens? The reference to the workflow runtime you get through WorkflowWebRequestContext.Current corresponds to an instance that has already been configured with services and started. To control the services that are bound to the runtime in an ASP.NET application you need to tweak the Web.config file, as shown in Figure 5.

Figure 5 Web.config for Consuming Workflow Components (Close Figure 5)
<?xml version="1.0"?>
<configuration
  xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
  <configSections>
    <section name="WorkflowRuntime"
      type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection,
      System.Workflow.Runtime, ..."/>
  </configSections>

  <WorkflowRuntime Name="WorkflowServiceContainer">
    <Services>
      <add type=
        "System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService,
        System.Workflow.Runtime, ..." />
      <add type=
        "System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService,
        System.Workflow.Runtime, ..." />
      <add type=
       "System.Workflow.Runtime.Hosting.DefaultWorkflowTransactionService,
        System.Workflow.Runtime, ..." />
    </Services>
  </WorkflowRuntime>

  <system.web>
    <httpModules>
      <add name="WorkflowHost"
        type="System.Workflow.Runtime.Hosting.WorkflowWebHostingModule,
        System.Workflow.Runtime, ..." />
    </httpModules>
  </system.web>
</configuration>

Configuring ASP.NET Apps for Workflow

Windows Workflow Foundation uses the <workflowRuntime> section in the configuration file to store information for internal use. This section is not a default part of the configuration schema; for this reason, you have to register it explicitly.

<configuration>
  <configSections>
     <section name="WorkflowRuntime"
              type="WorkflowRuntimeSection, System.Workflow.Runtime" />
  </configSections>
  ...
</configuration>

In ASP.NET 2.0, when you register a new configuration section, you specify the name and assembly of a class that inherits ConfigurationSection. For the <WorkflowRuntime> section, the class is WorkflowRuntimeSection. The class is defined in the System.Workflow.Runtime assembly as part of the System.Workflow.Configuration namespace. Note that in ASP.NET 2.0, custom configuration sections are created in a different way from ASP.NET 1.x, where you had to create a class and implement the IConfigurationSectionHandler interface. The interface methods required that you parsed the XML markup below the node. In ASP.NET 2.0, the base class contains a default parser and, at least in most cases, requires that you define the expected public interface of the section through properties.

The <WorkflowRuntime> section contains a nested section named <Services> where you list all services that you want to be bound to the runtime. Windows Workflow Foundation comes with a number of predefined services including SqlWorkflowPersistenceService and ManualWorkflowSchedulerService. The former service automatically persists the workflow instance to the specified SQL Server™ database as soon as the workflow becomes idle (while waiting for human intervention, for instance). Even more relevant for ASP.NET applications is the scheduler service. You need the service to ensure that the ASP.NET pooled thread in charge of executing the current request waits until the workflow is completed. More precisely, the service guarantees that the execution of the workflow is synchronous and that the Start method returns only when the workflow has ended or is idle.

Calling Workflow Components from ASP.NET

Calling workflow components from within ASP.NET is definitely possible once you resolve two issues: persistence and dispatching. It is interesting to note that, in general, you can create an instance of the workflow runtime from a configuration section—and not just a section in the ASP.NET Web.config file. You do this by using another overload of WorkflowRuntime constructor:

WorkflowRuntime wr = new WorkflowRuntime(sectionName);
The section is assumed to have the structure of the <WorkflowRuntime> section discussed earlier, but can have any name. The WorkflowWebRequestContext takes advantage of this feature and imposes the name <WorkflowRuntime> on the section of the Web.config file. But the underlying mechanism is quite general.

Figure 6 Web Front-End for the HelpDesk Workflow
Figure 6 Web Front-End for the HelpDesk Workflow

You can also use the <WorkflowRuntime> section to register custom services. As Figure 6 shows, the HelpDesk workflow that I created in last month's column works through an ASP.NET client. The HelpDesk workflow is based on a custom service providing external events to the workflow. In a Windows Forms client, you would use the following code:

theHelpDeskService = new HelpDeskService();
wr.AddService(theHelpDeskService);
In an ASP.NET client, you resort to an extra node under the <Services> section of the <WorkflowRuntime> element:
<Services>
   <add type="Samples.HelpDeskService, HelpDeskWorkflow" />
</Services>

There are two main types of workflow: sequential and state machine. Sequential workflows represent a sequence of activities and terminate only when all activities have been completed. State machine workflows are an event-driven, asynchronous graph of activities in which each activity represents a state and can transition to a well-known subset of states. Both types of workflow can be consumed from within ASP.NET applications. Interestingly, you could even craft a user interface, wrap it up in a Web Part, and host the workflow in a SharePoint® (Service Pack 2) application or in a portal-like ASP.NET 2.0 application.

Sequential workflows, though, might be dangerous for ASP.NET applications because they express potentially lengthy operations. A sequential workflow that doesn't stop waiting for external events might be engaged in long-running transactions and represents a serious threat to the scalability of the overall application.

Asynchronous pages are the perfect countermeasure for pages that need to perform long tasks, as Jeff Prosise explained in his October 2005 Wicked Code column. Asynchronous ASP.NET pages are based on a pair of Begin/End methods. As of Beta 2 of Windows Workflow Foundation, there's no such pair of built-in methods to start a workflow. However, a Windows Workflow Foundation workflow can be published as a Web service and, just like a Web service, can be invoked asynchronously through the ad hoc methods of the Web service proxy class.

Exposing a Workflow as a Web Service
Figure 7 Workflow
Figure 7 Workflow

The Windows Workflow Foundation framework makes it possible for you to expose a workflow as a Web service to .NET-based clients as well as to other workflows. To expose a workflow as a Web service, you have to write it in a special manner. Only a workflow that uses the WebServiceReceive activity can be published as a Web service. The idea is that you create a workflow project and, at least in the simplest case, add a single pair of WebServiceReceive and WebServiceResponse activities to it. In more complex cases you might want to use a Listen activity to receive input about which activity, or set of activities, should run. In the simplest common case the workflow is activated by calling a method on the Web service, proceeds to the end, and returns output values.

The interface exposed through the Web service is defined inside the workflow class. After creating, say, a new sequential workflow, you add an interface definition to the codebehind class:

public interface ILoginWorkflow
{
    bool Authenticate(string user, string pswd);
}
Only the methods listed in the interface will be callable from external clients through the Web service. The structure of the workflow may be as simple as a pair of WebServiceReceive and WebServiceResponse activities (see Figure 7).

The WebServiceReceive activity is bound to a particular method of the Web service interface and receives data when the method is called. For example, you bind the WebServiceReceive activity to the Authenticate method of the ILoginWorkflow interface. The input arguments of the method must be bound to public members of the workflow class:

   public string receivedUser = default(System.String);
   public string receivedPswd = default(System.String);

The WebServiceResponse activity responds to a request made through a Web service interface. Note that you can only use a WebServiceResponse activity in a workflow that also has a WebServiceReceive activity. An essential property of the activity is ReceiveActivityId, which indicates the ID of the related WebServiceReceive activity. You can only use WebServiceReceive activities located in the same workflow. The return value of the method must also be bound to a public member in the workflow class:

public bool returnedAuthenticate = default(System.Boolean);

Basically, the interaction between the client the Web service, and the workflow can summarized as follows. The client references the Web service and places a call to one of its methods. The Web service starts the workflow and invokes the WebServiceReceive activity, passing input parameters. The receive activity caches the input data. Possible intermediate activities (Code, Parallel, If, and so on) run as usual. At the end, the WebServiceResponse activity is invoked to generate the return value. The WebServiceResponse activity is expected to cache the return value into the bound public member, the Boolean returnedAuthenticate member in the previous line of code.

A workflow exposed through a Web service can be thought of as a regular workflow that is wrapped by a pair of WebServiceReceive and WebServiceResponse activities.

To generate handlers that manage input and output data, you right-click on the receive/response activities and click Generate Handlers. At the minimum, do that for WebServiceResponse so that some return value can be sent to the caller. When you're done with the code, you right-click on the project and select Publish as Web service (see Figure 8). The workflow designer creates a small Web service project with a Web.config file, an .asmx endpoint, and the assembly that contains the workflow. Then you should deploy these files on IIS 6.0 and call the workflow.

Figure 8 Publishing a Workflow
Figure 8 Publishing a Workflow

It is interesting to take a look at the Web.config file that is generated for you. In addition to the <WorkflowRuntime> section that I discussed earlier in the column, the configuration requires an HTTP module:

<httpModules>
   <add type="System.Workflow.Runtime.Hosting.WorkflowWebHostingModule,
              System.Workflow.Runtime"
        name="WorkflowHost"/>
</httpModules>
The HTTP module is responsible for instantiating and managing the workflow runtime and routing Web service requests to the appropriate workflow instance. The HTTP module maps incoming requests to the right workflow instance using the ASP.NET session state information.

In production code, you should significantly increase the timeout of your session state (the default is 20 minutes) and opt for database storage of session state to ensure that workflow instance IDs are not lost in the event of AppDomain shutdown and process recycling. Note that the HTTP module is designed to work well in Web farm scenarios.

The HTTP module leverages the ASP.NET session state locking mechanism to ensure that requests directed at the same workflow instance are serialized. The ASP.NET session state locking mechanism guarantees that requests in the same session are queued. Due to this locking mechanism, you cannot have interleaved receive/response pairs in a workflow. Most of the time, to expose a workflow through a Web service, you simply take an existing workflow and wrap it with a pair of receive/response activities. The input for the workflow is gathered by the WebServiceReceive activity and the WebServiceResponse component collects the return values.

Conclusion

ASP.NET applications are yet another possible host for Windows Workflow Foundation workflows. Due to the special nature of ASP.NET applications, hosting a workflow requires a bit of attention. In particular, you need to enrich the run-time environment with a couple of run-time services that come out of the box—namely the threading and SQL Server persistence services.

The threading service, in particular, ensures that the page is rendered only when the workflow completes or becomes idle. What if a workflow takes too long to complete? In this case, you can consider exposing the workflow as a Web service and binding it to an ASP.NET asynchronous page. Having workflow exposed as Web services also increases the reach of the workflow and allows you to call it from other platforms.

Send your questions and comments for Dino to cutting@microsoft.com.

Dino Esposito is a mentor at Solid Quality Learning and the author of Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Based in Italy, Dino is a frequent speaker at industry events worldwide. Get in touch with Dino by e-mailing him at cutting@microsoft.com or join the blog at weblogs.asp.net/despos.


Extreme ASP.NET: A New Solution to an Old State Storage Problem

Extreme ASP.NET
A New Solution to an Old State Storage Problem
Fritz Onion

Code download available at: ExtremeASPNET0604.exe (120KB)
Contents
Profile Fundamentals
Serialization
User-Defined Types as Profile Properties
Optimizing Profile
Going the Custom Route
Conclusion

State management in Web applications is a contentious issue. Should you store user data per session or should you persist it across sessions? You can easily store information temporarily while someone navigates your site by using session state. That's typically an in-memory data store indexed by a unique session key assigned to each new user and that lasts only for the duration of the "session" with the client. Storing information across sessions is typically done by building your own back-end data store indexed by some user identifier (typically obtained after a user logs onto the site).

There's a grey area between these two levels of per-client storage. What if you want to store data across sessions for anonymous users as well? Wouldn't it be convenient if you could store data on behalf of clients in a persistent store without having to implement an entire back-end data store? This niche is now filled quite nicely by the Profile feature of ASP.NET 2.0.

Profile provides a simple way of defining database-backed user profile information. With a few configuration file entries, you can quickly build a site that stores user preferences (or any other data, for that matter) into a database, all with a simple type-safe interface for the developer. Profile looks and feels much like Session state, but unlike Session state, Profile is persistent across sessions. Profile is tied into the ASP.NET 2.0 membership system so that data for authenticated clients is stored in association with their real identities instead of with some arbitrary identifier. Anonymous clients have an identifier generated for them stored as a persistent cookie, so that on subsequent access from the same machine they will retain their preferences.

As an ASP.NET developer, it is important to understand the options you have for storing state and when each of these options is most appropriate. In this column I'll explore Profile and where it fits in the state management spectrum.

Profile Fundamentals

The first step when using Profile is to declare the properties you would like to store on behalf of each user in your Web.config file under the <profile> element. The example in Figure 1 shows three property declarations, one each for the user's favorite color, favorite number, and favorite HTTP status code.

Figure 1 Property Declarations (Close Figure 1)
<configuration>
  <system.web>
     <profile enabled="true">
        <properties>
          <add name="FavoriteColor" defaultValue="blue"
                    type="System.String"
                    allowAnonymous="true" />

          <add name="FavoriteNumber" defaultValue="42"
                    type="System.Int32"
                    allowAnonymous="true" />

          <add name="FavoriteHttpStatusCode"
                    type="System.Net.HttpStatusCode"
                    allowAnonymous="true"
                    serializeAs="String"
                    defaultValue="OK" />
        </properties>
    </profile>
  </system.web>
</configuration>

When ASP.NET compiles your site, it creates a new class that derives from ProfileBase with type-safe accessors to the properties you declared. These accessors use the profile provider to save and retrieve these properties to and from whatever database the provider is configured to interact with. Figure 2 shows what the generated class looks like for the properties declared in Figure 1.

Figure 2 Generated Class (Close Figure 2)
public class ProfileCommon : ProfileBase {

    public virtual HttpStatusCode FavoriteHttpStatusCode {
        get {
            return ((HttpStatusCode)(this.GetPropertyValue(
                       "FavoriteHttpStatusCode")));
        }
        set {
            this.SetPropertyValue("FavoriteHttpStatusCode", value);
        }
    }

    public virtual int FavoriteNumber {
        get {
            return ((int)(this.GetPropertyValue(
                              "FavoriteNumber")));
        }
        set {
            this.SetPropertyValue("FavoriteNumber", value);
        }
    }

    public virtual string FavoriteColor {
        get {
            return ((string)(this.GetPropertyValue(
                                  "FavoriteColor")));
        }
        set {
            this.SetPropertyValue("FavoriteColor", value);
        }
    }

    public virtual ProfileCommon GetProfile(string username) {
        return ((ProfileCommon)(ProfileBase.Create(username)));
    }
}

The second thing that happens is that ASP.NET adds a property declaration for a property named Profile to each generated Page-derived class in your site. This is a type-safe accessor to the current Profile class (which is part of the HttpContext):

public partial class Default_aspx : Page
{
   protected ProfileCommon Profile {
      get { return ((ProfileCommon)(this.Context.Profile)); }
   }

   //...
}
This lets you interact with your profile properties in a very convenient way. For example, here's a snippet of code that sets the profile properties based on fields in a form:
void enterButton_Click(object sender, EventArgs e)
{
  Profile.FavoriteColor = colorTextBox.Text;
  Profile.FavoriteNumber = int.Parse(numberTextBox.Text);
  Profile.FavoriteHttpStatusCode =
      Enum.Parse(typeof(HttpStatusCode), statusCodeTextBox.Text);
}

If you look in the database used by the profile provider (by default a local SQL Server™ 2005 Express database in your application's App_Data directory), you will see a table called aspnet_Profile with five columns that have the following names:

UserId
PropertyNames
PropertyValuesString
PropertyValuesBinary
LastUpdatedDate
For the previous example, these columns were populated with the following values:
405A7333-2C8D-4E63-AB56-BA54398D47DF
FavoriteColor:S:0:3:FavoriteNumber:S:3:2:FavoriteHttpStatusCode:S:5:16:
red42MovedPermanently
<empty>
2006-1-1 09:00:00.000
You can see that, by default, the profile provider uses string serialization with property names (and string lengths), carefully stored on a per-user basis. In the example the user was anonymous, so a GUID was generated and used to index the property values in the aspnet_Profile table. The UserId column is actually a foreign key reference to the UserId column of the aspnet_Users table where the Membership system keeps user information (anonymous users are also stored in this table).

Serialization

As you saw, the default serialization method for properties stored in Profile is to write them out as strings, storing the property names and substring indices in the PropertyNames column. You can control how your properties are serialized by changing the serializeAs attribute on the add element in Web.config. These attributes can have one of the four following values: Binary, ProviderSpecific, String, or Xml.

The default is actually ProviderSpecific, which might better be called TypeSpecific since the type of the object will determine the format of its serialization. What ProviderSpecific means with the default SQL Provider implementation is that the property will be written as a simple string if it is either a string already, or a primitive type (int, double, float, and so forth). Otherwise it defaults to XML serialization, which makes sense because it will work with most types (even custom ones) without any modification to the type definition itself. So ProviderSpecific could have named StringForPrimitiveTypesAndStringsOtherwiseXml, which is obviously quite a mouthful, so something shorter was in order.

This can lead to some confusing behavior if you're not looking out for it. For example, you should consider the following two Profile property definitions:

<add name="TestCode" type="System.Net.HttpStatusCode"
     defaultValue="OK" />
<add name="TestDate" type="DateTime"
     defaultValue="1/1/2006"/>
After using integer and string profile properties, adding an enum and a DateTime in this manner seems reasonable. Because the default serialization is ProviderSpecific, you now know that these two types will be serialized with the XmlSerializer, so specifying default values as simple strings is not going to fly (as you will find out quickly once you try accessing the properties). You have two ways of dealing with this problem. One is to specify the XML-serialized value directly in the configuration file (taking care to escape any angle brackets):
<add name="TestCode" type="System.Net.HttpStatusCode"
     defaultValue="&lt;HttpStatusCode&gt;OK&lt;/HttpStatusCode&gt;" />
<add name="TestDate" type="DateTime"
     defaultValue="&lt;dateTime&gt;2006-01-01&lt;/dateTime&gt;" />

The other (and perhaps more appealing) option is to change the serialization from ProviderSpecific (which you know turns into XML) to String. String serialization only works for types that have TypeConversions defined for strings, which is fine here as both enums and the DateTime class have string conversions defined. (I will discuss how to write your own string conversions in the next section.) If you look carefully at Figure 1, you will notice that it specifies a serializeAs attribute of String for the FavoriteHttpStatusCode, so that a simple string default value of "OK" could be used:

<add name="TestCode" type="System.Net.HttpStatusCode"
     serializeAs="String"
     defaultValue="OK" />
<add name="TestDate" type="DateTime"
     serializeAs="String"
     defaultValue="2006-01-01" />

The other option for serialization is Binary, which will use the BinaryFormatter to serialize the property, and with the default SQL provider, will write the binary data into the PropertyValuesBinary column in the database. This is a useful option if you want to make it difficult to tweak the profile values directly in the database, or if you are storing types whose entire state is not properly persisted using XmlSerializer (classes with private data members that are not accessible through public properties fall into this category, for example). Before you can use the binary option, the type being stored must be marked as Serializable or must implement the ISerializable interface. Keep in mind that selecting the binary serialization option makes it difficult to specify a default value, so it is typically used only for complex types for which a default value doesn't make sense anyway. If you do need to specify a default value for binary serialization, you can binary serialize the instance you want for a default value, base64 encode the resulting byte array, and use the base64 encoded representation for the value of the defaultValue attribute.

User-Defined Types as Profile Properties

One of the advantages of the Profile architecture is that it is generic enough to store arbitrary types, and as you have seen, supports several different persistence models. This means that it is quite straightforward to write your own classes to store user data and store the entire class in Profile. Suppose that you wanted to provide a shopping cart for your users. You might write a class to store an individual item containing a description and a cost, and another class that keeps a list of all of the items in the current cart as well as exposing a property that calculates the total cost of all items in the cart (see Figure 3).

Figure 3 Shopping Cart Class (Close Figure 3)
namespace MsdnMag
{
    [Serializable]
    public class ShoppingCart
    {
        private List<Item> _items = new List<Item>();

        public Collection<Item> Items
        {
            get { return new Collection<Item>(_items); }
        }

        public float TotalCost
        {
            get
            {
                float sum = 0F;
                foreach (Item i in _items) sum += i.Cost;
                return sum;
            }
        }
    }

    [Serializable]
    public class Item
    {
        private string _description;
        private float  _cost;

        public Item() : this("", 0F) { }

        public Item(string description, float cost)
        {
            _description = description;
            _cost = cost;
        }

        public string Description
        {
            get { return _description;  }
            set { _description = value; }
        }

        public float Cost
        {
            get { return _cost;  }
            set { _cost = value; }
        }
    }
}

Note that these classes are marked with the [Serializable] attribute in anticipation of using binary serialization. You can then add a profile property of type ShoppingCart to your collection, and you have a fully database-backed per-client persistent shopping cart implemented!

<profile enabled="true">
  <properties>
    <add name="ShoppingCart" type="MsdnMag.ShoppingCart"
         allowAnonymous="true" serializeAs="Binary" />
  </properties>
</profile>

Using the shopping cart in your application is as simple as accessing the ShoppingCart property in Profile, and adding new instances of the Item class as needed (the sample available for download has a complete interface for users to shop using this class as the storage mechanism).

Profile.ShoppingCart.Items.Add(
    new Item("Chocolate covered cherries", 3.95F));

Optimizing Profile

You may be wondering at this point what sort of cost is incurred by leveraging Profile to store your per-client data, especially if you start using complex classes like the ShoppingCart that may end up storing significant amounts of information on behalf of each user. Those of you who have taken advantage of the SQL Server-backed session state feature introduced in ASP.NET 1.0 may be especially leery, since by default each request for a page incurred two round-trips to the state database to retrieve and then flush session state from and to the database. The good news is that by default, the Profile persistence mechanism is reasonably efficient. Unlike out-of-process Session state, it performs lazy retrieval of the profile data on behalf of a user (loading on demand only), and only writes the profile data back out if it has changed.

Unfortunately, if you are storing anything besides strings, DateTime classes, or primitive types, it becomes impossible for the ProfileModule to determine whether the content has actually changed, and it is forced to write the profile back to the data store every time it is retrieved. This is obviously true for custom classes as well, so be aware that adding any types beside string, DateTime, and primitives will force Profile to write back to the database and the end of each request that accesses Profile. Internally there is a dirty flag used to keep track of whether a property in Profile has changed or not. You can explicitly set the IsDirty property for a profile property to false. If you do this for all properties associated with a specific provider instance, then when that provider instance is asked to save the profile data, it will see that all the properties passed to it are not dirty and it will skip communication with the database. This approach relies on knowledge of the underlying SettingsBase, SettingsProperty, and SettingsPropertyValue types (all in System.Configuration). For a profile property called "Nickname", you could force it to not be considered dirty with code like the following:

Profile.PropertyValues["Nickname"].IsDirty = false;

Note that you can disable automatic profile saving using the automaticSaveEnabled attribute on the <profile/> element in the configuration file (this attribute defaults to true). You can set automaticSaveEnabled to false to stop ProfileModule from storing the Profile on your behalf automatically. It is up to you to call Profile.Save if you want to store data back to the database. Alternatively, you can hook the ProfileModule's ProfileAutoSaving event. If you set the ContinueWithProfileAutoSave property on the event argument to false, then the ProfileModule will not call Profile.Save.

As you saw earlier, it is possible to specify String, Binary, or Xml as the serialization mechanism for your properties. If you are storing your own custom classes like the ShoppingCart example, you can take steps to reduce the amount of space used to store instances of your class in one of two ways: by writing your own TypeConverter for the class to support conversion to string format, or by implementing the ISerializable interface to control the format of the binary data used by the BinaryFormatter. Figure 4 shows the default serialization of the ShoppingCart class with four items in it, in XML.

Figure 4 XML-Serialized Shopping Cart with Four Items (Close Figure 4)
<?xml version="1.0" encoding="utf-16"?>
<ShoppingCart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Items>
    <Item>
      <Description>Chocolate covered cherries</Description>
      <Cost>3.95</Cost>
    </Item>
    <Item>
      <Description>Toy Train Set</Description>
      <Cost>49.95</Cost>
    </Item>
    <Item>
      <Description>XBox 360</Description>
      <Cost>399.95</Cost>
    </Item>
    <Item>
      <Description>Wagon</Description>
      <Cost>24.95</Cost>
    </Item>
  </Items>
</ShoppingCart>

By default, you cannot use the serializeAs="String" option for custom types, since there is no way to convert the types to and from a string format in a lossless way. You can provide such a conversion yourself by implementing a TypeConverter for your class. This involves creating a class that inherits from TypeConverter, implementing the conversion methods, and then annotating your original class with the TypeConverter attribute that associates it with your conversion class. You must also decide how to persist your class as a string (and then parse it from a string) which can be a non-trivial task, so make sure it's worth the effort before taking this tack. As an example, Figure 5 shows a TypeConverter class for the Item class that represents items in the shopping cart. In this case I chose to use a non-printable character as a delimiter. Since the Item class consists of two pieces of state that are easily rendered as strings, the parsing becomes trivial using the Split method of the string class. The converter class is then associated with the Item class using the TypeConverter attribute.

Figure 5 Type Converter Class for Item (Close Figure 5)
public class ItemTypeConverter : TypeConverter
{
    private const char _delimiter = (char)10;

    public override object ConvertFrom(
        ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        string sValue = value as string;
        if (sValue != null)
        {
            string[] vals = sValue.Split(_delimiter);
            return new Item(vals[0], float.Parse(vals[1]));
        }
        else return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context,
        CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            Item i = value as Item;
            return string.Format("{0}{1}{2}",
                i.Description, _delimiter, i.Cost);
        }
        else return base.ConvertTo(context, culture,
            value, destinationType);
    }

    public override bool CanConvertFrom(
        ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;
        else return base.CanConvertFrom(
            context, sourceType);
    }

    public override bool CanConvertTo(
        ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(string))
            return true;
        else return base.CanConvertTo(
            context, destinationType);
    }
}

[Serializable]
[TypeConverter(typeof(ItemTypeConverter))]
public class Item
{
  ...

With these classes in place, the Item class can be used with string serialization in a profile property. For the shopping cart to be completely serializable as a string, you also need to write a type converter for the ShoppingCart class, a sample of which can be found in the downloadable samples accompanying this column. The advantage of controlling the persistence in this way is that the serialization of the same shopping cart filled with four items now only takes 79 characters!

Going the Custom Route

When you find yourself spending a lot of time trying to make an architecture do what you want, it is important to make sure that all this work is less than doing it entirely yourself. Profile is a great example of a feature that is convenient and easy to use, but may be too constraining as your design evolves. Let's look at the features Profile specifically provides.

  • Anonymous and authenticated clients
  • Anonymous users identified through a new cookie (or alternatively through an embedded ID with URL mangling, including support for auto-detect cookieless mode)
  • Arbitrary type storage, strongly typed through the use of a configuration file
  • A per-client persistent data store
  • A management class for cleaning up unused profile data

One of the drawbacks to using Profile to store client data is that it stores all of the data in one column of the database table (or in two if you are using both string and binary serialization). This means that it is practically impossible to make modifications to the profile data without going through the Profile API. It's also impractical to generate any reports from the data or otherwise collect information from the database directly.

If you want more control over the storage of per-client state in your application, you have two choices: build a custom profile provider or forget Profile and just write data yourself. Building a custom profile provider gives you the ability to change the location that Profile actually writes the data to, but because of the nature of the provider interface, it doesn't really make it any easier to write property values to specific columns in a table. For more information and samples on building custom profile providers, take a look at the ASP.NET provider model toolkit. Also see the ASP.NET 2.0 Table Profile Provider sample.

If you decide to forgo Profile and write the serialization of client data yourself, be aware that you can still exploit the identification features of Profile even if you aren't using the storage features. Specifically, there is a UserName property on the ProfileBase class that will contain either the name of the current authenticated user or the GUID that was generated for an anonymous user. You can use this UserName property as a unique index into a custom database table to easily store and retrieve user data. Just make sure that Profile and anonymousIdentification are enabled in your application, and you can use the same client identification mechanism as Profile:

<anonymousIdentification enabled="true"/>
<profile enabled="true" />

Writing your own client persistence back end using the unique identifier provided by Profile gives you several unique advantages:

  • The ability to write stored procedures against client data.
  • The ability to retrieve only the portions of data you need for a client at any given time instead of relying on Profile to just load the whole chunk into memory. Note, though, that each property's <add /> element in the configuration file includes a provider attribute, and with that attribute you can slice up your Profile properties across multiple providers. For example, you could map frequently used properties to one provider instance and infrequently used properties to a different provider instance. If you never access the infrequently used properties on a page at run time, the Profile feature will only load information associated with the "frequent use" provider. This is another example of how to take advantage of the lazy-load feature of Profile.
  • The ability to cache per-client data across requests for efficiency (you could add this by deriving from SqlProfileProvider and overriding GetPropertyValues and SetPropertyValues to add per-user caching semantics).
  • Complete control over the serialization and can map onto existing tables instead of creating new data stores that you may already have in place.

The sample available for download on the MSDN®Magazine Web site contains an alternate implementation of the shopping cart described earlier, using a custom database table to store cart items and exploiting the unique client identifier available through the ProfileBase class. You may even consider using Profile to get things started, and then later migrate some of the profile data into custom tables with a separate data access layer. In this sense, the use of Profile is an easy way to store per-client data, with an obvious path forward to factoring data out into a more strongly typed schema.

Conclusion

Profile fills a hole in state management for Web applications written with ASP.NET. Falling somewhere between in-process session state and a full-blown custom per-client data store, Profile should prove extremely useful in almost any Web application. Like any general purpose solution, however, you give up some performance and flexibility over a completely custom implementation, so be aware of its capabilities as you integrate it into your designs.

Send your questions and comments for Fritz to xtrmasp@microsoft.com.

Fritz Onion is a cofounder of Pluralsight, a Microsoft .NET training provider, where he heads the Web development curriculum. Fritz is the author of Essential ASP.NET (Addison Wesley, 2003) and the upcoming Essential ASP.NET 2005 (Addison Wesley, 2006). Reach him at pluralsight.com/fritz.


Service Station: Migrating to WSE 3.0

Service Station
Migrating to WSE 3.0
Aaron Skonnard

Code download available at: ServiceStation0604.exe (139KB)
Contents
Major Changes in WSE 3.0
Migrating Projects from WSE 2.0 to WSE 3.0
General Class Library
DIME/MTOM
Security
Policy/Filters
Policy in WSE 3.0
Migrating Custom Filter Code
Migrating Custom Policy Assertions
Defining a Policy in Code
Applying a Policy to a Service
Applying a Policy to a Client
Defining a Policy in XML
Mapping Assertion Elements to Code
Configuring and Applying an XML Policy
Now Where?

You've probably heard that the new version of Web Services Enhancements (WSE) for the Microsoft® .NET Framework simplifies the process of building secure Web services. What you may not know is that most of these improvements derive from some core architectural changes made in WSE 3.0. This column discusses what's changed and examines the major migration issues you'll face in moving to WSE 3.0.

Major Changes in WSE 3.0

MSDN® recently published an article entitled "What's New in WSE 3.0," written by Mark Fussell, Lead Program Manager on the WSE team. This article summarizes the major changes and improvements to the product since WSE 2.0, including integration with the .NET Framework 2.0 and Visual Studio® 2005, simplified security through a new-and-improved policy architecture, and support for sending large amounts of data with Message Transmission Optimization Mechanism (MTOM). Additional changes include improved session management (via WS-SecureConversation), support for hosting ASMX services outside of IIS, and support for the latest WS-* specifications.

Although the WSE team tried hard to ensure backward compatibility, some of these changes affect the core programming model that you must address to update existing WSE 2.0 solutions.

The good news is, once you've migrated to WSE 3.0, you'll have more communications options. WSE 3.0 supports the latest WS-* specifications—the same ones supported by Windows® Communication Foundation (WCF)—making WSE 3.0 and WCF wire-compatible. This means WCF clients can interoperate with WSE 3.0 services and vice versa, removing the need for continued migration.

Migrating Projects from WSE 2.0 to WSE 3.0

When it comes to moving existing code forward to WSE 3.0, integration with the .NET Framework 2.0 and Visual Studio 2005 is most important. WSE 3.0 is built on the .NET Framework 2.0 and integrated with Visual Studio 2005—it does not run on the .NET Framework 1.1 nor does it integrate with Visual Studio .NET 2003. Hence, when migrating WSE 2.0 solutions forward, the first step is to move your .NET Framework 1.1 solutions forward to the .NET Framework 2.0. Luckily, Visual Studio 2005 provides automated migration tools that greatly simplify this step.

You can start migrating an existing Visual Studio .NET 2003 solution simply by opening it in Visual Studio 2005. This launches the Visual Studio Conversion Wizard, where automatic conversion to the new Visual Studio 2005 format begins. Once the conversion is complete, the wizard produces a report so you can see what it did and inspect any errors or warnings. Now you can build the solution, and the compiler will help with the code changes you'll need to get up and running against the .NET Framework 2.0.

Next, you need to re-integrate WSE 3.0 with your solution. Remove the reference to Microsoft.Web.Services2.dll, add a reference to Microsoft.Web.Services3.dll, update your configuration file, and revise any code affected by changes to the WSE 3.0 class library.

The easiest way to get started is to open the WSE configuration tool by right-clicking on each project and selecting WSE Settings 3.0. If the tool detects a WSE 2.0 configuration section in the existing config file, it displays a dialog asking you if you'd like it to automatically import the settings to the new WSE 3.0 configuration section. Say yes and the tool makes the necessary changes to the file (or at least as many as it can). It does things like register the WSE 3.0 configuration section, change the <soapExtensionType> element to the new <soapServerProtocolFactory> element, and move most of the existing WSE 2.0 settings over to the new WSE 3.0 configuration section. Also, the tool automatically updates the reference to Microsoft.Web.Services3.dll for you.

You can also launch the WSE 3.0 configuration tool in standalone mode (outside of Visual Studio .NET) from the Start menu. You'll need to select a configuration file manually (using the File menu), but it will still migrate WSE 2.0 settings forward.

Although the WSE 3.0 configuration tool gives you a great start on the migration process, it doesn't take care of everything. When the tool detects configuration items that don't move forward automatically, it leaves them in place. Then it displays the warning dialog stating that some errors were found, advises you to take a look at the log, and asks if you want to continue. It writes the specific details that need attention to a log file (WseSettingsErrors.log). This is a cue that you have more work to do.

The next step in the process is to deal with any changes that break your code. Breaking changes can be classified into four groups: general class library, Direct Internet Message Encapsulation (DIME)/MTOM, security, and policy/filters.

General Class Library

Although the WSE 3.0 configuration tool will automatically update your project with the new assembly reference and the new configuration information, you still have to manually update your source code to handle some changes to the WSE 3.0 class library. For example, you'll have to update all source files to use the new WSE 3.0 namespace (Microsoft.Web.Services3). Global search-and-replace usually works for this.

Figure 1 highlights the main namespace differences between WSE 2.0 and WSE 3.0. Since most of the architectural changes occurred in the area of policy, WSE 3.0 changed the policy namespace to Microsoft.Web.Services3.Design. As you may have noticed, a few WSE 2.0 namespaces have been removed and a few new ones have been added.

Figure 1 WSE 2.0 to WSE 3.0 Namespace Changes (Close Figure 1)

WSE 2.0 Namespace | Corresponding WSE 3.0 Namespace
Microsoft.Web.Services2 | Microsoft.Web.Services3
Microsoft.Web.Services2.Attachments | (removed)
Microsoft.Web.Services2.Dime | (removed)
Microsoft.Web.Services2.Policy | Microsoft.Web.Services3.Design
Microsoft.Web.Services2.Policy.Assertions | Microsoft.Web.Services3.Design
Microsoft.Web.Services2.Policy.Configuration | Microsoft.Web.Services3.Configuration
Microsoft.Web.Services2.Security.Policy | Microsoft.Web.Services3.Design
Microsoft.Web.Services2.Security.X509 | System.Security.Cryptography.X509Certificates
(didn't exist—similar to Dime classes) | Microsoft.Web.Services3.Messaging.Framing
(didn't exist) | Microsoft.Web.Services3.Mime

Aside from namespace changes that have been made for WSE 3.0, many of the core library classes have remained intact. For example, typical WSE 2.0 messaging applications (those that use SoapSender/SoapReceiver) should migrate cleanly without a hitch. You simply have more choices now with the new support for ASMX. The areas of the class library that have changed are discussed in the following sections. If your solution compiles after making the necessary namespace changes, it should run as is on WSE 3.0, which still uses the WSE 2.0 pipeline by default when no policy has been applied to the application.

DIME/MTOM

Another area that will need attention is any WSE 2.0 code dealing with DIME attachments. For example, the following WSE 2.0 code returns a JPEG file to the caller as a DIME attachment:

[WebMethod]
public void GetFile(string fileName)
{
   SoapContext respContext = ResponseSoapContext.Current;
   DimeAttachment dimeAttach = new DimeAttachment(
     "image/jpeg", TypeFormat.MediaType, fileName);
   respContext.Attachments.Add(dimeAttach);
}

In WSE 3.0, the Microsoft.Web.Services2.Dime namespace has been removed from the assembly (there is no longer a DimeAttachment class) and the Attachments property has been removed from the SoapContext class. Therefore, this code must be modified to use the new WSE 3.0 MTOM model.

With MTOM, you simply model binary data (what you used to pass as a DimeAttachment) as a byte array in the signature, as shown in the following:

[WebMethod]
public byte[] GetFile(string fileName)
{
    byte[] response = File.ReadAllBytes(filePath);
    return response;
}

Next, you enable MTOM via the WSE 3.0 configuration section (take a look at the Messaging tab in the configuration tool) or by using the RequireMtom property on a proxy. Once enabled, all transmitted byte arrays are automatically encoded using the MTOM optimizations.

If you are currently passing multiple DimeAttachments with a single message, you can port this scenario to MTOM by encapsulating multiple byte arrays in a single class that you use in the signature. The MTOM support automatically encodes all byte arrays it finds in a message.

Security

WSE 3.0 greatly simplifies the process of securing Web services. A suite of turnkey security policy assertions covers the most common cases and scenarios. This new approach to security is built on the improved WSE 3.0 policy framework (more on this shortly).

If you currently use the WSE 2.0 policy framework, focus on moving your current policy files over to the new WSE 3.0 policy format and the new turnkey assertions. Manually running the WSE 3.0 security policy wizard is the easiest way to do this. Since the WSE 2.0 policy approach required very little code, you won't have to touch many source files, just the code that supplies tokens at run time. For example, you'll no longer use PolicyEnforcementSecurityTokenCache (removed in WSE 3.0); instead you'll use SetClientCredential<T>.

If you wrote imperative security code by interacting with the Security property of SoapContext directly, you'll have a little more work to do. The Security property has actually been deprecated in WSE 3.0, so if your code references it, you'll get a compiler warning that indicates you should consider writing a security filter instead. I'll explain this later.

The security classes themselves haven't actually changed much, just how you interact with them. Instead of performing security tasks directly within a WebMethod, WSE encourages you to do it within a security filter implementation that will reside in the WSE pipeline, which makes the security code more reusable. Most of your actual security code can be moved to a security filter class without changes. I'll discuss how to write custom filters and policy assertions in the sections that follow.

The only major class library change on the security front is that the Microsoft.Web.Services2.Security.X509 types have been replaced by equivalent types that now ship with the .NET Framework 2.0 (see System.Security.Cryptography.X509Certificates).

Policy/Filters

It seems lower-level extensibility points are always the hardest to migrate, and that definitely holds true for WSE 3.0. The biggest changes to the WSE 3.0 class library result from new policy architecture. The WSE 3.0 policy framework not only replaces the policy framework introduced with WSE 2.0, it also replaces the old mechanism for working with filters in the pipeline. You need to understand the differences between the WSE 2.0 and WSE 3.0 policy frameworks and how the WSE 3.0 pipeline works to migrate security, policy, and filter-related code.

The WSE 2.0 policy framework relied on WS-Policy as the format for expressing policy. It supported most of the policy assertions defined in the WS-PolicyAssertion and WS-SecurityPolicy specifications. In addition to these built-in assertions, there's an extensibility mechanism for custom policy assertions.

A WSE 2.0 wizard generated security policies containing the built-in security assertions (primarily <Integrity> and <Confidentiality>). The policy was then associated with an application via its configuration file. The policy file itself contained a mapping section to associate policy elements with specific service endpoints based on their URIs. At run time, WSE 2.0 inspected the configured policy file to determine which policy to enforce while sending/receiving messages at a particular URI.

The WSE 2.0 policy framework was implemented as a filter placed nearest to the application in the WSE pipeline (see Figure 2). All policy logic was performed during this particular step of the pipeline. WSE 2.0 made it possible to inject custom code into the policy step by writing and configuring custom policy assertions. WSE 2.0 also gave you direct control over the configuration and manipulation of the entire WSE pipeline. You could inject code into the pipeline before or after the policy step by writing and configuring custom filters.

Figure 2 Policy in the WSE 2.0 Pipeline
Figure 2 Policy in the WSE 2.0 Pipeline

The WSE 2.0 policy framework contained several shortcomings. First, the different extensibility options often confused matters. It wasn't clear when to write a custom filter and when to write a custom policy assertion.

Second, using WS-Policy as the syntax for configuring policies was far too complicated and verbose for most developers. This forced developers to rely heavily on the security policy wizard and left them somewhat stranded when they had to tweak settings not exposed by the UI.

Third, the mapping mechanism for associating policies with endpoints sometimes led to mismatches. WSE 2.0 attempted to locate the appropriate policy (from the configured policy file) using the target URI of the incoming/outgoing message. Often, you weren't sure if the right policy was actually being applied due to potential URI differences. And each time you changed the location of your services (such as after deployment or by requiring HTTPS on a directory), you had to update all of your policy files to match the current addresses.

The last major shortcoming was that the supported assertions required you to think in terms of low-level building blocks, even while using the nice security policy wizard. For example, you had to specify the requirements of a request message independently from the corresponding response message. In each case, you had to make decisions about signatures and encryption and what type of token to use for each without much direction or best practices. This often led to security policies that weren't very secure.

Policy in WSE 3.0

The most fundamental change in WSE 3.0 is that the policy framework is no longer implemented as a filter in the pipeline. Instead, WSE 3.0 actually uses policy to drive the overall creation of the pipeline (see Figure 3). It essentially merges the worlds of policy assertions and pipeline filters, offering a single unified model for configuring and extending WSE behavior.

Figure 3 Policy in the WSE 3.0 Pipeline
Figure 3 Policy in the WSE 3.0 Pipeline

WSE 3.0 accomplishes this by making policy assertions and pipeline filters one and the same. When WSE 3.0 initializes, it inspects the supplied policy and builds the pipeline out of the filters corresponding to the specified assertions. If the application is not configured to use policy, it simply creates a default pipeline configuration equivalent to what was used in WSE 2.0.

Since WSE 3.0 uses policy to configure the pipeline, it no longer includes a <filters> element in its configuration section and the configuration tool no longer includes a Filters tab. All pipeline configuration is now accomplished via policy. The corresponding WSE 3.0 classes have also been revised to accommodate this architectural shift.

Another significant change is how you express policy in WSE 3.0. Instead of using the vocabulary defined by WS-Policy and friends, WSE 3.0 decided to define its own vocabulary for expressing policy and built-in assertions. The resulting XML is much cleaner and easier to use—some developers will even feel comfortable editing it directly without the help of a tool. However, WSE 3.0 still provides a security policy wizard for generating policies in the new format, and this is the easiest way to migrate your existing WSE 2.0 policies.

WSE 3.0 also defines a new mechanism for mapping policies to service endpoints. The WSE 3.0 mechanism is more explicit about which policy will be applied at run time. You associate a policy identifier (either a name or a type) with your service or client code. At run time, WSE will find the specified policy using the provided identifier without ambiguity. This shift alleviates the issues that result from mapping policies to service endpoints by URI.

The last major change you'll notice is that WSE 3.0 supports a different set of built-in security assertions. The new security assertions no longer focus on the low-level building blocks of integrity and confidentiality. Instead, they focus on higher-level turnkey security scenarios that represent today's best practices (such as mutual certificate authentication). A single security assertion describes the requirements for both the request and response messages simultaneously using a best-practice approach. Think of it this way: WSE 2.0 assertions focused on message-level security requirements, while the new WSE 3.0 assertions focus on operation-level security requirements.

These WSE 3.0 changes address the shortcomings described earlier, but most of them require significant changes to your code and policy files. Let's dive into some code to see how it all works.

Migrating Custom Filter Code

The major policy concepts I've discussed so far map directly to classes in the WSE 3.0 class library as outlined in Figure 4.

Figure 4 WSE 3.0 Policy and Filter Classes (Close Figure 4)

Concept | Type
Policy | Microsoft.Web.Services3.Design.Policy
Assertion | Microsoft.Web.Services3.Design.PolicyAssertion
Filter | Microsoft.Web.Services3.SoapFilter

If you are using custom filters or policy assertions in your existing WSE 2.0 solutions, you'll need to make several changes to bring your code forward to this new class design. Let's walk through the details.

Assume that your services require all incoming requests to be validated against their corresponding schema definitions, and that you've currently implemented that with a WSE 2.0 custom filter.

Writing a custom filter in WSE 3.0 is almost the same as in WSE 2.0. In WSE 2.0, there were separate base classes for input versus output filters (SoapInputFilter versus SoapOutputFilter). In WSE 3.0 there is just a single base class that you derive from: SoapFilter.

SoapFilter contains a single abstract method named ProcessMessage that you need to override, as shown here:

public class ValidationFilter : SoapFilter
{
    public override SoapFilterResult ProcessMessage(
        SoapEnvelope envelope)
    {
        ... // process the message here
    }
}

You write the code to implement the requirement at hand inside ProcessMessage. You might want to inspect the message, transform it, or modify the message context in different ways. The return type for ProcessMessage has also changed in WSE 3.0. You return a SoapFilterResult value (SoapFilterResult.Continue or SoapFilterResult.Stop) to indicate whether the pipeline should continue processing the message.

WSE 3.0 also provides some new statement management collections that are available as part of the SoapContext. You'll find properties called MessageState, OperationState, and SessionState that provide different scopes for sharing state between filters, across message exchanges, and even across multiple operations.

To continue the validation example, say I need to validate the incoming SoapEnvelope against its schema. I'll require the user of the filter to provide the XmlSchemaSet object containing the schemas required for validation in this case. I'll only allow the message to continue in the pipeline if it passes schema validation. The complete ValidationFilter implementation is shown in Figure 5.

Figure 5 ValidationFilter (Close Figure 5)
public class ValidationFilter : SoapFilter
{
    XmlSchemaSet schemas;

    public ValidationFilter(XmlSchemaSet schemas)
    {
        this.schemas = schemas;
    }

    public override SoapFilterResult ProcessMessage(
        SoapEnvelope envelope)
    {
        XmlReaderSettings settings = new XmlReaderSettings();
        settings.Schemas = this.schemas;
        settings.ValidationType = ValidationType.Schema;

        using (XmlReader r = XmlReader.Create(
            new XmlNodeReader(envelope), settings))
        {
            while (r.Read()) ; // just pass through document
        }

        return SoapFilterResult.Continue;
    }
}

If your custom filter deals with security, you need to derive from ReceiveSecurityFilter or SendSecurityFilter, depending on the direction of communication. Both of these classes derive from SoapFilter and provide an extra method related to message security. ReceiveSecurityFilter defines an abstract ValidateMessageSecurity method, while SendSecurityFilter defines an abstract SecureMessage method. Both methods hand you the Microsoft.Web.Services3.Security.Security object that you can use to perform your imperative security tasks. Check out the article "Using the Policy Framework to Secure Web Apps with Web Services Enhancements" for a complete example of how to implement a security filter.

The changes you'll have to make to bring your existing custom filters forward are pretty straightforward—simply change the base class name, modify the return type of ProcessMessage to SoapFilterResult, and modify the implementation accordingly.

With the ValidationFilter example in place, you now need to inject it into the service's receive pipeline. In WSE 2.0, you could accomplish this via the <filters> configuration element or by programmatically injecting an instance into the pipeline at run time. In WSE 3.0, you use the policy framework to configure the pipeline. The first thing you need to do is write a custom policy assertion class to wrap the filter.

Migrating Custom Policy Assertions

In both WSE 2.0 and WSE 3.0, policy assertions are classes that derive from PolicyAssertion. However, the structure of the PolicyAssertion class and the role it plays in the pipeline are completely different.

In WSE 2.0, the role of a PolicyAssertion class was to validate and enforce requirements on incoming and outgoing messages. In WSE 3.0, the role of a PolicyAssertion class is to serve as a factory for SoapFilter objects—in this case the SoapFilter objects validate and enforce the requirements. WSE 3.0 calls each PolicyAssertion class within the configured policy as it builds the send/receive pipeline within a client/service.

In WSE 3.0, you write a custom policy assertion by deriving a new class from PolicyAssertion and overriding its abstract members. The code in Figure 6 illustrates how to get started on your ValidationAssertion.

Figure 6 ValidationAssertion (Close Figure 6)
public class ValidationAssertion : PolicyAssertion
{
    public override SoapFilter CreateClientInputFilter(
        FilterCreationContext context)
    {
        return null;
    }

    public override SoapFilter CreateClientOutputFilter(
        FilterCreationContext context)
    {
        return null;
    }

    public override SoapFilter CreateServiceInputFilter(
        FilterCreationContext context)
    {
        return null;
    }

    public override SoapFilter CreateServiceOutputFilter(
        FilterCreationContext context)
    {
        return null;
    }
}

PolicyAssertion defines four abstract methods that you must implement: CreateClientInputFilter, CreateClientOutputFilter, CreateServiceInputFilter, and CreateServiceOutputFilter. When the policy assertion is used in a client policy, WSE calls the CreateClientXxx form, where Xxx is InputFilter while building the receive pipeline and OutputFilter when creating the send pipeline. When the assertion is used for a service policy, the calls take the form CreateServiceXxx.

These methods give you an opportunity to inject code into the various WSE pipelines as they're built at run time. Notice that each method returns a SoapFilter. WSE takes the returned SoapFilter object and places it in the corresponding pipeline in the order it occurs in the policy. Therefore, the order of assertions in a policy is significant. The first assertion always ends up nearest to the application in the pipeline, while the last assertion always ends up nearest to the wire (see Figure 4).

You don't have to return a SoapFilter from each method unless you actually want it in the corresponding WSE pipeline. For example, if a particular policy assertion never applies on the client side, you can return null from the CreateClientInputFilter and CreateClientOutputFilter methods. In the validation example, you only care about validating messages received by the service, so CreateServiceInputFilter is the only factory method that needs to return a ValidationFilter object.

In this particular example, you need to allow users of the ValidationAssertion class to specify the location of schemas needed for validation. This is so you can load them into an XmlSchemaSet object and supply it to the new ValidationFilter. One way to accomplish this is to have users specify the location in the constructor. The code in Figure 7 shows a sample implementation of the ValidationAssertion example.

Figure 7 ValidationAssertion Implementation (Close Figure 7)
public class ValidationAssertion : PolicyAssertion
{
    XmlSchemaSet schemas = null;

    public ValidationAssertion() { }

    public ValidationAssertion(string schemaLocation)
    {
        LoadSchemas(schemaLocation);
    }

    private void LoadSchemas(string schemaLocation)
    {
        schemas = new XmlSchemaSet();
        foreach (string file in Directory.GetFiles(
            schemaLocation))
        {
            using (FileStream fs = new FileStream(file,
                FileMode.Open, FileAccess.Read))
            {
                schemas.Add(XmlSchema.Read(fs, null));
            }
        }
    }

    public override SoapFilter CreateServiceInputFilter(
        FilterCreationContext context)
    {
        return new ValidationFilter(schemas);
    }

    ... // remaining methods omitted
}

If you have custom policy assertions in your existing WSE 2.0 solutions, you'll need to rework them as SoapFilter implementations and then wrap them with a PolicyAssertion class like the one shown in Figure 7.

Once you have custom PolicyAssertion classes in place, you can begin using them in your client and service policies. In WSE 2.0, you have to define policies using the WS-Policy syntax. The new WSE 3.0 policy framework, however, allows you to define policies imperatively in code or declaratively using an XML vocabulary.

Defining a Policy in Code

In WSE 3.0, the Policy class models a policy containing assertions. It has a public property named Assertions (of type Collection<PolicyAssertion>) for managing a collection of PolicyAssertion instances. You can define a policy programmatically by instantiating a Policy object and adding PolicyAssertion objects to its Assertions collection, as illustrated here:

Policy myPolicy = new Policy();
myPolicy.Assertions.Add(new RequireActionHeaderAssertion());
myPolicy.Assertions.Add(new RequireSoapHeaderAssertion(
    "MyHeader", "http://example.org/myheader"));
myPolicy.Assertions.Add(new ValidationAssertion(
    HttpContext.Current.Server.MapPath("xsd")));

This policy contains three assertions. The first two are built-in assertions (part of the WSE 3.0 class library), and the last is the custom validation assertion. The first requires the WS-Addressing <Action> header to be present in the message. The second requires a custom header named MyHeader to be present in the message. The last requires the message to pass schema validation. The order of these assertions in the Assertions collection determines the order in which they will appear in the pipeline.

Instead of building the policy from scratch each time you need it, you can also derive a class from Policy and add the PolicyAssertion objects in its constructor, as shown here:

public class MyPolicy : Policy
{
    public MyPolicy()
    {
        this.Assertions.Add(new RequireActionHeaderAssertion());
        this.Assertions.Add(new RequireSoapHeaderAssertion("MyHeader",
            "http://example.org/myheader"));
        this.Assertions.Add(new ValidationAssertion(
            HttpContext.Current.Server.MapPath("xsd")));
    }
}
This packages the policy definition into a class that users can simply instantiate whenever they need it. Now that there is a policy in place, you can apply it to either a service or a client proxy.

Applying a Policy to a Service

You apply a policy type to a particular service by using the new [Policy] attribute on your Web service class—this is the new mapping mechanism I referred to earlier. The attribute can be applied to either ASMX or SoapReceiver-derived classes. Here's an example that associates MyPolicy with a simple ASMX class:

[Policy(typeof(MyPolicy))]
[WebService(Namespace = "http://example.org/math")]
public class MathService: WebService
{
    [WebMethod]
    public double Add(double x, double y) { return x + y; }
    ...

This tells WSE that the MyPolicy class should be used to build the pipeline for this service. For this to work, you need to enable WSE 3.0 within the application by using the WSE settings tool. Since it's an ASP.NET Web site in this case, you also need to enable the WSE Soap Protocol Factory (see the General tab). But you don't need to enable policy within the WSE settings tool (via the Policy tab)—you only need to do this when using XML-based policy files. In this case, I've defined the policy in code and associated it with the Web service class so that WSE has everything it needs.

Applying a Policy to a Client

You can apply a policy with a client using one of two techniques. The first technique is to call the new SetPolicy method that you'll find on all WSE 3.0-generated proxy classes. Here's an example:

MathServiceWse proxy = new MathServiceWse();
proxy.SetPolicy(new MyPolicy());
double sum = proxy.Add(2, 3);

If you want to permanently associate the policy with the proxy class, you can annotate it with the [Policy] attribute like you did on the service, alleviating the need to call SetPolicy. Here's an example of this technique:

// attributes omitted for clarity
[Policy(typeof(MyPolicy))]
[WebServiceBinding(Name="MathService",
   Namespace="http://example.org/math")]
public partial class MathServiceWse : WebServicesClientProtocol { ... }
The ability to define policies in code and apply them like this offers a great deal of run-time flexibility. It allows you to dynamically build policies on the fly, choosing to include or omit assertions based on application state or configuration settings.

It's not hard to imagine how you could specify the entire list of policy assertions in your application configuration file and load them dynamically using the techniques shown in the previous sections. This would allow you to make changes to the policy itself without recompiling or redeploying the code. To provide this flexibility, WSE 3.0 offers a built-in solution for defining policies in XML.

Defining a Policy in XML

Unlike WSE 2.0, WSE 3.0 does not use the WS-Policy vocabulary for expressing policy. Instead, it uses the following vocabulary:

<policies
   xmlns="http://schemas.microsoft.com/
   wse/2005/06/policy">
  <policy name="Policy1">
     <!-- assertions go here -->
  </policy>
  <policy name="Policy2">
     <!-- assertions go here -->
  </policy>
     <!-- additional policies can go here -->
</policies>
The policy file must contain a root <policies> element from the http://schemas.microsoft.com/wse/2005/06/policy namespace. It contains a list of <policy> elements (from the same namespace). You identify each <policy> by using the name attribute—you'll use this name to refer to the policy from within your code.

Each <policy> element contains a list of assertion elements. Each assertion element corresponds to a PolicyAssertion-derived class. The following example defines a policy named MyXmlPolicy with the same two built-in assertions used earlier:

<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
  <policy name="MyXmlPolicy">
    <requireActionHeader/>
    <requireSoapHeader name="MyHeader"
        namespace="http://example.org/myheader"/>
  </policy>
  <!-- additional policies can go here -->
  ...
</policies>

Because I'm using built-in assertions, I don't have to specify what PolicyAssertion classes the elements represent—WSE already knows. If you're editing the policy file in Visual Studio 2005, you'll even get IntelliSense® tips showing you all of the built-in assertion elements the IDE knows about (see Figure 8). WSE 3.0 also provides a new wizard for generating security policies in this format; this is the easiest way to move WSE 2.0 security policies forward.

Figure 8 Policy IntelliSense
Figure 8 Policy IntelliSense

Mapping Assertion Elements to Code

You can explicitly control the mapping between the assertion element names and the corresponding PolicyAssertion classes. The new <extension> element maps an element name to a PolicyAssertion class. The following code shows how to do this for the custom ValidationAssertion:

<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
  <extensions>
    <extension name="validation"
        type="ValidationAssertion, ValidationAssertion" />
  </extensions>
  <policy name="ValidationPolicy">
    <requireActionHeader/>
    <requireSoapHeader name="MyHeader"
        namespace="http://example.org/myheader"/>
    <validation schemaLocation="C:\services\mathservice\xsd"/>
  </policy>
</policies>

You must update the ValidationAssertion class to read the schemaLocation attribute from the <validation> element. PolicyAssertion defines the ReadXml method for this purpose. Figure 9 shows how to implement ReadXml within the ValidationAssertion class.

Figure 9 ReadXml (Close Figure 9)
public class ValidationAssertion : PolicyAssertion
{
    // remaining methods omitted

    public override void ReadXml(XmlReader reader,
        IDictionary<string, Type> extensions)
    {
        string schemaLocation = reader.GetAttribute("schemaLocation");
        reader.Read(); // advance the reader

        if (null == schemaLocation)
            throw new Exception("Expected schemaLocation attribute");

        LoadSchemas(schemaLocation);
    }
}

This implementation expects to find the schema location in an attribute named schemaLocation. Notice how it advances the reader so that it will be positioned properly for the next assertion. With this in place, users can now choose whether to use this assertion in code or XML-based policy.

Configuring and Applying an XML Policy

To apply an XML-based policy to your services and clients, you must first configure the application with the location of the policy file. You can do this by using the Policy tab in the WSE settings tool, or you can just add the following section to your application configuration file:

<configuration>
  ...
  <microsoft.web.services3>
    <policy fileName="wse3policyCache.config" />
  </microsoft.web.services3>
</configuration>

Once WSE knows where to look for your XML-based policy, you can apply it to your code by specifying the policy name (found in the policy file) when using the [Policy] attribute, as shown here:

[Policy("MyXmlPolicy")]
[WebService(Namespace = "http://example.org/math")]
public class MathService: WebService
{
    [WebMethod]
    public double Add(double x, double y) { return x + y; }
    ...
You can do the same thing when calling SetPolicy on a proxy class, as illustrated here:
MathServiceWse proxy = new MathServiceWse();
proxy.SetPolicy("MyXmlPolicy");
double sum = proxy.Add(2, 3);
Defining policies in XML and configuring your code this way offers the best compromise between flexibility and developer productivity—it's probably the approach most developers will take.

As you can see, once you've migrated your custom filters and policy assertions forward to WSE 3.0, you have to decide how to define your policies (in code or XML). Then you have to update your client/service code to apply the policies using the [Policy] attribute or the SetPolicy method, since the mapping is no longer part of the policy file itself. All of these changes require some manual intervention and even redesign.

Now Where?
WSE 3.0 Resources

To learn more about the WSE 3.0 policy framework, with a focus on the new approach to security, check out Tomasz Janczuk's article, "WSE Security: Protect Your Web Services Through the Extensible Policy Framework in WSE," in the February 2006 issue of MSDN Magazine.

Also read What's New in WSE 3.0, by Mark Fussell, and Security Features in WSE 3.0, by Keith Brown.

You will also find these resources helpful: WSE 3.0 Hands-On Lab—Exploring Security, and Web Services Policy Framework (WS-Policy).

Migrating existing solutions to WSE 3.0 requires more effort in some areas than in others. Now that you're armed with knowledge about some of the easier migration scenarios and the revamped WSE 3.0 policy framework, where the tougher migration issues exist around custom filters and policy assertions, you should be well prepared for your own migration.

Send your questions and comments for Aaron to sstation@microsoft.com.

Aaron Skonnard is a cofounder of Pluralsight, a Microsoft .NET training provider. Aaron is the author of Pluralsight's Applied Web Services 2.0, Applied BizTalk Server 2006, and Introducing Windows Communication Foundation courses. Aaron has spent years developing courses, speaking at conferences, and teaching professional developers. Reach him at pluralsight.com/aaron.


Security Briefs: A First Look at InfoCard

Security Briefs
A First Look at InfoCard
Keith Brown

Contents
What is Identity?
The Spirit of InfoCard
InfoCard: An Identity Metasystem
Establishing Digital Identity with InfoCard
Message Flow
The Laws Revisited
Sharing My Reputation
Self-Issued Cards
Get Started Today

The Web can be annoying at times. I'm certain that I'm not alone in my frustration with filling out the same old forms on every Web site I visit. Like most other techies, I've acquired many tools over the years to help combat this repetition, and I even wrote my own password manager for my hundreds of different identities on the Web. To me this constant repetition is simply an annoyance, but to someone like my mom, it can be very confusing, and often confusion can lead to security vulnerabilities, such as using the same password everywhere.

Not only is this forced fragmentation of identity on the Web annoying, it's also really limiting the Web's potential. In this column, I'll introduce you to a system that I truly hope will be a uniting force for identity on the Web. It's called InfoCard, and it's planned to be available with Windows Vista™.

I am going to talk about InfoCard purely in the domain of Web services, because that's the only place you can use the prerelease bits as I write this column (see Microsoft Federated Identity and Access Resource Kit for Sept 2005 Community Technology Preview). But in the future it will also work with browser-centric Web apps. I've used Thawte and eBay as concrete examples because they're familiar. These examples are entirely fictitious—whether or not Thawte or eBay decides to implement InfoCard is entirely up to them.

What is Identity?

What is identity, anyway? The technical implications of this question are vast (never mind the philosophical implications). How should people be identified on the Web? How should entities such as services, companies, organizations, and the like be identified?

Take me, for example. My name is Keith Brown. I live in a suburb of Denver, and you can contact me via my Web site. I like rock music, vert skating, and Go. And I'm willing to share many of these details with Web sites that I trust, if I feel there's some benefit. These are all things that I can say about myself. But other people have things to say about me as well.

On eBay, I have a stellar reputation as a buyer and a seller. On Slashdot, my posts have been ranked "insightful," and I'm an upstanding member of a number of other communities. These reputations have value, but today they are stuck in silos. When I join a new community, there's no way to prove to people that I'll be an honest, intelligent, easygoing member. I have to start from scratch at every site, because there's no common way to share my eBay or Slashdot identity (with all the reputation I've worked hard to build). Sure, I can say, "I'm Keith over on eBay," but who is going to believe me?

I've made a bunch of statements about who I am. Do you believe them? Are they assertions of fact or merely claims? That really depends on who is making the statements and how much you trust that person or organization. For example, if you wanted to trade with me on eBay, you could easily discover that eBay says I have an excellent record for paying quickly. If you regularly trade on eBay, you probably have a reasonable degree of trust in their feedback system, so this is a claim you'll accept.

One approach that InfoCard takes is that it doesn't make any assumptions about the veracity of statements made about a given identity. It's up to you to decide whether you believe the claims I've made about myself, or the claims eBay makes about me. Thus the use of the word, "claim" as opposed to something stronger, like "assertion."

In fact, this leads directly to a definition of identity from the InfoCard perspective: A digital identity is a set of claims made by one digital subject about itself or another digital subject. This definition was proposed by Kim Cameron on his identity blog (www.identityblog.com). Kim is an architect at Microsoft working on InfoCard, and he's made many important contributions to the current thinking on identity.

Oh, and one more thing, in case you think you know where I'm going. The problem of having hundreds of identities today is not solved by merging them into a single identity that's used everywhere. That would introduce more problems than it solves. InfoCard allows you to have as many identities as you need. Looking into the future, you might have one identity that's issued by your government, one that's issued by your bank, and one issued by a Web community of which you're a long-standing member with a good reputation. You will likely have several self-issued identities, a few of which contain false information! I don't know about you, but I usually lie when a Web site asks me for personal information that I don't want them to have. I really don't live at 1313 Mockingbird Lane, Anytown, USA. But it satisfies the Web site and reduces my risk. InfoCard won't change your ability to do this.

The Spirit of InfoCard

Kim Cameron has postulated that there must be a set of objective laws that govern whether any given identity system will succeed or fail in a given context. No matter how an organization positions an identity system, if it breaks these laws, it will very likely fail in some way. Perhaps the failure will be a breach of security, allowing such mischief as identity theft. Or maybe people will simply reject the system because they don't feel comfortable with it. Regardless of the reasons, the InfoCard architects don't think they will succeed without adhering closely to these laws.

Seven Laws of Successful Identity Systems

The following laws are shaping the design and implementation of InfoCard. The architects of InfoCard simply don't believe that a system that ignores any of these laws could survive.

1. User Control and Consent The user should be in control of her information. She must be able to decide which bits of information to reveal to another party.

2. Minimal Disclosure No system that asks you for personal information is 100 percent secure, so the more you disclose, the more you expose yourself to identity theft and other attacks. Stable identity systems don't disclose more information than necessary in a given context, and they use identifiers that are designed specifically for the context. Let's say the context is you checking out a book at your local library. You shouldn't have to present your Social Security number, when the library can simply issue you its own unique identifier.

3. Justifiable Parties When applying for social services, it makes sense for me to present a government-issued ID card. But when I gamble online, I'm going to use a different identity. All parties involved must have a justifiable reason for being a part of the transaction. While it might make sense to use a Microsoft Passport when downloading MSDN® subscription content, I'd rather not use it for my PayPal account.

4. Directed Identity I value my privacy. I don't broadcast it for everyone to see. However, there are many public entities that need to be easily discoverable and therefore prefer to act like beacons. Amazon.com is a beacon. It broadcasts its identity to the world via its SSL certificate. But when I shop at amazon.com, I expect them to use a private identifier to track my activity at their site. Stable identity systems must support both omnidirectional identity (beacons like amazon.com) and unidirectional identity (my private relationship with amazon.com).

5. Pluralism of Operators and Technologies No single identity system is going to suffice in all contexts, and no single identity provider is going to be justifiable in all contexts. In order to succeed, the system must make it possible for many identity providers and technologies to work together seamlessly. No single identity system will rule them all, but there can be a system of systems (a metasystem) that presents a unified abstraction to users.

6. Human Integration The end user must be considered the endpoint of the authentication protocol. As an industry, we've done a fantastic job securing messages that travel over copper wires for thousands of miles. It's the last couple of feet between the computer console and the human where most bad things happen. Phishing and other social engineering attacks exploit the vulnerable user interfaces in use today. A stable identity system mitigates these threats to the greatest possible extent.

7. Consistent Experience A stable identity system presents an easy-to-understand abstraction to the user that is consistent no matter what underlying technology or identity provider is involved. The user must be able to select the most appropriate identity for a given context, and it should feel real. Digital identity shouldn't be an abstract thing to the user. It should feel as concrete as a library card in your pocket.

So to get a feel for the environment in which InfoCard has been incubating, let me take you on a brief tour of the seven laws. I have abbreviated them in my own words in the sidebar "Seven Laws of Successful Identity Systems," but you can take a look at the complete text at The Laws of Identity.

InfoCard: An Identity Metasystem

InfoCard encompasses a lot of different tasks. It orchestrates a few of the WS-* protocols to make the secure interchange of identity information feasible. It also presents a GUI that allows a user to choose among several digital identities, each of which is represented visually as a card. Figure 1 shows a very early "wire frame" UI. I'll talk a bit more about some of the other things it does later on, but first let me introduce the three players that are always involved in any InfoCard transaction.

Figure 1 Digital Identity Made Concrete for Users
Figure 1 Digital Identity Made Concrete for Users

First is the subject. An example of a subject is a user who wants to represent herself digitally on the Internet. Second is the identity provider (IP), an organization that issues digital identities. A close analogy is a company like Thawte, the issuer of the secure sockets layer (SSL) certificate that I use to identify my company's Web site. Finally, there's the relying party (RP), which relies on digital identity for its operation. Most Web sites and Web services today allow users to submit some form of identity, anything from a simple e-mail address to a digital certificate or something more exotic.

In accordance with the fifth law of successful identity systems, InfoCard is a metasystem that establishes a few guidelines and protocols to make it possible for many different identity systems to play nicely together. Lots of people have been thinking about digital identity for quite some time, and lots of different techniques have emerged. LID, SXIP, Liberty Alliance, and Microsoft® Passport are just a few examples.

Establishing Digital Identity with InfoCard

At this point I'm going to introduce a concrete example that I can build on throughout the column. Imagine that Thawte has decided to become an identity provider for InfoCard. I visit the Web site at Thawte and use a Web Form over HTTPS to set up a digital identity that they will manage for me. I might provide an e-mail address, home phone number, snail mail address, and so on. In fact, I might even set up a few different identities that I could use in different contexts.

Depending on the type of identity I want to establish and the amount of money I'm willing to pay for their service, Thawte may authenticate me before issuing an identity. Authentication could be as simple as verifying my e-mail address, or as sophisticated as calling around to verify every detail of my identity.

For each of these identities, Thawte will send me an InfoCard, an XML document that acts as a reference to the particular digital identity I just established. How they send it to me isn't dictated by InfoCard; they could e-mail it or I could download it from their Web site. Once I receive the InfoCard, I import it onto any computers from which I want to use it. Note that the InfoCard itself doesn't actually contain any of my identity details. That phone number I gave to Thawte isn't in the InfoCard.

What the InfoCard does carry is metadata that describes the shape of the identity. This information includes what claims it has inside it, such as an e-mail address, a home phone, and so forth, and what identity technology it uses such as Security Assertion Markup Language 1.1 (SAML). It also includes who issued it (Thawte), and a unique identifier that Thawte can use to look up my identity and get the values of each of the claims. So while my actual digital identities are safely tucked away on a highly secure server at Thawte, the InfoCards I've imported onto my computer are really just a way to remember that I have those identities. An InfoCard gives you the means to use and manage a digital identity.

My corporate Web site uses Active Directory® Federation Services (ADFS) to act as an identity provider. I can sign up for another identity there. The Web site authenticates me using Kerberos and issues an InfoCard that I can use to access my corporate identity for use with partners on the Internet. I import this card onto my computer. Once I've got some cards, I can use them as long as they are valid. The Thawte identity might be valid for a year (or however long I want to pay to maintain it), and my corporate identity is valid as long as I'm employed there.

Message Flow

In order to get a feel for how an InfoCard transaction works, I'm going to walk through an example to show the flow of messages between the players. For now, I'm going to ignore an awful lot of details such as how messages are secured, which protocols are used, and so on, to make sure the basic flow is clear.

Imagine that eBay has a Web service that supports InfoCard (the relying party). I launch my eBay auction manager application, a rich client app that helps me manage the multitude of items I'm auctioning on any given day. My app knows that it needs to present a digital identity to eBay in order to manage my auction items, but it has no idea which identity I want to use. So it first asks eBay what shape of credential it supports, what identity providers it trusts, and what types of claims it needs. eBay responds:

Credential type: SAML 1.1 or 2.0
Identity Providers: Entrust, Thawte, Verisign
Requested Claims: email address

My app then takes this information from eBay and hands it off to the InfoCard system on my laptop. Up pops up a dialog that displays a list of cards that could satisfy eBay's needs. This includes all identities I've registered with Thawte that include e-mail addresses. It does not include my work identity because eBay didn't list my company as an identity provider that it trusts. In this dialog, eBay's logo is prominently displayed. Along with other countermeasures, this helps me verify that I'm not sending personal information to an imposter. Figure 1 should give you an idea of what this looks like. I click on the card that I use to sell items on eBay, and the InfoCard system makes a request to Thawte to get the one claim that eBay cares about: the e-mail address. Thawte returns a SAML token with this information.

InfoCard now pops up a view that shows me exactly what information I'm about to disclose to eBay, which is the e-mail address in the SAML token. I consent by pressing Submit, which allows InfoCard to pass the token to my application so it can fire it off to eBay and get started managing my auction items.

A lot happened here, but from my perspective as a user, I found that the interaction was straightforward. When my application connected to eBay, I was prompted to choose an identity to present, asked to confirm the personal information I was about to disclose, and then I was able to immediately start working with my auction items. And I didn't have to bother with a password. Under the covers, the strong crypto specified by InfoCard was at work ensuring the authenticity of the transaction.

The Laws Revisited

Consider this interaction in light of the laws of identity. The first law was satisfied: as the user, I was asked for consent, and shown exactly the information to be disclosed. I was also shown the logo of the relying party, so I could make an informed decision.

The second law was also satisfied. My home phone number and snail mail address were not sent to eBay, even though they were part of my digital identity maintained at Thawte. Only the information needed for that particular interaction was sent: the e-mail address. And really even that wasn't necessary; InfoCard is happy to craft a unique, opaque identifier that a relying party can use to identify you without having to know any personal information about you at all. This is called a Personal Private Identifier (PPID), and is computed as a function of the relying party's public key, the InfoCard id, and a random salt value. As a relying party, you can request a PPID if you want to track requests for someone without tying your tracking data to their personal information. This is a great way to protect the privacy of your users.

What about the third law? When I use a Web service at work, I use my corporate-issued identity. But I don't trust the sysadmins with my eBay identity. So I choose to use Thawte to manage my external identities. They have a lot more to lose if they misuse my identity and get caught doing it! There is a little circle of trust that comes together here: me, Thawte, and eBay. All parties to the transaction are justified in being there.

The fourth law also came into play. Anyone who uses InfoCard to talk to eBay sees eBay's logo. That's an omnidirectional identity, a beacon. But my relationship with eBay is private. The InfoCard system on my laptop isn't broadcasting my e-mail address via Bluetooth. Even if it wanted to reveal my e-mail address, it doesn't store that information, remember?

Because I was able to choose my own identity provider and identity technology, the fifth law was satisfied. Granted, the identity provider and relying party must support at least one technology in common for this to work, but InfoCard in no way limited which type of credential (SAML or otherwise) I used. The fifth law is really all about freedom of choice, and I'm happy to say that InfoCard buys into it.

The sixth law talks about securing the channel between the user interface and the user's brain. By using certificates with logotypes (RFC 3709), InfoCard avoids confusing the user by forcing them to look at certificates, and instead uses images that the user can recognize to identify the entities involved. There are other countermeasures that happen behind the scenes as well. For example, the entire InfoCard UI is actually hosted on a separate, more tightly secured desktop (similar to the one that WinLogon uses when you press Ctrl+Alt+Delete). Note that it's up to the identity provider to ensure that a bad guy doesn't register a certificate with a logo that looks like another company's logo. But that's exactly the sort of service that an identity provider supplies, and companies that participate will pay for that service.

Finally, the seventh law is satisfied by presenting the user with something that appears concrete: the cards themselves. And no matter what type of credential is used with the card, the user experience is the same. The user is shown a text representation of every claim that's going to be sent to the relying party, no matter what type of token or what type of claim it is. How can this be possible? InfoCard can't possibly predict all the types of credential formats that might spring up in the future. To solve this problem, the identity provider can include an optional "display token" that accompanies the real token. The display token contains a simple name-value collection that can be used to display the contents of the token to the user in text, even if InfoCard can't parse the actual token itself.

Sharing My Reputation

I hinted earlier that it would be really neat to have the choice of sharing my reputation from one community to another. If I could prove that I have a great reputation on eBay and Slashdot, I could use that as currency for other communities that I might want to join so I wouldn't have to start from scratch. And I might want to be known by the same handle at a few different online communities, so my friends know who I am in all those places. It would be up to me to make these linkages, but having that flexibility would be incredibly powerful.

This can happen under InfoCard. Say eBay decided to become not only a relying party, but an identity provider as well. In this case, I could get an InfoCard from eBay stating that I'm a reliable buyer and seller with 99.8 percent positive feedback. That's simply a claim that eBay is making about me, but remember the definition of identity: a digital identity is a set of claims made by one digital subject about itself or another digital subject.

Web communities that care about reputation might support eBay and Slashdot as trusted identity providers for these types of claims. I could now sign up for an account at one of these communities using my Thawte card, and then immediately bump up my reputation with my eBay card. I can show as many cards as I want to a community.

The downside to this is that the relying party can now correlate any identity information I give it, but it's ultimately my choice. If I trust the community and want to share more information with it, I can choose to do so. And a well-designed relying party will ask for the smallest possible amount of identifying information. I'd feel perfectly comfortable disclosing a PPID for my Thawte identity along with my eBay reputation. Even someone malicious couldn't do much with that!

Self-Issued Cards

A lot of the infrastructure I alluded to in my examples simply doesn't exist today. There aren't many identity providers out there that support InfoCard. The current version of ADFS doesn't yet support it either. So the InfoCard team has supplied a built-in provider that allows you to issue your own InfoCards to yourself to bootstrap things. In fact, the example cards shown in Figure 1 are actually self-issued cards.

To understand how this works, consider a traditional Web site that allows you to register with a user name and password. If that Web site accepted self-issued InfoCards, you could use a self-issued InfoCard to register. There would be no password; just select a card when prompted, and the Web site would record your PPID and make a note of the long-term key sent in the token. That key would replace the traditional password, and would be unique for every site you visit. Under the covers this key would be computed as a function of a master key, which would be generated randomly for each self-issued InfoCard, and the public key of the relying party. It would be as if you'd chosen a unique password for each Web site you visit, without actually going through the hassle of managing all those passwords yourself.

Self-issued cards are a great way to get started with InfoCard. If you have a Web service and want to make it a relying party, you can get a quick start by simply testing with self-issued cards. But in the long term, using a third-party identity provider will be much more powerful, because it enables user-constrained sharing of personal information between different systems on the Web.

Get Started Today

InfoCard is right around the corner, and it has incredible potential. On the social Web, it will pave the way for all kinds of new innovations by individuals and companies. For corporations, InfoCard will make it much simpler to connect with partners online. But InfoCard will only succeed if there are innovative people like you who are willing to take the time to build interesting relying parties and identity providers.

Subscribe to Andy Harjanto's blog (blogs.msdn.com/andyhar) if you're interested in experimenting with InfoCard today. Andy, a member of the InfoCard team, has provided links to specs and downloads (blogs.msdn.com/495649.aspx), as well as code samples for building relying parties and identity providers using Windows® Communication Foundation (WCF).

I hope this has whetted your appetite for InfoCard. In my next column, I'll open the hood and show you what makes InfoCard tick. Stay tuned!

Send your questions and comments for Keith to briefs@microsoft.com.

Keith Brown is a cofounder of Pluralsight, a Microsoft .NET training provider. Keith is the author of Pluralsight's Applied .NET Security course, as well as several books, including The .NET Developer's Guide to Windows Security, which is available both in print and on the Web. Learn more at www.pluralsight.com/keith.


Pure C++: How Templates and Generics Work Together

Pure C++
How Templates and Generics Work Together
Stanley B. Lippman

Contents
The CAI of the STL
Redesigning the STL for .NET
Templates are Non-Public Assembly Types

I'm closing out this series of columns on generic programming in the Microsoft® .NET Framework by showing how templates and generics do and do not work together, and pointing out a pitfall with the current implementation of templates under Visual C++® 2005. I've chosen to present this material through a discussion of the ongoing work in bringing the Standard Template Library (STL) to .NET. First, I'd like to get everyone on the same page by reviewing the basic elements of the STL.

The CAI of the STL

There are three primary elements to the design of the Standard Template Library: containers, algorithms, and iterators (CAI). The STL vector and list classes represent sequential containers. A sequential container maintains a first element, a second element, and so on through a last element. The program representation of a function parameter list, for example, is typically implemented as a vector holding elements of type string:

vector<string> paramlist;

The map and set classes represent associative containers. An associative container supports fast lookup. A map, for example, represents a key/value pair: the key is used for lookup, and the value represents the data you store and retrieve. To represent a telephone directory, you would declare a map with a string key and an integer value:

map<string,int> phonedir;
A multimap would allow you to support multiple phone entries for a single key.

The STL also provides a set of algorithms, including those for find, sort, replace, and merge, designed to operate over these containers. These are called generic algorithms because they are independent of both the type of element they are operating on (for example, an int, double, or string) and the type of container within which the elements are held (for example, whether it is a vector, list, or built-in array).

The generic algorithms achieve container independence by operating indirectly on the container. Rather than being passed the container, they are passed an iterator pair (first, last] marking the range of elements over which to iterate—the last element acts as a sentinel, or marker, to indicate one past the set of element, and that halts the algorithm:

sort( paramlist.begin(), paramlist.end() );
Here, begin() and end() are methods provided by all STL containers that return an iterator to the first and one past the last elements. For example, take a look at the following sequence of declarations:
void f()
{
    int ia[4] = {21, 8, 5, 13 };
    vector<int> ivec( ia, ia+4 );  // initialize ivec to ia ...
    list<int>   ilist( ia, ia+4);  // initialize ilist to ia ...

         // ...
}
Notice that ia+4 actually points to an address one past the last element, 13. (People using the STL at first trip over that distinction, and might, for example, pass in ia+3, which would cause the element 13 not to be included in the values.

An iterator provides a uniform and type-independent way to access, traverse, and compare elements of a container. Iterators are a class abstraction that provides uniform pointer operations (like ++, --, *, ==, !=) independent of whether the container is a built-in array, a list, or any other conforming STL container:

void f()
{
    // ... same as above ...

    // invoke the same generic algorithm each time ...
    sort( ia, ia+4 );
    sort( ivec.begin(), ivec.end() );
    sort( ilist.begin(), ilist.end() );
}
In each of the three invocations of sort, the resulting sequence is, of course, 5, 8, 13, 21, the fourth through seventh elements of the Fibonacci sequence.

Now, this is an idealized view of the STL that does not prove feasible in practice under both a formal and a practical constraint. The formal constraint is that not all container types support all the algorithmic operations. Neither a map nor a set, for example, support random_shuffle since any reordering of the elements would violate the container type, much as indexing into a stack would violate the semantic characteristics of a stack.

More practically, it is more expensive to use sort or find to get an element in a list container with the generic algorithms than to do the same operation on a vector. As a result, the list container type provides its own class methods, which are more efficient than the generic algorithms. Similarly, it is faster to find a map element using its lookup method rather than using the find algorithm by passing it the begin and end iterators of the map and its key.

One would wish for the algorithms to perform equally on all containers, but in fact, the algorithms are biased towards block containers and built-in arrays rather than the list container and the associative container types. In fact, when I worked with Alex Stepanov at Bell Laboratories, he referred to the generic algorithms as block algorithms.

Redesigning the STL for .NET

The first thing that must be done in order to move the STL over to .NET is reimplement the containers as common language runtime (CLR) types. For various reasons that I don't have the space to go into here, it's best to make the containers reference classes rather than value classes. For example:

// simplifying the declaration for now ...
template <class elemType>
ref class vector { ... };

template <class Key, class Value>
ref class map { ... };

In the native STL, the containers are all non-polymorphic. A declaration of a vector gives the actual vector object. The following shows an example:

// Native STL vector
//       ivec.empty() == true
//       ivec.size() == 0
vector< int > ivec;

//      ivec2.empty() == false
//      ivec2.size() == 10
vector< int > ivec2( 10 );

Under C++/CLI, however, when you declare a reference type, you define a tracking handle—the vector itself sits in the managed heap. By default, the handle is set to nullptr. Take a look at Figure 1.

Figure 1 Reference Classes (Close Figure 1)
public ref class sequence {
protected:
    vector<int> ^elems;
};

public ref class fibonacci : sequence { ... };

    // equivalent test
    if ( elems == nullptr ) ...
    if ( ! elems ) ...
}

// ivec == nullptr;
vector< int >^ ivec;

// ivec2 != nullptr ...
// ivec2->empty() == true
// ivec2->size == 0
vector< int > ^ivec2 = gcnew vector<int>;

// ivec3 != nullptr ...
// ivec3->empty() != true
// ivec3->size() == 10 ...
vector< int > ^ivec3 = gcnew vector<int>( 10 );

The next design requirement is to allow the containers to be consumed by other languages, such as C# and Visual Basic®, that do not support templates. The simplest strategy is to have the template containers implement one or more of the System container interfaces, divided into the two namespaces, as shown in Figure 2.

Figure 2 System Container Interfaces (Close Figure 2)

System::Collections:: | System::Collections::Generic::
ICollection | ICollection<T>
IList | IList<T>
IDictionary | IDictionary<K,V>

In general, you will want to support both the Collections and Generic interfaces so that clients currently using the Collections interfaces will be able to use your types. Here is how you might declare support for both:

template <class T>
ref class vector :
    System::Collections::ICollection,
    System::Collections::Generic::ICollection<T>
{ ... };

In order to implement the container interfaces of the System collection namespaces, you must also implement instances of IEnumerator and IEnumerator<T>:

generic <class T>
ref class vector_enumerator :
    System::Collections::IEnumerator,
    System::Collections::Generic::IEnumerator<T>
{ ... };
The downside of implementing a System container interface is that while it makes the elements available, it loses the operations of the STL/CLR type container. So, an additional design support is to also provide a generic shadow container type that allows the other language to consume an actual container type. There are two general strategies for doing this: the Plauger Way and the Tsao Way, named for the two primary architects, P. J. Plauger and Anson Tsao.

The Plauger Way can be thought of as providing a generic shadow type. That is, you create the shadow generic class (you could call it generic_vector). It holds a copy of the vector template. Here's an example:

generic <typename T>
public ref class vector_generic
{
     vector<T>^ m_templ;    // oops ...

public:
     vector_generic( vector<T>^ );
};
The oops comment on the declaration line of m_templ represents a constraint on the use of templates under .NET. As it happens, you cannot store a template requiring instantiation in a generic type. The reason is the different instantiation times of the two parameterized type facilities. Generics are instantiated by the runtime; templates are instantiated by the compiler. Therefore, a template can hold a generic, but a generic cannot hold a template.

The solution under the Plauger Way is to create a common generic interface from which both a template and generic are derived. For an example, see Figure 3. The solution under the Tsao Way follows from the observation that the interfaces are always references to the instance of the template container (which is instantiated in a particular assembly). Therefore, you simply provide an interface and the template implementation. The generic shadow type is eliminated.

generic<typename T>
interface class vector_interface : ICollection {...};

template<typename T>
ref class vector : vector_interface, ICollection<T> {...};

Figure 3 Common Generic Interface (Close Figure 3)
generic<typename Value>
interface class vector_interface {...};

template<typename Value>
ref class vector : vector_interface{...};

generic <typename T>
public ref class vector_generic : vector_interface
{
     vector_interface<T>^ m_templ;

public:
     vector_generic( vector_interface<T>^ );
     // ...
};

In all cases, everyone other than those within the assembly program go through the generic instance rather than the STL/CLR container. This is because templates under C++/CLR cannot be public assembly members. The next section discusses this.

Templates are Non-Public Assembly Types

In order for .NET to recognize a type, it requires two elements: the program code representing the type to be translated into the Common Intermediate Language (CIL), and metadata describing the details of the type. At the moment, unfortunately, templates are unable to be represented to .NET with either element. Templates, like destructors, don't exist for .NET.

.NET can only know of the concrete instances of a template; it can't know they are a kind of template. For example, .NET can see the CIL and metadata for vector<int> and vector<double>. What it cannot see is the shared template vector that each is an instance of. One side effect of this blindness is that you cannot reflect on templates. That is, you cannot ask of the vector<int> instance, "Are you a template? If so, please pass me your list of parameters and your class definition."

Generics, on the other hand, have direct CIL support in CLR 2.0, and an extended Reflection namespace that fully supports generic reflection. That should be one consideration in your design choice of whether to use a template or generic type in your application. A second consideration is whether it needs to be shared across assemblies. Templates cannot be shared across assemblies while generics can. Therefore, if that is of a central concern to your design, you should choose a generic class over that of a template. Otherwise, you will need to provide some form of common interface to accomplish a cross-assembly handshake such as I described for the STL/CLR design.

The reason templates are not recognized across assemblies is that .NET has an extended notion of type that includes its origin. That is, under .NET, a type has a location, unlike in native C++, where a name floats freely in a shared global space. This global name pollution makes it difficult to combine diverse components into a working application. Here's a further distinction: Namespaces provide a program-level solution, and adding a location to a type provides an assembly-level solution.

That is, globally public names are, in effect, partitioned by their assembly so that the assemblies can be combined without name conflict. Under .NET, a type has a location. This means that a vector<int> of one assembly is not recognized as the same vector<int> of a second assembly since the types are tagged by separate assembly names. Generics do not suffer this problem because of the runtime instantiation provided by the CLR.

So, given these constraints, why did we choose to provide both templates and STL/CLR? The working C++ programmer has built up an expertise with this library as well as an existing body of code. We wanted to not only provide a migration path for existing code, but also for existing expertise as well. If you have previously depended on the STL in your C++ programming, its absence under .NET was felt as a loss. Well, not with STL/CLR. Last I heard, the plan was to make it available for download when it's ready. Stay tuned! I know I will.

Send your questions and comments for Stanley to purecpp@microsoft.com.

Stanley B. Lippman began working on C++ with its inventor, Bjarne Stroustrup, in 1984 at Bell Laboratories. Later, Stan worked in feature animation both at Disney and DreamWorks and served as a Software Technical Director on Fantasia 2000. He has since served as Distinguished Consultant with JPL, and an Architect with the Visual C++ team at Microsoft.


C++ at Work: Implement Triple-Click, Subclass the Main Window

C++ At Work
Implement Triple-Click, Subclass the Main Window
Paul DiLascia

Code download available at: CAtWork0604.exe (163KB)

Q In many Windows-based apps, clicking the mouse moves the cursor and double-clicking selects the word containing the cursor. In Microsoft® Internet Explorer, if I triple-click the mouse, Internet Explorer selects the entire paragraph. I want to do this in my app, but while there's a WM_LBUTTONDBLCK, there's no WM_LBUTTONTRIPLECLICK. How can I implement triple-click?

Tony Veteri

A It's not just Internet Explorer, it's also Microsoft Word and Outlook®, though Outlook is slightly different in that triple-click selects lines instead of paragraphs. You're right, there's no WM_LBUTTONTRIPLECLICK, but it's not hard to implement one yourself. After all, what's a triple click but three clicks in rapid succession? Or a double-click and single-click in rapid succession. All you have to know is how quickly do the clicks have to arrive to count as a triple-click? To find out, you can call the appropriately named ::GetDoubleClickTime, which returns the double-click time in milliseconds. So if you get a double-click and then a single-click within this many milliseconds, it counts as a triple-click.

To implement triple-clicks, I wrote a class, CTripleClick, that lets you easily handle triple-clicks in any window. All you have to do is instantiate a CTripleClick object and call CTripleClick::Install with a pointer to your window and the message codes you want for left, middle, and right-button triple-click:

m_tripleClicker.Install(this,
  MYWM_LBTRIPLECLICK,
  MYWM_MBTRIPLECLICK,
  MYWM_RBTRIPLECLICK);
The last two message codes are optional (default=0, ignore). Once you Install your CTripleClick, it sends the appropriate message code to your window whenever the user triple-clicks one of the mouse buttons. I wrote a test program, Click3, to verify that CTripleClick works as claimed. Click3 is a simple Single Document Interface (SDI) text editor based on CEditView. Triple-clicking selects an entire paragraph of text. Figure 1 shows the view class for Click3. It has a handler, OnLbTripleClick, that handles an application-defined message, MYWM_LBTRIPLECLICK, by selecting the paragraph that contains the mouse. See CMyView::SelectPara for details of how to select a paragraph.

Figure 1 View (Close Figure 1)

View.h
#pragma once
#include "Doc.h"
#include "TripleClick.h"

/////////////////
// Standard Edit view. Handles triple-click: select entire paragraph.
//
class CMyView : public CEditView {
public:
   virtual ~CMyView();
   virtual void OnDraw(CDC* pDC);
   CMyDoc* GetDocument() { return (CMyDoc*)m_pDocument; }
protected:
   CTripleClick m_tripleClicker;

   CMyView();
   void SelectPara(CEdit& edit);

   DECLARE_DYNCREATE(CMyView)
   DECLARE_MESSAGE_MAP()
   afx_msg LRESULT OnLbTripleClick(WPARAM wp, LPARAM lp);
   afx_msg LRESULT OnMbTripleClick(WPARAM wp, LPARAM lp);
   afx_msg LRESULT OnRbTripleClick(WPARAM wp, LPARAM lp);
   afx_msg int OnCreate(LPCREATESTRUCT lpcs);
};
View.cpp
#include "StdAfx.h"
#include "View.h"
#include "Click3.h"

...

const UINT MYWM_LBTRIPLECLICK = WM_APP + 1;
const UINT MYWM_MBTRIPLECLICK = WM_APP + 2;
const UINT MYWM_RBTRIPLECLICK = WM_APP + 3;

LPCTSTR FINDPARA = _T("\r\n\r\n");

////////////////////////////////////////////////////////////////
// CMyView
//
IMPLEMENT_DYNCREATE(CMyView, CEditView)
BEGIN_MESSAGE_MAP(CMyView, CEditView)
   ON_WM_CREATE()
   ON_MESSAGE(MYWM_LBTRIPLECLICK, OnLbTripleClick)
   ON_MESSAGE(MYWM_MBTRIPLECLICK, OnMbTripleClick)
   ON_MESSAGE(MYWM_RBTRIPLECLICK, OnRbTripleClick)
END_MESSAGE_MAP()

CMyView::CMyView() {}

CMyView::~CMyView() {}

int CMyView::OnCreate(LPCREATESTRUCT lpcs)
{
   // install triple-click handler
   m_tripleClicker.Install(this,
      MYWM_LBTRIPLECLICK,
      MYWM_MBTRIPLECLICK,
      MYWM_RBTRIPLECLICK);
   return CEditView::OnCreate(lpcs);
}

void CMyView::OnDraw(CDC* pDC)
{
   CEditView::OnDraw(pDC);
}

LRESULT CMyView::OnLbTripleClick(WPARAM wp, LPARAM lp)
{
   SelectPara(GetEditCtrl());
   return 0;
}

LRESULT CMyView::OnMbTripleClick(WPARAM wp, LPARAM lp)
{
   TRACE(_T("CMyView::OnMbTripleClick\n"));
   return 0;
}

LRESULT CMyView::OnRbTripleClick(WPARAM wp, LPARAM lp)
{
   TRACE(_T("CMyView::OnRbTripleClick\n"));
   return 0;
}

//////////////////
// This fn selects the paragraph containing the current selection
//
void CMyView::SelectPara(CEdit& edit)
{
   int begin, end;
   edit.GetSel(begin, end);

   // Get edit control memory.
   HLOCAL h = edit.GetHandle();
   LPCTSTR text = (LPCTSTR)::LocalLock(h);

   // Search backward for paragraph break. Wimpy algorithm.
   for (; begin>0; begin--) {
      if (_tcsncmp(&text[begin],FINDPARA,4)==0)
         break;
   }

   // Search forward for paragraph break. Wimpy algorithm.
   int max = edit.GetWindowTextLength();
   for (; end<max; end++) {
      if (_tcsncmp(&text[end],FINDPARA,4)==0)
         break;
   }
   ::LocalUnlock(h);                    // unlock buffer
   edit.SetSel(begin, end);             // select paragraph
}

The code in Figure 2 shows how CTripleClick works. CTripleClick is derived from CSubclassWnd, a class I use frequently in my columns. CSubclassWnd lets you handle messages sent to another window without deriving a new window (CWnd) class. CSubclassWnd subclasses the window by installing its own window procedure that calls CSubclassWnd::WindowProc. Derived classes override this virtual function to intercept messages sent to the target window; CTripleClick overrides it to intercept mouse messages. When CTripleClick gets a double-click (WM_LBUTTONDBLCLK, WM_MBUTTONDBLCLK, or WM_RBUTTONDBLCLK), it sets a flag and calls clock() to note the clock time.

Figure 2 CTripleClick (Close Figure 2)

TripleClick.h
#pragma once
#include "Subclass.h"

//////////////////
// Triple-click handler. To use, instantiate in your window and
// call Install:
//     m_tripleClick.Install(this, MY_MESSAGE);
// where MY_MESSAGE is the message code to send when the user
// triple-clicks the left mouse button. To handle other buttons
// (middle, right), call with additional message codes.
//
class CTripleClick : public CSubclassWnd {
protected:
   enum { LEFT=1, MIDDLE, RIGHT };    // codes for which button
   UINT m_uMsgs[4];                   // callback message codes (1-offset)
   UINT m_uWhichButton;               // which button clicked?
   UINT m_uTimeLastDblClk;            // clock time of last double click
   UINT m_uClocksPerDblClk;           // max clocks for double-click

   // Return enum/button code or FALSE if not a double-click.
   UINT IsDoubleClick(UINT msg)
   {
      return msg==WM_LBUTTONDBLCLK ? LEFT :
         msg==WM_MBUTTONDBLCLK ? MIDDLE :
         msg==WM_RBUTTONDBLCLK ? RIGHT : FALSE;
   }

   // Return enum/button code or FALSE if not a button-down.
   UINT IsButtonDown(UINT msg)
   {
      return msg==WM_LBUTTONDOWN ? LEFT :
         msg==WM_MBUTTONDOWN ? MIDDLE :
         msg==WM_RBUTTONDOWN ? RIGHT : FALSE;
   }

public:
   CTripleClick() { }
   ~CTripleClick() { }

   BOOL Install(CWnd* pWnd, UINT uMsgLeft,
      UINT uMsgMid=0, UINT uMsgRight=0);
   virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp);
};
TripleClick.cpp
#include "StdAfx.h"
#include "StdAfx.h"
#include "TripleClick.h"

...

//////////////////
// Install triple-click handler. Hooks window using base CSubclassWnd.
//
BOOL CTripleClick::Install(CWnd* pWnd,
   UINT uMsgLeft, UINT uMsgMiddle, UINT uMsgRight)
{
   m_uMsgs[LEFT]   = uMsgLeft;
   m_uMsgs[MIDDLE] = uMsgMiddle;
   m_uMsgs[RIGHT]  = uMsgRight;
   return HookWindow(pWnd);
}

//////////////////
// Window got a message: Look for double-click followed quickly by
// button-down.
//
LRESULT CTripleClick::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
   if (IsDoubleClick(msg)) {
      m_uWhichButton = IsDoubleClick(msg); // save which button
      m_uTimeLastDblClk = clock();      // ..and current time
      // update double-click time. This is inefficient, but
      // expedient because I don't have to worry about WM_SETTINGCHANGE.
      m_uClocksPerDblClk = GetDoubleClickTime() * CLOCKS_PER_SEC / 1000;

   } else if (IsButtonDown(msg) && IsButtonDown(msg)==m_uWhichButton) {
      CSubclassWnd::WindowProc(msg, wp, lp); // do default operation
      if ((clock() - m_uTimeLastDblClk) < m_uClocksPerDblClk) {
         // if this is a triple-click, send callback message to client
         msg = m_uMsgs[m_uWhichButton]; // callback message to send
         m_uWhichButton = FALSE;          // reset state
         return SendMessage(m_hWnd, msg, wp, lp); // send the message!
      }
   }

   return CSubclassWnd::WindowProc(msg, wp, lp);
}

The clock time is the number of clock ticks since the process started. How many clock ticks does a clock-ticker tick? The #define symbol CLOCKS_PER_SEC tells the answer: 1000. That is, a thousand ticks per second, or a millisecond per tick. On my system, the double-click time is 480, or about half a second.

If CTripleClick sees one of the button-down messages (WM_LBUTTONDOWN, WM_MBUTTONDOWN or WM_RBUTTONDOWN), and the button is the same one that was double-clicked, CTripleClick compares the clock times of the two events. If the elapsed time is less than the double-click time as defined by ::GetDoubleClickTime, CTripleClick sends a triple-click message to its client's window, whichever message code the client supplied. For details, see Figure 2.

Figure 3 Mouse Control Panel
Figure 3 Mouse Control Panel

Performance-conscious readers have already noticed that CTripleClick calls ::GetDoubleClickTime every time it gets a double-click message. This is somewhat inefficient since the double-click time rarely changes. I said rarely, because it can change. The user can use the Mouse control panel applet to change the double-click time (see Figure 3). That means you can't call ::GetDoubleClickTime to grab the double-click time when your app first starts, because the user can change it while your app is running. To get the current value, you must call ::GetDoubleClickTime when you need it. Alternatively, you can store the double-click time and handle WM_SETTINGCHANGE to update your stored value whenever it changes. Which leads me naturally to the next question.

Q I'm trying to write a component (child control) that displays some text using the same font as the current menu font. My application calls SystemParametersInfo to get the NONCLIENTMENTRICS with the menu font in lfMenuFont. Currently my application calls SystemParametersInfo to get the menu font whenever I need to display it. It would be more efficient to load the font once when my control is initialized, then handle WM_SETTINGCHANGE to reload it if the user changes the menu font. The problem is that Windows® only sends WM_SETTINGCHANGE to the top-level window; my window is a child window. I can make the client app call my control when the font changes, but I would prefer to update my font automatically, without making clients call a function. How can I handle WM_SETTINGCHANGE from my child control?

Tom Ng

A You're right to look for a way to handle WM_SETTINGCHANGE on your own. Polite programmers always strive to make their code as self-contained and easy to use as possible—even for other programmers. But alas, Windows was designed in the bygone time of monolithic apps that exercised total control. In those days, it made sense to send messages like WM_SYSCOLORCHANGE and WM_FONTCHANGE to top-level windows— what else was there? The idea of separately installable controls and components hadn't been conceived yet or, if it had, was beyond the technical capacity of Windows. But today we dwell in a Tinkertoy world where programmers cobble apps from pluggable pieces. If the user changes the menu font or system colors, you want to update your control automatically—and don't bother the app about it, please!

So how do you handle WM_SETTINGCHANGE in your control when this message goes to the main window? By subclassing, of course. The same CSubclassWnd class I described in the previous question works here, too. Just derive from CSubclassWnd and override the virtual WindowProc function to handle WM_SETTINGCHANGE. If you don't want to use CSubclassWnd, you can write your own class and window proc—but one way or another you have to subclass the main window in order to intercept WM_SETTINGCHANGE. Then you can examine WPARAM to see what changed. If it's the font, update your font. In practice, many programmers don't bother to check WPARAM, but simply update all stored parameters whenever any one of them changes:

case WM_SETTINGCHANGE:
  UpdateMySystemSettings();
  break;
Of course, to subclass the main window you have to find it first. If you have a handle or CWnd pointer to the client window, you can call GetParent repeatedly until you get the top-level window, or use MFC's CWnd::GetTopLevelParent. If you don't have a client window, you can call AfxGetMainWnd. And if that fails too, you can use the technique I described in my July 2002 column (see C++ Q&A: Get the Main Window, Get EXE Name): call EnumWindows to iterate all the top-level windows, looking for one whose process ID matches the running process. A process can have more than one top-level window, but for the purpose of catching WM_SETTINGCHANGE, any one will do. Just make sure you don't find a modeless dialog that's transient (hint: check the window class name).

Since handling WM_SETTINGCHANGE is a useful thing to do, I implemented a little class, CSystemChange, that encapsulates the grungies. In my March 2006 column, I described how to implement an event mechanism for C++ (see C++ At Work: Event Programming, Part 2). CSystemChange is a singleton C++ class that catches WM_SETTINGCHANGE and several other main-window system-change messages, and translates them into C++ events any class can handle—even non-window classes. Just derive from ISystemChangeEvents, override the handler functions for the events you want to handle, and call CSystemChange::Register to register your target class:

// during object initialization
theSystem.Register(this);
Just like MFC's theApp, theSystem is a global singleton CSystemChange object. It raises the events when the system changes. Actually, there are two theSystem symbols: CSystemChange::theSystem is a static data member of CSystemChange; ::theSystem is a global reference to CSystemChange::theSystem. In code, it looks like this:
CSystemChange CSystemChange::theSystem;
CSystemChange& theSystem = CSystemChange::theSystem;
This syntactic sleight-of-hand lets me keep the CSystemChange constructor private and thus prevents anyone from creating another instance. There's no need to create another CSystemChange object, since one global is all you need for any client to register.

Figure 4 shows the event interface ISystemChangeEvents. It has an event SettingChange (OnSettingChange method) that's raised when Windows sends WM_SETTINGCHANGE, as well as events for other system-changed messages like WM_SYSCOLORCHANGE, WM_FONTCHANGE, and WM_DISPLAYCHANGE. You can handle these events by overriding the corresponding ISystemChangeEvents methods (for example, OnDisplayChange).

Figure 4 SysChange (Close Figure 4)

SysChange.h
#pragma once
#include "Subclass.h"
#include "EventMgr.h"

/////////////////
// Interface for system change events. To handle system-change events,
// derive from this and override the handlers you want.
//
class ISystemChangeEvents {
   DECLARE_EVENTS(ISystemChangeEvents);
public:
   DEFINE_EVENT2(ISystemChangeEvents, SettingChange, UINT, LPCTSTR);
   DEFINE_EVENT0(ISystemChangeEvents, SysColorChange);
   DEFINE_EVENT0(ISystemChangeEvents, FontChange);
   DEFINE_EVENT0(ISystemChangeEvents, TimeChange);
   DEFINE_EVENT2(ISystemChangeEvents, InputLangChange, UINT, LANGID);
   DEFINE_EVENT0(ISystemChangeEvents, UserChanged);
   DEFINE_EVENT2(ISystemChangeEvents, DisplayChange, UINT, CSize);
   DEFINE_EVENT2(ISystemChangeEvents, DeviceChange, UINT, DWORD_PTR);
   DEFINE_EVENT0(ISystemChangeEvents, ThemeChanged);
};
IMPLEMENT_EVENTS(ISystemChangeEvents);

//////////////////
// Singleton class to raise system change events.
//
class CSystemChange : public CSubclassWnd
{
protected:
   CEventMgr<ISystemChangeEvents> m_eventmgr;
   CSystemChange() { }
   ~CSystemChange() { }
   static HWND FindMainWindow();

public:
   static CSystemChange theSystem;
   BOOL Register(ISystemChangeEvents* client, CWnd* pWnd=NULL);
   BOOL Unregister(ISystemChangeEvents* client)
   {
      m_eventmgr.Unregister(client);
   }
   virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp);
};

// Global, like theApp. By making it a reference, I can keep ctor
// private and prevent anyone from creating an instance.
extern CSystemChange& theSystem;
SysChange.cpp
#include "StdAfx.h"
#include "SysChange.h"
#include "WinList.h"
...
// Global system object raises changed events:
CSystemChange CSystemChange::theSystem;
CSystemChange& theSystem = CSystemChange::theSystem;

//////////////////
// Register object to receive system-change events. Optional window used
// to find main top-level window. Otherwise will try to find it myself.
//
BOOL CSystemChange::Register(ISystemChangeEvents* client, CWnd* pWnd)
{
   if (m_hWnd==NULL) {
      pWnd = pWnd ? pWnd->GetTopLevelParent() : AfxGetMainWnd();
      HWND hwnd = pWnd->GetSafeHwnd();
      if (hwnd==NULL) {
         hwnd = FindMainWindow();
      }
      ASSERT(hwnd); // app has no main window?!
      VERIFY(HookWindow(hwnd));
   }
   m_eventmgr.Register(client);
   return TRUE;
}

//////////////////
// Find (a) main window for current running process: look for top-level
// window with same process ID.
//
HWND CSystemChange::FindMainWindow()
{
   DWORD myProcessId = GetCurrentProcessId();
   CWinList wins;
   for (WINLIST::iterator it=wins.begin(); it!=wins.end(); it++) {
      HWND hwnd = *it;
      DWORD pid;
      GetWindowThreadProcessId(hwnd, &pid);
      if (pid==myProcessId) {
         return hwnd;
      }
   }
   return NULL;
}

//////////////////
// Main window got a message: Look for system change messages.
//
LRESULT CSystemChange::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
   switch (msg) {
   case WM_SETTINGCHANGE:
      m_eventmgr.Raise(ISystemChangeEvents::SettingChange((UINT)wp,
         (LPCTSTR)lp));
      break;
   case WM_SYSCOLORCHANGE:
      m_eventmgr.Raise(ISystemChangeEvents::SysColorChange());
      break;
   .
   . // etc.
   .
   }
   return CSubclassWnd::WindowProc(msg, wp, lp);
}

To show how it works in practice, I wrote a test program, SysMon, that monitors all the different system-changed events. SysMon uses a class, CTraceSysChange, that handles ISystemChangeEvents by displaying a message in a client-supplied window.

Figure 5 shows the messages displayed when I changed the theme on my computer. The "what" parameter for OnSettingChange is the SystemParametersInfo code for the system setting that changed; for example, code 13 (0x000D) corresponds to SPI_ICONHORIZONTALSPACING.

Figure 5 Displaying Messages
Figure 5 Displaying Messages

Internally, CSystemChange holds a data member, CEventMgr<ISystemChangeEvents>, that uses the template class CEventMgr defined in EventMgr.h to instantiate an event manager for ISystemChangeEvents. (Read my March 2006 column for a description of CEventMgr.) CSystemChange::Register calls CEventMgr::Register to register the client with the event manager. Register also calls a helper function FindMainWindow to find the main window and then subclasses it. You can pass a pointer to a CWnd to use for finding the main window; otherwise, FindMainWindow looks for AfxGetMainWnd or a top-level window with the same process ID as the running process. Once CSystemChange subclasses the main window, CSystemChange::WindowProc handles the various setting-change messages by converting WPARAM and LPARAM to type-safe parameters and raising the corresponding event. For example:

// in CSystemChange::WindowProc
switch (msg) {
case WM_SETTINGCHANGE:
  m_eventmgr.Raise(ISystemChangeEvents::SettingChange((UINT)wp,
    (LPCTSTR)lp));
  break;
You can use CSystemChange for all your system-changed event needs. Figure 6 shows the messages handled. For full details, see Figure 4 or download the source at the MSDN®Magazine Web site.

Figure 6 Messages Handled by CSystemChange (Close Figure 6)

Message | Description
WM_SETTINGCHANGE | SystemParametersInfo or policy settings have changed. WPARAM and LPARAM tell what changed.
WM_SYSCOLORCHANGE | System colors have changed—these are the colors used with GetSysColor.
WM_FONTCHANGE | A font was added or removed from the system.
WM_TIMECHANGE | The system time was changed.
WM_INPUTLANGCHANGE | The input language was changed. WPARAM is the new character set and LPARAM the locale ID.
WM_USERCHANGED | The user logged on or off.
WM_DISPLAYCHANGE | The display resolution changed. WPARAM is the new image depth (bits per pixel) and LPARAM is the new screen resolution.
WM_DEVICECHANGE | The hardware configuration changed. See documentation for details.
WM_THEMECHANGED | The Windows XP theme changed.

Happy programming!

Send your questions and comments for Paul to cppqa@microsoft.com.

Paul DiLascia is a freelance software consultant and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). In his spare time, Paul develops PixieLib, an MFC class library available from his Web site, www.dilascia.com.


{ End Bracket }: The World-Wide Telescope

{End Bracket}
The World-Wide Telescope
Jim Gray and Alexander Szalay

Someday all scientific literature and data will be online and accessible to everyone everywhere. The astronomy community has made unusually good progress toward this vision of online science in addition to addressing the associated challenges of data publication.

Much of astronomy involves comparing data from many instruments (from different parts of the electromagnetic spectrum) taken at different times. The Crab Nebula provides a good example of the temporal and multispectral nature of astronomy. The Crab Star supernova was first observed on July 4, 1054. Now we see it as the Crab Nebula—a gas cloud expanding at relativistic speeds. The cloud's core appears to be a black hole. Matter falling into this hole emits two energy beams that illuminate the cloud. Looking at this system in the X-ray spectral band shows the beams. Looking at the system in the optical and infrared spectral bands shows the gas escaping from the black hole. Each provides complementary information that together presents a fairly complete picture.

Astronomy obeys Moore's law: it is producing about two times more data each year. Current instruments typically produce nearly a terabyte per night. Managing huge data archives and processing complex data are now among the major astronomy challenges.

The Sloan Digital Sky Survey (SDSS) is a good example. It is a 5-band optical survey of the northern sky, observing about 400 million sources as images and 1 million with spectra. These spectra allow detailed studies of large star and galaxy populations. The traditional way of accessing this data is to place it in files and let users FTP the files they want to their local systems for analysis.

We built an online catalog of the SDSS data as a Web-accessible database, along with visual tools to analyze the data (SkyServer.sdss.org). The result is a SQL Server™ database with approximately 14 billion rows. It gives full GUI and SQL access to the SDSS data. Now everyone can use one of the world's best telescopes. The site has been a big success—about 10 percent of the visitors are students using online courses, but the main users are astronomers analyzing the available data.

SkyServer must support many browsers running on many platforms, so we took a thin-client approach in which most processing is done on a server that produces standard HTML. Much of the server-side logic is implemented in T-SQL stored procedures. The Web services rendering images are coded in C#. The database schema is self-documenting, and the design allows users to plug their catalog and image data into a spatial search framework and Web service. A C# variant of the spatial data search is part of the SQL Server 2005 samples. You can download a personal copy of all the SkyServer code (Web site and database) along with a version of the database from SkyServer.org.

SkyServer has quite a few Web services to give users programmatic access to the data and analysis tools, a classic service-oriented architecture. But some of the astronomy queries run for hours, so we set up a system to let users submit long-running jobs (the Catalog Archive Serve Jobs System, or CASJOBS). We also allowed users to create personal databases (MyDB) near the server. MyDB stores intermediate results and uploaded user data, allowing users to do multistep analyses on huge datasets. The article "Batch is back: CasJobs, serving multi-TB data on the Web,", describes this system.

The International Virtual Observatory Alliance is a grassroots group of astronomers who want to federate world-wide astronomy data, cross-index it with the literature, and provide analysis tools to the community. The SDSS data is now part of this federation. A user can point to a SkyServer object and find all the literature on that object, as well as all other public data sets about that object. This is all architected using Web services defined by the IVOA. The IVOA is also defining a standard schema and ontology for astronomy data (www.ivoa.net/Documents).

We are now much closer to reaching the goal of the World-Wide Telescope federating all the world's astronomy data and literature. OpenSkyQuery.Net offers a prototype federating 29 archives. The astronomy community is well on the way to building the next generation of this design.

Jim Gray works at Microsoft Research and focuses on eScience scaleable systems and databases. He has been active in building online databases like terraService.Net and SkyServer.sdss.org. See research.microsoft.com/~gray.

Alexander Szalay is a Professor at the Johns Hopkins University, both in the Department of Physics and Astronomy, and the Department of Computer Science. He is the Project Director of the NSF-funded National Virtual Observatory. See www.sdss.jhu.edu/~szalay.