 MSDN Magazine January 2003

MSDN Magazine January 2003

Design: Inspect and Optimize Your Program's Memory Usage with the .NET Profiler API

Developers using .NET often make memory leak tracking a low priority because the common language runtime takes care of garbage collection. What few developers realize, however, is that their objects' lifespans, along with their size and what other objects have been instantiated, all affect how they are cleaned up. Depending on the particular circumstances, these combinations can negatively affect performance, especially over the lifetime of an application. This article presents a way for developers to see memory usage and understand garbage collection using the .NET Profiler API. Along the way, a sample application to demonstrate these principles is built.

Jay Hilyard Debug: Detect and Plug GDI Leaks in Your Code with Two Powerful Tools for Windows XP

In a previous article, the author devised a simple method to detect Graphical Device Interface (GDI) objects that are not properly released by Win32-based applications on Windows 9x platforms. Because some newer versions of Windows require a slightly different approach to GDI leaks, the author has updated his techniques for those operating systems. He builds and explains two tools designed to detect and eradicate GDI leaks in applications running on Windows XP, Windows 2000, and Windows NT.

Christophe Nasarre Scale: Real-World Load Testing Tips to Avoid Bottlenecks When Your Web App Goes Live

Load testing should be part and parcel of every Web development effort, and it should be performed early in the process. However, if you think you can load test using your development environment, you're going to have some surprises when you go live. In this article, the authors outline the process of planning your load testing effort, considering which machines to use, how many users to simulate, which tools are right for you, and how to interpret your results.

Jeff Dunmall and Keith Clarke Test: Build Quick and Easy UI Test Automation Suites with Visual Studio .NET

The .NET Framework provides a surprising new way to quickly and easily create user interface test automation. By using objects in the System.Reflection and System.Threading namespaces, you can write automated tests in minutes instead of hours. This article walks you through the building of a typical Windows-based application that will be used as the test subject. The author then runs through the creation of a C#-based test tool that simulates clicking the test app's UI controls and checks the application's state. After the tool is built, the author explains in detail how it works so you can modify and extend it for your own use.

James McCaffrey Go Mobile: Create Compact, Robust Mobile Apps with SQL Server CE 2.0 and the .NET Compact Framework

Developers have myriad options when it comes to creating solutions for mobile devices. One of the greatest challenges facing mobile developers is finding a compact yet robust local storage solution. SQL Server CE 2.0 promises to deliver on both fronts. This new release represents a tremendous leap in terms of features and performance over its predecessor. This article will review some of the platform and tools choices developers have today. The authors will compare and contrast the significant new features in SQL Server CE 2.0 with the previous release. Following that, they will build a sample app for illustration.

Mark Brown and David Meunier Editor's Note: Elevating Test and Debug

Programming is fun. It's one of the world's great jobs, pure mental exercise with a golden payoff at the end. There's nothing like the feeling of accomplishment when that program you wrote is working, looks good, and is ready to ship.

New Stuff: Resources for Your Developer Toolbox

Wanderlust Software LLC has released licX Licensing Component for . NET, a solution for licensing applications and components written for Microsoft® . NET. Featuring drag and drop integration with existing Visual Studio® .

Theresa W. Carey Web Q&A: Releasing Memory in JScript, Bulkload Problems, and More

Edited by Nancy Michell The XML Files: Introducing XPath 2.0

Over two years ago in one of the first installments of this column, I wrote about XPath version 1. 0 (see The XML Files: Addressing Infosets with XPath for a quick review). As stated in the original specification: "XPath is a language for addressing parts of an XML document.

Aaron Skonnard Data Points: Expressions in ADO.NET

Managing relations in a database became a whole lot easier with ADO. NET. Instead of returning a single rowset to a data store, you can return a series of rowsets and relate them within a DataSet. There are numerous advantages to using related DataTables in ADO.

John Papa Cutting Edge: ASP. NET Client-side Hosting with Cassini

In the September and October 2000 issues of MSDN® Magazine I discussed how to build a client-side environment for ASP applications; that is, a serverless environment to run ASP pages (see Cutting Edge: A Client-side Environment for ASP Pages and Cutting Edge: A Client-side Environment for ASP Pages—Part 2).

Dino Esposito Advanced Basics: Automatically Generating a Web Service

Ken Spencer Basic Instincts: Implementing Callbacks with a Multicast Delegate

This month's column is a follow-up to the December 2002 installment in which I introduced the basic concepts and programming techniques associated with delegates. I am going to assume you have already read that column and that you are familiar with the fundamentals of programming delegates.

Ted Pattison .NET Column: Safe Thread Synchronization

By far, the most common use of thread synchronization is to ensure mutually exclusive access to a shared resource by multiple threads. In the Win32® API, the CRITICAL_SECTION structure and associated functions offers the fastest and most efficient way to synchronize threads for mutually exclusive access when the threads are all running in a single process.

Jeffrey Richter C++ Q&A: Progress Indicator in the Status Bar, International UI Terms

Paul DiLascia Resource File: Patterns & Practices and the Developer Roadmap

If you are looking for technical architectural guidance, check out Microsoft patterns & practices. This site offers design practice guidelines for specific software needs utilizing multiple Microsoft products within the .


Design: Inspect and Optimize Your Program's Memory Usage with the .NET Profiler API

Design

Inspect and Optimize Your Program's Memory Usage with the .NET Profiler API

Jay Hilyard
This article assumes you're familiar with C#, C++, COM, and .NET
Level of Difficulty 1 2 3
Code download available at: NetProfilerAPI.exe (1,492KB)
SUMMARY

Developers using .NET often make memory leak tracking a low priority because the common language runtime takes care of garbage collection. What few developers realize, however, is that their objects' lifespans, along with their size and what other objects have been instantiated, all affect how they are cleaned up. Depending on the particular circumstances, these combinations can negatively affect performance, especially over the lifetime of an application. This article presents a way for developers to see memory usage and understand garbage collection using the .NET Profiler API. Along the way, a sample application to demonstrate these principles is built.

Contents
Good Memory Usage
Overview of the Profiler API
Detecting Memory Usage with the Profiler API
Profiler Debugging Tips
Conclusion

P
rograms running under Windows® allocate memory in order to represent different types of resources that are needed. These allocations can be thought of as objects that encapsulate the memory and any other resource state that the program needs.

When an application runs correctly, the resources and memory that are used are freed for use by other programs in the system. There are cases, however, when something goes wrong in the application and either the resource state or the memory or both are not properly freed, which results in a resource or memory leak. These errors can be difficult to identify. The garbage collector (GC) ensures that the memory a program allocates to accomplish tasks is freed without the developer needing to worry about it.

The more you know about garbage collection, the better you can construct your program to work with it. Objects in .NET are allocated from a piece of memory known as the managed heap. The heap is described as managed because after you ask it for memory, the garbage collector takes care of cleanup. This may seem like a lot of overhead since the garbage collector has to keep track of every object allocated in the .NET common language runtime (CLR), but it actually works quite efficiently.

Objects can be small, containing a few integers or larger, holding a database connection and lots of state info. Objects can be self-contained or contain or use other objects internally. The GC's job is to determine when objects should be collected to free memory for other programs, and it does this when it thinks it is full by marking the objects that can be deleted and then removing them from the managed heap. The garbage collector thinks it's full when it attempts to allocate a new object and sees that there is no more memory available in the managed heap. When the GC attempts to allocate memory and determines that it is full, it will then attempt to clean up some of the memory you have allocated in your application in order to make room for the new object.

The GC thinks about your objects in slightly different ways and takes their differences into account when deciding when to collect an object it considers no longer useful. One of the ways it does this is that it has a set of root objects it uses to determine what objects are available for collection. A reference to an object is considered to be a root if it generally falls into one of the following categories: a global or static object pointer, all local variables and parameter object pointers on a thread's stack, or any CPU registers containing pointers to objects in the managed heap. If a reference to an object is a root reference, it may or may not have child objects associated with it that would also survive the garbage collection. The GC starts with the root objects and follows the references to find the other objects that are referenced by the root so that these objects are not collected either.

As you can see in Figure 1, there are four objects allocated in the managed heap: (S)mall, (L)arge, (F)inalized, and (R)eferenced. Assume that each object is identified by its dominant characteristic (for example, there are no small objects that are holding references or other combinations). When these objects are allocated in the heap, they will be placed immediately next to one another in memory. I also have a root reference at (G)lobal scope that is holding a reference to Z.

Figure 1 Managed Heap
Figure 1 Managed Heap

When the GC starts a garbage collection, it begins by assuming all objects are unnecessary until proven necessary. An object proves that it is necessary essentially by who it "knows" or references, or who it is referenced by or knows it. For the GC, root references provide the starting point for who knows whom. The GC follows the references from the root objects down the object hierarchy to determine if an object is reachable or could potentially be used by another object. If the object is proven to be reachable, it is not part of this garbage collection cycle. If the object is proven to be unreachable by any references, the GC marks the object collectable and it is discarded. The GC uses a "mark and compact" methodology, which means that once the GC determines that an object is garbage, another part of the GC removes the unreachable objects and compacts the space in the heap to ensure that allocations will continue to be very fast.

The GC views the objects involved in the cycles of collection in terms of generations. Every time an object is considered reachable, it gets promoted to the next generation. This means that the more objects referencing your object or the greater the scope your object operates in, the longer it will live. The GC has up to three generations currently going from 0 to 2. Generation 0 is typically populated with the smaller, short usage objects and is collected the highest number of times. This means that if you have small or little-used objects, they will be collected frequently. Generations 1 and 2 are repositories for the longer-lived and more frequently accessed objects and are subsequently collected less often. One of the fundamental assumptions in the GC is that there are more smaller, shorter-lived objects in your program and you benefit by cleaning them up more frequently. This is important to understand because how you design your system can have a big impact on how much memory you use and how long you hold it, since your working set will be large. The more memory you use, the more application performance will suffer.

Objects under 85,000 bytes are considered small and are allocated directly from the main part of the managed heap. Objects over 85,000 bytes are allocated from a special part of the managed heap referred to as the large object heap. There are two main differences in the way that the managed heap treats small and large objects. First, small objects are moved within the managed heap when it is compacted; large objects are not. Second, large objects are always considered part of generation 2, while small objects are typically considered part of generation 0. If you allocate many short-lived large objects, it will cause generation 2 to be collected more frequently. Since each subsequent generation (going from 0 to 2) is more expensive to collect, this will hurt the performance of your application.

One final aspect of garbage collection that I'd like to discuss is the concept of finalization. Finalization helps developers free resources they use in their objects at the time the objects are collected by the GC. Objects need to implement a Finalize method for this to work. The GC will call the Finalize method when the object is being destroyed to allow the object to clean up its internal resources and state. In C# and managed C++, the Finalize method is actually disguised in the syntax of a destructor (~Object) with the important difference that the Finalize method is only called when the GC cleans up the object, not when the object goes out of scope as in a true C++ destructor. Adding a Finalize method to your object means that it will always be called by the GC, but be careful because when you add a Finalize method to an object, the object will always survive the first generational garbage collection. Therefore, all finalized objects live longer. Since you are trying to let the GC clean up as efficiently as possible, only use finalization when you have unmanaged resources to clean up or in special cases where the object is expensive to create (object pools).

Let's go back to the original example in Figure 1 which has a managed heap with four objects and a root reference. If a garbage collection happens at this point (which would be due to manual intervention of the developer since none of the garbage collection criteria for launching a collection are met here), the result is that the (S)mall object will be garbage collected.

The large object survives the garbage collection because large objects are assigned to generation 2. The finalized object is noted by the GC and the Finalize method will be called, but the object itself will remain until the next garbage collection (possibly longer in some scenarios). The object with the root reference G remains; since it is a root reference, it is reachable.

Now let's assume that the next garbage collection that occurs is for generation 0-2 (which can be done by calling the System.GC.Collect method with 2 as the parameter). The (L)arge object is collected during the generation 2 cleanup and the (F)inalized object is collected during the generation 0 collection since Finalize has been called and has ended previous to the start of the collection. Only the object with the global reference is still present and thus remains for the life of the application.

Good Memory Usage

The GC takes care of memory leaks, but it doesn't prevent memory retention. As a developer, you have control over your object's lifetime. If you can reduce the working set of your application, performance will improve. If your application is designed in such a way that many objects remain alive for a long time, an argument could be made that you have a memory leak. Even though the memory is cleaned up in the end, you are still paying a performance penalty, so it is worth knowing how long your objects live.

The GC is a big help, but it only addresses one of the original types of leaks I discussed. Resource leaks are still an issue, but if you wrap your unmanaged resources in finalized classes, the GC can help ensure that you dispose of them properly. It is preferable to implement a Close or Dispose method on the object so that when your object is used, the resources can be cleaned up as early as possible, without waiting for the GC to clean them up (which could be a long time from when you stop using them). If you implement Finalize for unmanaged resource-using classes and are using the managed heap, you are reasonably safe from true leaks. Of course, this doesn't mean you should let your application's working set get chubby while processing merrily away.

Overview of the Profiler API

In order to demonstrate how much memory your application is using and how long your objects stick around, I have developed an application called MemoryUsage. MemoryUsage has two distinct parts. The first part is written as a C# application that will launch the process to be monitored and set an environment variable in the target process that indicates that the CLR should load the .NET profiler. The second part is written as the C++-based .NET profiler called MemProfiler that the CLR loads via the information in the environment variable. The .NET profiler is written using the Profiler API provided as part of the CLR, and this allows the profiler to run as part of the process being monitored and to receive notifications when certain events occur. It provides you with various notifications as the application executes. In order to receive these notifications from the CLR, you provide a callback interface as specified in the Profiler API (ICorProfilerCallback) and the CLR invokes the methods on the callback interface when the various events occur (see Figure 2).

Figure 2 Getting Notifications
Figure 2 Getting Notifications

The main profiler callback methods of interest are the following: RuntimeSuspendStarted, RuntimeSuspendFinished, RuntimeResumeStarted, ObjectAllocated, ObjectsAllocatedByClass, MovedReferences, RootReferences, and ObjectReferences.

If you are unfamiliar with the Profiler API, you can access some more in-depth information in Profiler.doc, which is located in the \FrameworkSDK\Tool Developers Guide\docs folder under your Visual Studio® .NET installation.

There are a few things to consider when using a profiler, including thread safety and synchronization and the profiler's impact on performance. The Profiler API effectively allows you to run it as part of the CLR, so you must be cognizant of synchronization issues because multiple threads will call your profiler. The Profiler API specification Microsoft® provides states that callbacks are not serialized. It's up to the developer to protect his code appropriately by creating thread safe data structures and by locking the profiler code wherever necessary to prevent multiple threads from accessing the code in parallel.

I needed to synchronize access to the object tracking system as well as a few other items in the profiler callbacks I used in order to make the information come out correctly and, more importantly, to prevent threading bugs or deadlocks.

The performance impact of profiling can be significant. Running a .NET-based application using the profiling callbacks incurs roughly a 10x slowdown in application performance due to its intrusive nature. Because of the amount of information you process when looking at all of your options (detailed later in the article), you can have an even greater impact. The point of doing this sort of profiling is to figure out what is happening. The cost for making this discovery is that the application runs slower while you are looking at your options.

The .NET CLR will pull in exactly one profiler through the environment variable COR_PROFILER. This means I cannot work on something that already has another profiler loaded. Once the profiler is established for that instance of the CLR, you get an Initialize callback. Here is where you tell the CLR which notifications you want to receive by calling the ICorProfilerInfo::SetEventMask method with a DWORD containing masked values from the COR_PRF_MONITOR enumeration. The flags you should set in your profiler are shown in Figure 3.

Figure 3 Flags (Close Figure 3)

Flag | Description
COR_PRF_MONITOR_SUSPENDS | For GC notifications
COR_PRF_MONITOR_GC | For all GC calls except ObjectAllocated
COR_PRF_ENABLE_OBJECT_ALLOCATED | For the ObjectAllocated call
COR_PRF_MONITOR_OBJECT_ALLOCATED | For the ObjectAllocated call

The CLR passes the ICorProfilerInfo interface to the profiler in order to allow the profiler to query the CLR for further information. This is used in my profiler to set the event mask and retrieve the size of objects based on the object ID, the class name from a class ID, and the class ID from an object ID.

Profilers implement the ICorProfilerCallback notifications API that is provided by the CLR in order to find out exactly what is happening in the runtime while a .NET-based application executes. I can use this in my profiler to look at certain runtime events and garbage collection notifications.

In order to see what notifications the GC provides as it goes through a garbage collection, I need to pay attention to several of the runtime callbacks. The RuntimeSuspendStarted callback will pass in a COR_PRF_SUSPEND_REASON enumeration value when it is called:

HRESULT RuntimeSuspendStarted( COR_PRF_SUSPEND_REASON suspendReason )
The reason the runtime is suspending is stored in suspendReason.

One of the enumeration values that is of interest in this context is the COR_PRF_SUSPEND_FOR_GC value. When this value is encountered, it means the application is being suspended by the CLR prior to a garbage collection request. This request could be from managed code calling the System.GC.Collect method or it could be from the CLR itself when it is determined that the GC should run. This indicates that I am about to receive callbacks from the CLR regarding the garbage collection that is pending.

Once the RuntimeSuspendFinished callback is given to the profiler, the CLR will move on and execute the GC callbacks:

HRESULT RuntimeSuspendFinished()
HRESULT RuntimeResumeStarted()
All GC callbacks will take place between the time the RuntimeSuspendFinished and the RuntimeResumeStarted callbacks occur in the profiler.

Now that you know when the fun starts and ends for the garbage collector, you can look at the callbacks related to memory usage and object lifetimes, such as ObjectAllocated:

HRESULT ObjectAllocated( ObjectID objectID, ClassID classID )
Here, objectID indicates the object identifier for the object being allocated, and classID represents the class identifier for the object being allocated.

ObjectAllocated is called every time the CLR allocates an object in the managed heap. C++ programmers can think of the ObjectID as a this pointer that moves (more on this later). When ObjectAllocated is called, the profiler is given an ObjectID and a ClassID. These two tokens allow you to identify an instance of a .NET type that has been allocated. Incidentally, if you are interested in finding out about the class of this object, the ICorProfilerInfo interface can be used from within the callback to determine the information about the type by calling the GetClassIDInfo method with the classId token. Be aware that this callback will be invoked many times for even trivial programs. Due to the number of times this is called, Microsoft wanted to make sure that the profiler writer actually wanted these notifications, so you have to specify the following flags to the SetEventMask call during profiler initialization:

COR_PRF_ENABLE_OBJECT_ALLOCATED
COR_PRF_MONITOR_OBJECT_ALLOCATED
If you do not specify both of these flags, you won't get any ObjectAllocated callbacks.

ObjectsAllocatedByClass provides enough information to determine which .NET types have had instances allocated and how many allocations per .NET type have taken place since the last garbage collection occurred:

HRESULT ObjectsAllocatedByClass( ULONG cClassCount,
                                 ClassID classIds[],
                                 LONG cObjects[] )
The variable cClassCount indicates the number of classes being reported as allocated since the last garbage collection, classIds[] represents the class identifiers for the classes, and cObjects[] indicates the count of the number of object instances allocated for the corresponding classId index in the classIds array.

This is a nice shorthand way to look at object creation, but one important difference between this and ObjectAllocated is that it does not track objects allocated in the large object heap.

In order to track objects that are allocated in the managed heap, you need to be able to follow them as they move. The MovedReferences callback helps you do this by providing the "bunches" of objects that have moved since the last garbage collection:

HRESULT Moved References(ULONG cMovedObjectIDRanges,
                        UINT oldObjectIDRangeStart[],
                        UINT newObjectIDRangeStart[],
                        ULONG cObjectIDRangeLength[])
The count of the number of elements in the array parameters is indicated by cMovedObjectRefs. It represents the number of ObjectID ranges that were moved in the managed heap. The array oldObjectIDRangeStart holds starting points for the ranges of original object identifiers before they were moved by the garbage collector, and newObjectIDRangeStart is an array of starting points for the ranges of new object identifiers after they have been moved by the garbage collector. The array cObjectIDRangeLength holds the range sizes of the moved object IDs. It corresponds to the oldObjectIDRangeStart and newObjectIDRangeStart arrays by array position.

The GC tends to move the objects that survive the collection in blocks. This being the case, MovedReferences provides you with the number of ranges of objects that moved as a result of the garbage collection, the old object IDs for the objects, the new object IDs for the objects after the move, and a count of how many objects are in each range. As you can see in Figure 4, you start out with the first object ID in the range and check the object you're interested in to see if it is between the first object ID and the first object ID plus the size of the first range. If it is, then the first object ID has a new object ID. This new object ID is determined by taking the original object ID you were checking, subtracting the first old object ID in the range, and adding the first new object ID in the range. Repeat this procedure to obtain the rest of the object IDs you are tracking.

Figure 4 IDs
Figure 4 IDs

As an example take a look at Figure 4, which shows an initial managed heap starting with objects 2 through 8. Once the GC has run, it then determines that objects 2, 4, and 6 are collectable. This results in the object ID change mappings from the old object IDs to the new object IDs.

Figure 4 lists a series of ranges that indicates what the original object IDs were and what the new object IDs will be after collection. In Figure 4, the original range was one item with an original ID of 3 that has changed to a new ID of 1. Following this pattern, the next row is also for one item with an original ID of 5 that now has an ID of 2. Finally, the last row indicates that two items were moved starting with the original ID of 7 (so 7 and 8 were moved) and starting with the new ID of 4 (so 7 became 3, and 8 became 4).

The RootReferences callback will give the profiler the root references that the GC knows about during a garbage collection:

HRESULT RootReferences( ULONG cRoots, ObjectID objectIds[] )
The variable cRoots indicates the number of root references that are currently known by the garbage collector and objectIds indicates the object identifiers for the root references.

RootReferences can be called multiple times (depending upon the number of objects that the CLR considers to be "root" at garbage collection time) since this number can be greater than the internal storage the CLR uses to keep track of them. One interesting thing to note is that you can get NULL ObjectIDs back in the array of object IDs. This happens when you have declared an object reference on the stack.

The ObjectReferences callback is called one time for every object remaining in the managed heap after the garbage collection has been completed:

HRESULT ObjectReferences( ObjectID objectId,
                          ClassID classId,
                          ULONG cObjectRefs,
                          ObjectID objectRefIds[] )
The objectId indicates the object identifier for the object that is having its references reported. The class identifier for this object instance is classId. The number of references to other objects that this object instance holds is indicated by cObjectRefs, and objectRefIds[] represents the object identifiers for each one of the objects that are referenced by this object instance.

This can be short-circuited by returning an error-based HRESULT (E_FAIL) that will stop the callbacks for each object until the next garbage collection. ObjectReferences provides you with the object IDs for all of the objects that are referenced by the original object ID passed as the first parameter. You can then see how and when these two references callbacks are used in tandem, and the profiler can build an object call graph to represent the layout of the objects in memory.

References are significant because if your object has references to it, it stays in memory longer. In order to produce a streamlined working set, you should consider the objects you create in terms of who will hold references to them and for how long.

Detecting Memory Usage with the Profiler API

Now that you have an idea of what you can do with the Profiler API calls for the GC and CLR, let's walk through the MemoryUsage sample I have created so I can illustrate some of the items I spoke about earlier. I decided to use these callbacks to tell me the following things about managed memory usage under .NET:

  • Of each .NET type that is used, how many object instances are being allocated?
  • How big are the instances of each type?
  • What notifications does the GC provide as it goes through a garbage collection and what can you find out?
  • When does the GC collect the object instances?

In order to answer these questions, I have created a bit of code to walk you through the use of a profiler to gather information about garbage collection and memory usage. There are three main pieces to the sample code (available at the link at the top of this article): the MemoryUsage application—a C# shell to help launch the target process, a C# console application called MemApp that will be the target, and lastly the C++-based .NET profiler known as MemProfiler.

MemoryUsage simply takes the path to a .NET-based application that you want it to analyze and launches the target process with the correct environment variables, allowing the CLR to load the .NET profiler (MemProfiler). The MemProfiler collects information to a log while the target application runs (MemApp). Once it ends, the MemoryUsage application loads the log of memory information I have gleaned from the GC callbacks and the object tracking I'm doing in the profiler (see Figure 5).

Figure 5 Generating a Log
Figure 5 Generating a Log

MemApp is a simple C# application that creates lots of objects and then intermittently forces garbage collection so that MemProfiler will be notified of the state of the application. The interesting code for MemApp is located in MemClass.cpp. The class running the show is called MemClass and has the Main method in it and no other members. The C# Main function in MemApp allocates a bunch of objects in different scenarios in order to exercise the garbage collector in many different ways, as shown in Figure 6.

Figure 6 Putting the GC to Work (Close Figure 6)
class MemClass
{
  static void Main(string[] args)
  {
    // This function calls GC.Collect a number of times to
    // generate activity in the profiler callbacks so that you
    // can see what is being used in memory by the CLR and how
    // the objects are treated over the life of the function.
    // You would not want to do this in your application,
    // (Don't try this at home as your performance will
    // suffer...) but it is done here for illustration purposes

    // create a bunch of objects
    // (increase the numbers here if you want to see a lot of
    // activity in MovedReferences)
    for(int j=0;j<100;j++)
    new MemTarget(10);

    // create single object to be around after collection
    MemTarget gct = new MemTarget(1);

    // collect to see if we can force relocation in Moved
    // References
    GC.Collect();

    // make a reference to the single object after the collect
    // so that the GC promotes at least one object to the next
    // generation
    System.Diagnostics.Debug.WriteLine("MemApp",gct.ToString());

    // make a few more objects to muddy the waters...
    MemTarget []tgts = new MemTarget[5];
    for(int i=0;i<5;i++)
    tgts[i] = new MemTarget(i);

    // get rid of some of the objects
    GC.Collect();

    // make a reference to the array after the collect so the
    // GC promotes some of the objects to the next generation
    System.Diagnostics.Debug.WriteLine("MemApp",tgts[2].ToString());

    // force a collection
    GC.Collect();

    // make an item that is sure to get collected next
    // collection
    new System.ArithmeticException("Not enough math used!");

    // perform the last collection
    GC.Collect();
  }
}

The MemTarget class is designed to illustrate that if you are using object references inside other objects, you may have a lot of items causing memory retention. MemTarget illustrates this scenario by having an array member that holds references to MemInnerTarget instances. The MemInnerTarget instances are created in the constructor of MemTarget and the references are then assigned to the array, as you can see in Figure 7.

Figure 7 MemInnerTarget and MemTarget (Close Figure 7)
public class MemInnerTarget
{
    // trivial data storage
    int m_y = 0;
    public void SetValue(int y)
    {
        m_y = y;
    }
    public int GetValue()
    {
        return m_y;
    }
}

public class MemTarget
{
    int m_x;
    // hold an array of references to objects
    MemInnerTarget[] m_InnerTargets;

    public MemTarget(int i)
    {
        // how many do they want?
        m_x=i;
        // make that many inner objects
        m_InnerTargets = new MemInnerTarget[i];
        for(int a=0;a<i;a++)
        {
            m_InnerTargets[a] = new MemInnerTarget();
            m_InnerTargets[a].SetValue(a);
        }
    }

    public override string ToString()
    {
        // provide the easy .NET way to display data
        string sz;
        sz = "Values: " + m_x + " ";
        for(int i=0;i<m_x;i++)
        {
sz += "Inner[" + i + "]==" + m_InnerTargets[i].GetValue() + " ";
        }
        return sz;
    }
}

MemProfiler has two main source files where most of the action takes place: MemProfilerCallback.cpp and MemState.cpp. MemProfilerCallback is the implementation of the ICorProfilerCallback interface for the profiler. MemState.cpp holds the implementation of the object tracking system that I built to keep track of memory and object lifetimes in the application. There are two types of objects I used to hold the information that I encountered: ObjectInstance and ClassDetail.

ObjectInstance holds information about the actual instances that are allocated for each class type and ClassDetail holds information about the .NET types (classes) that are used. I stored these objects in a couple of ATL map classes called Objects and Classes, respectively. For those of you who have not used the map class in ATL, it is basically a collection with a lookup key for each entry. ObjectInstance keeps track of quite a few things for the instance I am recording. It holds the object ID and class ID for the object, a history of the objects that this instance referenced, whether this instance was a root reference, a history of the object IDs that at one time were the valid object ID for this object, the size of the object, and whether the object was collected.

ClassDetail holds the class name, the class ID, and the number of times that the class has had instances allocated. When a new object ID is encountered for the first time, I add a new ObjectInstance to the tracking system and when a class ID is encountered for the first time, I add a new ClassDetail. When I am notified of object ID changes via MovedReferences, all of the ObjectInstances are updated to the new object ID so that if the object ID is reused by the CLR, it will be appropriately tracked.

Since the profiler is a COM DLL, you need to register the MemProfiler.dll from the MemoryUsage\bin directory using regsvr32 first. Once that is done, in order to monitor the MemApp application, fire up the MemoryUsage application from the MemoryUsage\bin directory and select File\Analyze from the menu. This will prompt you for the path to the application to run, so select the MemoryUsage\bin directory and pick MemApp.exe. This will launch MemApp.exe with the correct environment variables to pull in MemProfiler so that it can monitor MemApp's execution.

In order to look at the instances of objects that the program uses, I am going to use the ObjectAllocated callback. Once I know that I have allocated an object, I save the information about that object and its type as well as the size of the instance. I also save the information about this ClassID and the number of times it has been allocated. I retrieve the size of the instance through a call to ICorProfilerInfo::GetObjectSize, passing the ObjectID I obtained from the callback. This returns the size of the instance in bytes, so now I know that the CLR has used n bytes on my behalf. By keeping a list of all of these, you can see how it becomes relatively easy to determine how much memory the CLR is using from the managed heap. Now I know how many instances of each type are being allocated as well as how big the instances are.

Depending on what the program being monitored allocates, you may see only some of the garbage collection callbacks, but when you have a program using enough memory to create a large amount of garbage collection activity, you should see all of them. Either way, the callbacks will happen in the following order: ObjectAllocated (many times), ObjectsAllocatedByClass, MovedReferences, RootReferences, and ObjectReferences.

As the application executes, remember some things I noticed while building the MemProfiler. If you are going to do any inspection using the object IDs in the ranges provided via MovedReferences (to get the name associated with the object by using the ICorProfilerInfo::GetClassFromObject and ICorProfilerInfo::GetClassIdInfo methods, for example), you need to use the old Object IDs since the MovedReferences callback is performed before the objects are actually collected. The GC figures out how all of these objects will be shuffled in the managed heap and calls you back before it actually moves them. Since the Profiler documentation indicates that error checking of items passed into ICorProfilerInfo is minimal, you will immediately see some spectacular crashes if you give a bad object or class ID to the ICorProfilerInfo interface, so be warned. MovedReferences also does not fire for low memory programs, so if you run a Hello World type of program it is unlikely that it will allocate enough items and hold onto them long enough to require not only a garbage collection (which can be forced) but the moving of blocks of object IDs which would give you the MovedReferences callback.

It is possible to get a class ID for a .NET type (class) that has not been loaded yet in the ObjectAllocated callback. This means that if you tried to use the class ID to retrieve information about it from ICorProfilerInfo, you would fail. If you see this situation, usually the ObjectAllocated notification would be followed by a ICorProfilerCallback::ClassLoad notification for this class (when the corresponding assembly is loaded) after which the class ID would be valid for use with ICorProfilerInfo.

ObjectAllocatedByClass does not track allocations of the large object heap, but ObjectAllocated does. By comparing the notifications from the two, an enterprising soul should be able to figure out what is in the large object heap as opposed to the normal managed heap.

The table of information given to the profiler by MovedReferences not only indicates what object IDs have changed but also that the old IDs not represented in the new IDs section have been collected. This tells you which garbage collection (the one happening during the MovedReferences callback) the objects were collected in. How can you know that an object was collected if you don't get any MovedReferences callbacks? You can actually look at the object IDs coming into ObjectAllocated and if you get a duplicate object ID, check the class ID. If you find that the class ID is different, then you know that object instance was collected since the CLR will reuse collected object IDs for other items. If it had moved the object, it would have called you back and you would have already dealt with that. Even if the class ID is not different, the duplicate object ID indicates that the CLR recognizes this as something else, so you should too.

Figure 8 Summary Report
Figure 8 Summary Report

The application should have finished executing and now you can see the summary report that the profiler generated (see Figure 8). The log file containing the summary report will, by default, be located next to the MemoryUsage executable, allowing you to look at the results in a different editor. If you delve into the default summary report a bit, you can see the name of the log file that the information was generated to, the settings for reporting, and the reported events that the settings called for. In the default case, the only flag set is LOG_MEMORY_USAGE_SUMMARY, which says to look at what objects were allocated, the number of instances, and the different number of classes used in this application. By default, I am showing the runtime callbacks to give a bit of context, since you can see the garbage collection cycles happen. At the top of the report, you can see the path to this log file as well as which options were enabled during the log generation. The flags for reporting various types of information are listed in Figure 9 and are enabled by using the Tools | Options menu in MemoryUsage before running the target application.

Figure 9 MemProfiler Flags (Close Figure 9)

Flag | MemoryUsage Options
LOG_OBJECT_ALLOCATED | Include ObjectAllocated information
LOG_OBJECTS_ALLOCATED_BY_CLASS | Include ObjectsAllocatedByClass information
LOG_OBJECT_REFERENCES | Include ObjectReferences information
LOG_REFERENCED_OBJECTS | Include RootReferences information
LOG_ROOT_REFERENCES | Include referenced objects information
LOG_MOVED_REFERENCES | Include MovedReferences information
LOG_MEMORY_USAGE_SUMMARY | Include summary information for memory usage
LOG_MEMORY_USAGE_ALL | Include all information for memory usage
LOG_OUTPUT_DEBUG_STRING | Write log entries to debug window

The Options dialog in MemoryUsage allows you to control the type of information that will be reported. The default log file is named output.log and is located in the directory that MemoryUsage.exe is running from. You can change this on the Options dialog to wherever you prefer by using the Browse button to select a new file path.

The default information that the profiler tracks includes the summary information and some of the profiler flow notifications (runtime, garbage collections, and various summary statistics). If you want more detailed information, such as the actual instances that were allocated, sizes, who referenced whom, and more, just enable a few of the following different options.

The LOG_OBJECT_ALLOCATED flag enables the reporting of specific object instances that were reported via the ObjectAllocated callback. The report line in the log looks like this:

ObjectAllocated: System.OutOfMemoryException (0x00BA104C)
This says that you allocated an instance of the System.OutOfMemoryException class with an object ID of 0x00BA104C.

The LOG_OBJECTS_ALLOCATED_BY_CLASS flag enables the reporting of the block of information given to you by the CLR about all of the objects allocated since the last garbage collection occurred, broken down by class. The report section for this in the log looks like this:

ObjectsAllocatedByClass: 98 Classes
    ClassID: 0x79B595C4    Number of Allocations:    1

(Many more of the ClassID lines)

Total Objects Allocated since last GC:    2053
This tells you how many of each class was allocated, the class ID for each class, how many classes were represented by this callback, and the total number of objects allocated.

The LOG_OBJECT_REFERENCES flag enables the reporting of the information that is made available in the ObjectReferences callback. The report line for the basic object and its references looks like this:

ObjectReferences: ObjectID 0x00BA110C    ClassID 0x79B52404
Number of references 1    ClassName: System.SharedStatics
This line shows the name of the class of the object, its object and class IDs, and the number of references it holds.

When the LOG_REFERENCED_OBJECTS flag is enabled, the actual information about the object is reported in the ObjectReferences callback. One line will be used for every object referenced and will look like this:

ObjectReferences: ObjectID 0x00BA110C    ClassID 0x79B52404
Number of references 1    ClassName: System.SharedStatics
    References: ObjectID 0x00BA2E08    ClassID 0x79B5FBF4
Class System.Security.PermissionTokenFactory
This flag will also enable the ObjectInstance report line output by LOG_MEMORY_USAGE_ALL, as shown here:
ObjectInstance: objectId 0x00BDA530 with classId 0x7B3764FC;
Size: 36 bytes;
Allocated prior to GC 1; Survived until GC 4; Referenced 3 objects
and was a root reference [NOT COLLECTED]

        Referenced Object: objectId 0x00BB6274  count 4
        Referenced Object: objectId 0x00BDA554  count 4
        Referenced Object: objectId 0x00BDAC68  count 4

When the LOG_ROOT_REFERENCES flag is enabled, this illustrates the root references for a garbage collection cycle in the following manner:

Root Reference: ObjectID 0x00BA1AAC    ClassID 0x02D4106A    Class
System.String[]
This details the object, class IDs, and class name of the root reference.

When the LOG_MOVED_REFERENCES flag is enabled, it shows the information received through the MovedReferences callback. Since MovedReferences only fires when the GC is actually compacting and reassigning object IDs, it is not seen in lower memory applications like MemApp by default. In order to generate the sample information shown in Figure 10, I changed the original allocation of the MemTarget array from 100 to 1000 items in the first line of Main in MemClass.cs in the MemApp target program. The MovedReferences section will show all of the ranges of IDs that changed along with a total count. It will also show each object that moved, its class name, and which one of the table items caused the move.

Figure 10 Log Output (Close Figure 10)
MovedReferences:
    Table provided by MovedReferences:
        Starting Old Object ID Range: 0x00BA104C    Starting New Object
ID Range: 0x00BA1040    Size of moved ID value range        452
        Starting Old Object ID Range: 0x00BA1284    Starting New Object
ID Range: 0x00BA1204    Size of moved ID value range        468
(More items...)

       35888 references moved
    Object Types moved:
        Moved Object System.OutOfMemoryException from ObjectID 0x00BA104C
(start 0x00BA104C with 0x000001C4 items) to ObjectID 0x00BA1040
(start 0x00BA1040 with 0x000001C4 items)
        Moved Object System.StackOverflowException from ObjectID 0x00BA108C
(start 0x00BA104C with 0x000001C4 items) to ObjectID 0x00BA1080
(start 0x00BA1040 with 0x000001C4 items)
(More items...)

The LOG_MEMORY_USAGE_SUMMARY flag is on by default and the output represents the basic events happening during execution. You see an Initialize callback followed by a series of runtime callbacks interspersed with the number I assigned to each garbage collection. The end summary reports the total number of objects allocated, how many bytes were used in the managed heap, the lifetime statistics about the objects, and how many different classes were used. Figure 11 shows the default summary log entries.

Figure 11 Log Entries (Close Figure 11)
Initialize

RuntimeSuspendStarted: Garbage Collection

RuntimeSuspendFinished

***** GC 0 starting *****

RuntimeResumeStarted

***** GC 0 ending *****

RuntimeSuspendStarted: Garbage Collection

RuntimeSuspendFinished

***** GC 1 starting *****

RuntimeResumeStarted

***** GC 1 ending *****

RuntimeSuspendStarted: Garbage Collection

RuntimeSuspendFinished

***** GC 2 starting *****

RuntimeResumeStarted

***** GC 2 ending *****

RuntimeSuspendStarted: Garbage Collection

RuntimeSuspendFinished

***** GC 3 starting *****

RuntimeResumeStarted

***** GC 3 ending *****

RuntimeSuspendStarted: Garbage Collection

RuntimeSuspendFinished

***** GC 4 starting *****

RuntimeResumeStarted

***** GC 4 ending *****

RuntimeSuspendStarted: Shutdown of the CLR

RuntimeSuspendFinished

Reporting Memory Usage...
    4211 objects allocated

    Total Managed Heap Memory used in 4211 object instances: 318438 bytes

    Objects Lifetime Statistics:
        GC 0 : 2056 objects allocated before, 13 objects survived until
        GC 1 : 1347 objects allocated before, 4 objects survived until
        GC 2 : 771 objects allocated before, 20 objects survived until
        GC 3 : 21 objects allocated before, 771 objects survived until
        GC 4 : 3 objects allocated before, 1347 objects survived until
        GC 5 : 13 objects allocated before, 2056 objects survived until

    4211 instances of 169 classes used

Shutdown

Notice that objects are being allocated early in the program (before garbage collection 0 and 1 occur) and stick around until very late in the program (garbage collection 4 or 5). This indicates memory retention in my target application (as it should, since the code is designed to exhibit this behavior).

The LOG_MEMORY_USAGE_ALL flag gives you all the details about the objects that were allocated, how many instances of each class were used, and how big each instance was, along with the object instance detail. Detailed information available about each object instance, such as object lifetime, number of instances created, and sizes is shown here:

ObjectInstance: objectId 0x00BDA530 with classId 0x7B3764FC;
Size: 36 bytes;
Allocated prior to GC 1; Survived until GC 4; Referenced 3 objects
and was a root reference [NOT COLLECTED]

Instance 0x00BA104C  64 bytes  Class System.OutOfMemoryException
Instance 0x00BA108C  64 bytes  Class System.StackOverflowException
Instance 0x00BA10CC  64 bytes  Class System.ExecutionEngineException

Class: MemApp.MemTarget (0x0038512C) allocated 106 times
Class: MemApp.MemInnerTarget (0x003854A8) allocated 1011 times
This LOG_OUTPUT_DEBUG_STRING flag will tell the profiler to not only write this information to the log file, but to write it to the debug stream as well through an OutputDebugString call.

You can watch the information in the summary report being generated by running the MemProfiler.DLL under the debugger and making the target application MemApp.exe. This is a good way to get a feel for how things are working as the garbage collections occur.

Profiler Debugging Tips

The sample application I provided (MemoryUsage) does the work of setting up the proper environment for the profiler. All you have to do is run MemoryUsage and select your target application in order to see the summary output. If you'd like to get a better feel for how the profiler notifications flow by running the profiler under a debugger, I'll offer a few tips on how to do that. If you haven't actually tried to create or use a profiler with .NET, there are a few things I'd like to share that should relieve some frustration that can result from trying to debug a profiler under Visual Studio .NET.

The first item of business is to indicate to the CLR that you want to be a profiler. In order to do this, you need to set certain environment variables that the CLR is looking for. These variables indicate that your profiler should be used by the CLR.

The COR_ENABLE_PROFILING environment variable turns on the profiling capacity in the CLR and should be set to 0x1 to indicate that you want to profile. Now that you have indicated your willingness to profile, you have to tell the CLR which COM-based profiler to pull in by specifying the COR_PROFILER environment variable and setting it equal to the CLSID for the profiler.

Here is an example for enabling the environment for the MemProfiler sample I have provided:

SET COR_ENABLE_PROFILING=0x1
SET COR_PROFILER={04059918-5D57-4068-9FB8-F1AB0B24B3BC}
Now that you have the right environment, you should be able to debug, right? There is one other thing to consider here—where the CLR (the one that will be pulling your profiler in) lives. Since your .NET-based application is launched from Visual Studio .NET, it passes along the environment to the application being debugged. This means that if Visual Studio .NET doesn't have these environment variables set, it will not pass them to the .NET-based application you are trying to profile and will consequently leave you wondering why you never hit your initial breakpoint. Why go through this? Visual Studio .NET acts as a CLR host so if you just set the environment variables first, and then run devenv /useenv (to use the current environment settings), Visual Studio .NET will drag in your profiler to monitor what it's doing in its instance of the CLR. This makes it rather difficult to debug, so I add in the environment variables after I start up so that they are simply passed on to the process being debugged (MemApp.exe in this case).

In order to ensure you have the environment correct, you should use the following procedure:

  1. Open a Visual Studio .NET command prompt (from the Visual Studio .NET Tools submenu).
  2. Run the following command to start up Visual Studio .NET:
    devenv
  3. Use Task Manager to get the process ID of the devenv process.
  4. Run the GSET program (found in the bin directory in the sample code) with the following command line:
    Gset [devenv process id]
    GSET is a tool that a colleague of mine at the NuMega Lab (Yoshi Watanabe) wrote for modifying the environment of an already running process. When you run it you will see the current environment of the process (see Figure 12). You can use the New button to add the following environment variables to the development environment:
    COR_ENABLE_PROFILING      0x1
COR_PROFILER        {04059918-5D57-4068-9FB8-F1AB0B24B3BC}
  5. Load the MemUsage.sln solution and change the debugging target for MemProfiler to point at MemApp.exe in the \bin\Debug directory. Make sure that MemProfiler is the startup project.

This will set up the Visual Studio .NET IDE with the correct environment settings. Now you have a debugging environment with the profiling environment variables set properly so you can debug through the profiler.

Figure 12 Modifying a Process
Figure 12 Modifying a Process

At this point, I would suggest putting a breakpoint in the Initialize callback and running the application you want to profile. You should hit the breakpoint and be on your way to inspecting and implementing lots of cool profiling features.

Conclusion

In this article, I wanted to find out how many instances of each type were being used as well as their size, so I tracked the objects I saw in the ObjectAllocated callback and then used the ICorProfilerInfo::GetObjectSize call to get the instance size. By saving this information for each item that I received a ObjectAllocated callback for, I was able to display the information in my summary report at the end of the program.

By using a few of the runtime callbacks, specifically RuntimeSuspendStarted, RuntimeSuspendFinished, and RuntimeResumeStarted, I could see the ordering of the garbage collection notifications as it proceeded through the garbage collection. By looking at the garbage collector callbacks, I could then find statistics on the objects allocated for each .NET type, which objects were considered root references at a given time, which objects referenced other objects at a given time, and finally when the instance was collected by the garbage collector.

There is more memory involved in an application than just the managed heap, but being aware of how garbage collection works allows you to develop leaner, more efficient applications.

For related articles see:
Under the Hood: The .NET Profiling API and the DNProfiler Tool
Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework
Garbage Collection-Part 2: Automatic Memory Management in the Microsoft .NET Framework

For background information see:
Applied Microsoft .NET Framework Programming by Jeffery Richter (Microsoft Press, 2002)

Jay Hilyardis a software engineer in the BoundsChecker group with Compuware/NuMega Lab. When Jay is not busy spending time with his family or watching the Patriots, he can be reached at hilyard@attbi.com.

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


Debug: Detect and Plug GDI Leaks in Your Code with Two Powerful Tools for Windows XP

Debug

Detect and Plug GDI Leaks in Your Code with Two Powerful Tools for Windows XP

Christophe Nasarre
This article assumes you're familiar with Win32 and C#
Level of Difficulty 1 2 3
Code download available at: GDILeaks.exe (13,279KB)
SUMMARY

In a previous article, the author devised a simple method to detect Graphical Device Interface (GDI) objects that are not properly released by Win32-based applications on Windows 9x platforms. Because some newer versions of Windows require a slightly different approach to GDI leaks, the author has updated his techniques for those operating systems. He builds and explains two tools designed to detect and eradicate GDI leaks in applications running on Windows XP, Windows 2000, and Windows NT.

Contents
Windows 9x versus Windows XP
How GDI Manages Handles
Tracking Object Allocation with API Hooking
Using Stack Tracing to Monitor Allocations
Debugging to Determine DLLs to Patch
Injecting Running Code into Another Process
Debugger to Debuggee Communication
Deadlock and Timing Issues
Displaying GDI Objects
Conclusion

I
n Windows® 95, Windows 98, and Windows Me, a graphics device interface (GDI) handle is a 16-bit value, and any application can use it to call functions from the GDI API. In the March 2001 issue of MSDN® Magazine, I explained how to take advantage of the 16-bit side of these platforms to build GDIUsage, a tool that lists, compares, and displays the GDI objects used by all applications (see "Resource Leaks: Detecting, Locating, and Repairing Your Leaky GDI Code"). This article will show you how to write the same kind of tool for Windows XP. The same method that I will use here will apply equally well to Windows 2000 and Windows NT® 4.0, but for the purposes of this article, I will use Windows XP to represent all three platforms.

Figure 1 GDI Use in Windows 2000
Figure 1 GDI Use in Windows 2000

This article describes the differences between Windows 9x and the Windows XP platforms and suggests some solutions to solve the problems that arise during the implementation of the tool that you will see in Figure 1. I'll explain how to determine the GDI resource consumption for a process using a code injection mechanism and how to patch a process or a DLL to be notified when a GDI object is created. Following that, I will show you how to write a Win32® debugger to drive a process, how to let the process and the debugger communicate with each other, and how to implement a call stack manager to provide additional information about GDI object's resource allocation.

Windows 9x versus Windows XP

With Windows XP, a list of GDI objects is associated with each process, mostly managed in kernel mode by win32k.sys, the device driver responsible for USER and GDI implementation. Win32 applications call these system services through the API provided by user32.dll and gdi32.dll. Since Windows keeps a record of GDI objects on a per-process basis, only the application that created a GDI object is able to use the corresponding GDI functions with it.

The Windows 9x version of GDIUsage uses the GetObjectType API function which provides the type of GDI object for a given handle value to check if a random value is a valid GDI handle. Unlike Windows 9x, however, a Windows XP GDI object handle is a full 32-bit value. The possible range is from 0 to 0xFFFFFFFF, and, in order to list all real GDI objects, each possible handle value needs to be given to GetObjectType. This creates a real performance issue. The following code takes minutes to execute, and unfortunately, a lot of GDI objects detected by the running test application are not real (over five hundred of them!):

DWORD dwObjectType;
DWORD hGdi;
for (hGdi = 0; hGdi < 0xFFFFFFFF; hGdi++)
{
   dwObjectType = ::GetObjectType((HANDLE)hGdi);
   if (dwObjectType != 0)
   {
      TRACE("0x%08x -> %u\n", hGdi, dwObjectType);
   }
}

This means that additional (and probably lengthy) testing code using GetObject is necessary to obtain a reliable list. The duration of the loop makes this method unusable. This article presents two other solutions. The first uses the handles table that GDI manages, and the second works by hooking functions from the GDI API. It's worth noting the first solution is not supported nor guaranteed to behave the same in future OS versions. Finding another way to get the list of real GDI objects is the first problem that needs to be solved.

With GDIUsage for Windows 9x, it is possible to display a GDI object using functions corresponding to each GDI object type such as BitBlt for a bitmap or FillRect for a brush. But since these API functions fail when called with a GDI object created by another process, the key feature of the tool I developed, which is the ability to "see" the leaking resource, disappears. On the other hand, the displaying code works fine in the application that created the GDI object. The solution is obvious—the display engine must run into the other process context. An implementation, based on Windows hooks as an interprocess communication mechanism, is described later in the article.

Finally, the Win32 debugging API is used to glue these techniques together, thereby allowing the implementation of a version of GDIUsage that works on the Windows XP and other 32-bit Windows platforms.

How GDI Manages Handles

In my August 2002 article WinDBG is used to obtain the description of the Process Environment Block (PEB) structure. In that article, the GdiSharedHandleTable field, reprinted here as Figure 2, should have attracted your attention. In fact, this is a pointer to a table where GDI stores its handles, even those created by other processes. In his book Windows Graphics Programming: Win32 GDI and DirectDraw (Prentice Hall, 2002), Feng Huan provides another way to access this table, but he also describes the structure of each of the 0x4000 entries of this table, as you can see here:

typedef struct
{
   DWORD pKernelInfo;

// 2000/XP layout but these fields are inverted in Windows NT
   WORD  ProcessID;
   WORD  _nCount;

   WORD  nUpper;
   WORD  nType;
   DWORD pUserInfo;
} GDITableEntry;

Figure 2 PEB Structure Using WinDbg and kdex2x86 (Close Figure 2)
0:000> !kdex2x86.strct PEB
Loaded kdex2x86 extension DLL
struct   _PEB (sizeof=488)
+000 byte     InheritedAddressSpace
+001 byte     ReadImageFileExecOptions
+002 byte     BeingDebugged
+003 byte     SpareBool
+004 void     *Mutant
+008 void     *ImageBaseAddress
+00c struct   _PEB_LDR_DATA *Ldr
+010 struct   _RTL_USER_PROCESS_PARAMETERS *ProcessParameters
+014 void     *SubSystemData
+018 void     *ProcessHeap
+01c void     *FastPebLock
+020 void     *FastPebLockRoutine
+024 void     *FastPebUnlockRoutine
+028 uint32   EnvironmentUpdateCount
+02c void     *KernelCallbackTable
+030 uint32   SystemReserved[2]
+038 struct   _PEB_FREE_BLOCK *FreeList
+03c uint32   TlsExpansionCounter
+040 void     *TlsBitmap
+044 uint32   TlsBitmapBits[2]
+04c void     *ReadOnlySharedMemoryBase
+050 void     *ReadOnlySharedMemoryHeap
+054 void     **ReadOnlyStaticServerData
+058 void     *AnsiCodePageData
+05c void     *OemCodePageData
+060 void     *UnicodeCaseTableData
+064 uint32   NumberOfProcessors
+068 uint32   NtGlobalFlag
+070 union    _LARGE_INTEGER CriticalSectionTimeout
+070 uint32   LowPart
+074 int32    HighPart
+070 struct   __unnamed3 u
+070 uint32   LowPart
+074 int32    HighPart
+070 int64    QuadPart
+078 uint32   HeapSegmentReserve
+07c uint32   HeapSegmentCommit
+080 uint32   HeapDeCommitTotalFreeThreshold
+084 uint32   HeapDeCommitFreeBlockThreshold
+088 uint32   NumberOfHeaps
+08c uint32   MaximumNumberOfHeaps
+090 void     **ProcessHeaps
+094 void     *GdiSharedHandleTable
+098 void     *ProcessStarterHelper
+09c uint32   GdiDCAttributeList
+0a0 void     *LoaderLock
+0a4 uint32   OSMajorVersion
+0a8 uint32   OSMinorVersion
+0ac uint16   OSBuildNumber
+0ae uint16   OSCSDVersion
+0b0 uint32   OSPlatformId
+0b4 uint32   ImageSubsystem
+0b8 uint32   ImageSubsystemMajorVersion
+0bc uint32   ImageSubsystemMinorVersion
+0c0 uint32   ImageProcessAffinityMask
+0c4 uint32   GdiHandleBuffer[34]
+14c function *PostProcessInitRoutine
+150 void     *TlsExpansionBitmap
+154 uint32   TlsExpansionBitmapBits[32]
+1d4 uint32   SessionId
+1d8 void     *AppCompatInfo
+1dc struct   _UNICODE_STRING CSDVersion
+1dc uint16   Length
+1de uint16   MaximumLength
+1e0 uint16   *Buffer

Each entry stores the details of a GDI handle whose value is easily calculated. Its lower 16 bits are the index in the table and the upper 16 bits are saved in the nUpper field. As its name implies, the ProcessID field contains the ID of the process that created the object. With this information, a simple loop allows you to list the objects that a given process is consuming and this is exactly what GDIndicator does, as shown in Figure 3.

Figure 3 Running Processes and Their GDI Objects Consumption (Close Figure 3)

If you are interested in knowing the different ways to get the running process list, you should read my June 2002 article. Each process has an ID, which is used to collect the GDI objects it consumes from the shared table, using the ProcessID field to make the comparison. The resulting counts are displayed in GDIndicator under each object type column.

Unlike the other columns, the third column displays two values. The first value is the result of a call to GetGuiResources (this is supposed to return the count of handles to GUI objects in use by the process), and the second, bracketed value is the sum obtained during the parsing of the GDI handles shared table. As you see in Figure 3, these two values are often different and GetGuiResources always returns a larger count. There is no documented explanation for this difference, nor any obvious relationship with stock objects or unreleased objects. It is possible that, behind your back, GDI is allocating objects that are not stored in the shared table and therefore are objects for which you are not responsible.

One example of this kind of hidden allocation occurs during icon manipulation. When you create or load an icon, Windows needs more than one bitmap to implement the transparency effect. Usually there's one for the mask and one for the visible graphic. Unlike bitmaps, icons are handled by the USER system component, not GDI. This might be the reason that these allocations seem not to be tracked by the code behind GetGuiResources when called for GDI consumption.

Tracking Object Allocation with API Hooking

As you have seen, it is not easy to know what GDI objects are consumed by a given process. How can you know if an object has been allocated by the application code or by the GDI itself behind your back? If you could be notified by Windows when a GDI object is created, it would be very easy to store its handle value and build a list of the objects that are allocated by an application. Unfortunately, the Win32 API does not provide developers with that kind of notification mechanism.

If you want to know when a new object is created, you have to catch the function calls listed in Figure 4.

Figure 4 Objects that Create GDI Objects (Close Figure 4)

Object Type | API Functions
bitmap | LoadBitmapA, LoadBitmapW, CreateBitmap, CreateBitmapIndirect, CreateCompatibleBitmap
brush | CreateBrushIndirect, CreateSolidBrush, CreatePatternBrush, CreateDIBPatternBrush, CreateDIBPatternBrushPt, CreateHatchBrush
device context | CreateCompatibleDC, CreateDCA, CreateDCW, CreateICA, CreateICW, GetDC, GetDCEx, GetWindowDC
font | CreateFontA, CreateFontW, CreateFontIndirectA, CreateFontIndirectW
metafile | CreateMetaFileA, CreateMetaFileW, CreateEnhMetaFileA, CreateEnhMetaFileW, GetEnhMetaFileA, GetEnhMetaFileW, GetMetaFileA, GetMetaFileW
pen | CreatePen, CreatePenIndirect, ExtCreatePen
region | PathToRegion, CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreatePolyPolygonRgn, CreateRectRgn, CreateRectRgnIndirect, CreateRoundRectRgn, ExtCreateRegion
palette | CreateHalftonePalette, CreatePalette

Fortunately, in his article "Learn System-Level Win32 Coding Techniques by Writing an API Spy Program" in the December 1994 issue of MSJ, Matt Pietrek explained how to write an API spy engine for the Win32 world. Given a particular module (process or DLL), this engine can replace the address of a function that is called (exported by a DLL) with the address of a function of your own. Once this substitution has been performed, each time the spied module calls a hooked function, your own handler will be executed in its place.

This API hooking principle has been refined over the years (see the John Robbins Bugslayer column in the February 1998 and June 1999 issues of MSJ) and if you need to know the possible implementations for the different Windows platforms, you should read Chapter 22 of Programming Applications For Microsoft Windows Fourth Edition" (Microsoft® Press, 1999) by Jeffrey Richter and Debugging Applications (Microsoft Press, 2000) by John Robbins. I have used the John Robbins method here.

Figure 5 Memory Layout for Call to GetDC
Figure 5 Memory Layout for Call to GetDC

John's HookImportedFunctionsByName helper function takes a list of functions to patch, the loading address of the system DLL that exports them, the module that calls the functions to be patched, and a list of stubs to be redirected to. On exit, it fills a list containing the addresses of all the functions that were patched. For example, if App.exe is calling GetDC from USSER32.DLL, you get the memory layout shown in Figure 5. If I call HookImportedFunctionsByName with the following input parameters, it would result in the different layout shown in Figure 6.

  • a list of system functions to patch (GetDC)
  • the address of the DLL that exports fucntions (USER32.dll)
  • the module calling (App.exe is directly calling GetDC)
  • the list of patching functions (_GetDC from Hook.dll)

In this particular example, the list containing the addresses of all the patched functions would be initial@.

Figure 6 Memory Layout for Patched Call to GetDC
Figure 6 Memory Layout for Patched Call to GetDC

In addition to being called for each function that you saw listed in Figure 4, the same mechanism is used to hook the functions that free GDI objects (as shown in Figure 7). With these two types of notifications, it is possible for you to keep track of the live and running GDI objects.

Figure 7 Functions that Free GDI Objects (Close Figure 7)

Object Type | API Functions to Free Handles
Display context | DeleteDC
Metafile | DeleteMetaFile CloseMetaFile
Enhanced Metafile | DeleteEnhMetaFile CloseEnhMetaFile
Others | DeleteObject

The CGDIReflect class is responsible for providing the static stub methods that will be called in place of the system functions. This class derives from CAPIReflect, whose main goal is to redirect a function call from a given module into a static class member using macros. The replacement is done through a call to DoReflect that takes the caller module handle as a parameter. The responsibility of the derived class is to map each system function that you're interested in receiving information about to an appropriate stub counterpart, as you'll see later in this article.

Following the message map mechanism, a set of macros has been defined to help you automate the definition and declaration of stub functions. With the help of the /P compiler option, you can get a .i file for each source file that contains the code with all macros expanded. You need to watch the size of the resulting files, which can be huge, but this method shows you exactly which code is executed, as you will see in the figures and source code that I'll show you later in this article.

Three steps, which I'll outline in a moment, are needed to redirect a function from USER and GDI. Using the same example, I'll show you how to patch GetDC in user.dll.

The first step is to declare a static method in CGDIReflect using the DECLARE_REFLECT_APIxxx macro, where xxx is the number of parameters of the function (1 for GetDC, which takes an HWND as a parameter). This declaration is bracketed by BEGIN_REFLECT_LIST, which defines a hidden TraceReflectCall helper method that provides trace services, and END_REFLECT_LIST, which does nothing:

  BEGIN_REFLECT_LIST()
     DECLARE_REFLECT_API1(GetDC, HDC, HWND, hWnd)
  END_REFLECT_LIST()
However, the DECLARE_REFLECT_API macro needs to be fed by a system function name, its return type, and its parameters list (both type and name). Supplying this information allows the macro to be expanded into a static method of CGDIReflect (actually a stub) that shares the same prototype and executes the following steps. First, the original system function is called through an alias (instantiated later via DEFINE_API_REFLECT). Then, the handle allocated by the previous call and its type are stored in a CHandleInfo structure (see Figure 8), as is some additional data for future use (see the discussion of DoStackAddressDump in the next section). Finally, a static map member of CGDIReflect is updated to associate the newly allocated handle, the creation of which you wanted to trap, with the aforementioned structure.

Figure 8 CHandleInfo Class Declaration (Close Figure 8)
class CHandleInfo
{
public:
   CHandleInfo()
   {
      m_hObject   = NULL;  // handle
      m_Type      = 0;     // type (bitmap, brush,...)
      m_pStack    = NULL;  // call stack
      m_Depth     = 0;     // call stack depth
   }

   virtual ~CHandleInfo()
   {
      if (m_pStack != NULL)
         delete [] m_pStack;
   }

public:
   HANDLE   m_hObject;
   DWORD    m_Type;
   DWORD    m_Depth;
   DWORD*   m_pStack;
};

Step two implements each static member as an alias of the hooked system functions address through the following macro:

DEFINE_API_REFLECT(CGDIReflect, GetDC);
In my example, that macro expands to:
CGDIReflect::GetDCProc CGDIReflect::__GetDC = 0;

During the third and final step, all these members need to be instantiated, then used at execution time. The method FillStubs is called in both cases. First, FillStubs is called via Init with APIR_STATE_INIT as a parameter and there is no module handle to patch. This happens when the system function's address is calculated using GetProcAddress, and then the address is stored in the alias member (__GetDC for GetDC).

Next, FillStubs is called when a new DLL needs to be patched. DoReflect calls FillStubs with APIR_STATE_ENABLE as a parameter. It redirects every system call (GetDC) made by the module, whose loading address is passed as a parameter, to the corresponding static stub method (_GetDC in my example). This method follows the same pattern as the MFC message map:

BEGIN_IMPLEMENT_API_REFLECT()
•••
IMPLEMENT_API_REFLECT(hModule, "USER32.DLL", GetDC);
•••
END_IMPLEMENT_API_REFLECT()

To help debug macros during execution, a few AfxTrace calls are scattered throughout the expanded code. SetTraceLevel allows you to choose which action will be traced, according to the values shown in Figure 9.

Figure 9 Flags for SetTraceLevel (Close Figure 9)

Flag | Effects
API_TRACE_LEVEL_NONE | No trace
API_TRACE_LEVEL_GETPROCADDRESS | Trace if a GDI function can be redirected
API_TRACE_LEVEL_GETMODULEHANDLE | Detect if a module to be patched is there
API_TRACE_LEVEL_REFLECT_CALL | Notify when a call is redirected to a stub function
API_TRACE_LEVEL_DOREFLECT | Show which module is patched in DoReflect
API_TRACE_LEVEL_INIT | Trace FillStub when it gets the GDI function's address
API_TRACE_LEVEL_HOOK_IMPORTED_FUNCTION | Log which GDI function is hooked for a given module

You now have the CGDIReflect class, which allows you to redirect any calls issued by a given module into a stub's method whose unique role is to store the newly created GDI objects into a map. There is, however, one limitation to this implementation—it is not thread safe. If the application you need to check is multithreaded and several threads are calling GDI functions, the resulting behavior might be indeterminate because access to the handles map is not synchronized across threads.

Using Stack Tracing to Monitor Allocations

Each call to the functions of interest ends up in a stub that carries out two actions. The first is to wrap both the allocated handle and its type into a CHandleInfo object declared in GDIReflect.h. The second is more interesting. In the CHandleInfo object, each function address in the current call stack is stored in m_pStack—an allocated array of DWORDs—and the number of addresses held in m_pStack is saved to m_Depth. Therefore, in addition to presenting a GDI object in a graphical way, it is also possible to show the stack of function calls that lead to a specific GDI object allocation, as shown in Figure 10.

Figure 10 Cells that Lead to Objects
Figure 10 Cells that Lead to Objects

When it's time to walk the stack, imagehlp.dll and dbghelp.dll are your best friends. And to make your life even easier, John Robbins has wrapped this engine into the CSymbolEngine class whose evolution was described in the MSJ Bugslayer columns from April 1998 and February 1999. The last version, using DBGHELP, is detailed in John Robbins' book Debugging Applications which I mentioned earlier.

The CSymbolEngine class is a low-level layer on top of the numerous functions exported by DBGHELP. The CStackManager implemented in StackManager.cpp (in the /Common directory in the code download at the link at the top of this article) provides higher-level features with only three interesting methods. The first one is DoStackAddressDump, which allocates and fills a dump of the current call stack using CSymbolEngine. This method is called by every stub and stores each function address that led to the object's allocation.

Hexadecimal addresses are good for computers, but not for people. In order to transform the array of addresses returned by DoStackAddressDump into a human-readable format, as you saw in Figure 10, it is necessary to call DumpStackAllocation. This method takes both a stack dump and its depth, then returns the translated stack in a CString. The caller of the method is able to choose what kind of line separator he wants between each address, either \r\n for displaying the CString in an edit box or \n for simply logging it using Trace or OutputDebugString. There is no magic behind this method; it calls ConvertStackAddressIntoFunctionName for each address in the given array.

The magic is somewhere else. When the stack is dumped by DoStackAddressDump and an address is stored in the returned array, this method also finds the symbol corresponding to the address using SymGetModuleInfo, SymGetSymFromAddr, and SymGetLineFromAddr defined in CSymbolEngine (see ConvertAddress in StackManager.cpp in the code download for implementation details). Why do the conversion now? The answer is quite simple: at this particular moment you are sure that the corresponding DLL is loaded, but this might not be the case when DumpStackAllocation is called later.

If GDI objects are created frequently, a lot of stack dumps will be generated and stored in m_HandleMap. But the CHandleInfo object saved in this map keeps the address array, not an array of the translated strings. The trick is to use a map member, such as m_AddressToName, to keep track of the translation. This avoids storing long strings instead of DWORDs for each address in the stack dumps and therefore cuts down on memory consumption. The other benefit is that the stack dump is performed faster because m_AddressToName is used as a cache to avoid queries to the symbol engine.

Even though you know how to hook a list of GDI functions, you still need to know in which module those GDI calls are made. Let's say that an application using MFC in a shared DLL is creating a CPen object to manipulate APIs related to Windows pens. The real call to CreatePen (which returns the pen handle) is done within the MFC DLL, not in the calling application code. If you hook only the API functions called from the executable, you miss the calls from all the DLLs used by the application.

Debugging to Determine DLLs to Patch

Under Windows XP, it is easy to get the list of all DLLs loaded by a process at a given time thanks to the PSAPI function EnumProcessModules, as Matt Pietrek explained in "Under the Hood" in the August 1996 issue of MSJ. For dynamically loaded libraries, however, the problem is a little trickier. In addition to hooking system functions, it also becomes mandatory to hook the LoadLibrary calls that the main program issues (both ANSI and UNICODE versions) in order to detect when a new DLL is loaded and recursively apply the same hooking treatment to it.

Two final questions need to be answered. First, how can you know which DLLs should be patched inside the spied process? Second, how can you ensure this code executes inside another process? If such a solution exists, it should also be possible to use it to display a graphical representation of any GDI object handle. In the article on debugging published in August 2002, the Win32 debugging API was used to detect the DLLs that are loaded, either statically or dynamically by a debugged process. The main drawback of this method is the need to start and debug the application. Unlike the Windows 9x version of GDIUsage, the detected GDI objects are necessarily the ones allocated by the debugged application, as you saw in Figure 1.

The Win32 debugging API allows you to write code that launches an application (the debuggee) and is then notified when events occur, such as when a new DLL has been loaded. This is exactly what you need. In order to easily write a debugger, you should simply overload virtual methods that will be called when a debug event occurs. The CGDIDebugger class is derived from the CApplicationDebugger, which I introduced in my August 2002 article. Figure 11 shows the names of the methods that are overloaded by CGDIDebugger, as well as an explanation of what each method does. I will discuss the communication mechanisms between the debugger and the debuggee later.

Figure 11 Methods Overloaded by CGDIDebugger (Close Figure 11)

OverloadedMethod | Note
PreLoadingProcess | Needs to perform members initialization.
OnCreateProcessDebugEvent | StartTraceGDI is called by the overloaded method and it must make the DLL code inject itself into the debuggee.
OnExitProcessDebugEvent | StopTraceGDI is called by the overloaded method and it must perform debugging session cleanup.
OnLoadDLLDebugEvent | PatchModule is called by the overloaded method and it must add the DLL to an internal list and notify the code in the debuggee that each call to the GDI must be hooked.
OnUnloadDLLDebugEvent | Needs to update the internal DLL list.
OnThreadMessage | Receives notification from the injected code running within the debuggee.
Figure 12 Search for a String
Figure 12 Search for a String

In addition to these methods, OnOutputDebugStringDebugEvent has been overloaded in order to redirect the traces issued by the debuggee (prefixed by >) to a dedicated listbox. It is also possible to copy a selection to the clipboard or search for a string, as shown in Figure 12. When you add trace using TRACE or OutputDebugString, it appears in the listbox. This is an efficient mechanism to debug the code and mark the difference between outputs from the debuggee (prefixed by >) and the debugger.

Injecting Running Code into Another Process

There is one remaining problem to be solved now: there must be a way to make some of the code run in the context of another application. Luckily, Jeffrey Richter dealt with this problem a long time ago in his article "Load Your 32-bit DLL into Another Process's Address Space Using INJLIB" (MSJ, May 1994).

Since we are usually only interested in applications that are using the GDI API, we can assume that such an application uses at least one window to display its drawings. (Otherwise, why would it need GDI?) Therefore, from among the different solutions, the one based on Windows hooks seems like the best option. When you call the following hook, Windows will call your GetMessageHookProc callback when any thread executes GetMessage in any process:

SetWindowsHookEx(WH_GETMESSAGE, GetMessageHookProc, hInstance, 0)
Since this is a system-wide hook (the last parameter is 0), the code of the callback must be in a DLL that is mapped into the address space of each process where a thread calls GetMessage.

If the hook procedure and the driver application agree, it is easy to establish a communication channel between them using predefined messages. This would be a good way to allow the debugger to send requests to some injected code that runs in the context of the spied application; exactly what is needed for the GDI object display dialog box! When the hook procedure intercepts the first message, it first redirects calls for the DLL that's already loaded. Then it starts a new thread that is dedicated to handling requests from the debugger. This creates a communication channel between the debugger and the debugee (see StartInfiltratedThread in GDITrace.cpp for details).

The functions called by the debugger and the debuggee from the hook procedure to the infiltrated thread function have been gathered inside GDITrace.dll, whose behavior is implemented by the CGDITraceApp class. This DLL is statically linked to the debugger application GDIUsage, but is dynamically loaded within the processes that trigger the Windows hook. The functions to be called by the debugger are declared in _GdiTrace.h and grouped together in GdiTrace.cpp to help you understand which part is used by the debugger and which by the debuggee. But why mix such different codes in the same DLL? A few variables need to be shared between both codes and it is easy to share a variable's values between instances of the same DLL, as shown in Figure 13.

Figure 13 Sharing Variables Between Processes (Close Figure 13)
// shared variables
//
#pragma data_seg(".shared")
// debugger
   DWORD s_dwCallingThreadID  = 0;

// debuggee
   DWORD s_dwInfiltratedThreadID = 0;
   DWORD s_dwProcessID           = 0;

// API hooking initialization flag
// --> set to TRUE once the injected hook function has been called
   BOOL  s_bDebuggeeIsStarted = FALSE;
#pragma data_seg()

// Be careful not to add any unwanted whitespaces in the following
// pragma!
#pragma comment(linker, "-section:.shared,rws")

The code defines a PE section named .shared, with the read/write/shared attributes (rws) that contain the five variables prefixed by s_ that need to be shared. Based on this declaration, Windows will store these variables in a memory block that is shared by any process loading the DLL. Therefore, these variables have the same value in every process, especially the debugger and the debuggee. Let's take a look at what goes on when the debugger starts a debuggee and how these variables are used.

When the debuggee starts, the debugger thread receives a CREATE_PROCESS_DEBUG_EVENT, which is handled by OnCreateProcessDebugEvent, which in turn calls StartTraceGDI. This function executes SetSharedVariables, setting the value of s_dwCallingThreadID with the ID of the debugger thread. If the current process ID is the same as the one saved in s_dwProcessID, the hook procedure knows that it is running in the debuggee context and starts to patch GDI calls from the DLLs that have already been loaded. The dedicated thread, which is started next by the hook procedure, stores its ID in s_dwInfiltratedThreadID. Finally, s_bDebuggeeIsStarted is set to TRUE when the hook procedure work is successful and is then used by the debugger to decide if the infiltrated thread is ready to answer requests.

As you need to pass or retrieve a list of GDI object handles between the debugger and the debuggee, a shared buffer larger than just a DWORD or a BOOL is required. In addition to the five variables, a memory mapped file named GDITrace_SharedBuffer is used, and the corresponding memory is pointed to by the CGDITraceApp member m_lpvMem. It is initialized during the DLL startup (see CGDITraceApp::InitInstance in GDITrace.cpp for implementation details). This buffer needs to be created or initialized only when the DLL is loaded by two processes: GDIUsage as a debugger and its current debuggee.

The s_dwProcessID shared variable is used to recognize the difference between the two processes. Its value is always 0 when there is no started debuggee; otherwise, it contains the debuggee process ID. When the DLL is loaded into a process, its InitInstance checks whether s_dwProcessID is equal to 0 (should be GDIUsage) or to GetCurrentProcessId (should be the debuggee) in order to create the memory mapped file.

Debugger to Debuggee Communication

The s_dwInfiltratedThreadID shared variable is used by the debugger to post a request (through a simple Windows message using PostThreadMessage) that will be handled by the thread infiltrated in the debuggee. The other s_dwCallingThreadID shared variable is needed when it's time for the debuggee to notify the debugger that such a request has been fulfilled. For example, when the user clicks the "Take Snapshot!" button, GDIUsage needs to collect the allocated GDI objects from the debuggee.

GDIUsage posts a TM_GET_LIST message to the thread infiltrated into the debuggee whose value is stored in s_dwInfiltratedThreadID. It routes the execution to the OnGetList function along with the parameter, UM_SNAPSHOT_READY, that will be used as the callback message to be received by GDIUsage main dialog. Why not simply use the same TM_GET_LIST? The answer relates to sharing code. Both the "Take Snapshot!" and "Compare" buttons need to get the same list of allocated GDI objects (even though this list is used in a different way) in order to update the corresponding part of the GDIUsage user interface.

To sum up what I've just discussed in the previous paragraphs, the TM_GET_LIST thread message triggers the processing of the GDI objects on the debuggee side. In addition, there are two ways to update GDIUsage main dialog based on two user-defined messages: UM_SNAPSHOT_READY and UM_COMPARE_READY.

The infiltrated thread wakes up and asks OnGetList to handle the request. This CGDITraceApp method enumerates the GDI allocations detected within the debuggee through the patched stubs and copies each object description (handle value and type) into the memory mapped file shared buffer pointed to by m_lpvMem under the following GDI_LIST format:

typedef struct
{
   DWORD    dwType;
   HGDIOBJ  hObject;
} GDI_ITEM;

typedef struct
{
   DWORD    dwCount; // count of meaningful GDI_ITEM slots in Items
   GDI_ITEM Items[];
} GDI_LIST;

In order to notify the debugger that the list is ready, the same TM_GET_LIST message is posted back to the thread identified by s_dwCallingThreadID. This message is received in GDIUsage context by the thread responsible for debug events and routed to CGDIDebugger::OnThreadMessage. This method notifies the UI thread by sending the right user message (UM_SNAPSHOT_READY for this example) to the main dialog with a pointer to the shared memory defined by the memory mapped file where the GDI objects have been stored by the debuggee. This serialized and automatically marshaled list is used to instantiate a CGdiResources by using its CreateFromList method. This class is used to wrap a GDI objects list and also provide services such as enumeration and graphical display.

Deadlock and Timing Issues

Before digging into the graphical display of a remote GDI object, you should be aware of a possible deadlock issue. The previously discussed technique for collecting GDI objects is asynchronous because it relies on Windows messages exchanged between the debugger and the debuggee. If you need strong synchronous communication, you could use Win32 events. For example, the infiltrated thread waits for a specific named event and calls MsgWaitForMultipleObjectsEx to wake up when either a message is posted to its queue or the event gets signaled (see CGDITraceApp::InfiltratedThreadProc in GDITrace.cpp for source code details). In this implementation, the event is used to ask the thread to end its life and therefore is not really synchronous.

Another case that would require true synchronous behavior is the patching of GDI calls when a DLL is loaded by the debuggee. In order to intercept the GDI calls, the debugger must notify the infiltrated thread as soon as possible. Otherwise, the debuggee might start allocating GDI objects before the interception stubs are installed. Here is the scenario that would be a good idea for you to implement:

  1. The debugger thread is notified that a DLL is loaded in the debuggee through a LOAD_DLL_DEBUG_EVENT returned by WaitForDebugEvent.
  2. The OnLoadDLLDebugEvent overridden method receives the hModule corresponding to the DLL, stores it in a new shared variable, and asks the debuggee to patch the GDI calls for that particular DLL by signaling an event.
  3. In order to avoid reentrancy, if another DLL gets loaded by the debuggee, OnLoadDLLDebugEvent waits for another event that is signaled when the debuggee finishes its patching work.
  4. The debuggee-infiltrated thread wakes up from MsgWaitForMultipleObjectsEx since one of the events it is waiting for has been signaled.
  5. The CGDITraceApp::OnNewDLL method redirects the GDI calls for the DLL loaded at the address defined in the shared variable filled by the debugger with the DLL hModule.
  6. The event the debugger is waiting for is signaled by the infiltrated thread.
  7. The infiltrated thread calls MsgWaitForMultipleObjectsEx waiting for another request to be fulfilled.
  8. The debugger thread is resumed since the event it was waiting for has been signaled.

Note that the last two steps have the same sequence number because their code is running in two different threads that are scheduled by Windows. You should not expect one to be executed before the other.

Even though this scenario seems to be perfect, it leads to a deadlock between the debugger thread and the thread injected into the debuggee. There is a snag between Steps 3 and 4. The Win32 debugging API assumes that the debugger is waiting for a debuggee event using WaitForDebugEvent. When this function returns, and until ContinueDebugEvent is called, all threads in the debuggee are suspended, even those that have not generated the debug event. Therefore, the debugger thread stays in Step 3, and Step 4 will never be executed by the debuggee since ContinueDebugEvent is supposed to execute after the synchronous communication ends. You should remember that it is not possible to synchronize an action in the debuggee from a debugging event received by the debugger.

In the case of GDIUsage, the message-based mechanism seems to provide fast enough notification. The real problem is somewhere else. Since the infiltrated thread is started after a first message has been retrieved by the debuggee, all statically linked DLLs have already been initialized. If some of them have allocated GDI objects, this consumption will never be detected by GDIUsage. In this case, you need another way to get the patching code to be executed. GDIUsage helps you to detect and locate GDI object creations that are not released during the life of your application, but at its startup.

Displaying GDI Objects

The GDIUsage user interface, which you saw in Figure 1, allows the user to take a snapshot of the GDI object that is being consumed by a process at any given point in time and compare it later with the current state of the same process. Each set of objects is stored in a CGdiResources object. As you have seen in the TM_GET_LIST explanation, once the objects list has been serialized and marshaled between the debuggee and the debugger through the memory mapped file, a CGdiResources object is initialized using CreateFromList.

In the Windows 9x version of GDIUsage, the class CGdiResourcesDlg, responsible for displaying GDI objects, takes a pointer to a CGdiResources object as a parameter. Unfortunately, in the Windows XP version of GDIUsage, a CGdiResourcesDlg is worthless within the context of the debugger since the GDI functions used to graphically display a GDI object created inside the debuggee will always fail.

The solution is to move CGdiResourceDlg and CGdiResources inside GDITrace.dll, which is loaded into the debuggee by the Windows hook, as discussed earlier. After retrieving the list of allocated objects, it is time to display such a list. The same communication mechanism as the one discussed earlier is used, but before the list can be displayed and before posting a message to the infiltrated thread, the list must be saved in the debugger context to the memory mapped file. The debugger serializes a CGdiResources list (either the current snapshot or the result of a compare, depending on the button clicked by the user) into the memory mapped file and posts a TM_SHOW_LIST message to the thread infiltrated into the debuggee with the GDIUsage main dialog window handle as a parameter. This treatment is carried out by CGDIDebugger::ShowList for the serialization and ShowRemoteList for the message posting.

Like TM_GET_LIST, the TM_SHOW_LIST message is handled by the infiltrated thread, but is next routed to CGDITraceApp::OnShowList. This method initializes a CGdiResources from the serialized and marshaled data stored in the memory mapped file. It is now possible to let a CGdiResourcesDlg display the corresponding GDI objects.

Unlike TM_GET_LIST message, the debugger does not need any return code or information for this display command posted to the debuggee. In any case, the user expects that GDIUsage stays disabled until he dismisses the CGdiResourcesDlg dialog. This is the reason why the handle of the GDIUsage main dialog is passed as a parent window to the CGdiResourcesDlg object.

The implementation of this class has not been changed from the Windows 9x version except for two enhancements. The first one uses the way a GDI handle is built to detect if it references a stock object or not and adds a * to the corresponding line in the listbox. This feature is invisible in GDIUsage since no API to create a stock object has been patched, but you might want to implement it when writing your own code.

The second enhancement is the need to detect when the user clicks or double-clicks a GDI object of the list in order to present the corresponding stack trace, as you saw in Figure 10. Since the CGdiResourcesDlg code should not be linked to the stack trace code, a generic callback interface, INotificationCallBack, has been defined. The CGDITraceApp class derives from it, implements OnDoubleClick, and registers itself using CGdiResourcesDlg::SetNotificationCallBack.

When the user double-clicks a GDI object, CGdiResourcesDlg checks if a callback has been registered. If so, it calls the OnDoubleClick method of the callback with the CGdiObj description corresponding to the double-clicked GDI object as a parameter. Then, CGDITraceApp extracts the call stack from the passed CGdiObj and instantiates a CCallStackDlg with it in order to display the stack trace to the user (see CGDITraceApp::OnDoubleClick in GDITrace.cpp for implementation details).

This feature has also been added to the GDIndicator tool that was shown in Figure 3. Unlike GDIUsage, the injection mechanism is based on CreateRemoteThread and has already been discussed in my August article. One interesting fact to note is that the serialization and display of the code is exactly the same as GDIUsage; only the remoting mechanism has changed. And since there is no recorded stack trace, no handler for double-click is registered.

There are two final pitfalls that must be dealt with. Since the dialog that displays GDI objects runs into another process context, some weird things happen. First, under Windows 2000 and Windows XP, it is not easy to set the foreground window from another process. You must call AllowSetForegroundWindow within the process that owns the foreground window in order to let another process set one of its windows as the new foreground window. Until the user cancels it, the dialog box code runs as an infinite loop in the context of the current thread of the other process. As a result, GDIndicator hangs until the dialog is dismissed. In order to avoid this unwelcome behavior, its main window is hidden during the lifetime of the dialog.

The second side effect is more difficult to work around. The windows created in the thread taken over by the dialog will no longer receive all their messages because the dialog procedure filters them. For example, if you use the tools outlined here to find the GDI objects that are allocated by Notepad, its repainting works fine and you can easily navigate into its menus, but the selected commands won't trigger until after the dialog is dismissed. This is a tricky feature to use but it is really valuable when you're hunting down elusive GDI leaks.

Conclusion

Between Windows 9x and Windows XP, things have changed in the GDI. Even though a lot of code in my program has been reused from its Windows 9x implementation, such as the management of a set of GDI objects and their graphical display, the inner mechanisms to get the list of GDI objects allocated by a process is new. The Windows XP version is based on the Win32 debugging API, DLL injection, Windows hooks, and API patching and provides more features than its Windows 9x counterpart.

In addition to the list of calls leading to each GDI allocation, a final touch has been added. When the remote process ends, the ExitInstance method of the injected DLL gets called. CGDITraceApp takes advantage of this notification to make a last enumeration of the GDI objects and to dump the ones that are still valid. This is the same kind of final dump you get for your MFC application when you use DEBUG_NEW as your memory allocator in debug builds in order to detect memory leaks.

These mechanisms can be used to find any other type of leak, assuming that you know the API functions that are called to create the resources in which you are interested. For example, kernel objects leaks (such as files and registry keys) might be found using existing tools such as oh.exe and dh.exe from the Platform SDK or ProcessExplorer from http://www.sysinternals.com. Even though they provide the list of kernel objects used by any process (and comparisons for ProcessExplorer), you could take advantage of the call stack and final leaks dump offered by the techniques presented in this article to track down your system leaks more easily.

For related articles see:
Windows XP Escape from DLL Hell with Custom Debugging and Instrumentation Tools and Utilities
Windows XP: Escape from DLL Hell with Custom Debugging and Instrumentation Tools and Utilities, Part 2

For background information see:
Windows Graphics Programming: Win32 GDI and DirectDraw by Feng Huan (Prentice Hall PTR, 2000)
Debugging Applications by John Robbins (Microsoft Press, 2002)
Programming Applications for Microsoft Windows by Jeffrey Richter (Microsoft Press, 1999)

Christophe Nasarreis a development manager for Business Objects in France. He has written several low-level tools for Windows since version 3.0. You can reach Christophe at cnasarre@montataire.net.


Scale: Real-World Load Testing Tips to Avoid Bottlenecks When Your Web App Goes Live

Scale

Real-World Load Testing Tips to Avoid Bottlenecks When Your Web App Goes Live

Jeff Dunmall and Keith Clarke
This article assumes you're familiar with IIS and Web Development
Level of Difficulty 1 2 3
SUMMARY

Load testing should be part and parcel of every Web development effort, and it should be performed early in the process. However, if you think you can load test using your development environment, you're going to have some surprises when you go live. In this article, the authors outline the process of planning your load testing effort, considering which machines to use, how many users to simulate, which tools are right for you, and how to interpret your results.

Contents
Start Early
Define Your Environment
Define Your Testing Strategy
But How Many Users?
Know Your Metrics
Interpret Your Metrics
Choosing the Right Load Testing Tool
Know Your History
Conclusion

L
et us propose a scenario. You're wrapping up a six-month development effort on a complex Internet application or Web Service and you're getting ready to deploy it. The development team was meticulous in its design of a loosely coupled, n-tier Web application. All the necessary ingredients for a scalable, stable, high-performance application were carefully built into the system architecture from day one. The QA team has thoroughly tested your system, removed the most severe bugs, and considers the remaining bugs to be known. So your deployment should go pretty smoothly, right? Think again.

Did you implement load testing as part of your development effort? If not, you should accept the fact that somewhere in the complexity of your design you will have introduced concerns around performance, scalability, and stability. Bottlenecks are elements of your system that impede the normal flow of traffic. Although good design is crucial to building a successful Web application, experience has taught us that the majority of these kinds of bugs can only be found when your system is placed under load. These are issues you won't discover by testing the system as a single user during the development process. By implementing a load testing plan early enough, you can help ensure that any surprises at deployment time are truly minimal.

In this article, we will steer away from the conventional load testing strategy to describe an approach that is based on real-world experience. Having led numerous load testing teams, we've learned some lessons that you might find helpful.

We'll discuss the merits of starting your testing effort early and cover key considerations for setting up your testing environment. We'll help you to determine which metrics are appropriate for your implementation and describe some tools for interpreting them. In addition, we'll show you why the familiar question "can my site handle x users simultaneously?" is too vague to answer accurately. Finally, we'll discuss some important considerations for choosing the appropriate load testing tool for your particular needs and make recommendations on tracking your test results.

We'll use the term load testing to describe performance, scalability, and stability testing. The term scalability testing is too often used to describe all three, and your team is likely doing more than just that. Figure 1 describes these goals.

Figure 1 The Goals of Load Testing (Close Figure 1)

Testing Type | Goal
Performance | To reduce the time necessary to execute a request. May involve the optimization of methods, stored procedures, and transaction lengths.
Scalability | To exceed the number of concurrent users anticipated at peak load in production. The number of concurrent users supported should be proportional to the amount of hardware.
Stability | To reduce component memory leaks and system crashes.

Start Early

You should begin planning your load testing effort during the design phase. From our own experience, we can suggest that you take a "no surprises" approach to your development effort. Always work with the assumption that you will find problems. The architecture for distributed Web applications and Web Services is increasingly complex, allowing potential problems to be inherent in the design of your application.

We recently conducted load testing well into the development phase on a complex n-tiered Web architecture. We made two underestimations. First, we underestimated how many problems we would uncover in the system once testing began. Our first test run failed at just 2 users and 100 orders processed. Second, we underestimated the length of time necessary to set up the test environment. Fortunately, we started planning for testing early enough that we had time to resolve or minimize the problems found prior to the deployment date. By paying close attention to design, the scalability of the system improved very quickly once the first few issues were successfully resolved.

You can begin planning for your testing effort by defining your testing environment. Depending on the size of your development effort, this may be a significant task.

Define Your Environment

In defining the testing environment, the first task is to evaluate what kind of effort is required. A general guideline we use for resource costs is that 15-20 percent of implementation time should be spent on testing, with approximately one third of this time dedicated to load testing.

It is important to create a separate testing environment that is comparable to production. If the machine configuration, speed, and setup aren't the same, extrapolating performance in production is nearly impossible. In other words, you can make a determination as to whether adding more hardware to your system will achieve greater scalability, but you cannot accurately answer questions like "how many users can one Web server in production handle?" One of your main tasks should be to reduce the uncertainty and answer questions with conclusive evidence. Without comparable hardware, you'll be forced to make educated guesses at best.

You may be cringing at the cost of putting production machines in your load testing environment, but consider the cost of finding hardware-related problems in production and the value in accurately predicting the load a single Web server can handle. Variables like processor speed and the available RAM affect the available system resources and consequently may change how scalability problems manifest themselves. In the lab, situational variables are your nemesis. Too many, and you cannot identify the source of the problem. Consider accelerating your production hardware purchase for use in the load testing lab if a separate environment is out of the question. The lab equipment can also be used as a standby for the production equipment once the system is deployed. Another benefit is that you will be able to iron out the system wrinkles long before your go-live date.

There are several reasons why you shouldn't test with your development environment. See the sidebar "Don't Use Your Dev Environment for Load Testing" for details. The same goes for the system test environment used by your QA team. It is intended for single user testing with the purpose of tracking functional bugs that appear independent of the system load. This kind of testing relaxes the constraints on the type of hardware used in the system test environment. It also receives more frequent software updates from the development team. In load testing, only releases that affect system performance should be installed, minimizing the amount of time spent tweaking load scripts.

In addition to the resources necessary to run the scalability lab, the success of your load testing effort depends on other roles within your organization. Figure 2 summarizes the roles.

Figure 2 Load Testing Team Required Roles (Close Figure 2)

Role | Contribution and Importance
Load test lab team | Takes ownership of the effort and runs the system test. Minimum of one person is necessary. Importance: critical.
Database | Identifies and solves database problems
administrator (DBA) | such as row locking and transaction timeouts. Importance: critical.
Development team | Identifies and tracks problems involving stability, performance, or transaction length. Helps identify areas where code practice can be improved. Importance: critical.
Business intelligence | Defines the quality bar for the site based
analyst | on available or projected information. This is an ongoing task since the level of traffic and the site features change over time. Importance: high.
Network team | Sets up the scalability lab hardware and builds it to production specs. Provides information about traffic on the production system. Identifies and resolves bottlenecks. If you use the production environment, you must increase this role. Importance: medium.
QA manager | Ensures that any external builds scheduled for deployment have been tested in the load test lab in conjunction with the regression and quality testing efforts of the QA team. Importance: medium.

We cannot overemphasize the fact that the most important role outside of the lab is a strong database administrator (DBA). Scalability problems are most likely rooted in the database, the data access strategy (such as stored procedures, prepared statements, or inline SQL), or data access technologies (such as ADO, ODBC, and so on). The DBA can help identify and solve problems related to the database such as expensive indexing, excessive locking, and transaction timeouts. Ideally, you should have a dedicated, well-qualified DBA available as a full-time resource for key points in the load test effort.

We also recommend that you dedicate a member of your development team to the test lab on a rotating schedule so that each team member participates in this testing effort. If you do this, you'll get great cross-training while providing the lab with a continuous stream of fresh ideas.

Define Your Testing Strategy

By now you've had that meeting in which the customer leaned across the boardroom table and asked "Will this system handle thousands of users?" The conventional approach to load testing requires you to write scripts and perform tests that attempt to accurately answer this question. With this kind of testing, you need to define what you mean by handle and what 1000 typical users would be doing on the site. You need to define test cases to represent various user activities like buying a stock or registering a new account. Next, you must estimate the distribution of users across these test cases. Assumptions are made for how much think time (or wait time) is required to simulate a real user interaction with the application. A cross-section of activity during a load test would therefore approximate what the same number of real users would be doing on the site.

There are several flaws with this methodology. First, the results are only as good as the assumptions you make. Obviously, incorrect assumptions will skew the results.

Second, approximating real users requires a lot of client hardware. Given the amount of processing power and memory required per virtual user, a typical client machine can handle about 200 virtual users. Testing for a level of concurrency of 2000 users would therefore require 10 client machines—a significant investment. Testing the site using HTTPS will require substantially more client hardware.

Finally, this approach makes it difficult to provide action-oriented information to your development team. When something does go wrong, it is often difficult to reproduce the problem.

As an alternative, we suggest you design your test cases around these key questions:

  • Where is the system bottleneck, and how many synchronized concurrent requests can it handle?
  • How many nonsynchronized super users can one machine handle before response time becomes unacceptable?
  • Do the results scale linearly as you add additional hardware?
  • Are there any stability issues that will prevent the site from operating in a production environment?

This approach uses additional information from the development team which anticipates where problems might arise. Focus on these areas. Using our previous example, the order submission area is likely to be a bottleneck. From here you can derive more specific questions such as "how many simultaneous requests can the submit process handle?" Attacking these specific areas is the fastest and cheapest way to provide action-oriented information to your development team so that they can improve the system. While using this approach, we suggest you remember to follow these suggestions.

Focus the Load Testing As we've already mentioned, the first thing to do is build scripts that load potential bottlenecks and stability issues. This "data first, assumption second" approach lets you gather raw data from your application and then, based on your assumptions, determine higher-level results. Don't worry about scripting areas of the site which have been identified as low risk. For example, scripting the help area of the site or a read-only documentation area is unlikely to reveal a system bottleneck.

Synchronize the Requests Hit your bottleneck with synchronized requests. The idea here is to simulate the worst-case scenario—every user on your site hitting the bottleneck at exactly the same time. By synchronizing your users, you make this test repeatable. Not synchronizing the results makes it difficult to reproduce a failure. You can achieve this by using synchronization points, a feature found in most of the more robust (and costly) testing tools. A synchronization point forces each virtual user to wait until the remaining users reach a defined point in the script before beginning the next request. It allows you to accurately and repeatedly determine the number of concurrent users that can be handled by a potential bottleneck area of the site. As an example, your lower limit might be seven concurrent, synchronized users.

Create Circular Test Case Scripts Make your test cases circular. Put another way, the site should be in the same state before and after each test case iteration. This allows you to repeatedly run the test case over a long period of time.

Use Super Users Finally, use what we call super users. As mentioned before, super users run with think time set to zero. Remember that the think time assumption is used in conventional testing to make your virtual users simulate real users. However, if you were to halve the virtual user think time, you would effectively double load on the servers. Put another way, the only variable your servers really care about in relation to load is the number of requests per second. The number of virtual users and their think times are combined to produce that load.

Let's do some math to make this concept clearer. The following formula calculates the load generated (requests/second) by real users accessing the site:

For example, a site with 100 concurrent users, assuming 10 seconds of download time and 30 seconds of think time, would produce about 2.5 pages per second. If we assume 3 requests per page, that would translate to 7.5 requests per second on the Web server.

Watch the number of requests per second as you run your tests with super users and compare against the number just calculated. In our experience, the ratio of real users to super users is typically around 15:1. Using the same example, that would mean that super users (100/15) would generate the same load as 100 normal users. To give you another example, let's say response time becomes unacceptable after 10 super users. Note the number of requests per second at that point to convert back to real users. Now you can make any think-time assumption you like and even change it without rerunning the test. After a few days of testing, you'll be able to intuitively convert from super users to real users. This approach allows you to keep the user count manageable, reduce the amount of client hardware required, and contain the cost of the load testing software.

These super user test cases are useful for more than just single-machine testing. To test the scalability of the site, add a second Web server and a load balancer and repeat your super user test. Ideally, you will be able to double the number of super users before you see the same response times.

To answer the stability question, run a test to sustain a reasonable number of concurrent, unsynchronized super users for an extended period of time. We used overnights and 24-hour periods on our last project, but the duration is application dependent. We refer to this as a "burn-in" test. Once you've taken steps to identify and potentially resolve the bottleneck found, repeat the synchronization point test to see if the lower limit has increased. Then rerun the "burn-in" test with the new number for concurrent users supported. Repeat this cycle with the goal of striving to improve upon this number until the quality bar is reached.Don't Use Your Dev Environment for Load Testing

There are several reasons why it is best to build a load test environment that is isolated from development and system test environments. First, even if the hardware is the same as production, the chance that you control the software installed and the configurations is small.

Second, load test scripts require a stable environment, as you'll want to minimize the amount of time spent rerecording scripts between tests. Some products allow you to ignore HTML or code changes to a Web page.

Yet page names, frame names, and query string changes will certainly cause your scripts to break. You want to minimize the number of times you allow code changes into the load test environment. Maintaining a separate environment means that rerecording may only be necessary between builds rather than throughout your testing day.

Also, because the development team will be adding additional load on the environment when testing functionality, it becomes difficult to isolate problems.

Finally, it is crucial to ensure version control for testing. You must be able to relate your metrics gathered to a specific build number. Without it, you can't accurately quantify the difference in performance between test runs when a change is made.

But How Many Users?

While this approach provides valuable information to the development team, it makes it more difficult to answer that boardroom question. However, you can approximate an answer. For example, say the worst-case bottleneck of the site shows response time over 10 seconds with more than 20 super users per machine. Based on your calculations from the formula we suggested, you approximate 300 real users (20 super users × 15 real users). At this point you can make the same kinds of assumptions as you would have made in the conventional case. What percentage of users would typically be using this area of the site? Say 50 percent are expected in this area and the other areas, like documentation or reading from the database, are not as popular. That means a system with one Web server would handle approximately 600 users.

So far we've discussed what to do if you can definitely point to one bottleneck area of the site, but what should you do if there is more than one area that might be affecting performance? The answer is to create test scripts that look at each area individually. First run the scripts in isolation, and then run them together. Then compare the results to see how much of an effect one area of the site has on another.

Know Your Metrics

The next step is to clearly define your metrics. Examples of metrics include the number of orders processed per minute or the number of milliseconds required to execute a request on an ASP page. Metrics allow you to quantify the results of the changes made between each of your test runs. They provide a comparison to the standard defined for your Web application.

In order to decide which metrics you need to track, there are a number of steps to work through. You need to define the questions for which you need answers, define the quality bar for each of these questions, and then determine which metrics are necessary to compare your test results to your quality bar.

The first step is straightforward. For example, you might want to know the checkout response time. Remember to form the questions in relation to the testing strategy and avoid vague questions you won't be able to test.

The next step is to define the quality bar for each of these questions. Let's use a typical order submission process as an example. We may decide that the site must handle 10 orders per minute during peak load and a user should not wait more than 30 seconds for a request to execute. In order to establish such a standard, you might look to a number of different sources. Talk to the business community first to get a feeling for the acceptable levels of performance for the system. Bringing historical data to these meetings can help facilitate the discussions and can often be used to manage expectations. If a version already exists in production, data can be gathered from current site activity and short-term projections of increased traffic, or by querying an existing database for activity trends.

With a list of questions and a quality standard for each question, you now need to determine which metrics to use. Based on the last example, orders per minute and the number of orders in a given test would be good high-level metrics serving as indicators of how the site measures up against the quality bar. These are the kind of metrics you'd report to management when you want to update them on the progress of your testing.

The lower-level metrics measure performance and help you resolve or minimize system bottlenecks and stability issues. Increasing performance may have a direct impact on your high-level metrics. For example, decreasing the transaction time of a specific activity may result in an increased number of orders per minute.

Most load testing tools allow you to set timers on individual pages or groups of pages and provide average times for running a test case. Both kinds of metrics allow you to account for the progress of your high-level metrics from one test run to the next, but neither metric assists you in providing any insight into what exactly needs improvement.

This is where Windows® performance counters are useful. For example, you could monitor Process:Private Bytes of the dllhost process to detect a memory leak in your server package. A good, detailed description of individual Microsoft® Internet Information Services (IIS) counters is available at The Art and Science of Web Server Tuning with Internet Information Services 5.0, and Figure 3 has a description of the main counters used in load testing and the trends to watch for.

Figure 3 Key Performance Counters (Close Figure 3)

Performance Counter | Description/Trends
Active Server Pages: Requests Per Second | If the counter is low during spikes in traffic, the application is causing a performance bottleneck.
Active Server Pages: Requests Executing | If pages execute quickly and don't wait for I/O, this number is likely to be low. If pages must wait for I/O, the value is likely to be higher.
Active Server Pages: Requests Queued | Should remain close to zero, but will fluctuate under varying loads. As the server reaches capacity, this number should start increasing to avoid resource contention.
System: Processor Queue Length | Displays the number of threads waiting to be executed in the queue shared by all processors in the system. If the value is consistently much higher than the number of processors, you have a processor bottleneck.
System: Context Switches Per Second | Combined rate at which threads on the computer are switched from one to another. Increasing threads may increase the number of context switches to the point where performance degrades instead of improves. Ten or more per request is high.
Process: Private Bytes | Current number of bytes this process has allocated that cannot be shared with other processes. Log this counter over several hours to pinpoint memory leaks in your components.
Processor: % Processor Time | Percentage of time that the processor is executing a non-idle thread. Processor bottlenecks may exist when this number is high while the network adapter card and disk I/O remain below capacity.
Distributed Transaction Coordinator: Active Transactions | Number of currently active transactions.

However, performance counters are only useful in identifying the symptoms of a problem, not the cause. If your system breaks at 20 concurrent users, the Active Server Pages:Requests Timed Out counter may indeed confirm that at least one user timed out, but determining the cause of the timeout becomes a bit like looking for a needle in a haystack. This is due to performance counter data providing information primarily at the OS and network level. To successfully pinpoint the source of the problem, you need to access the data at the application level. Building a distributed logging system to retrieve and centrally store error and performance data from within your application is critical to this task. It allows you to know immediately whether your system is working. If it isn't, you have the information necessary to pinpoint the problem areas.

Interpret Your Metrics

With all these metrics at your disposal, you now have access to a lot of data. So how do you make sense of it in an efficient manner? There are three options we'll discuss for interpreting performance counter data: Performance Monitor, Perfcol, and performance data integrated with load testing tools.

The Performance Monitor in Windows 2000 allows you to display the progress of various counters graphically. A useful feature is the ability to capture readings in log files, allowing you to visually examine the entire test run upon its completion. Figure 4 illustrates how site activity on an online ordering application can be interpreted within Performance Monitor.

Figure 4 Interpreting Site Activity with PerfMon (Close Figure 4)

Along the same line as Performance Monitor, the Windows DNA Performance Kit Beta contains a tool called Perfcol. This tool serves a purpose similar to Performance Monitor, except that it stores sampled data in a database rather than writing it to a file.

Some load testing tools such as the Microsoft Application Center Test (ACT) and the e-TEST suite from Empirix include built-in performance counter functionality that can record measurements over the duration of the test run. Counter data is then written to a database for later access. ACT, which is included with Visual Studio® .NET, integrates Performance Monitor counters, allowing all test data to be stored in a single repository.

Whether or not your load testing tool integrates some form of performance counter monitoring, you may find that you still need the support of a tool like Performance Monitor, particularly if your load generating servers do not have the appropriate security access to monitor the application servers, as frequently occurs when the environment includes firewalls.

Regardless of the monitoring tool you choose, the key is to store each test run's metrics for future evaluation. Going back to past data is critical to understanding how the system is reacting to the changes being made.

For application-level data generated by a logging system, we suggest building a viewer that enables you to obtain immediate access to error and performance information in one location. It's worth the effort, considering the alternative is generating a SQL query at the command line each time you require feedback.

Choosing the Right Load Testing Tool

To implement this testing strategy, you need to be able to choose a suitable load test tool. A complete evaluation of the available load testing tools is beyond the scope of this article, but we do want to help identify some options and considerations when making a decision on which kind of tool is appropriate.

The first option to consider are free tools like the Windows Application Stress Tool (WAST). On the other side of the scale, you can go with a more flexible tool such as ACT or Empirix's e-TEST suite. Figure 5 shows the interface for e-Load, the load-generating portion of the e-TEST suite.

Figure 5 Empirix e-Load Tool (Close Figure 5)

There are obviously some functional differences between the tools. WAST is a good tool to use for smaller sites that aren't too complex. You can easily test a couple of key pages on your site and get a good idea of what the response rate should be. However, it's more of an isolation test tool than one capable of testing a multipage site. Also, there are a few significant features not available in WAST that are necessary for testing complex sites (and implementing some of the recommendations in this article). Achieving complex results with WAST would require you to customize your application in order to load test it, which is obviously undesirable.

To perform the kind of testing that we're suggesting for complex sites, one of the more robust testing tools such as ACT or the e-TEST suite makes more sense. If you're developing in .NET, then ACT will integrate throughout your development cycle. However, it does require programming skills and knowledge of the ACT object model to produce powerful test scripts. If you decide instead to use a tool like e-TEST, you will need to pay licensing fees.

Figure 6 ACT Results Interface
Figure 6 ACT Results Interface

A quality tool must not only test the site effectively but also report the results of the test in a useful manner. Both ACT and e-TEST provide detailed reporting environments allowing you to graph your results as required. The ACT results interface is shown in Figure 6. Figure 7 provides a summary of common features and a description of what each type of tool has to offer.

Figure 7 Load Test Tool Feature Comparison (Close Figure 7)

Feature | Empirix e-TEST Suite | Application Center Test | Web Application Stress Tool
Browser emulation | Emulates Web traffic. Uses object-level recording that insulates the user from minor changes in code. Generates multithreaded browser requests that look like real traffic. | Provides programmable access to the load-generating scripts. Will automatically handle such browser details as cookies. | Records HTTP requests and then plays back copies of the recording. Doesn't take into account threading issues, client-side script, and other factors that affect how a real browser requests a page. The input response is independent of the output request.
Synchronization points | Available through a property setting. | Available through manually programming the script's behavior. | Not available.
Test duration | Allows you to specify time, the number of iterations, or allows you to stop the test manually. | Allows you to specify time, the number of iterations, or allows you to stop the test manually. | Tests are timed, so it does not guarantee that the start state for a user will equal the end state.
Regression testing | Serves the purpose of both a script creation tool and a regression test tool. | The same scripts that are used for load testing can be used for functional testing. Programming of the scripts is required to identify errors. | Not available. Specifically designed for load testing.
Distribute load across client machines | Tests can be coordinated from multiple client machines. | Not available. | Tests can be coordinated from multiple client machines.
Reporting | Stores session data in a database. Provides page timers and profile (test case) timers. Capable of graphically displaying test results such as the average times for the duration of the test run. | Stores session data such as server statistics and average time to first/last byte for a response. Can graphically display improvements between test runs. | Stores session data in a database. No page or profile timer functionality. Reporting available intabular format only from performance counters.
Executes client-side JavaScript | Yes, but depending on the script, a client agent that consumes more memory may be required. Try using the thin client first. | No. | No.

If you do decide that a more robust tool is necessary, don't underestimate the time necessary to get up and running. Some tools will claim that it takes mere hours to write the scripts necessary to begin testing. This may be true if you have some previous experience with it or a similar load testing tool, but be prepared to spend a few days or even weeks in preparation, depending on the complexity of your site. Our first test case took approximately three weeks to get up and running. You may find that you cruised through the sample tutorial, but there are several tricks that can only be learned through experience and many calls to the support line. The cost of hours spent learning the tool may far outweigh the cost of formal training or the presence of an experienced consultant. Also, you can't afford the lost time if you're starting your testing effort late in the development phase, in which case one or both of these resources is strongly recommended.

Know Your History

The number of tests you run in a day or even a week may vary. If you're tuning the Web server, you may decide to run a series of hourly tests. If your goal is to test the stability of the application, you would likely run the test overnight. Either way, keeping track of the variables and the progress made from one test to the next is going to be difficult unless you keep a documented history. It's crucial that you can easily pinpoint what has already been tested for, what was found, and what should be tested next.

At a minimum, you should record the start and end time of the run, the number of virtual users in the test, and a start description that describes the goal of the test and what has been changed. Complete the run with an end description that describes the results of the test.

Conclusion

To successfully deploy a complex Web application, you must first adopt a "no surprises" approach to testing that goes beyond system testing. Load testing—consisting of scalability, performance, and stability testing—is the only way to uncover major problems inherent in the architecture. In order to achieve this, you'll need a separate environment with comparable production hardware, a robust load test tool, and the cooperation of several people in your organization.

Appropriate metrics provide the means for determining whether your system measures up to the quality bar. Of these, the most valuable to the scalability lab team is the error and performance data captured by a distributed logging system, since it provides information at the application level.

By using the recommendations discussed in this article and making sure to document as you go, you're well on your way to ensuring a smooth deployment date.

For background information see:
Scalability
Performance
Scalability Overview
The Art and Science of Web Server Tuning with Internet Information Services 5.0

Jeff Dunmallis a principal consultant at imason inc., an Internet consulting company specializing in legacy integration for companies building business-to-business and line-of-business applications. You can reach him at jeff.dunmall@imason.com.

Keith Clarkeis an independent consultant based in Toronto. Along with his expertise in scalability testing, he specializes in .NET server technology consulting and technical training. He can be reached at keith.clarke@imason.com.


Test: Build Quick and Easy UI Test Automation Suites with Visual Studio .NET

Test

Build Quick and Easy UI Test Automation Suites with Visual Studio .NET

James McCaffrey
This article assumes you're familiar with Visual Studio .NET and C#
Level of Difficulty 1 2 3
Code download available at: UITestAutomation.exe (83KB)
SUMMARY

The .NET Framework provides a surprising new way to quickly and easily create user interface test automation. By using objects in the System.Reflection and System.Threading namespaces, you can write automated tests in minutes instead of hours. This article walks you through the building of a typical Windows-based application that will be used as the test subject. The author then runs through the creation of a C#-based test tool that simulates clicking the test app's UI controls and checks the application's state. After the tool is built, the author explains in detail how it works so you can modify and extend it for your own use.

Contents
The Problem
The Application
The Solution
How it Works
Running the Automated Test
Conclusion

I
f you've ever tried to write an automated test for user interfaces, then you know it's time consuming and tricky. But using reflection and the ThreadPool object in the Microsoft® .NET Framework, you can write powerful user interface test automation quickly and easily. In this article I'll walk you through the creation of a simple Windows®-based application and a small, powerful test tool that will showcase these .NET features.

The Problem

Suppose you're developing an application for Windows that has a standard user interface. Visual Studio® .NET and the .NET Framework make it easy to create buttons, menu items, and all sorts of other controls. Of course, during your development efforts you perform implicit manual testing of the user interface by checking basic functionality as you code. But suppose you want to write an automated test that will do a more thorough job of checking the user interface. If your product's design is stable, and if you have lots of time and resources, the solution would be to buy dedicated UI test software. While there are several good tools available, they do have drawbacks. They are relatively expensive, they often use a proprietary scripting language, and they tend to require a long time to redo scripts if the product changes significantly. For these reasons, this solution is not practical in many development environments. There must be a better way to do UI test automation.

With that thought in mind, I set out to create a UI test automation tool that allows you to create a test script in under 15 minutes with less than one page of code, can be used by novice testers, and uses only .NET functionality with no external dependencies.

After some experimentation I discovered that .NET does in fact provide the resources to create user interface test automation that meets all three design goals. Because the solution is quick to implement, you can afford to create test automation even when your product's design changes frequently. Because the solution is easy to understand, other people working with you can maintain your test automation code with little or no ramp-up time. And because the solution uses only native .NET code, there are no external dependencies to break the test automation.

The Application

Let's create a simple app that will serve as a basis for testing. Launch Visual Studio .NET and create a new C# Windows Application Project named MyWinApp. From the Toolbox, add three button controls, one textbox, and one listbox. Leave all the properties of the controls alone. Your design should look like Figure 1.

Figure 1 A Simple App
Figure 1 A Simple App

Double-click button1 to register an event handler for the button and then add the following code to print "Hello World" in textBox1:

private void button1_Click(object sender, System.EventArgs e)
{
  textBox1.Text = "Hello World";
}
As you can see, button1_Click takes two parameters. These parameters will become important when I write the test automation that simulates clicking the button. The sender parameter represents the object that generates the associated event—in this case, button1. The EventArgs parameter represents additional information for the associated event. In the case of a button click, no additional information is necessary.

Double-click button2 and add code that displays the two-line message in listBox1:

private void button2_Click(object sender,
  System.EventArgs e)
{
  listBox1.Items.Add("Goodbye World");
  listBox1.Items.Add("Come back again!");
}
Observe that manipulating the listBox1 field involves calling a method (Add) on a property (Items). When I automate, I'll need to access fields, properties, and methods.

Finally, double-click button3 and add the following code to erase any messages in textBox1 and listBox1:

private void button3_Click(object sender,
  System.EventArgs e)
{
  textBox1.Text = "";
  listBox1.Items.Clear();
}

Build and run the application. The application has distinct states. The initial state is {textBox1 = "textBox1", listBox1 = (empty)}. The state in Figure 2 is {textBox1 = "Hello World", listBox1 = "Goodbye World / Come back again"}. When I write the test automation, I'll need to be able to check state.

Figure 2 button2 Clicked State
Figure 2 button2 Clicked State

The Solution

Now let's create a simple but powerful automated test tool that will simulate clicks on the MyWinApp buttons and then check its state. Launch a new instance of Visual Studio .NET. Create a new C# Windows Application Project and name it MyWinAppTester. Using the Toolbox, add three button controls and two textbox controls. Adjust the text properties of button1, button2, and button3 to LaunchApp, Invoke Method, and Run Test, respectively.

The key to this user interface test automation is in the System.Reflection and System.Threading namespaces. Go to Code View and add two using statements to the six that were generated by Visual Studio .NET:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

using System.Reflection;
using System.Threading;

Now comes the heart of the test tool. Add the code shown in Figure 3 to your project just below the Main method. Before I explain this code, let's make the test automation work. Double-click the LaunchApp button control to get to its click method, and double-click the Invoke Method button and the Run Test button. Add the code in Figure 4 to the click methods. You'll have to modify the path to MyWinApp.exe in button1_Click to reflect its location on your machine.

Figure 4 Test Tool, Button Action Code (Close Figure 4)
// Launch MyWinApp
private void button1_Click(object sender, System.EventArgs e)
{
  testAssembly = Assembly.LoadFrom("C:\\MSDN\\MyWinApp\\bin\\Debug\\MyWinApp.exe");
  Type t = testAssembly.GetType("MyWinApp.Form1");
  testForm = (Form)testAssembly.CreateInstance(t.FullName);
  ThreadPool.QueueUserWorkItem(new WaitCallback(RunApp), testForm);
}

// Invoke Method
private void button2_Click(object sender, System.EventArgs e)
{
  object[] p = {this, new EventArgs()};
  string meth = this.textBox1.Text.ToString();
  InvokeMethod(testForm, meth, p);
}

private void button3_Click(object sender, System.EventArgs e) // Run Test
{
  object[] p = {this, new EventArgs()}; // arguments for a button click
  bool pass = true;
  for (int count = 1; count <= 3; ++count)
  {
    InvokeMethod(testForm, "button1_Click", p);
    Thread.Sleep(1000);
    if ( (string)(GetProperty(GetField(testForm,"textBox1"), "Text"))
      != "Hello World")
      pass = false;

    ListBox.ObjectCollection lboc = (ListBox.ObjectCollection)
        GetProperty(GetField(testForm, "listBox1"), "Items");
    if (lboc.Contains("Goodbye World") )
      pass = false;

    InvokeMethod(testForm, "button2_Click", p);
    Thread.Sleep(1000);
    // check that textBox1 has "Hello World" & listBox1 has "Goodbye
    // World"

    InvokeMethod(testForm, "button3_Click", p);
    Thread.Sleep(1000);
    // check to make sure that textBox1 and listBox1 are both empty
  }

  this.textBox2.Text = (pass) ? "Pass" : "Fail"; // display test result
}
Figure 3 Test Tool, Core Methods and Objects (Close Figure 3)
Assembly testAssembly = null;
Form testForm = null;
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic |
                     BindingFlags.Static | BindingFlags.Instance;

static void RunApp(object state) // Need this function to pass into
                                 // WaitCallBack().
{
  Application.Run((Form)state);
}

private void InvokeMethod(Form form, string methodName,
                          params object[] parms)
{
  EventHandler eh = (EventHandler)Delegate.CreateDelegate(typeof
                    (EventHandler), form, methodName);
  if (eh != null)
  {
    form.Invoke(eh, parms);
  }
}

private object GetField(object obj, string fieldName)
{
  Type t = obj.GetType();
  FieldInfo fi = t.GetField(fieldName, flags);
  return fi.GetValue(obj);
}

private object GetProperty(object obj, string propertyName)
{
  Type t = obj.GetType();
  PropertyInfo pi = t.GetProperty(propertyName, flags);
  return pi.GetValue(obj, new object[0]);
}

Now you're ready to build the project and run it. Click the LaunchApp button and MyWinApp will be launched, as shown in Figure 5. If you click button1, button2, or button3 on MyWinApp, you'll notice that it functions normally. I can use the MyWinAppTester to invoke individual methods in MyWinApp. For example, in the textbox next to the Invoke Method button, type "button2_Click" (case sensitive) and then click the Invoke Method button. This will simulate clicking button2 on MyWinApp by calling its associated method.

Figure 5 Tester Launching the App
Figure 5 Tester Launching the App

Now for the grand finale. Close MyWinApp, then relaunch it by clicking the LaunchApp button. Click the Run Test button on MyWinAppTester. You will see the actions of a simulated user clicking button1, button2, then button3, three times each in succession. The test checks the state of MyWinApp after each simulated click and finally displays a Pass or Fail message in the last textbox.

How it Works

The core method behind the technique is as follows:

private void InvokeMethod(Form form, string methodName,
                          params object[] parms)
{
  EventHandler eh = (EventHandler)Delegate.CreateDelegate(typeof
                    (EventHandler), form, methodName);
  if (eh != null)
  {
    form.Invoke(eh, parms);
  }
}

This method is primarily a wrapper for the Invoke method of a Form object. The method requires an event handler that was created using CreateDelegate so that the existing thread is used instead of spawning a new thread. Another way to understand this method is to examine its parameters. To invoke a method, I need to know what form the method belongs to, the name of the method, and any parameters that the method requires. The flags variable has the following value:

BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic |
                     BindingFlags.Static | BindingFlags.Instance;
This is a filter required by several Reflection methods. Typically, the methods that you will be interested in are private instance methods, but because the | operator means "or", I will catch more than what is absolutely necessary.

Although not essential, it is very useful to have two helper methods that return fields and properties with a given name from a given object (see Figure 6). In each case, I get the type of the object, get its information, and then return the field or property. The GetValue call in GetProperty requires the new object[0] argument because properties can be optionally indexed.

Figure 6 GetField and GetProperty Helpers (Close Figure 6)
private object GetField(object obj, string fieldName)
{
  Type t = obj.GetType();
  FieldInfo fi = t.GetField(fieldName, flags);
  return fi.GetValue(obj);
}

private object GetProperty(object obj, string propertyName)
{
  Type t = obj.GetType();
  PropertyInfo pi = t.GetProperty(propertyName, flags);
  return pi.GetValue(obj, new object[0]);
}

Launching the application to test uses two elegant .NET features, Reflection and the ThreadPool object:

Assembly testAssembly = null;
Form testForm = null;

// Launch MyWinApp
private void button1_Click(object sender, System.EventArgs e)
{
  testAssembly =
Assembly.LoadFrom("C:\\MSDN\\MyWinApp\\bin\\Debug\\MyWinApp.exe");
  Type t = testAssembly.GetType("MyWinApp.Form1");
  testForm = (Form)testAssembly.CreateInstance(t.FullName);
  ThreadPool.QueueUserWorkItem(new WaitCallback(RunApp), testForm);
}
Here I declare an Assembly object that represents the assembly containing the .exe I am going to test. (This hardcoded path could also be chosen from a File Open dialog.) Similarly, I declare a Form object that represents the Windows Form of the test application. To launch the test application, I first use Assembly.LoadFrom to get the test assembly. Then I use GetType to get the class of the application Form. Notice that I must use the full name of the form. These methods are part of the System.Reflection namespace.

The last line of the previous snippet does most of the work. The QueueUserWorkItem method creates a new thread of execution. By doing this instead of creating a new process, the test automation can communicate with the test application because the two threads are running in the same process. QueueUserWorkItem requires a WaitCallback delegate, which in turn requires a method to call (RunApp) and any parameters that the method requires (testForm). This line of code basically means execute the RunApp method using testForm as its argument.

Because of the structure of the WaitCallback delegate, I need to define the RunApp method, which is simply a wrapper around Application.Run:

   // Need this function to pass into WaitCallback().
   static void RunApp(object state)
   {
     Application.Run((Form)state);
   }

Running the Automated Test

With all the parts in place, let's review the code that simulates user interface test automation (see Figure 7).

Figure 7 User Interface Test Automation (Close Figure 7)
private void button3_Click(object sender, System.EventArgs e) // Run Test
{
  object[] p = {this, new EventArgs()}; // arguments for a button click
  bool pass = true;
  for (int count = 1; count <= 3; ++count)
  {
    InvokeMethod(testForm, "button1_Click", p);
    Thread.Sleep(1000);
    if ( (string)(GetProperty(GetField(testForm,"textBox1"), "Text"))
      != "Hello World")
      pass = false;

    ListBox.ObjectCollection lboc = (ListBox.ObjectCollection)
        GetProperty(GetField(testForm, "listBox1"), "Items");
    if (lboc.Contains("Goodbye World") )
      pass = false;

    InvokeMethod(testForm, "button2_Click", p);
    Thread.Sleep(1000);
    // check that textBox1 has "Hello World" & listBox1 has
    // "Goodbye World"

    InvokeMethod(testForm, "button3_Click", p);
    Thread.Sleep(1000);
    // check to make sure that textBox1 and listBox1 are both empty
  }

  this.textBox2.Text = (pass) ? "Pass" : "Fail"; // display test result
}

Remember that if I want to call a button_Click method, I need to pass it an object and EventArgs. I start by creating variable p which is an array of objects that has these two items. Next, I assume that the test will pass by setting a Boolean variable. The code is contained in a for loop that arbitrarily executes three times.

First, I call the InvokeMethod helper that calls the method names button1_Click, which is part of testForm, and I pass p (a sender object and an EventArgs object) to button1_Click. In short, I simulate clicking button1.

Thread.Sleep(1000) is an arbitrary delay of 1,000 milliseconds (1 second). Now, I use the GetField and GetProperty helpers to get the Text property of the textBox1 field in the testForm, and check to see if it is "Hello World". In short, I check the state of the test application. Checking listBox1 is a bit trickier, as you saw earlier. First, I get the Items property and store it into variable lboc, and then use the Contains method to examine it and make sure that it does not have anything in it yet.

In a similar way, I simulate clicking button2 and check its state, and do the same for button3. If no incorrect states are found, the value of the pass variable remains true, otherwise the value of pass will have been set to false. The result is displayed as a string in the TestResult textbox.

Conclusion

The .NET Framework has many features that have improved the development experience. A byproduct of these features is a powerful new way to write test automation. The technique presented in this article is a practical way to automate user interface testing in Windows. There are always costs involved in creating test automation, but because this technique is so easy to implement, it is ideal when product design is relatively dynamic or when you have limited resources for test automation.

For related articles see:
CLR Types: Use Reflection to Discover and Assess the Most Common Types in the .NET Framework
Advanced Basics: Best Practices for Windows Forms Applications

For background information see:
HOW TO: Submit a Work Item to the Thread Pool by Using Visual C# .NET
Visual Basic .NET Code Sample: Reflection

James McCaffreyworks for Volt Information Sciences Inc., where he manages technical training 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.


Go Mobile: Create Compact, Robust Mobile Apps with SQL Server CE 2.0 and the .NET Compact Framework

Go Mobile

Create Compact, Robust Mobile Apps with SQL Server CE 2.0 and the .NET Compact Framework

Mark Brown and David Meunier
This article assumes you're familiar with SQL Server and Visual Studio
Level of Difficulty 1 2 3
SUMMARY

Developers have myriad options when it comes to creating solutions for mobile devices. One of the greatest challenges facing mobile developers is finding a compact yet robust local storage solution. SQL Server CE 2.0 promises to deliver on both fronts. This new release represents a tremendous leap in terms of features and performance over its predecessor. This article will review some of the platform and tools choices developers have today. The authors will compare and contrast the significant new features in SQL Server CE 2.0 with the previous release. Following that, they will build a sample app for illustration.

Contents
Developing Mobile Solutions
Mobile Development Tools
SQL Server CE 2.0 Language Features
Server Features
Writing an App
Application Overview
Deployment Considerations
SQL Server CE 2.0—Call to Action

T
he explosive growth of mobile devices in recent years has enabled a new breed of mobile applications for consumers and enterprises alike. In light of this, Microsoft has been cranking out tools and frameworks so software developers will be able to fully exploit the capabilities of these smart devices. Combining these toolsets with a wide array of device options sets the stage for creating the most advanced mobile applications.

Microsoft® SQL 2000 Windows® CE Edition (also known as SQL Server™ CE 2.0) extends even more features of Microsoft SQL Server to Windows CE-based devices. SQL Server CE 2.0 provides all of the critical functionality you need to build robust database applications on the Windows CE or Windows CE .NET operating system (such as data storage, optimizing query processor, multiple connectivity options, and so on). This new edition also provides lateral compatibility with SQL Server 2000. Incredibly, it delivers all of this functionality, yet it is able to maintain an extremely small memory footprint.

This release provides significant new features and performance improvements over its predecessor. If you've worked with SQL Server CE in the past, you'll notice that performance has been bolstered in this new version and the programming model extended to support the .NET Compact Framework.

In this article, we'll take a look at several key features that are new to this release of SQL Server CE and examine some code that highlight these features. Keep in mind that this article does not attempt to address all facets of the SQL Server CE product. It should, however, whet your appetite for creating mobile applications with this new edition of SQL Server CE, whether you're a veteran mobile developer or just getting started.

Developing Mobile Solutions

If you're new to creating mobile applications, you'll find the list of Windows-powered devices, development tools, and frameworks daunting. SQL Server CE 2.0 is only one weapon in the mobile developer's arsenal. It would be impossible to recommend the perfect combination of tools and devices for creating your particular mobile solution. There are, however, some variables that you'll want to consider.

Today developers can target a range of Win-dows CE-powered devices. Both the Pocket PC (PPC) and Handheld PC (HPC) have been around for a while. Many of these can be equipped with wireless LAN modules such as Bluetooth and 802.11b, making them extremely functional devices that can stay well connected at all times. Devices based on the 3.x operating system tend to be consumer oriented, but businesses are realizing that these devices can also be used to empower their workforce as well as their customers.

Most recently, manufacturers have begun to embed wireless access technologies into the device itself. Gone are the days of the bulky PC Card sleeves piggybacked on the device in order to accommodate a wireless LAN card. The cellular phone is also converging with the Pocket PC, which makes for an interesting combination of technologies.

Embedded devices that are equipped with Windows CE have penetrated the workplace as well. And to widen their acceptance, many of these devices have been physically strengthened in order to survive a drop from several feet without breaking.

It's interesting to note that many of these embedded OEM devices have even newer versions of the Windows CE operating system than those available on consumer-oriented devices. The newest OEM devices are running version 4.x of Windows CE. These devices include the .NET Compact Framework by default. This makes this platform attractive for enterprise developers who want to be able to take advantage of the next generation tools and frameworks offered by Microsoft.

Windows CE is also being coupled with a variety of interesting hardware technologies including Compact Flash (CF) readers, bar code scanners, magstripe readers, Global Positioning Systems (GPS), fingerprint authentication, and cameras. Figure 1 explains some of the typical mobile solutions that companies are developing today using Windows CE-based devices and tools. For obvious reasons, these mobile applications require a compact yet robust local storage solution.

Figure 1 Mobile Application Examples (Close Figure 1)

Industry | Application
Sales Force Automation (SFA) | Provides access to product specs and client records in the field
Transportation (Logistics) | Delivery time, location, quantity, and signature for proof-of-delivery
Real Estate | Multiple Listing System (MLS) services
Medical | Patient profiles (known allergies, recent lab results, active medication orders) pharmaceutical information at the point of care
Consulting | Time and expense tracking, commission tracking, invoice creation, resource scheduling

The biggest challenge for mobile application developers has been the relatively small amount of memory available on mobile devices (typically 16MB-32MB). As the memory capacity of mobile devices increases, so too will the complexity of mobile applications. Of course, a local storage mechanism must have a small memory footprint. Memory considerations aside, your application will most likely need to perform complex queries against the local storage as well.

Furthermore, these applications need to work equally well in both online and offline scenarios. This necessitates having both local and remote data access APIs, as well as some form of synchronization to the back-end database. As luck would have it, SQL Server CE 2.0 provides a framework for building applications that meets all these requirements.

In offline mode, SQL Server CE provides storage on the local device itself. Database changes can be reliably tracked on the device and synchronized to the back-end database server when connectivity is reestablished. The same is true of server-side changes. SQL Server CE supports a feature called Merge Replication, which when used in conjunction with SQL Server 2000, provides two-way synchronization of all database changes.

Mobile Development Tools

Mobile application developers have the option of using either current or next-generation tools with SQL Server CE 2.0.

For those who prefer using eMbedded Visual Tools 3.0 to develop mobile applications, these legacy tools will continue to be supported with SQL Server CE 2.0. Applications can continue to be written in both eMbedded Visual Basic® and eMbedded Visual C++®. If you choose to use eMbedded Visual Tools, be aware that SQL Server CE 2.0 needs version 3.x or later of the Windows CE operating system on the device.

Rapid mobile application development will be possible using next-generation tools and frameworks from Microsoft. With the shift toward developing applications based on the Windows .NET platform, mobile applications can take advantage of many innovations that are available on the desktop platform today. Applications that are written in eMbedded Visual Basic or Visual C++ will be upgradeable to Visual Basic .NET, C#, and ADO.NET.

Microsoft has unveiled its Smart Device Extensions (SDE) which enable mobile developers to use a unified toolset based on Visual Studio® .NET. Visual Studio .NET combined with SDEs will provide a seamlessly integrated development platform for creating Windows CE-based applications.

The next release of Visual Studio .NET (code-named "Everett") will provide native support for the .NET Compact Framework, Pocket PC 200x, and Windows CE .NET 4x devices.

SQL Server CE 2.0 Language Features

SQL Server CE 2.0 includes its own native OLE DB provider. Unlike the SQL Server 2000 OLE DB provider, which exposes many properties to specify a server and database, SQL Server CE uses only a single property—DBPROP_INIT_DATASOURCE—which takes the full path to the database file to be opened. For example, to open the database file \Windows\Northwind.sdf, pass the string \Windows\Northwind.sdf in the DBPROP_INIT_DATASOURCE property when calling IDBProperties::SetProperties.

SQL Server CE also supports a file-level access control mechanism called a database password. This password must be passed each time the database is opened. The property DBPROP_SSCE_DBPASSWORD in the provider-specific property set DBPROPSET_SSCE_DBINIT should be used to specify this value. When creating a new database, this property can be utilized to specify a password that must be used.

Intrinsic functions that you've come to know and love in SQL Server 2000 are now supported. Now you can use mathematical, string, and system functions in your queries to perform operations and return scalar values. Previously you needed to use eMbedded Visual Basic (or eMbedded Visual C++) functions to accomplish this. The use of intrinsic functions results in a tremendous performance gain over SQL Server CE 1.0.

For example, Figure 2 demonstrates how to convert a column to a char(50) column, in order to make the results in the ListBox easier to format. Using a UNION on a SELECT statement in SQL Server CE combines the results of two or more queries into a single result set consisting of all the rows belonging to all queries in the union. The following snippet illustrates how to use the new UNION clause in a SELECT statement:

Dim strSQL As String = "SELECT * FROM TableA UNION SELECT * FROM TableB"

Figure 2 Support for Intrinsic Functions (Close Figure 2)
Dim strSQL As String = "SELECT CONVERT(char(50), title), _
  td_sales FROM titles;"
Dim ceCn As New SqlCeConnection("data source\ssceSample.SDF")
' create datareader to populate list box
Dim ceDr As SqlCeDataReader

Try
  ceCn.Open()
  Catch a As SqlCeException
  MsgBox(a.ToString())
End Try

Try
  Dim ceCmd As New System.Data.SqlServerCe.SqlCeCommand(strSQL, ceCn)
  'Create an instance of the ListBox.
  Dim listBox1 As New ListBox()

  With listBox1
    ' Set the size and location of the ListBox.
    .Size = New System.CeDrawing.Size(208, 160)
    .Location = New System.CeDrawing.Point(8, 64)

    ' Add the ListBox to the form
    Me.Controls.Add(listBox1)
      ceDr = ceCmd.ExecuteReader()
    While ceDr.Read()
      .Items.Add(ceDr.GetString(0))
    End While
  End With
  Catch a As SqlCeException
    MsgBox(a.ToString())
  Catch a As Exception
    MsgBox(a.ToString())
End Try

ceDr.Close()

Try
  ceCn.Close()
  Catch a As SqlCeException
  MsgBox(a.ToString())
End Try

SQL Server CE 2.0 has added support for parameterized queries through ADO.NET. This feature is also available to developers using eMbedded Visual Tools 4.x using the OLEDBCE provider. Those of you who are familiar with parameterized queries in SQL Server 2000 will note that there are some differences. Since there is no built-in mechanism for storing queries in SQL Server CE, the parameters are actually placeholders ("?") for values that are supplied at run time. The other obvious difference between SQL Server 2000 and SQL Server CE is that named parameters are not supported. Parameterized Query support, as shown in the following code, is new to SQL Server CE 2.0.

Dim strSQL As String = "INSERT INTO TableA (col1, col2) VALUES (?, ?)";

Index pull is another new feature. Applications call the Pull method to extract data from a SQL Server database and store it in a database table in SQL Server CE. The initial version of SQL Server CE only brought down the primary key from a table. SQL Server CE 2.0 brings down the primary key and all additional indices.

Using pull in applications can track the changes made on a SQL Server CE table by setting the appropriate tracking options. SQL Server CE tracks all inserts, updates, and deletes (see Figure 3).

Figure 3 Using the Pull Method (Close Figure 3)
' create RDA object
Dim ceRda As New RemoteDataAccess()
Dim strSQL = "SELECT * FROM Customers;"
Dim strRemoteConnect = "provider=sqloledb;data source=" &_
    strDataSource & ";Initial Catalog=Northwind;user id=sa;password=sa"

Try
   ceRda.InternetUrl = strInternetURL
   ceRda.LocalConnectionString = strLocalConnect

   ' pull down FOO table
   ceRda.Pull("FOO", strSQL, _
       strRemoteConnect, _
       Data.SqlServerCe.RdaTrackOption.TrackingOn)
   Catch a As SqlCeException
     MsgBox(a.ToString())
End Try

Another improvement over the previous version of SQL Server CE is the addition of more robust error handling. As you may have noticed in some of the previous code fragments, applications that are written in Visual Basic .NET and C# using the .NET Compact Framework can utilize the built-in try/catch support. If you've written any desktop applications using Visual Studio .NET, you're probably already familiar with this style of exception handling. Developers who have been confined to using the On Error GoTo style of error handling in eMbedded Visual Basic will really appreciate this new language feature. To complement the try/catch construct, SQL Server CE has added a new SqlCeException class that will let you catch native SQL Server CE errors and handle them gracefully.

Unlike SQL Server CE 1.0, this release provides developers with long error descriptions, not just error numbers. The error description mechanism is implemented as an optional DLL that you can place in the path of your SQL Server CE application on the device. While you are debugging your application, you can deploy the DLL with the application. Once you have completed debugging your application, you can remove the DLL from the device.

Server Features

SQL Server CE 2.0 includes a new Connectivity Setup Wizard, which simplifies the creation of virtual directories and data access permissions (see Figure 4). Those of you who have used prior versions of SQL Server CE know that specific virtual directories and access rights are required to communicate between Windows CE-based devices and SQL Server. Using the connectivity wizard, you can create and manage virtual directories on a computer running Microsoft Internet Information Services (IIS). In addition, you can configure and manage NTFS permissions on the computers running IIS and SQL Server.

Figure 4 Connectivity Wizard (Close Figure 4)

Like the previous release, this version uses an ISAPI DLL running under IIS to facilitate communications between the device running Windows CE and SQL Server 2000. This implies that if your device has HTTP access to the SQL Server CE virtual directory (that is, you can browse it using Pocket Internet Explorer), you'll be able to connect to your back-end database using Remote Data Access (RDA) or Merge Replication.

As with the previous version of SQL Server CE, you can now help protect the security of the local database either by using a password or by using a password along with 128-bit encryption. Using a password on the local database will prevent programmatic access to the local database, but it will not prevent any data from being read as clear text from the database file.

Using a password and encryption together will prevent programmatic access and will store user data in an encrypted form. If you are using Pocket PC 2002 extensions, you have the SSL native RSA-enhanced DLL with 128-bit encryption installed by default. If you are not using Pocket PC 2002, you will need to download the high-encryption pack (http://www.microsoft.com/mobile/pocketpc/downloads).

Currently Microsoft recommends that you enable SSL on your IIS box and use Basic Authentication. Microsoft plans to add Kerberos support in future versions of Windows CE .NET (4.x and above). This will allow for the delegation of credentials between a Windows 2000 or .NET Server and CE.NET devices. Once Windows CE .NET 4.x devices begin shipping, SQL Server CE 2.0 will provide a support document that will explain how to take advantage of Kerberos support.

Another improvement is that SQL Server CE 2.0 now matches SQL Server 2000 in terms of the number of indices that are supported. The initial release maxed out at 32 indices, whereas this latest release now supports 249 indices.

The SQL Server Client Data Provider is another component that is included with SQL Server CE 2.0. This is implemented as a managed wrapper and allows applications to talk directly to the back-end SQL Server database. This should probably only be used in well-connected scenarios where your mobile device has a persistent network connection, and where you don't have a requirement for local data storage.

Another option the SQL Server Client Data Provider presents is the ability to query through the provider to SQL Server to retrieve data to be placed into a local SQL Server CE database.

SQL Server CE 2.0 also includes a significant enhancement to ISQLW (see Figure 5). This is the SQL Server CE counterpart to SQL Server Enterprise Manager. You can install the executable manually onto the device by doing a file copy using ActiveSync®. ISQLW is automatically installed on the Start menu of your device when you deploy your Visual Studio .NET SDE or eMbedded Visual Tools application for the first time.

Figure 5 ISQLW
Figure 5 ISQLW

With the previous version of ISQLW, there was no easy way to view your database schema. This made it difficult to write queries unless you had the entire schema memorized. Now you can view your database objects in a tree-view, much like in the desktop version of SQL Query Analyzer.

SQL Server CE 2.0 provides two primary ways of connecting to back-end SQL Server databases: RDA and Merge Replication. RDA access supports connectivity to SQL Server 7.0 (and later) databases. Merge Replication, on the other hand, requires SQL Server 2000 on the back end. See Figure 6 for an architecture diagram of these two connectivity options.

Figure 6 Connectivity Options
Figure 6 Connectivity Options

RDA in SQL Server CE 2.0 provides a simple way for a Windows CE-based application to pull data from a remote SQL Server database table and store it in a local device database table. Your application can then read and update the local database table. SQL Server CE can optionally track all changes that are made to the local table. Using this information, the application can later push the changed records from the local table back to the SQL Server table.

Windows CE-based applications can also use RDA to submit SQL statements to be executed on a remote SQL Server database. For example, an application could submit SQL statements that insert, update, or delete records to a remote SQL Server table.

With SQL Server CE, RDA is Internet-based. Simply put, SQL Server CE communicates with back-end SQL Server databases through IIS. By connecting via IIS, RDA takes advantage of IIS authentication and authorization services. And since the communication protocol is HTTP, the machine running SQL Server can be located behind a firewall and can be accessed through a publishing rule using Microsoft ISA Server (or Microsoft Proxy Server).

To reduce the amount of transmitted data, RDA uses compression. This makes RDA well suited to wireless transports. Optionally, encryption can be used to help safeguard sensitive user data. RDA also has a mechanism to handle communication failures. If a failure should occur, retransmission will resume from the last successfully transmitted message buffer.

RDA control provides programmatic access to a SQL Server 2000 or SQL Server 7.0 database. RDA access is provided by a Visual Studio .NET or eMbedded Visual Tools application.

SQL Server CE Merge Replication is based on SQL Server 2000 and uses the same Publisher/Subscriber model. Using Merge Replication enables data to be updated independently on the device and the server. This data can then be synchronized when the device is later connected to the server. Before you can create a subscription, you must configure SQL Server 2000 replication, create the SQL Server publication, including setting up a shared snapshot folder to store published data, and then enable the publication for anonymous subscriptions.

The SQL Server CE Replication object has methods for adding and dropping subscriptions and for initializing, invoking, and terminating Merge Replication on a subscription. SQL Server Replication is more sophisticated and more complex than RDA, although not difficult to program once it has been correctly configured.

An exciting new feature of SQL Server CE 2.0 is the addition of the SQL Server CE Managed Provider (or wrapper) that provides an interoperability layer for Visual Basic .NET and C# mobile applications using the .NET Compact Framework. If you already use ADO.NET in your desktop applications, you will be comfortable using the SQL Server CE Managed Provider with the .NET Compact Framework. This programming model gives you the ability to interact with the SQL Server CE database in a very intuitive way.

This wrapper actually leverages the native SQL Server CE DLLs. The beauty of this is that an eMbedded Visual Basic (or eMbedded Visual C++) application and a Visual Studio .NET application can run side-by-side on the same device. However, this does not imply that concurrent access to the same SQL Server CE database is supported. It just means that applications written using either platform can co-exist on the same device. SQL Server CE is still single-user database engine.

Two new namespaces were added to support managed code using the .NET Compact Framework. These are: System.Data.SqlClient and System.Data.SqlServerCe. Also a class called System.Data.SqlServerCE.SqlCeException provides try/catch support for Visual Basic .NET and C#.

To describe the power of the new managed code wrapper using Merge Replication in your application, a single call to the Synchronize method will bring the entire database schema and underlying data down to a device without having to write a single line of data description language (DDL).

Writing an App

Now let's demonstrate some of the new features of SQL Server CE 2.0 using a code project. We're going to create a simple C# SmartDevice application using Visual Studio .NET and the .NET Compact Framework. We'll deploy the application to the Pocket PC 2002 emulator to simplify development and testing.

For the purposes of this example, we'll use a database that is created when you install the NorthwindCE sample application that comes with the .NET Compact Framework SDK. If you're a veteran of SQL Server, you're probably already familiar with the Northwind database that ships with SQL Server 2000. The NorthwindCE sample application includes a setup script called SetupRepl.bat, which creates a copy of the database called Nwind_SQLCE. The script also installs a Publisher with a local Distributor on the computer where you are running SQL Server 2000. Furthermore, it creates a merge publication named SQLCEReplDemoNet from the Nwind_SQLCE database. We will instruct our example application to subscribe to this same publication.

Figure 7 New Publication
Figure 7 New Publication

Once your SQL Server instance has been configured as a Publisher/Distributor for replication and has a snapshot folder defined with the appropriate permissions, you can run the SetupRepl.bat that comes with the NorthwindCE sample. If all goes well, you should see that the publication has been successfully created, as shown in Figure 7.

Application Overview

The application we've designed will illustrate several of the new features of SQL Server CE 2.0 that were discussed earlier in the article, such as how to perform replication using the SqlCeReplication.Synchronize method. We'll demonstrate full (two-way) synchronization between the SQL Server 2000 Nwind_SQLCE database and a local SQL Server CE subscription database, as well as other features such as the new SqlCeException object and the use of parameterized queries.

Figure 8 Windows Form
Figure 8 Windows Form

Figure 8 shows the Windows Form that we've designed for our SmartDevice application. The application allows us to query the Employees table of the local subscription database by entering a portion of the Last Name, and it will return the results in a ListView control. We've also added a Synchronize menu item that allows us to force any changes on the SQL Server 2000 database to be replicated down to our local SQL Server CE subscription database. SmartDevice may not be a thrilling application, but the way we get there is very interesting!

Before we get started, we'll assume that you've run the SetupRepl.bat script from the NorthwindCE sample. Make sure the SQL Server CE Server Agent has been configured with an IIS virtual directory name of "sqlce" and that it uses HTTP Anonymous access (certainly not a best practice for a production app). You can verify these settings using the SQL Server CE Connectivity Management MMC snap-in that comes with SQL Server CE 2.0.

The first part of the code we're going to look at demonstrates how to use the SQL Server CE SqlCeEngine object to create an empty database and the Replication object to synchronize with our existing SQL Server publication for the Nwind_SQLCE database. The DBInit function is called from our Form_Load event. It first creates an empty subscription database (if it doesn't already exist) and then calls DBSync to synchronize the publication database to the subscription database (see Figure 9).

Figure 9 Creating and Synchronizing a Database (Close Figure 9)
private void DBInit()
{
    try
    {
        if (!System.IO.File.Exists(sSubscriptionDB))
        {
            SqlCeEngine oEng = new SqlCeEngine("data source=" +
                               sSubscriptionDB);
            oEng.CreateDatabase();
            DBSync();
        }
    }
    catch (SqlCeException ex)
    {
        ShowErrors(ex);
    }
    catch (System.Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

In our DBSync method, we've hardcoded all of the properties of the Replication object for illustrative purposes only. You would obviously want to maintain these in some sort of application-defined store, such as a SQL table or an XML file. We're using SQL Server authentication in our example. The Synchronize method creates the subscription database and brings down the entire schema for all database objects in the publication, including related indices, as shown in Figure 10.

Figure 10 Synchronize Method (Close Figure 10)
private void DBSync()
{
    // instantiate replication object
    SqlCeReplication oRepl = new SqlCeReplication();

    try
    {
        // set publisher properties
        oRepl.Publisher = "MBLAP01";
        oRepl.PublisherDatabase = "Nwind_SQLCE";
        oRepl.Publication = "SQLCEReplDemoNet";

        // set publisher security properties
        oRepl.PublisherLogin = "sa";
        oRepl.PublisherPassword = "sa";

        // set subscriber properties
        oRepl.SubscriberConnectionString =
            "Provider=Microsoft.SQLServer.OLEDB.CE.2.0;
            Data Source=" + sSubscriptionDB;
        oRepl.Subscriber = "MBPPC01";

        // set internet properties
        oRepl.InternetUrl = "http://mblap01/sqlce/sscesa20.dll";

        // perform full synchronization
        oRepl.ExchangeType = ExchangeType.BiDirectional;
        oRepl.Synchronize();
    }
    catch (SqlCeException ex)
    {
        ShowErrors(ex);
    }
    catch (System.Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    finally
    {
        oRepl.Dispose();
    }
}

Once the subscription database has been created, and all objects in our publication have been replicated locally, we can use our application in a disconnected scenario. Changes made on either the SQL Server 2000 or SQL Server CE databases are resolved using merge replication the next time the DBSync method is called.

Please note the try...catch...finally construct technique of trapping exceptions in our SDE application. SQL Server CE exceptions are displayed in detail by the ShowErrors method. The code in Figure 11 shows how the SQL Server CE Exception stack is unraveled and displayed in a message box on the client.

Figure 11 Trapping Errors (Close Figure 11)
public static void ShowErrors(SqlCeException ex)
{
    SqlCeErrorCollection oErrors = ex.Errors;

    StringBuilder oStrBld = new StringBuilder();

    Exception oInner = ex.InnerException;

    foreach (SqlCeError oErr in oErrors)
    {
        oStrBld.Append("\nError Code: " + oErr.HResult.ToString("X"));
        oStrBld.Append("\nMessage   : " + oErr.Message);
        oStrBld.Append("\nMinor Err.: " + oErr.NativeError);
        oStrBld.Append("\nSource    : " + oErr.Source);

        foreach (int iNumPar in oErr.NumericErrorParameters)
        {
            if (iNumPar != 0)
                oStrBld.Append("\nNum. Par.  : " + iNumPar);
        }
        foreach (String sErrPar in oErr.ErrorParameters)
        {
            if (sErrPar != String.Empty)
                oStrBld.Append("\n Err. Par.  : " + sErrPar);
        }
        MessageBox.Show(oStrBld.ToString(), "SqlCeException");
        oStrBld.Remove(0, oStrBld.Length);
    }
}

The last code sample that we'd like to show is the FindEmployees method. This is called from the Click event of the Find Now button. The method begins by instantiating the SqlCeConnection object and connecting to our local subscription database.

Next, we construct a SqlCeCommand object which consists of a parameterized query against our Employees table, as shown in Figure 12. Notice how we're using the RTRIM intrinsic function to remove trailing spaces from the FirstName and LastName columns so that we can format these nicely in our ListView control.

Figure 12 Trimming Spaces (Close Figure 12)
private void FindEmployees()
{
    // instantiate the SqlCeConnection object
    SqlCeConnection oCon = new SqlCeConnection("DataSource=" +
        sSubscriptionDB);

    try
    {
        oCon.Open();

        SqlCeCommand oCmd = oCon.CreateCommand();

        oCmd.CommandText = "SELECT EmployeeID, RTRIM(LastName) + ', ' +
            RTRIM(FirstName) AS \"Full Name\" FROM Employees
            WHERE LastName LIKE ? ORDER BY LastName";

        // pass the search term as a parameter
        oCmd.Parameters.Add(new SqlCeParameter("p1", txtLastName.Text +
            "%"));

        SqlCeDataReader oReader = oCmd.ExecuteReader();

        // populate the list view control
        lvwEmployees.Items.Clear();
        while (oReader.Read())
        {
            ListViewItem oItem = lvwEmployees.Items.Add(new
                ListViewItem(new String []{oReader.GetString(1),
                oReader.GetInt32(0).ToString()}));
        }
    }
    catch (SqlCeException ex)
    {
        ShowErrors(ex);
    }
    catch (System.Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    finally
    {
        // close and release the connection
        if (oCon.State == ConnectionState.Open)
            oCon.Close();

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

Using the Parameters collection of the SqlCeCommand object, we can pass in the search pattern we want to use with the LIKE operator of our SELECT statement. Finally, we use the SqlCeDataReader to execute our query and populate the ListView control, then close and release the database connection that we opened to the subscription database.

Figure 13 Pocket PC Emulator
Figure 13 Pocket PC Emulator

Figure 13 shows what our completed application looks like running in the Pocket PC 2002 Emulator.

Deployment Considerations

Finally, we want to review a few things to consider when deploying your applications to the field. Although the SQL Server CE database engine itself is extremely reliable, any device it runs on can be susceptible to failure, primarily due to limited battery life. Consequently, any mobile solution you develop should always be designed with disaster recovery in mind.

Durable storage can be achieved by using persistent memory technologies such as CF Media. Plan to keep your application database and application executables stored in CF in order to allow your users to recover in the event that a cold boot is required. Creating a CAB file for your application greatly simplifies installation of your application by employees in the field.

SQL Server CE 2.0—Call to Action

SQL Server CE 2.0 will enable you to start building your own enterprise-class data management solutions for mobile devices. This release of SQL Server CE represents a significant leap in terms of features and performance over the prior release. We encourage everyone to get their hands on this exciting new release and start building the next "killer" mobile application. You can download SQL Server CE 2.0 at http://www.microsoft.com/sql/ce/downloads/ce20.asp.

For related articles see:
SQL Server CE: New Version Lets You Store and Update Data on Handheld Devices
Windows CE: Develop Handheld Apps for the .NET Compact Framework with Visual Studio .NET
Windows CE: eMbedded Visual Tools 3.0 Provide a Flexible and Robust Development Environment

For background information see:
.NET Compact Framework by Srinivasa Sivakumar, Craig Morris, Peter Stanski, and Andrew Polshaw (Wrox Press, February 2002)

Mark Brownis Chief Software Architect for IdentityMine Inc. (http://www.identitymine.com), an ISV based in Tacoma, WA. IdentityMine designs and builds next-generation Internet business solutions using Microsoft .NET Enterprise server technologies. You can reach Mark at mark.brown@identitymine.com.

David Meunieris a Principal Software Engineer for IdentityMine Inc., an ISV based in Tacoma, WA. IdentityMine designs and builds next-generation Internet business solutions using Microsoft .NET Enterprise server technologies. You can reach David at david.meunier@identitymine.com


Editor's Note: Elevating Test and Debug

Editor's Note
Elevating Test and Debug

Programming is fun. It's one of the world's great jobs, pure mental exercise with a golden payoff at the end. There's nothing like the feeling of accomplishment when that program you wrote is working, looks good, and is ready to ship. Then you sit back, sip your margarita, and watch the sales pour in, right?

Of course, it's never that easy. You can think up a program, slap a few lines of code into Visual Studio, and watch it work. But the actual implementation is only a small portion of the development process. You need to design the program. You need to redesign it to take system limitations into account. You need to test it. You need to debug things that don't work properly. You need to track down memory leaks—even if you're using the .NET Framework.

All these tasks are too often thought of as secondary to the task of programming software, and that can quickly be to your project's detriment. For too long, design, testing, and debugging have been treated as second-class jobs by many in the world of programming. Many companies have built-in animosity between dev and test. Testers are seen as people who can't handle development and who make the devs' jobs harder by running unlikely scenarios just to try to break perfectly good software.

The former idea—testers are lightweight devs—should never be true in a quality software process. The latter—testers are annoying sticklers—should, on the other hand, always be true. As the complexity of software increases, and as more people are affected by a piece of code, the role of testers becomes more visible than ever. Our magazine has long covered the development angle, with a bit of debugging thrown in. This month, we felt it was time to recognize the role of the whole development process by discussing a variety of aspects from architecture to testing to scaling.

You'll find several articles this month that talk about how to add quality to your software products. One of the most persistent coding errors in Windows-based programming over the past decade has resulted in the GDI memory leak. We'll show you how to track down and eradicate many of these problems in your code before it starts running more slowly than a toddler on a trike.

The best idea of all might be one that's still not popular, but is gaining a little momentum. Instead of creating lifelong dev and test career tracks at your organization, why not try a little swap? Every 18 months (or once a project), your devs go into test, and your testers go into dev. Today's world of testing requires a lot more than just running prepackaged tests. A good tester should be able to code their own test suite and really needs to understand the inner workings of software just as well as the developer does. Knowing how testing works will be valuable to devs, too, because they'll be less likely to fall into coding traps that should be short-circuited in advance. And, most importantly, it will help break down the wall between the two disciplines and foster greater understanding and a more cohesive workplace.

While we're on the subject of design and change, flip through this issue for a minute. Notice anything different? We've made a minor upgrade to the magazine's look this month. Gone are the pastel column headers; in their place is a bolder, crisper look. It's easier to spot from a distance, and we feel it looks better.

When we merged MSJ and MIND back in 2000, the magazines were converging naturally, but we kept a distinct look between the front-of-book columns (which were mostly from MIND) and the back-of-book ones (mostly MSJ). Even the title bars on the columns looked a bit different, making a distinction that no longer holds true. Not many people ever noticed this, and the line between Internet-related and systems-centric content has blurred even more over the past three years, especially with .NET and Web Services holding the day.

Our art and production department gets full credit for this redesign. They spend many hours each month making what we feel is the most readable magazine in the industry (if not in the world of periodicals). Somehow, they manage to keep the readability high, avoiding normal yet annoying tricks like "continued on" jumps for articles. We don't talk about the design of the magazine all the time, but that's because they just make it happen every month. It's like the sun coming up in the morning.

So just as C++ and Visual Basic have now been merged into a single program with a single user experience, we've done the same with our columns. All the columns you like are still there, and we're even going to be adding a couple of new ones in the months to come. Keep an eye out for them, because we're more excited than ever about the year ahead.

—J.T.

Active Directory, ActiveSync, ActiveX, BizTalk, JScript, Microsoft, MSDN, Visual Basic, Visual C++, Visual Studio, Windows, Windows NT, and Win32 are registered trademarks of Microsoft Corporation. Visual J++ is a trademark 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.


New Stuff: Resources for Your Developer Toolbox

New Stuff
Resources for Your Developer Toolbox
Theresa W. Carey

Contents
Licensing Component for .NET
Put it in Your Pocket (PC)
Telephony under .NET
Plug in a Logic Flow Map
Cross-platform Design and Debug
High-volume Transaction Environments
Disaster Recovery
Search and Edit Source Code
Automated Testing Tools
Create a Flash Demo without Flash
The Bookshelf

Licensing Component for .NET

Wanderlust Software LLC has released licX Licensing Component for .NET, a solution for licensing applications and components written for Microsoft® .NET. Featuring drag and drop integration with existing Visual Studio® .NET projects, licX allows developers to add licensing support to their products quickly and easily. licX provides explicit support for redistributable components, ensuring that only properly licensed components can be redistributed.

By supporting evaluation licenses, licX allows a single product distribution to behave differently depending on the license key used. This allows a developer to package a free download of their full product with an evaluation license key that provides only limited functionality. licX can provide periodic visual notifications (so called "nag" screens) to the evaluating user for purchasing the product. Once purchased, a full license key can be sent via e-mail so that the existing product can be "unlocked" with no need to distribute new binaries.

Put it in Your Pocket (PC)

SYWARE Inc. is now shipping Visual CE 7, a database and forms development toolset designed for building customized databases on Pocket PC and Windows® CE-based handheld devices. Visual CE 7 features an array of development tools, including support for Global Positioning System (GPS) devices, picture control, multidevice synchronization, and a wide range of numeric, string, and date functions.

Because it supports GPS, Visual CE 7 can open the serial port on the handheld device, read the NMEA standard statements containing positioning data, extract the latitude, longitude, and altitude coordinates, and close the serial port. This allows Visual CE to read and store positioning information for a variety of field applications that have relied on paper-based communication.

In addition, pictures can be integrated into any Visual CE database and multiple handhelds can be synchronized to the same desktop table, making it easy to keep the latest product catalog or maintenance data in the hands of the field force.

Visual CE allows strings to be written to or read from the serial port, providing access to a broad range of external devices, including scanners, test equipment, and medical devices. In addition, button colors can be set to prompt users visually and grids can be ordered by specific values. For those with SYMBOL devices, a scan can launch a macro program.

Integration with mEnable, SYWARE's wireless architecture, extends handheld apps with real-time, interactive access to server data, allowing mobile devices to be tied to enterprise business processes.

Telephony under .NET

Pronexus Inc. has announced the release of VBVoice 5.0, a telephony development environment that runs under Visual Studio .NET. VBVoice 5.0 allows communications developers to choose among a variety of languages including Visual Basic® 6.0, Visual Basic .NET, and C#. New telephony controls include VoIP, SpeechWorks OSR, and Web integration. Standard telecom processes are organized into user-programmable ActiveX® controls, and programs can call any .NET or ActiveX component directly. New controls in VBVoice 5.0 include Voice over IP, which allows both inbound and outbound voice calls over the H.323 protocol.

This integration of supported IP cards means that developers can continue to use existing VBVoice controls regardless of the underlying hardware. VBVoice allows developers to incorporate speech recognition into an application, adding integrated support for SpeechWorks OSR to its speech recognition capabilities. A new Voice Commands control enables developers to make systemwide voice commands (Help, Operator, and so on) available for the duration of a call without requiring additional recognition sessions.

The optional WebQuery control allows data retrieval from any Web site identified by a URL. This is useful for accessing directory information or other Web publications for use in a VBVoice application.

For large-scale installations, VBVoice 5.0 introduces Modularity, an optional, fully distributed master/slave architecture. Developers can host system resources and services such as telephony hardware and TTS and ASR engines on one or more master machines while the applications themselves run concurrently in separate processes on separate machines.

Plug in a Logic Flow Map

AppsChannel Inc. has released Visual Workbench, a plug-in tool which extends the Visual Studio .NET IDE. Visual Workbench uses a flowchart metaphor to conceptualize and render a programming solution in one step. The resultant logic flow map is stored as XML and can be used repeatedly to generate in-context source code directly to the code editor window. By storing its logic flow maps as XML, Visual Workbench provides compatibility with Microsoft .NET and XML Web Services as well as implementing a methodology independent of programming language restrictions. Currently, the Visual Workbench XML maps can be reused in projects employing Visual Basic .NET and C#. AppsChannel has announced plans to support Visual J#® .NET and Visual C++® .NET.

Cross-platform Design and Debug

SoftLite Ltd. recently released ScriptWorx.NET, an evolution of the ScriptWorx Professional Web applications development suite. Designed for the Web developer, ScriptWorx.NET offers support for key technologies, including ASP.NET, XML, Visual Basic .NET, C#, and SOAP. A range of new features have been implemented for the design and debugging of Web applications and Web Services, adhering to industry standards for an independent route to .NET. ScriptWorx.NET is compatible with other members of the ScriptWorx family, sharing a common user interface.

High-volume Transaction Environments

Xtremesoft Inc. has published new versions of its AppMetrics for Transactions and AppMetrics for BizTalk Server products. AppMetrics for Transactions 2.5 is aimed at organizations with applications built using Microsoft Transaction Services (Windows NT®) and COM+ (Windows 2000) application servers. It has a variety of new features focused on making the applications more available. These include automatic detection and correction of performance problems, such as component hangs and memory leaks.

AppMetrics for BizTalk Server 2.0 is designed for organizations with applications built using both Microsoft BizTalk® Server 2002 and 2000. AppMetrics' new Suspended Document Manager helps you deal with the exceptions that occur in any high-volume environment. The Business Process Manager enables operations staff to identify the status of workflows and their associated documents.

Disaster Recovery

Aelita Software Corporation has announced the newest releases of ERDisk 6.5 for Windows NT 4.0, Windows 2000, and Windows XP, and ERDisk 6.5 for Active Directory. These programs are designed to help organizations recover from computer emergencies. ERDisk for Windows NT 4.0, Windows 2000, and Windows XP automate the process of creating and using emergency repair disks and system state backups. ERDisk for Active Directory provides backup and online granular recovery of Active Directory®. Version 6.5 of ERDisk for Active Directory extends its online granular recovery capabilities with attribute-level recovery. It includes features that enable Active Directory change analysis and troubleshooting and improved Group Policy Object (GPO) management.

ERDisk for Active Directory now provides "snapshot" comparisons of two versions of Active Directory, helping with the challenge of isolating differences between two backup copies of Active Directory, or a backup copy and a live version. A new analysis and reporting engine also supports improved change tracking and troubleshooting. Group Policy backup/restore is expanded through support for GPO replication across domains. This gives administrators the ability to model GPO changes in a test lab and then copy them to the production environment. GPOs can also be replicated across live domains to ensure consistent GPO usage across the enterprise.

Version 6.5 of ERDisk for Windows provides control over the backup through an Intelligent Selective backup feature that enables collection of only those files that are not available from other sources, such as the system installation CD or a Service Pack. This feature speeds data collection and reduces the bandwidth and storage required by backups by 85 to 95 percent. During recovery, ERDisk automatically retrieves files from the original source as required.

Search and Edit Source Code

Hurricane Software Inc. recently announced that WinGREP 4 is available for download. WinGREP is a search tool that integrates with multiple IDEs and editors to allow developers to find and edit source code, documentation, and archived files. Click on the desired string and WinGREP will launch the appropriate IDE, open the file, and position the cursor on the match. Searches allow the use of regular expressions, file masking, exact case, multiple strings on any single line, and more. Developers can include or exclude multiple files and save settings using the projects feature for fast recall.

Automated Testing Tools

Seapine Software Inc. recently released QA Wizard 1.6 for testing Web applications. This version provides a tighter integration with TestTrack Pro, Seapine's defect management solution. QA Wizard's improved integration feature automates moving defects into TestTrack Pro. Seapine's product architecture allows multiple copies of QA Wizard to feed defects into TestTrack Pro on a LAN or remotely over the Internet.

Another new feature is automated exception handling, which lets QA Wizard continue testing when specific exceptions occur during script playback. This can be helpful during unattended testing sessions. For example, while testing a Web application, an error message dialog may appear requiring user interaction. Instead of disrupting the testing process, QA Wizard records the message, closes the dialog, and continues playing back the script.

Two additional new features are the ability to add commands and checkpoints to scripts without rerecording and an undo/redo command.

Create a Flash Demo without Flash

eHelp Corporation has announced the availability of its newest product, RoboDemo 3.0, for creating software demos and tutorials in Flash format without having to learn Flash. By combining Flash compression technology within an editing environment, RoboDemo 3.0 lets users build Flash tutorials and demos with small file size and high fidelity that can be viewed via the Web and on different platforms without requiring proprietary plug-ins. Companies can use RoboDemo 3.0 to create marketing and sales demonstrations, eLearning tutorials, online support, and more. RoboDemo 3.0 can record Flash movies of an application in use or on-screen activity, which can then be played back as a demo or tutorial. Tutorials and demos can be edited frame by frame.

The Bookshelf

Developing .NET Custom Controls & Designers Using C# by James Henry is targeted for experienced developers who have a basic understanding of C# and the .NET Framework. The book takes you to the next level of .NET programming: creating custom controls and designers for both Windows and the Web. It starts with an introduction to basic prerequisites, including data binding and event handling. It then discusses type conversion and editor implementation topics.

The book covers the basic architecture of Windows Forms and Web Forms. Topics include tools and services for enhancing design-time support for components, irregularly shaped, borderless Windows Forms, and advanced techniques on building simple and complex UITypeEditors.

Henry also details how TypeConverters work and offers guidelines on implementing new ones. In addition, you'll find a component licensing model and architecture with sample demonstrations, a reusable Windows Forms wizard control, and a reusable Web Forms tab control. More details are available at http://www.bluevisionsoftware.com.

Wrox Press recently published Professional .NET Network Programming, by Doug Paterson, which is intended to give the programmer familiar with C# the information necessary to put network programming at the heart of their .NET-based applications.

This book will give you an understanding of the general concepts of networking, information about network programming in .NET with C#, and skills to build network-based applications in .NET. The book includes an overview of the architecture of physical networks, network protocols, the OSI model, streams in .NET, and the classes provided to facilitate access to streams such as StreamReader and StreamWriter.

Then the book moves on to sockets programming, with an introduction to the concept of sockets, and full coverage of using sockets in .NET. There's discussion of TCP, UDP, using multicast sockets in .NET, and application-level protocols built on top of these layers, such as SMTP and FTP.

The book also covers Internet programming with a look at HTTP and e-mail protocols, the .NET classes for sending e-mail via SMTP, and implementing POP3 and NNTP clients in .NET to read mailboxes and newsgroups. The book finishes with an exploration of how to secure network communications in .NET—encrypting network traffic, certificates and Secure Sockets Layer, authenticating and authorizing users with NTLM and Kerberos. All the code examples are in C#. Find out more at http://www.wrox.com.

Contacts

Aelita Software Corporation 6500 Emerald Parkway, Suite 400, Dublin, OH 43016, 800-263-0036, 614-336-9223, http://www.aelita.com

AppsChannel Inc. 14031 West Highway 51, Sand Springs, OK 918-455-4714, http://www.appschannel.com

eHelp Corporation 10590 West Ocean Air Drive, San Diego, CA 92130, 800-358-9370, 858-459-6365, http://www.ehelp.com

Hurricane Software Inc. 19401 East U.S. Highway 40, Suite 130, Independence, MO 64055, 888-946-4737, http://www.hurricanesoft.com

Pronexus Inc. 260 Terence Matthews Court, Kanata, Ontario, Canada K2M 2C7, 877-766-3987, 613-271-8989, http://www.pronexus.com

Seapine Software Inc. 5390 Courseview Drive, Suite 115, Mason, OH 45040, 513-754-1655, http://www.seapine.com

SoftLite Ltd. Forsyth House, Rosyth Europarc, Rosyth, Fife, KY11 2UU, United Kingdom, +44 (0) 1383 428026, http://www.softlite.net

SYWARE Inc. P.O. Box 425091, Cambridge, MA 02142, 617-497-1300, http://www.syware.com

Wanderlust Software LLC 1120 SE 12th Avenue, Deerfield Beach, FL 33441, 954-294-2294, http://www.wanderlust-software.com

Xtremesoft Inc. 800 West Cummings Park, Suite 1850, Woburn, MA 01801, 866-987-3631, 781-937-3800, http://www.xtremesoft.com

Send your New Stuff to Theresa at newstuff@microsoft.com.

Theresa W. Careyis a freelance writer who lives in California. Her byline has appeared in Barron's and PC World.


Web Q&amp;A: Releasing Memory in JScript, Bulkload Problems, and More

Web Q&A
Releasing Memory in JScript, Bulkload Problems, and More
Edited by Nancy Michell

Q I have some questions regarding garbage collection in JScript®. I think I have a memory leak in Microsoft® Internet Explorer 6.0, but on the other hand it might be a circular reference problem. In my code, I assume "new Object()" is owned by JScript but "document.createElement" is owned by the Internet Explorer DOM and therefore the garbage collector from JScript cannot release the DOM element. Is this correct? What is the best way to avoid this?

When you load the code in Internet Explorer 6.0 and click Refresh, you can observe increasing memory in Task Manager:

<html>
<body>
  <script type="text/JScript">
    for (i=0; i<1000; i++) {    // this loop enforces the effect
      var model = new Object();
      var element = document.createElement("<br>");
      model.myElement = element;
      element.myModel = model;
      model = null;
      element = null;
      }
  </script>
</body>
</html>

A This isn't a memory leak. You're creating new text on the page a thousand times! That's going to make the page bigger. It's only a memory leak if you create a bunch of objects that cannot be reached and also cannot be freed. Here you're creating a bunch of elements that Internet Explorer needs to hold onto to render the page correctly. Internet Explorer doesn't know that later you're not going to run a script that manipulates all those objects you just created.

When the page goes away—when you navigate away in the browser—the memory will be released. It will not leak. The circular reference is broken when the page is destroyed.

Q I'm trying to import some XML into a database by passing the XML to a stored procedure which is being called by the ADO.NET SQL provider. When the XML file is too large, it fails with a SqlException (which says that a severe error occurred). I've tracked it down to a failure in sp_xml_preparedocument.

It works fine with an XML file that has 43403 characters, but if I add one more character (by changing the final Vendor field from "Micros" to "Microso"), I get the failure. The parameter being passed to the stored procedure is of type ntext. I can't use bulkload. Is this a known issue? Is the size limit coming from ADO.NET, SQL, or sp_xml_preparedocument? And finally, is there a workaround?

A Actually, one way you could use bulkload is to load the data into staging tables and then do your insert and update from there. As far as sp_xml_preparedocument failing, such problems have happened before. Most of them were due to one of the following: a single wrong character (or character in wrong encoding) that trips up the parser, using an nvarchar that truncates the XML, or using text instead of ntext (which leads to improper characters).

However, on the off chance that none of these are the actual cause of your problem, the next step you should take is to try the latest SQL Server™ service pack (http://www.microsoft.com/sql) in order to see if that rectifies this problem.

Q I'm designing a search interface and I want to use translations for the word "Go". Many Web sites use "Go" in a variety of languages to signify the submit function. But should I use the word or some type of icon? Should I just use an arrow icon?

A Arrow symbols like those shown in Figure 1 are directional, so their interpretation depends on where they are placed. Text is not directional, so it doesn't matter where you decide to place it; the meaning doesn't change.

Figure 1 Arrow Icons
Figure 1 Arrow Icons

While studies may show self-reported user preference for one button over another, this is much less important than understanding the interface on first encounter. This is especially true for a tool or Web Service that may be used only occasionally.

No matter what label you choose for the submit button, however, remember it's most important to support the Enter key as an option for search query submission. Users rarely comment on the Enter key functionality unless it's not there; then you'll hear about it! Outside of that, for usability purposes, the iconography for submission means much less than the proximity of the submit button to the search text box (preferably to the right of it).

Q I have a question about persisting a data tree structure to a SQL database and having that tree be searchable and fast. XML could represent the structure and I could use SQL for the storage, but I want to be able to search the data tree quickly. Unfortunately, there will be a variable number of branches and leaves much like a directory structure on a file system, so I can't use a fixed table relationship. What is the best way to build this database structure?

A SQLXML seems to perform much better on shallow hierarchies than deep ones. As the nesting of the XML increases, so does memory usage and processing time. Take a look at this article on processing hierarchies in SQL: Expanding Hierarchies.

The deeper the XML nests, the more expensive it is to parse or construct (from flat data). Try parsing XML over a range of 1,000-10,000 levels deep using the world's fastest XML parser. Try running a simple XSLT script over that same data. Deep hierarchies are almost always more expensive than shallow hierarchies.

When retrieving XML through XPath, SQLXML uses the SQL Server FOR XML EXPLICIT mode, which essentially requires ordered outer unions. According to various industry studies, when constructing XML from flat relational data, the ordered outer union approach generally outperforms every other approach except for hybrid shredding (in which the data granularity is determined adaptively or otherwise adjusted to avoid unnecessary joins). SQLXML doesn't control or modify the user's data layout, so adaptive storage strategies are not an option.

When modifying XML through updategrams, SQLXML shreds the data (takes it from a hierarchial format into a flat format) and generates atomic DML statements, just like in your example. When inserting XML through bulkload, SQLXML again shreds the data, but uses a SQL bulkload operation. Both are very efficient.

On retrieval, the worst case is that you get roughly one select per element type (not per element), union all'd together and then ordered by primary key columns for nesting. Frequently, there are two main performance problems with this approach. First, SQL Server does not optimize cast primary key columns well (you should use lots of sql:datatype/xsd:type qualifiers in the annotated schema so that SQLXML can eliminate unnecessary casts from the query it generates). Second, ordered outer unions scale badly beyond, say, hundreds of element types.

The example you cite is not an apples-to-apples comparison because it does not generate XML. It's easy to generate flat data that represents a hierarchy. The expensive part is turning that flat representation into a true hierarchical representation (XML). The deeper the hierarchy, the more expensive the transformation. If you run a performance analysis tool over SQLXML, you'll see that most of the processing time is taken up by datatype conversions in OLEDB and conversion from the flat rowset into XML hierarchy, not in the execution of the SQL query itself. You can also try running a union query without the FOR XML EXPLICIT at the end and verify that the SQL query is not the bottleneck.

If you choose to use XML to persist the data tree structure, then you should consider using SQLXML3.0 to shred this document into a SQL Server database. One possible solution is to create an annotated XSD schema that describes the instance XML document. The max-depth annotation allows you to describe recursive tree structure. The SQLXML bulkload feature allows you to shred this document to the SQL Server database. The SQLXML help file has some examples. Take a look at the max-depth section where it describes how to treat XML documents with variable levels of hierarchy (as long as they are recursive and you can specify the maximum number of levels).

Q I have noticed that many Web sites which require a login in order to access some of their content or services don't place the login box directly on the home page. Most sites include a link to sign in somewhere at the top of the home page or don't even place a link on the home page at all, but instead direct the user to a login page when he navigates to a section of the site that requires login.

Should a Web site show a login section directly on the home page where the user can enter a user name and password? Or is a small sign-in link enough? Or is it better to go with the third option where the user is prompted to log in only when they need to?

A Rather than placing the user name and password request fields on just the home page or on a separate page dedicated for logging in, it might be appropriate in some cases to provide the user name and password fields on all pages. This does take some screen real estate, but it reminds unregistered users that there is more available if they log in. Taking users to a dedicated login screen is disorienting, even when you return them to the page they came from.

Q Suppose you have a function, and sometimes that function is also inside a try... catch..., but other times it isn't. And suppose when it is in a try catch, I was to throw the same error again, but when it isn't I don't want to because I'll get an error saying "an exception has been thrown but not caught."

So I want to rethrow the error only if there is a nested catch to catch it. Is there a way to tell if there is a catch to catch a throw before you throw? Here's the JScript code:

try
{  // Do something that causes an error
}
catch(e)
{
    if (<there is another catch(e)>)
    {
        throw(e);
    }
        else
    {   // do something
        }
}

A Sorry, there's no way to detect whether a caller will catch an exception you throw. However, your question reveals a potential design flaw in your application. The whole point of throwing exceptions is to say "something unexpected happened, and I don't know how to handle it." If you can get away without rethrowing the exception, then apparently you can handle this unexpected event. If you can handle it and continue, then don't ever rethrow the exception. If you can't handle it, defer to your caller to handle it. You should never have to make a decision based on what the caller can handle—make the decision based on what you can handle.

If no caller can handle the exception, the right thing to do is to halt the program. A program that can't handle an exception is badly broken and needs to be stopped before it does damage. If your caller can't handle an exception then either rewrite the caller or don't throw an exception.

Also, don't ever use exceptions as control flow for main-line cases. Exception handling was designed and implemented specifically to manage fatal errors, so that's all it should be used for.

Q In ASP.NET (specifically with Visual Basic®), how do you post form data to a Web page? When an ASP.NET Web page is rendered, the form's action field is automatically changed to post to itself. I need to show a login page, and when "Login" is clicked, post the form data to a different Web site (that Web site is set up to use Request.Form to capture my data). I know I can capture the data myself and pass it in a query string, but that's not an option. I must post it to the other site.

A If you're just using HTMLControls on your form, all you need to do to post to another page is remove the runat="server" from the FORM tag and set the action attribute to the URL you want to post to. If you don't add the runat="server", the FORM element will be sent to the client unaltered. This does not prevent you from setting values of the HTMLControls on your form in the Web form's Page_Load event.

If you are using WebControls, the runat="server" on the form is required. However, you can still post to another page. Details for this case can be found at: Passing Server Control Values Between Pages.

Got a question? Send questions and comments to WebQA@microsoft.com.

Thanks to the following Microsoft developers for their technical expertise: Avner Aharoni, Amy Alberts, Greg Alger, Tom Alphin, Bear, Michael Brundage, Eric Lippert, Rainer Maurer, Everett McKay, Tosh Meston, Manal Mohanna, Michael Rys, Marc Shepard, Kim Tapia St Amant, Mark Stevenson (MCS), Maarten van Dantzich.


The XML Files: Introducing XPath 2.0

XML Files
Introducing XPath 2.0
Aaron Skonnard

Contents
XPath 1.0 Limitations
XPath 2.0
One Data Model to Rule Them All
Sequences
Set Operations
Sequence Processing
Sequences and Predicates
Sequence Coercions
Comparisons
Explicit Quantification
Other Language Enhancements
Improved String Support
One Person's Trash, Another's Treasure

Over two years ago in one of the first installments of this column, I wrote about XPath version 1.0 (see The XML Files: Addressing Infosets with XPath for a quick review). As stated in the original specification: "XPath is a language for addressing parts of an XML document." Using XPath expressions, developers can easily identify nodes in an XML document for further processing. This has made it possible to replace complex traversal algorithms with simple declarative expressions. For example, the following expression identifies all child elements of the LineItem element with a Sku attribute value of 123 that are descendants of the root Invoice element:

/Invoice//LineItem[@Sku='123']/*

Writing the same logic with a traditional XML API would be extremely tedious and difficult to get right. As a result, XPath is commonly supported as a layered service in various API implementations today (such as DOM, XPathNavigator, and so on):

// C# DOM Code
XmlNodeList nodes =
   doc.SelectNodes("/Invoice//LineItem[@Sku='123']/*");
for (int i=0; i<nodes.Count; i++) {
    ... // process selection here
}

XPath is also used heavily in XSLT 1.0 for addressing input documents, as illustrated here:

<xsl:apply-templates
   select="/Invoice//LineItem[@Sku='123']/*"/>

Overall, XPath is a huge win for XML developers and has been quickly embraced as an essential part of their programming toolkit. The rest of this column assumes familiarity with XPath 1.0 (see http://www.w3.org/TR/xpath).

XPath 1.0 Limitations

Although XPath 1.0 simplified many common programming tasks, developers were left wanting more. The XPath 1.0 specification is limited or confusing in several areas and is in need of a facelift. The W3C has been pushing to add more significant features to the language, mostly in support of the other evolving W3C XML specifications (like XML Schema, XML Query 1.0, and XSLT 2.0).

Since XPath 1.0 was released, XML Schema has become a W3C recommendation and quickly positioned as the "official" type system for several other works in progress, such as XQuery and others related to Web Services. Because XML Schema is becoming a more integral part of the XML landscape, the W3C wants to make strongly typed XPath a reality (wouldn't it be nice to be able to select all elements of type double?). Furthermore, the recent work on XQuery 1.0 and XSLT 2.0 uncovered a great deal of common ground—areas in which both languages could share the same data model and expression syntax. This least common denominator has become XPath 2.0.

XPath 2.0

Those chartered to work on XPath 2.0 (http://www.w3.org/TR/XPath20) optimistically set out to fix the problems in XPath 1.0 and fulfill the following list of requirements (summarized from the XPath 2.0 Requirements document at http://www.w3.org/TR/xpath20req):

  • should maintain backward compatibility
  • must improve ease of use
  • must improve string manipulation and matching
  • must support XML family of standards (XSLT 2.0 and XQuery 1.0)
  • must support XML Schema (simple and complex types)

The reason the requirement for backward compatibility is not absolute is that this is less important than the other requirements that may get in the way, although the W3C has done a good job of respecting this requirement overall.

Improving ease of use and simplifying common use cases (such as working with strings) is a clear goal, but the last two requirements drive the more significant changes. In the remainder of this column, I'll highlight the most salient aspects to help you prepare for what's coming.

If you want to experiment with some of the sample expressions as you're reading through the column, download Michael Kay's SAXON 7.2 XSLT processor, which includes a fairly complete reference implementation (see http://saxon.sourceforge.net/saxon7.2/), with the exception of full XML Schema support. None of the Microsoft XML processors (or APIs) support XPath 2.0 as of the time of this writing, but it's expected along with their XQuery 1.0 and XSLT 2.0 implementations. There are no specific release dates available, but it would make sense to align it with the release of the specifications (probably in the coming year).

One Data Model to Rule Them All

The most profound change in XPath 2.0 can be found deep inside its data model. The details of the XPath 2.0 data model are found in the XQuery 1.0 and XPath 2.0 Data Model specifications, which again is shared by XPath 2.0, XSLT 2.0, and XQuery 1.0. The XPath 2.0 data model is based on the Infoset with the necessary extensions to support XML Schema. The data model defines seven node types that make up the structure of an XML document: document (root), element, text, attribute, namespace, processing instruction, and comment nodes.

The node types are similar to those found in the XPath 1.0 data model, but in the case of elements and attributes they've been extended to include XML Schema type information after validation occurs. The resulting "typed" data model is referred to as the Post Schema-Validation Infoset (PSVI).

In addition to the basic node types, XPath 1.0 defines four atomic types to assist in processing text values found in elements or attributes: node-set, string, number, and Boolean. This simple type system makes it possible to treat text values as specific types when necessary. For example, you should consider the following versions of a price element:

<price>10</price>

<price>10.0</price>

<price>10.00</price>
Testing for a specific value in a text-only world is quite cumbersome since you have to check all possible lexical formats:
price = '10' || price = '10.0' || price = '10.00'
But if you are armed with a numeric type, you can use the following expression that will take care of coercing the text into a numeric value automatically:
number(price) = 10
This shields you from having to deal with the various lexical formats that might have been used in the document.

In XPath 2.0, the atomic type system is based on this same principle, but it has been extended to include all of the data types defined in the XML Schema Datatypes spec (see http://www.w3.org/TR/xmlschema-2/). Figure 1 illustrates the XML Schema data type hierarchy, where derivation occurs by restriction. There are 19 primitive data types and many more derived types. XPath 2.0 also defines two new subtypes of duration, yearMonthDuration and dayTimeDuration, which simplify working with duration values. All of these types can be used in XPath 2.0.

Figure 1 XML Schema Datatype Hierarchy (Close Figure 1)

As before, the XPath 2.0 type system shields developers from most lexical details, allowing them to deal primarily in value-spaces, but there are many more value-spaces to choose from now. You can access a node's typed value through the data function and you can construct typed values (or cast values) with the various constructor functions defined in the XQuery 1.0 and XPath 2.0 Functions and Operators specification. For example, the following expression tests whether any birthdate element's value (of type xs:date where xs is bound to the schema namespace ) comes before January 1, 1972:

data(birthdate) < xs:date('1972-01-01')

In addition to being able to work with strongly typed values, the PSVI makes it possible to take any element or attribute and access its type information at run time, just as you can access basic name information today. Elements can be bound to either simple or complex types, while attributes are always bound to simple types. There are several new operators that facilitate type inspection and coercion: instance of, cast as, and treat as.

The instance of operator allows you to verify that the operand is of a specific type, as illustrated in the equivalent expressions that are shown here:

LineItem/node()[. instance of element of type xsd:double]

LineItem/*[. instance of xsd:double]
This expression identifies all children of the LineItem that are elements of type xsd:double. The cast as operator makes it possible to explicitly convert a value from one datatype to another. This operator may not be used with user-defined types. The treat as operator, on the other hand, may be used with user-defined types to check whether the value of the supplied expression is an instance of the supplied data type. If it is, the treat expression returns the value of the expression, otherwise it generates an error. Treat is typically used to guarantee that the dynamic type of a value is a particular subtype of the value's static type. The XQuery 1.0 and XPath 2.0 Functions and Operators specification defines numerous coercion rules for casting between types. This is probably the most complicated and overwhelming area of the specification.

The other major change in the data model has to do with node-sets, or lack thereof. In XPath 1.0 certain expressions returned node-sets while others returned atomic values. In XPath 2.0 everything is a sequence.

Sequences

A sequence is just what its name suggests: an ordered series of zero or more items. An item may be an atomic value or one of seven node types. When a node is placed in a sequence, it maintains its identity and can be duplicated in the same sequence.

Sequences are much different from XPath 1.0 node-sets, which are unordered and contain only nodes without duplicates. Although node-sets are unordered, they are still processed by XSLT 1.0 in document order. To help maintain backward compatibility, XPath 2.0 path expressions always produce sequences in document order without duplicates (this guarantee is not made for other expression types). And in XSLT 2.0, sequences are simply processed according to the order of the sequence. As you'll see shortly, sequences give you much more control and flexibility in your processing.

Every value in XPath 2.0 is a sequence. Sequences are written within parentheses using a comma delimiter, as shown here:

("Nathan", 1.32e0, true(), xs:date('2001-05-24'))
This sequence consists of four values of type: xs:string, xs:double, xs:boolean, and xs:date, respectively. The following sequence consists of a list of Sku elements, followed by a list of Price elements, followed by a list of Description elements:
(//Sku, //Price, //Description)

The empty sequence is written as: (). Even atomic values are considered sequences with a length of 1 (referred to as singleton sequences). Singleton sequences can be written as ("Nathan") or "Nathan". There is no distinction between the two.

Another important characteristic of sequences is that they're flat, meaning sequences may not contain other sequences. For example, the following three sequences are identical:

(1, 2, 3, 4)

((1, 2), (3, 4))

(((1), (2, 3), (4)))

Since the entire language revolves around sequences, there are a variety of operators and functions available for working with them.

Set Operations

I've already introduced you to one sequence operator, the comma (","). The comma represents the concatenation operator, which simply concatenates two sequences together, maintaining order. There are several additional set-based operators for working with sequences: the pipe ("|"), intersect, and except.

The | operator returns the union of the two sequences, removing duplicates (as in XPath 1.0). For example, given that $node1, $node2, $node3, and $node4 all reference distinct nodes, the following example illustrates a union:

($node1, $node2, $node3) | ($node2, $node3, $node4) ->

($node1, $node2, $node3, $node4)
The intersect operator returns the intersection of the two sequences, removing duplicates:
($node1, $node2, $node3) intersect ($node2, $node3, $node4)->

($node2, $node3)
And the except operator returns every node that occurs in the first sequence but not in the second sequence, removing duplicates:
($node1, $node2, $node3) except ($node2, $node3, $node4) ->

($node1)

In addition to these set operations, there are several other functions that facilitate working with sequences. For more information on operators, see http://www.w3.org/TR/xquery-operators.

Sequence Processing

XPath 2.0 provides the expected functions for basic list manipulation: insert, remove, item-at, index-of, and subsequence. The first two allow you to add and remove items from the sequence, as shown here:

insert((1, 3, 4), 2, 2) -> (1, 2, 3, 4)

remove((1, 2, 3), 2)    -> (1, 3)

The item-at function returns the item at the supplied index (equivalent to using a simple position predicate [index]), while the index-of function returns the positions of the items that match the supplied value:

index-of((10, 20, 30), 20) -> 2
Subsequence returns the contiguous subset identified by the supplied begin and end positions (as another sequence).

There are several ways to check the length of a sequence. You can use exists, empty, and count. Exists returns true if the sequence is not empty; it returns false otherwise. Empty returns true if the sequence is empty; it returns false otherwise. Count returns the number of items in the sequence, as it does in XPath 1.0.

empty(())         -> true

exists((1, 2, 3)) -> true

count((1, 2, 3)   -> 3

There are also several methods for performing math on a sequence: sum, avg, min, and max:

sum(1, 2, 3) -> 6

avg(1, 2, 3) -> 2

min(1, 2, 3) -> 1

max(1, 2, 3) -> 3

My favorite sequence-related function is distinct-values. It returns a new sequence containing only items with unique values (it removes items with duplicate values, just like SELECT DISTINCT in SQL). It assumes that the sequence either contains all nodes or all values, otherwise an error occurs. The following expression identifies all of the unique Sku elements in the document:

distinct-values(//Sku)
This is more convenient than the XPath 1.0 equivalent:
//Sku[not(preceding::Sku = .)]
There is also a distinct-nodes function, which removes duplicate nodes based on identity, not value.

Sequences and Predicates

In XPath 1.0 it's possible to apply predicates to specific node-sets through parentheses. Consider the following expression:

//Price[1]
At first glance it appears that this expression returns the first Price element in document order, but in fact it returns every Price element that is the first child of its parent (this is due to operator precedence). You can fix this by using parentheses around the node-set you want to filter:
   (//Price)[1]

This same model applies to sequences. You can use predicates to filter out unwanted items. Applying a predicate to a sequence returns a new sequence containing only the items that satisfy the predicate expression. For example, the following predicate tests the item's position (equivalent to item-at):

(10, 20, 30)[2] -> (20)
This example uses a slightly more sophisticated predicate to check the number of children along with the value of the id attribute:
$seq[count(*) > 1 and @id < 1000]

In general, everything you learned about predicates in XPath 1.0 also applies to using them on sequences in XPath 2.0.

Sequence Coercions

If you use a sequence in a context where another type is required, an implicit coercion is attempted. The rules for coercing sequences to strings, numbers, and Booleans are mostly the same as those found in XPath 1.0. For example, if you supply a sequence where a string is expected, the value of the first item will be coerced to a string and used. The rules for evaluating the string-value of a node are as follows: for text/attributes it's just the value, while for elements it's the concatenation of descendant text nodes. If you supply a sequence where a number is expected, it's first coerced to a string, which is then coerced to a double. The only rule that's a bit different is the one for coercing to Boolean. If the sequence is empty, it's coerced to false. A sequence containing a single Boolean value is treated as that value, otherwise non-empty sequence containing at least one node is treated as true.

Functions exist for explicitly coercing between the string, number, and Boolean types as in XPath 1.0 and there are several additional functions for coercing to the various XML Schema data types (xsd:date, xsd:unsignedInteger, and so on) as mentioned earlier in this column.

Comparisons

One of the more nastier areas of XPath 1.0 is how it evaluates node-set comparisons. For example, take a look at the following simple expression:

Catalog/Prices/Price = 9.95
This evaluates to true if at least one Price element exists (under Catalog/Prices) with a numeric value equal to 9.95. When sequences are used in Boolean expressions like this, the result is true if the comparison is true for any of its members, otherwise known as existential quantification. Interestingly, this applies to any comparison operator (<, <=, >, >=, and so on), even the not equal (!=) operator, as illustrated in this line:
Catalog/Prices/Price != 9.95
This evaluates to true if at least one Price element exists (under Catalog/Prices) with a numeric value that does not equal 9.95. Hence, it's quite possible, if not probable, that both comparisons will return true for the same node-set. Needless to say, this has been the cause for much confusion.

It is possible to test all items in a sequence, otherwise known as universal quantification, by using the Boolean not function. For example, to confirm that all Price elements have a value of 9.95, you use the logical not of the last expression:

not(Catalog/Prices/Price != 9.95)
To verify that not a single Price element has a numeric value equal to 9.95, you have to use the logical not of the first expression:
not(Catalog/Prices/Price = 9.95)

In XPath 2.0 they've tried to eliminate as much of this confusion as possible by distinguishing between different comparison expressions and making it possible to explicitly choose between existential and universal quantification. General comparison expressions, which use the standard =, !=, <, <=, >, and >= operators, keep the XPath 1.0 default behavior I've described (existential quantification or "for any" semantics) to preserve backward compatibility. They also added a few new functions, sequence-deep-equal and sequence-node-equal, for comparing two sequences based on either value or node identity, respectively.

XPath 2.0 defines a separate mechanism for simple value comparisons. Value comparisons use the following operators: eq, ne, lt, le, gt, and ge (which represent =, !=, <, <=, >, and >=), but for comparing single items as atomic values. XPath 2.0 provides some additional comparisons for dealing with single nodes. For instance, the is and isnot operators test node identity:

Price[1] is Price[1]  -> true

Price[1] isnot Sku[1] -> true
You can also test node ordering using the << and >> operators. The << operator returns true if the first operand node comes before the second operand node in document order. And >> does the reverse. Since value and node expressions expect singletons, the "for any" versus "for all" semantics simply don't come into play.

Explicit Quantification

XPath 2.0 also makes it possible to write explicit quantified expressions through a new expression type. You can express whether you want to use existential (some) or universal (every) quantification when applying the constraining expression (satisfies) to the nodes in the supplied sequence:

some $item in //LineItem satisfies
   (($item/Price * $item/Quantity) > 100)

every $item in //LineItem satisfies
   (($item/Price * $item/Quantity) > 100)
The first expression returns true if there's at least one LineItem element whose extended price (Price * Quantity) is greater than 100. The second expression returns true only if every LineItem's extended price is greater than 100.

Since the constraining expression can be anything, this model provides much more flexibility than =, !=, <, and the others. Furthermore, there's no limit to the number of sequences involved in the expression, as illustrated by this example that inspects the Cartesian product of the supplied sequences:

some $x in (1, 2, 3), $y in (2, 3, 4)
   satisfies $x + $y = 4

The XPath 2.0 specification is definitely easier to use and more flexible than XPath 1.0.

Other Language Enhancements

You can now supply multiple node tests in a single step, an enhancement to the way location steps work in XPath 2.0:

LineItems/(Sku|Price)/text()
In XPath 1.0, you would take the union of the following two expressions to achieve the same result:
LineItems/Sku/text() | LineItems/Price/text()

XPath 2.0 also provides a "for" expression for iteration purposes. This type of expression is useful for generating new sequences from multiple sources (such as joins) and is similar to explicit quantification expressions. For example, the following expression generates a new sequence containing the extended prices of each LineItem element:

for $i in //LineItem return ($i/Price * $i/Quantity)
This becomes extremely useful because you can pass the new sequence into another function for further processing. For example, the following expression calculates the invoice total:
sum(for $i in //LineItem return ($i/Price * $i/Quantity))

This wasn't possible at all in XPath 1.0 and was extremely tedious in XSLT 1.0 (requiring recursion and temporary result tree fragments). For expressions can be used for even more sophisticated transformations. This example generates a list of salespeople and the products they've sold:

for $sp in distinct-values(//Salesperson)
return ($sp,
        for $item in //LineItem[Salesperson = $sp]
        return $item/Description)
The resulting sequence will contain a sequence of Salesperson elements, each of which is followed by a sequence of LineItem elements that they sold.

XPath 2.0 also provides a built-in conditional expression (if). The syntax consists of the traditional if/then/else keywords. The following expression calculates a discounted price based on the magnitude of the original price:

if ($Price > 100)
   then ($Price * .90)
   else ($Price * .95)
These new language constructs can be used anywhere an XPath expression is expected.

Improved String Support

In addition to these enhancements, XPath 2.0 also beefed up its string processing support with several new functions: upper-case, lower-case, string-pad, matches, replace, and tokenize. Before upper-case and lower-case, the only way to uppercase or lowercase a string was through the tedious and inefficient translate function. And before the string-pad function, you had to resort to recursion for such things:

upper-case('Michael') -> 'MICHAEL'

string-pad('-', 7)    -> '-------'
The last three functions bring regular expressions to the table. The matches function allows you to test whether the supplied input string matches the supplied regular expression. The following sample checks the SSNumber element against the standard format for U.S. Social Security numbers:
matches(SSNumber, '\d{3}-\d{2}-\d{4}')

The replace function makes it possible to replace substrings matched by a pattern, while the tokenize function facilitates breaking strings into substrings separated by the supplied pattern. Regular expression processing adds a whole new level of flexibility to the language.

One Person's Trash, Another's Treasure

XPath 2.0 is positioned center-stage in the family of XML specifications. It serves as the official addressing language to be used by other specifications, like XQuery 1.0, XSLT 2.0, and potentially many others. XPath 2.0 improves usability while increasing functionality and maintaining backward compatibility as much as possible. It also adds support for XML Schema, which promises added value to developers working in strongly typed worlds.

At this point, it wouldn't be proper to leave out the fact that XPath 2.0 has its fair share of opponents. There are many XML developers (especially those deep in the trenches of XSLT) that are confused by the increased complexity of XPath 2.0. Their biggest complaint has to do with requiring support for XML Schema instead of making it an additional, optional layer. This long-running XPath 2.0 debate has recently boiled over into some pretty heated political discussions.

Developers working heavily with databases or Web Services will undoubtedly find the benefit worth the price. But I do feel sympathy for the XSLT wonks who want the new-and-improved XPath without having strong-typing forced upon them. How this will ultimately shake out is hard to tell. If you have an opinion on the matter, or would like to share any other comments related to the language, send them to the public W3C XPath/XQuery mailing list at public-qt-comments@w3.org.

Send your questions and comments for Aaron to xmlfiles@microsoft.com.

Aaron Skonnardis an instructor/researcher at DevelopMentor, where he develops the XML and Web Service-related curriculum. Aaron coauthored Essential XML Quick Reference (Addison-Wesley, 2001) and Essential XML (Addison-Wesley, 2000). Reach him at http://Skonnard.com.


Data Points: Expressions in ADO.NET

Data Points
Expressions in ADO.NET
John Papa

Contents
Expressions in SQL
DataColumn Expressions
Functions
Aggregates and Relations
Roll-downs and Computations

Managing relations in a database became a whole lot easier with ADO.NET. Instead of returning a single rowset to a data store, you can return a series of rowsets and relate them within a DataSet. There are numerous advantages to using related DataTables in ADO.NET, including the ability to store your data in a hierarchical structure, easier updating of data, and the ability to use expressions in columns.

This month, I'll provide a primer on column-based expressions and computations in ADO.NET. I'll cover the use of aggregate functions on DataColumns, calculating totals, performing other computations on entire DataSets, and linking DataColumns between DataTables. In addition, I'll provide some practical examples.

Summing and averaging a series of related rows in a SQL query is probably old hat to you thanks to aggregate functions like SUM and AVG that are part of ANSI SQL. SQL also allows calculations on columns such as multiplying the unit price by the quantity of products ordered to produce the extended price. Now ADO.NET offers a way to extend these features beyond the data source and into the middle and upper tiers of n-tiered applications. Using column expressions in ADO.NET, you can create your own columns by aggregating values within a DataSet, calculating a value from others in the same row, or even by grabbing a value from a parent or child DataTable through a DataRelation. With column-based expressions and computations in ADO.NET, new techniques for managing data have emerged.

Of course there are pros and cons to using expression-based columns, aggregations, and computations in ADO.NET. Expressions can be used within a single DataTable of a DataSet or across two DataTable objects of a DataSet through a DataRelation. I'll explain the difference between expression-based columns in ADO.NET and in SQL, and what you gain from each. Some of the operations I'll discuss in this column, including rolling up and down with expressions, rely on relations using the DataRelation object. (For more information on DataRelations, you can refer to the November 2002 installment of the DataPoints column.) In this column I will demonstrate how to create expressions in DataColumn objects, how to create aggregate functions with DataSets and SQL, how to roll up and down fields in DataSets, and how to perform calculations on columns in a DataSet.

Expressions in SQL

SQL expressions come in many forms, for many purposes, including string formatting, user-defined functions, and mathematical calculations. If a SQL statement concatenates a first name and last name together, calculates the extended price for an order's line item, or contains a user-defined function in SQL Server™ 2000 to retrieve an order date, then the SQL statement contains an expression.

Expressions provide great flexibility to developers to return values in a rowset that can be derived from other fields in the database. A relational and even somewhat normalized database will not store the extended price of an order's line item; rather, it will store the unit price and the quantity. The extended price can be calculated from these values, thus eliminating the possibility of getting your data out of synch. For example, if you store the quantity, unit price, and extended price in a row, imagine if somehow the data ends up being quantity = 10, unit price = $7.00 and extended price = $100.00. This should never happen, but the point is that it could happen if you store redundant data. The general rule for a transactional database is not to store any information that can be derived, such as the extended price.

This is where SQL expressions come into play. The extended price can be derived by creating a calculated column in the SQL statement that multiplies the unit price by the quantity (see Figure 1). The extended price can also be calculated by taking into account the discount amount, if any.

Figure 1 Math Expressions in SQL (Close Figure 1)
USE northwind
GO

SELECT OrderID,
       ProductID,
       UnitPrice,
       Quantity,
       (UnitPrice * Quantity) AS ExtendedPrice,
       Discount,
       ((UnitPrice * Quantity) * (1 - Discount)) AS
        ExtendedPriceWithDiscount
FROM    [Order Details]
ORDER BY
       OrderID,
       ProductID

The following SQL code shows how to concatenate strings in a SQL expression. This example takes the first name and last name and puts them together to return the uppercased full name with last name first:

USE pubs
GO

SELECT au_fname AS FirstName,
       au_lname AS LastName,
       au_lname + ', ' + au_fname AS FullName1,
       (UPPER(au_fname) + ' ' + UPPER(au_lname)) AS FullName2
FROM    authors
ORDER BY
       au_lname,
       au_fname

SQL expressions can format strings and perform calculations in a rowset, but there are some things to watch out for when you use them for this purpose. If a DataSet was filled from the SQL statement in Figure 1 and the first row's value for the quantity column was modified, the expression columns would remain unchanged. For example, if the quantity is 10 and the unit price is $7.00 and then the quantity is changed to 5, the ExtendedPrice column would still read $70.00 (10 * $7.00) and the data would be out of synch. The main point is that the expressions are not carried from the SQL statement to the ADO.NET DataSet.

DataColumn Expressions

Expressions can also be defined through the ADO.NET DataColumn object. Instead of calculating the extended price through the SQL statement, a DataColumn can be defined to represent the extended price. One difference between using an expression in a SQL statement and a DataColumn is that if any of the fields in the expression are modified, the DataColumn will automatically update a DataColumn defined as an expression field, but it will not update a DataColumn that resulted from a SQL expression (such as the one in Figure 1).

Figure 2 demonstrates how to populate a DataTable in a DataSet using a SQL statement and create a new DataColumn to represent a calculated expression using the other fields from the DataSet's only DataTable. The DataTable's default view is then bound to the ASP.NET DataGrid called grdOrderDetail.

Figure 2 Filling a DataSet and Adding an Expression (Close Figure 2)
private void LoadOrderDetailData()
{
    //
    //— Set up the connection
    //
    SqlConnection oCn = new SqlConnection("Data Source=papa;" +
        "Initial Catalog=northwind;User ID=sa;Password=secretpassword;");

    //
    //— Set up the SELECT command
    //
    string sSQL = "SELECT od.OrderID, od.ProductID, p.ProductName,
        od.UnitPrice, od.Quantity, od.Discount " +
        " FROM [order details] od INNER JOIN Products p ON od.ProductID =
        p.ProductID " + " ORDER BY od.OrderID, p.ProductName ";
    SqlCommand oSelCmd = new SqlCommand(sSQL, oCn);
    oSelCmd.CommandType = CommandType.Text;

    //
    //— Set the SqlCommand to the DataAdapter and fill
    //— the DataSet
    //
    SqlDataAdapter oDA = new SqlDataAdapter(oSelCmd);
    DataSet oDs = new DataSet();
    oDA.Fill(oDs, "OrderDetail");

    //
    //— Add the computed columns (Expressions)
    //
    oDs.Tables["OrderDetail"].Columns.Add("ExtendedPrice",
        typeof(decimal), "UnitPrice * Quantity");

    //
    //— Bind the data to the ASP.NET DataGrid
    //
    grdOrderDetail.DataSource = oDs.Tables["OrderDetail"].DefaultView;
    grdOrderDetail.DataBind();
    oDs.Dispose();
}

This code also creates and fills a DataSet with order detail information. Then a column is added to the DataSet's DataTable to represent the expression. This column is called ExtendedPrice, its datatype is set to type decimal, and its expression is the product of the unit price and the quantity columns. This expression can refer to any DataColumn of its DataTable. It gets the values from the DataRow that it is currently on, as well. For example, if the first row has a quantity of 10 and a unit price of $7.00, then the extended price column would use these values to arrive at $70.00.

Expressions can also include literal values as well as other columns from the DataTable in a calculation. For example, the expression that defines the extended price could be changed so that a discount is taken into consideration. With that in mind, the expression could be changed to reflect the discount, too, by changing the code that adds the ExtendedPrice column to look like this:

oDs.Tables["OrderDetail"].Columns.Add("ExtendedPrice",
    typeof(decimal), "(UnitPrice * Quantity) * (1 - Discount)");

Try modifying any of the UnitPrice, Discount, or Quantity DataColumns' values and, unlike a column derived from a SQL expression, the value of ExtendedPrice DataColumn is also updated. This feature is great for applications where the user can modify their shopping cart, apply the changes, and see the updated total price.

Expressions can also be used to represent other data types such as Boolean and string values. For example, the following code sample demonstrates how to add a field that indicates whether an author gets a discount.

oDs.Tables["OrderDetail"].Columns.Add("GetsDiscount", typeof(bool),
    "Discount > 0");
You can enhance the previous expression by creating a compound expression to consider multiple conditions by using AND, OR, or NOT. Other operators, including those such as LIKE and IN, can also be used in the expression.

Expressions can represent string values, too, such as taking the first name and last name columns from a DataTable and putting them together. The following sample code snippet concatenates ProductName and ProductID.

oDs.Tables["OrderDetail"].Columns.Add("stringfield", typeof(string),
    "ProductID + '-' + ProductName");

Functions

If you want your column to have an expression containing more complicated logic, try embedding some functions in it. Expressions can contain functions such as Len, Iif, IsNull, Convert, Trim, and Substring. Each of these allows greater flexibility in creating expressions. The Len function evaluates the length of a string:

oDs.Tables["OrderDetail"].Columns.Add("LengthOfProductName",
    typeof(int), "Len(ProductName)");

The Iif function is an iterative If statement like the IIf in Visual Basic® .NET. It takes three arguments and evaluates the first to determine if it's true or false. If the first argument evaluates to true, the second argument is returned from the Iif function; otherwise the third argument is returned. The following is basically a condensed If...Then...Else statement that can easily be used for an embedded expression:

oDs.Tables["OrderDetail"].Columns.Add("Inventory", typeof(string),
    "Iif(Quantity < 10,'A few left', 'Plenty in stock')");

The IsNull function evaluates its first argument to see if it is equal to System.DbNull. If it evaluates to false, then the function returns the value of the first argument. If the first argument evaluates to true, then the IsNull function returns the value of the second argument. This can be used in places where you don't want to allow null values and you need to replace them with an empty string or placeholder value, as shown here:

oDs.Tables["OrderDetail"].Columns.Add("DiscountString",
    typeof(string), "IsNull(Discount, '[null value]')");

The Trim function removes trailing whitespace characters from a string value. The Convert function changes the datatype for a value in the expression to the datatype of the second argument passed to the Convert function. The Substring function returns a part of the string value passed to it, like its T-SQL counterpart. Substring can be used to crop long string values to display only a limited number of characters. It can be used to return any part of a string and can be combined with the other functions as necessary:

oDs.Tables["OrderDetail"].Columns.Add("ShortProduct",
    typeof(string), "Substring(ProductName, 1, 10)");

Aggregates and Relations

Embedding functions within expressions can help you to create a column that represents more complex logic. But what if you want to create a column that calculates values across multiple rows? Enter aggregate functions.

Assuming you have a structure in a DataSet such as the Orders-to-Order Details relation in the SQL Server Northwind database, creating columns with expressions containing aggregate functions is pretty straightforward. The code in Figure 3 demonstrates how to create a DataSet containing a structure where the orders are in the parent DataTable and the order details are in the child DataTable. These DataTable objects are related to one another through the DataRelation called Orders2OrderDetails. For more information on DataRelation objects and how they help relate hierarchical and relational data structures in ADO.NET, see my November 2002 MSDN® Magazine column.

Figure 3 A Relation and Expressions (Close Figure 3)
private void LoadData()
{
    //
    //— Set up the connection
    //
    SqlConnection oCn = new SqlConnection("Data Source=papa; " +
        "Initial Catalog=northwind;User ID=sa;Password=secretpassword;");

    //
    //— Set up the SELECT command for the orders
    //
    string sSQL = "SELECT OrderID, CustomerID, OrderDate, ShipCity,
        ShipCountry " + " FROM    Orders " +
        " ORDER BY CustomerID, OrderDate DESC ";
    SqlCommand oSelCmd = new SqlCommand(sSQL, oCn);
    oSelCmd.CommandType = CommandType.Text;

    //
    //— Set the SqlCommand to the DataAdapter and fill
    //— the DataSet
    //
    SqlDataAdapter oDA = new SqlDataAdapter(oSelCmd);
    DataSet oDs = new DataSet();
    oDA.Fill(oDs, "Order");

    //
    //— Set up the SELECT command for the order details
    //
    string sSQL = "SELECT od.OrderID, od.ProductID, p.ProductName,
        od.UnitPrice, od.Quantity, od.Discount " +
        " FROM [order details] od INNER JOIN Products p ON
        od.ProductID = p.ProductID " +
        " ORDER BY od.OrderID, p.ProductName ";
    SqlCommand oSelCmd = new SqlCommand(sSQL, oCn);
    oSelCmd.CommandType = CommandType.Text;

    //
    //— Set the SqlCommand to the DataAdapter and fill
    //— the DataSet
    //
    SqlDataAdapter oDA = new SqlDataAdapter(oSelCmd);
    oDA.Fill(oDs, "OrderDetail");

    //
    //— Relate the DataTables
    //
    oDs.Relations.Add("Order2OrderDetail", oDs.Tables["Order"].Columns
    ["OrderID"], oDs.Tables["OrderDetail"].Columns["OrderID"]);

    //
    //— Add the Expressions to the OrderDetail Table
    //
    oDs.Tables["OrderDetail"].Columns.Add("OrderDate", typeof(string),
        "Parent.OrderDate");
    oDs.Tables["OrderDetail"].Columns.Add("ExtendedPrice", typeof(decimal),
        "(UnitPrice * Quantity) * (1 - Discount)");
    oDs.Tables["OrderDetail"].Columns.Add("GetsDiscount", typeof(bool),
        "Discount > 0");
    oDs.Tables["OrderDetail"].Columns.Add("stringtest", typeof(string),
        "ProductID + '-' + ProductName");
    oDs.Tables["OrderDetail"].Columns.Add("LengthOfProductName",
        typeof(int),
        "Len(ProductName)");
    oDs.Tables["OrderDetail"].Columns.Add("Inventory", typeof(string),
        "Iif(Quantity < 10, 'Only a few left', 'Plenty in stock')");
    oDs.Tables["OrderDetail"].Columns.Add("DiscountString", typeof(string),
        "IsNull(Discount, '[null value]')");
    oDs.Tables["OrderDetail"].Columns.Add("ShortProduct", typeof(string),
        "Substring(ProductName, 1, 10) + '...'");

    //
    //— Add the Expressions to the order table
    //
    oDs.Tables["Order"].Columns.Add("OrderTotal", typeof(decimal),
        "Sum(Child(Order2OrderDetail).ExtendedPrice)");
    oDs.Tables["Order"].Columns.Add("AvgQuantity", typeof(decimal),
        "Avg(Child(Order2OrderDetail).Quantity)");
}

In Figure 3 notice how the expression columns are created and added to the Order DataTable. The first expression creates a column that represents the sum total of each order's order detail items. Specifically, the OrderTotal DataColumn has an expression that sums the OrderDetail DataTable's expression-based DataColumn called ExtendedPrice. So, as you can see, you can use an aggregate function across a DataRelation and even use it on another expression-based column.

There are other aggregate functions available to you in ADO.NET, including Sum, Avg, Max, Min, StDev, Var, and Count. The example in Figure 3 also demonstrates how to use the Avg function to get the average order detail's quantity. One of the real keys here is the use of the parent and child keywords to link the data across the DataRelation, like this:

oDs.Tables["Order"].Columns.Add("AvgQuantity", typeof(decimal),
    Avg(Child(Order2OrderDetail).Quantity)");
The Child function accepts the name of the DataRelation to get to the child rowset. This argument is optional and only necessary when there is more than one DataRelation indicating a child relation from the source DataTable. So, if DataTable only has a single child DataTable, then the syntax can be simplified to exclude the name of the DataRelation since it is the only DataRelation:
oDs.Tables["Order"].Columns.Add("AvgQuantity", typeof(decimal),
    Avg(Child.Quantity)");

Roll-downs and Computations

The Parent function works just like the Child function except, of course, that it goes up the relation chain to the parent DataTable. Both of these functions help create a pseudo GROUP BY functionality in your ADO.NET code.

One other operation that these keywords make possible is to roll up and down values that are unchanged from one DataTable to another. I'm often asked how to rejoin a parent and child DataTable into a single DataTable so it can be displayed in a single DataGrid (non-hierarchically). Using the Parent keyword, it is possible to roll down fields to a child DataTable and then bind only the child DataTable to a DataGrid. For example, if I wanted to show the Order Date in each row of the OrderDetail DataTable from the example in Figure 3, I could add the DataColumn using the Parent keyword, like this:

oDs.Tables["OrderDetail"].Columns.Add("OrderDate", typeof(string),
    "Parent.OrderDate");

This feature allows you to roll up and down fields without performing any calculations on them. Using this rolling feature with the Child keyword, you can roll down fields from the parent table and bind to a DataGrid. By doing this, you end up with a single two-dimensional rowset, just like you get from a SQL statement. Keep in mind, however, that if you intend to use the data in a single rowset then you are better off returning all of your data in a single rowset to a single DataTable. However, if you want to use the relational structure in the DataSet, the Parent keyword gives you flexibility in displaying the data.

Another feature that is worth a look is the DataTable's Compute function, which calculates using an aggregate function on the current DataTable given a specific filter. For example, you could add a column to the DataTable that calculates the total number of orders with an order total over $999.00.

In the first argument of the Compute function, shown in the following code, an aggregate function is performed by counting all OrderTotal values that meet the filter requirement:

//————————————————————————————
//— Display the total number of orders over $999
//————————————————————————————
int iCnt = (int)oDs.Tables["Order"].Compute("Count(OrderTotal)",
   "OrderTotal >= 1000");
lblTest.Text = iCnt.ToString() + " orders are at least $1000";
The filter is specified in the second argument and limits the aggregate function to include only rows that meet its condition. The code is counting all rows where the OrderTotal is $1000.00 or greater. This is a great tool to quickly perform calculations on a DataTable, especially since you can filter. For example, you could easily find the number of customers who ordered product x and the number of customers who ordered product y, without writing a loop or querying the database again.

It is important to remember that DataColumn objects bound to expressions cannot be updated manually. These columns are bound to an expression so they cannot be overridden unless the expression is removed. Also, this data does not have a corresponding column in the database (or XML file) that serves as the data source. So if you want to save the data back to a database, you need to be aware that the expression won't be saved to the database.

This month I've discussed expressions both in SQL and ADO.NET. Now that you see the wide range of functionality provided by expressions, you'll want to use them to manipulate and add data in your own applications.

Send questions and comments for Johnny to mmdata@microsoft.com.

John Papais VP of Software Development at MJM Investigations in Raleigh, NC. He has authored several books on ADO, XML, and SQL Server and can often be found speaking at industry conferences such as VSLive. You can reach him at mmdata@microsoft.com.


Cutting Edge: ASP. NET Client-side Hosting with Cassini

Cutting Edge
ASP. NET Client-side Hosting with Cassini
Dino Esposito

Code download available at: CuttingEdge0301.exe (107KB)
Contents
Hosting the ASP.NET Runtime
The ApplicationHost Class
A Sample ASP.NET Host
The Cassini Personal Web Server
Your Web Site on a CD
Conclusion

In the September and October 2000 issues of MSDN® Magazine I discussed how to build a client-side environment for ASP applications; that is, a serverless environment to run ASP pages (see Cutting Edge: A Client-side Environment for ASP Pages and Cutting Edge: A Client-side Environment for ASP Pages—Part 2). Those columns were inspired by the following scenario.

Suppose that one of your clients needs to distribute some online content using a CD—an encyclopedia, yellow pages, or a collection of documents, for example. The client needs a viewer application to be included with the CD and a flexible software architecture to deliver the contents. Furthermore, the client would prefer that the CD didn't inconvenience the user with special system requirements other than a minimum processor and a recent version of Windows®. This means that the final application should not rely on Microsoft® Internet Information Services (IIS) or the Personal Web Server included in home versions of Windows. It should run offline in a pure, serverless environment.

In many cases, the client has an existing Web site for online content. In other cases, she plans to set up a Web site for online content as part of the project. In this context, using ASP or ASP.NET to do the job would be an excellent choice because they are flexible and powerful enough to let you build the viewer application quickly and effectively. But is it really possible to host ASP or ASP.NET in a custom application?

When I first approached the problem about three years ago, ASP.NET had yet to be announced; there were only rumors about something really cool, tentatively called ASP+, which was due to be released shortly. At that time, no tool was available to render ASP pages in an offline manner outside of IIS. Worse yet, ASP was not designed to make such hosting easy. Thus I came up with an ASP emulator made of two components: a specialized browser and an ASP server module. The browser was built to intercept any form submission and URL requests and redirect them to my own ASP server module. The ASP server, in turn, would load the resource from disk and parse its contents to generate HTML code dynamically. The browser was also responsible for instantiating and initializing pseudointrinsic objects with a programming interface nearly identical to that of the various Response, Request, and Server ASP objects. Figure 1 summarizes the overall architecture.

Figure 1 Offline Server
Figure 1 Offline Server

Although not ideal as a solution, it met my client's expectations and was integrated into a larger product that today still allows many professionals to consume content both online and offline. Judging from the feedback I had once the columns were published, it seems many developers faced similar challenges.

A few months later, Microsoft released the first beta of ASP.NET and I thought about revisiting my solution using the new product. ASP.NET is designed in a very modular way and, as such, lends itself quite well to being hosted in external applications, including IIS itself. However, the ability to host ASP.NET in a custom application doesn't equal an off-the-shelf solution for building an offline Web browser to ship on a CD. Hosting the ASP.NET runtime engine in a custom application is only the first step in the delivery of dynamic content offline. If you take a look at the architecture shown in Figure 1, you'll see that it's based on two distinct components—a request processor that also takes the user's input and an ASP source processor that produces the actual HTML code. Hosting the ASP.NET engine would simply replace the ASP server module from Figure 1. In reality, you need more—ideally, a browser and an embedded Web server.

The ASP.NET Cassini sample Web server (see http://www.asp.net/Projects/Cassini/Download) is a compact, local Web server that you can integrate and deploy with your solutions. Cassini employs the ASP.NET hosting API (the System.Web.Hosting namespace) to create a simple managed Web server. Socket connections are handled through the System.Net API. Cassini is available from Microsoft with source code. Figure 2 shows the typical architecture of an offline Web application based on Cassini. As you can see, the overall scheme is just like a typical Internet-based Web application, but much simpler. Obviously, Cassini is neither a full replacement for IIS nor the Microsoft version of an open source Web server. Cassini is a local Web server for handling local calls to local folders. I'll review the Cassini components and then conclude by showing you how to deploy a Web site on a CD.

Figure 2 Cassini Web App
Figure 2 Cassini Web App

Hosting the ASP.NET Runtime

An ASP.NET application doesn't require IIS as the host module. In fact, ASP.NET doesn't even require a Web server to run. It exposes a well-known interface that any caller can use to connect and to ask the internal HTTP pipeline to process a request.

Two classes play a key role in the hosting of the ASP.NET engine—HttpRuntime and ApplicationHost. The former is the entry point in the pipeline of objects that, much like an assemblage chain, transforms the original HTTP request for an .aspx resource into fresh HTML text. The latter makes it possible for client applications to host the ASP.NET engine. The ApplicationHost class is responsible for creating the AppDomain in the host process in which all incoming requests for the new application will be handled.

Tim Ewald and Keith Brown provided good coverage of the HttpRuntime class internals in their article "HTTP Pipelines: Securely Implement Processing, Filtering, and Content Redirection with HTTP Pipelines in ASP.NET" in the September 2002 issue of MSDN Magazine. The HttpRuntime class gets involved only when the application host has received and preprocessed the request. The application host packs all the information about the request in a request class derived from the HttpWorkerRequest abstract class or, more likely, from its standard implementation class named SimpleWorkerRequest. After an instance of the request class is ready to use, the host hands the processing over to HttpRuntime, calling its ProcessRequest static method, as shown in the following code:

SimpleWorkerRequest req;
req = new SimpleWorkerRequest(aspx, null, Console.Out);
HttpRuntime.ProcessRequest(req);

The previous snippet shows the core code that fires the processing of an ASP.NET page. The code's execution is governed by a particular method on the host class created through ApplicationHost. I'll return to this topic later. For now, suffice it to say that the constructor of SimpleWorkerRequest takes the virtual path of the ASPX resource to process, an optional query string, and a text writer object to use for the output. Instead of the standard output console, you could use a stream writer object to save the HTML code to disk.

The ApplicationHost Class

The interaction between the host and the ASP.NET HTTP runtime is ruled by a particular Microsoft .NET Framework class called ApplicationHost. From the perspective of the ASP.NET HTTP runtime, the host is simply the caller—namely, the module that has created the current AppDomain and that is invoking the ProcessRequest method to serve a particular request. The interface between the ASP.NET HTTP runtime and the host is all in the operations accomplished by the ApplicationHost class. The HTTP runtime is oblivious to the characteristics of the caller—be it a full Web server like IIS, a local Web server like Cassini, or even a simple application like the one I'll build in a moment. ASP.NET serves any module that can call HttpRuntime.ProcessRequest and pass correct information. Figure 3 describes the relationship between the ASP.NET HTTP runtime and the rest of the world.

Figure 3 ASP.NET versus the World
Figure 3 ASP.NET versus the World

The first step for hosting ASP.NET in an application is creating a new application host. This is accomplished by calling the CreateApplicationHost static method on the ApplicationHost class. CreateApplicationHost creates a new AppDomain in the caller's process. A new AppDomain is needed because ASP.NET relies on settings that can only be set at the AppDomain level, and some only before AppDomain is created. Some of these settings are the app base path and the directory for the shadow-copy cache location. CreateApplicationHost works on a per-virtual-folder basis, meaning that a new host, and subsequently a new AppDomain, are created whenever a new virtual folder is accessed for the first time. (Note that a simple Web server like Cassini works with only one virtual folder at a time, but this is just a special case.)

After the host interface object is created, a typical host application starts listening to requests. If the host works like a Web server, it starts listening to incoming messages through port 80; otherwise it's whatever port you indicate. A request is then packed into a request class and handed over to the ASP.NET runtime.

The HttpRuntime.ProcessRequest method routes the Request object through a pipeline of objects. At the end of the pipeline, a new object comes out—an instance of a dynamically created class that inherits from the Page class and represents the .aspx page being requested. To top off the request, the HTTP runtime calls the ProcessRequest method on the Page class. The page's ProcessRequest method performs a number of tasks on the Page object, each of which is signaled with an event. The page is first initialized by creating an instance of a server control for each markup element with the runat=server attribute set. Next, the ASP.NET code loads the view state of the page and merges it with any post data (such as values of textboxes and checkboxes). Finally, the runtime executes any server code the client may have requested (mostly postback events), saves back the view state, and writes the HTML to the output writer.

The CreateApplicationHost static method is the only member of the ApplicationHost class. Its C# prototype is shown here:

public static object CreateApplicationHost(
   Type hostType,
   string virtualDir,
   string physicalDir
);
The virtualDir argument shown in the previous snippet represents the virtual directory for the application domain being created, while the physicalDir argument indicates the file system path behind that virtual path—the disk folder from which requested .aspx files have to be loaded for that Web application. These pieces of information are associated with the domain and consumed by the ASP.NET factory objects to create HttpApplication objects (global.asax) and page objects (.aspx).

The first argument of CreateApplicationHost is a type object that evaluates to the type of the application-defined host class. This method returns an instance of the user-supplied class used to bridge the default AppDomain of the host program and the newly created AppDomain (see Figure 4). The host type object is a sort of proxy between the core code of the server-like application and the ASP.NET HTTP runtime living in the target AppDomain that CreateApplicationHost had previously created. The host executable might need to configure, start, and stop a particular Web application and subsequently need an ad hoc counterpart in the AppDomain. The host type, in turn, includes methods to listen to ports for HTTP requests. When a request arrives, a SimpleWorkerRequest object is created and passed to HttpRuntime.

Figure 4 Default AppDomain
Figure 4 Default AppDomain

The request object is created in the same AppDomain that will actually service the request. HttpWorkerRequest is the base abstract class that defines all the properties and methods used by ASP.NET to process requests. SimpleWorkerRequest is a lightweight implementation of the base class and provides the ASP.NET runtime with minimal information such as the requested URL and the query string. It also receives the ASP.NET output into a stream writer object. You can extend SimpleWorkerRequest and override the appropriate HttpWorkerRequest methods if you think you need more preprocessing functionality for things like parsing headers and posted data. (Look at the Cassini source code for a concrete example of a class that extends SimpleWorkerRequest.)

A Sample ASP.NET Host

So much for the underpinnings of the ASP.NET hosting. Let's write some code to demonstrate how to host ASP.NET in a Windows Forms application. The idea is to use ASP.NET to generate help pages for the various features of the program. The user of the sample application types in a keyword and is promptly displayed an HTML page. The HTML page is created on the fly by the ASP.NET page that corresponds to the help required.

Figure 5 shows the core code of the Windows Forms application. Upon loading, the form initializes the application host. The root virtual folder is called /dino; its files are located in the Help subdirectory of the current folder. Note that /dino is just a name, so there's no need to create a file system directory or an IIS virtual folder for it. The physical path must exist, otherwise a "file not found" exception will be thrown.

Figure 5 InitializeApplication Host (Close Figure 5)
private const string TEMPFILE = "__page__.htm";
private MyAppHost m_host;

private void Form1_Load(object sender, System.EventArgs e)
{
    // Create the application host
    InitHost();
}

private void InitHost()
{
    m_host = (MyAppHost)
        ApplicationHost.CreateApplicationHost(
        typeof(MyAppHost),           // class to interface
        "/dino",                     // virtual (root) directory
        Directory.GetCurrentDirectory() + "\\help");    // find ASPX here
}

private void theSearch_Click(object sender, System.EventArgs e)
{
    string helpFile = "";
    string keyword = theKeyword.Text;
    switch(keyword)
    {
        case "ASP.NET":
            helpFile = "aspnet.aspx";
            break;
        case "ADO.NET":
            helpFile = "adonet.aspx";
            break;
        default:
            helpFile = "std.aspx";
            break;
    }

    // Create a temp HTML file in the current directory
    string url = PrepareHelpPage(file);

    // Refresh the UI
    object o1=null, o2=null, o3=null, o4=null;
    doc.Navigate(url, ref o1, ref o2, ref o3, ref o4);
}

private string PrepareHelpPage(string file)
{
    string query = "keyword=" + theKeyword.Text;
    m_host.CreateHtmlPage(file, query, TEMPFILE);
    return "file://" + Directory.GetCurrentDirectory() + "/" + TEMPFILE;
}

The host type is a class called MyAppHost whose source code is shown in Figure 6. MyHostClass has only one method, CreateHtmlPage. The number and the prototype of the host class members are completely up to you. What really matters is that you devise a programming interface that reflects what you are going to do. In this case, the ultimate goal is fairly simple—just take the ASPX file and translate it to HTML. A SimpleWorkerRequest object fits the bill and the prototype of its constructor is exactly what's needed.

Figure 6 MyAppHost (Close Figure 6)
using System;
using System.Web;
using System.Web.Hosting;
using System.IO;

namespace Samples
{
    public class MyAppHost : MarshalByRefObject
    {
        public void CreateHtmlPage(string aspx, string query,
                                   string html)
        {
            StreamWriter stmw = new StreamWriter(html);

            SimpleWorkerRequest swr = new SimpleWorkerRequest(
                aspx,      // ASPX file name
                query,     // Query string without the ? char (i.e.
                           // p1=hello&p2=world)
                stmw);     // Output stream (i.e., Console.Out)

            HttpRuntime.ProcessRequest(swr);
            stmw.Close();
        }
    }
}

The first argument is the name of the ASPX file to process. The second argument is an optional query string. Note that if you use a URL with command-line arguments, it will be mistaken for the real name of the file. (IIS separates the URL from the query string while preprocessing the request.) The SimpleWorkerRequest class expects to receive URL and query as distinct entities. Furthermore, note that the initial ? character of the query string must be removed.

Finally, the third argument of the constructor is a stream writer object that will be used to buffer the output text. Everything that the ASPX page will send out through Response (and higher-level methods such as Page.Render) is accumulated in this writer. I make the writer work on top of a temporary HTML file, so when I close the writer the HTML file is created. Finally, the embedded WebBrowser ActiveX® control displays the page.

There is no limit to the ASP.NET features you can use—the ASP.NET HTTP runtime is the driving force. Feel free to use configuration files, ADO.NET adapters, view state, output caching, XML, HTTP modules, and whatever else involves ASP.NET and the .NET Framework. In addition, the application works irrespective of the status of IIS. You can keep IIS running or even stop it—the ASP.NET runtime hosted by the sample application won't be affected. (IIS is just another application host, albeit a particularly rich and complex one.)

Since ASP.NET is under the hood, the rules for which assemblies are needed are the same as in ASP.NET. This means that the assembly that contains the host class must be copied both in the ASP.NET host application's directory and in the Bin subdirectory of the ASP.NET application folder. Another option is to put that assembly in Global Assembly Cache (GAC) as Cassini does. The reason is that two modules need to load the host class: the host application that is mimicking IIS and the invoked ASP.NET worker process. Both executables expect to find the host class in their own paths for assemblies. For the host application the path is the current folder; for the ASP.NET worker process the path is the Bin subdirectory of the folder that contains the .aspx pages or the GAC. Just because the host class must be available in two distinct places, it's strongly recommended that you code it in a separate assembly. However, embedding application and host classes in the same executable is not illegal and works just fine. For the sample application, for instance, you must have a copy of MyAppHost in the same folder as the executable (the Folder\Bin\Release directory) and a second copy in the Folder\Bin\Release\Help\Bin folder (Bin is below the ASPX files, as a regular ASP.NET application would have required).

The Cassini Personal Web Server

The ability to host ASP.NET in a user application is perhaps less exciting than it sounds. Sure, you can build smart help systems and devise offline tools to precompile the pages of a Web site to HTML. But can ASP.NET hosting alone let you build serverless Web-like applications that run on a local machine without IIS?

If you're not convinced yet, think about what happens if the ASP.NET page contains interactive controls and posts back. Let's review this case in the context of the sample host application. An HTML page is displayed through a WebBrowser control—an instance of the browsing engine of Microsoft Internet Explorer. The browser always processes a click on a link by forwarding an HTTP POST command through port 80. Since the application host has no listener active on port 80, a "page not found" error is returned. You need to build a custom layer on top of the client to intercept and redirect the postback to the Request object specific to the application host. In other words, you can realistically exploit ASP.NET hosting only to render static, read-only pages. Incidentally, this aspect also proved to be the highest hurdle to overcome in the September 2000 column I mentioned earlier. While it's fundamental piece of the puzzle, ASP.NET hosting alone is not sufficient to build a real-world serverless environment to consume Web sites offline. It needs to be accompanied by a mini Web server that understands what the browser wants. The Cassini sample Web Server is the ideal tool for this task.

Figure 7 Starting Cassini
Figure 7 Starting Cassini

The Cassini Web server is a Windows Forms application called cassiniwebserver.exe. You start the Web server by specifying the port number to listen to, the virtual directory to monitor, and the physical path behind that. You start up Cassini through the form shown in Figure 7. Note that Cassini can process only one virtual folder at a time and only accepts requests made through localhost. This is exactly what you need to deploy existing Web sites on offline media such as a CD or DVD. Cassini is a managed application whose executables take no more than 70KB. In addition to the program, cassiniwebserver.exe, you also need to take into account the application host DLL—cassini.dll. The application host assembly must be stored in the GAC using the following command-line instruction:

gacutil /i Cassini.dll

Figure 8 lists all the public classes that make up the application host for the Cassini Web server. All classes are compiled in the cassini.dll assembly. The Server class is the entry point in the host executable. Its constructor creates the application host indicating the Host class as the host type.

void CreateHost() {
   m_host = (Host) ApplicationHost.CreateApplicationHost(
            typeof(Host), vdirPath, filesPath);
   m_host.Configure(this, port, vdirPath, filesPath,_aspnetPath);
}

Figure 8 Cassini Classes (Close Figure 8)

Class Name | Description
Server | Provides the public API to start and stop the Web server and creates the application host. Caches configuration information such as port number and virtual directory.
Host | Remotable object (inherits from MarshalByRefObject) represents the Cassini's bridge-head in the child AppDomain that hosts ASP.NET. The class is in charge of opening a socket on the port and accepts HTTP requests when they arrive.
Connection | An instance of this class is created to process the request. It is passed the opened socket and asked to create and activate a new Request object to actually carry out the request. The Request object receives a reference to the Connection object and uses it to write response data to the socket.
Request | Inherits from SimpleWorkerRequest and implements the logic necessary to process the HTTP request. Parses the HTTP payload, processes headers, prepares the response buffer, and finally hands the request over to the ASP.NET HTTP pipeline calling the HttpRuntime.ProcessRequest method. While processing the request and generating the response, ASP.NET calls back some of the methods of the Request class. The Request's implementation of these methods funnels the calls into the parent Connection object to access the socket.

Instances of the Server class are created in Cassini's AppDomain, whereas the Host object belongs to the AppDomain that was just created to process HTTP requests. The Server object also provides a sort of public API to start, stop, and configure the Web server. The Web server (cassiniwebserver.exe) sees this object as its unique point of contact with the ASP.NET back end.

The Host class represents a remotable object and inherits from MarshalByRefObject. A remotable object is an object that can be called from within a different AppDomain. The Host class is responsible for opening a socket on the specified port and listening to incoming packets. When a request arrives and is accepted, the class creates a new Connection object to process the request:

Connection conn =  new Connection(this, (Socket) acceptedSocket);
conn.ProcessOneRequest();
The Connection class receives the socket and starts working on the HTTP packets. The Connection object is primarily an intermediary between the host and the actual worker request. It first creates the Request object and then asks it to process the payload:
Request req = new Request(host, this);
req.Process();

The Request object derives from SimpleWorkerRequest. It examines the HTTP packet, extracts and preprocesses the message headers, and prepares the response buffer. When it's finished, it simply hands the request out to the ASP.NET HTTP pipeline for the actual generation of the corresponding HTML response text. The entry point in the ASP.NET pipeline is the ProcessRequest method of the HttpRuntime object. While carrying out the request, ASP.NET calls back several methods on the Request object. In particular, all the data sent out through the Response object results in a call being made to one of the public methods of the host's worker Request. The actual implementation of Cassini uses the Request class to pass this response data to the methods of the Connection object which, in turn, will route the data through the open socket and the port. For this reason, an instance of the Connection object is passed to the constructor of the Request class.

If you think about Cassini's architecture and compare it to Figure 4, you can't help but recognize a common pattern. Cassini is the right tool for building serverless ASP.NET applications because it combines two key features: the ability to host ASP.NET to process ASPX requests and a simple, local Web server infrastructure. Using Cassini, you don't need custom code running on top of the viewer in order to process all page postbacks. Cassini is the missing link that now makes it possible to deliver entire Web sites on a CD without IIS.

Your Web Site on a CD

When I first examined Cassini, I took the following steps to test its effectiveness. First, I stopped IIS and launched the Cassini executable, as shown in Figure 7. Note that in order to test Cassini you must first stop IIS, otherwise a port conflict will occur. Furthermore, Cassini only processes calls that go through http://localhost; you can't access the local pages using the machine name (like http://msdnmag) nor can you access Web pages on a Cassini-equipped machine from a remote machine.

After launching Cassini and stopping IIS, I opened Internet Explorer and typed http://localhost. To my surprise, I was running my ASP.NET-based localnet without IIS! It goes without saying that if pages contain links to Internet Web sites, they will work as usual as long as an Internet connection is available.

However, to successfully pack a Web site onto a CD, a few preliminary checks must be performed. For one thing, make sure that all the links in the pages are either relative or rooted explicitly to localhost. Otherwise, you'll get an HTTP 403 error (access forbidden). To deploy the Web site offline, copy all the files to the storage medium along with the Cassini executables and the .NET Framework redistributable. Configure the setup to copy the Web site tree and install the cassini.dll to the client machine's GAC. In addition, you should start the Cassini Web server. You should also have the setup check for IIS and stop it if it's running.

If you're using Cassini for your offline Web application, you are better off disabling IIS altogether. By the way, this is exactly what Web Matrix does when you try to build an ASP.NET page. Web Matrix is a community-supported, free IDE specifically designed for ASP.NET applications (download it from Microsoft at http://www.asp.net/webmatrix/download.aspx). Web Matrix ships Cassini, but allows you to work with IIS if you can provide an IIS virtual folder. If you choose to use Cassini as the Web server, then Web Matrix warns that it is going to stop IIS. From this point of view, Web Matrix behaves just like your ASP.NET host should. Figure 9 shows the code you can use in the setup to stop IIS, start Cassini, and open the root of the local host.

Figure 9 Stop IIS, Start Cassini (Close Figure 9)
using System;
using System.Diagnostics;

public class OfflineWebSite
{
    public static void Main()
    {
        OfflineWebSite o = new OfflineWebSite();
        return;
    }

    public OfflineWebSite()
    {
        StartCassini();
        Console.WriteLine("Done.");
    }

    private void StartCassini()
    {
        // stop IIS
        Console.WriteLine("Stopping IIS...");

        Process p = new Process();
        p.StartInfo.FileName = "net.exe";
        p.StartInfo.Arguments = "stop iisadmin /Y";
        p.Start();
        p.WaitForExit();
        Console.WriteLine("IIS stopped.");

        // start Cassini
        Console.WriteLine("Starting Cassini...");
        p.StartInfo.FileName = "cassiniwebserver.exe";
        p.StartInfo.Arguments = "c:\\inetpub\\wwwroot\\intranet 80 /";
        p.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
        p.Start();
        Console.WriteLine("Cassini started.");

        // open the the localhost
        Console.WriteLine("Opening the local host...");
        Process.Start("http://localhost");
    }
}

Conclusion

The System.Web.Hosting namespace has been available since the first beta of the .NET Framework, but it took a while to obtain full documentation for it. The fact that ASP.NET can be hosted by any managed executable is a great advancement. The ASP.NET runtime can also be hosted by unmanaged applications as long as they can host the CLR. This is exactly what happens with the ASP.NET worker process that IIS utilizes to serve ASPX requests—aspnet_wp.exe is a Win32® process, yet it hosts the common language runtime and subsequently ASP.NET. Cassini, on the other hand, is a determinant tool to allow ASP.NET applications to run offline in serverless scenarios.

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

Dino Espositois an instructor and consultant based in Rome, Italy. He is author of Building Web Solutions with ASP.NET and ADO.NET and Applied XML Programming for .NET, both from Microsoft Press. Dino spends most of his time teaching classes on ASP.NET, ADO.NET, and XML. Get in touch with Dino at dinoe@wintellect.com.


Advanced Basics: Automatically Generating a Web Service

Advanced Basics
Automatically Generating a Web Service
Ken Spencer

Code download available at: AdvancedBasics0301.exe (99KB)

Q My team's design pattern divides business objects between the client and the server, so I need a way to communicate between them. I can use Microsoft® .NET Remoting, but for many reasons I would prefer to use XML Web Services instead. Is there a way to automate the creation of the Web Service that communicates between the client and the server?

A I know you have decided on Web Services, but for those of you who want more information on choosing between .NET Remoting and Web Services, see Tim Ewald's MSDN® article at: ASP.NET Web Services or .NET Remoting: How to Choose). Now, to answer your question.

You could do this by using reflection to generate a Web Service that would map to the interface of an object and then create a Web Service that calls that interface. It took me a while to get this going (about four hours), but finally I made it work.

As a foundation I used code from the custom attribute application that I wrote for the installment. Let's take a walk through this code.

The interface is a simple form. The ButtonClick for the toolbar looks like this:

Select Case ToolBar1.Buttons.IndexOf(e.Button)
   Case 0 : OpenFile()
   Case 1 : GenerateWebService()
   Case Else
End Select
The GenerateWebService method shown in Figure 1 is the high-level method that does all the work. It calls functions that generate the code and output it to the new files. The rest of the code on this form is really simple and is not shown here, but the download for this column includes the complete code (see the link at the top of this article).

Figure 1 GenerateWebService (Close Figure 1)
Sub GenerateWebService()
    Dim t As Type
    Dim othis As Object
    Dim oAssembly As [Assembly]
    Dim arrayOfTypes() As Type
    StatusBar1.Text = ""

    If sFileName = "" Then
        Exit Sub
    End If

    Try
        oAssembly = [Assembly].LoadFrom(sFileName)
        arrayOfTypes = oAssembly.GetExportedTypes()
        txtOutputWSHeader.Text = ""

        For Each t In arrayOfTypes
            If t Is Nothing Then
                MsgBox("Type was not found — exiting")
                Exit Sub
            End If

            txtWSGenerated.Text &= t.Name & vbCrLf

           'Write ASMX file
            WriteFile(t.Name & ".asmx", GenerateWSHeader(t.Name))
           'Write Codebehind file
            WriteFile(t.Name & ".asmx.vb", GenerateWSCode(t))
            Next

    Catch exc As Exception
        StatusBar1.Text = "File " & sFileName & _
            "error occurred. " & exc.Message
    Finally
        oAssembly = Nothing
    End Try
End Sub

GenerateWebService calls the LoadFrom method to load the assembly you select, then loops through the classes it finds. For each class, it calls the GenerateWSHeader and GenerateWSCode methods to generate the code and save it to the .asmx and .vb files. It also adds the name of the class to the textbox. The WriteFile function outputs both the .asmx and the .vb files.

Now let's look at the code in the GenRoutines module. This file contains the code that generates the functions (see Figure 2). Programs that write code are not rocket science, but do they require patience. The problem is that every character and every space must be accounted for properly. You must also remember line breaks, tabs, and other formatting so you can understand the output code. Error handling and other housekeeping should also be incorporated into the code. It requires extra work, but it's really important.

Figure 2 GenRoutines Module (Close Figure 2)
Imports System
Imports System.Reflection
Module GenRoutines

Private Const localURI As String = "http://mycouri.org/"

    Public Function GenerateWSHeader(ByVal Name As String) _
        As String
        Dim localWSHeader As String
        If Name = "" Then
            Return ""
        End If

        localWSHeader = "<%@ WebService Language=""vb"" "
        localWSHeader &= " Codebehind=""" & Name & _
            ".asmx.vb"" Class=""" & Name & """ %>"

        Return localWSHeader
    End Function

Public Function GenerateWSCode(ByVal t As Type) As String
    Dim oMI As MethodInfo
    Dim oPR As PropertyInfo
    Dim oCN As ConstructorInfo
    Dim bAtLeastOne As Boolean
    Dim localOutput As String
    Dim localTempString As String
    Dim oParam As ParameterInfo
    Dim localMethodCall As String
    Dim localObjectName As String
    Dim localAssemblyName As String
    Dim i As Integer

    localOutput = "Imports System.Web.Services" & vbCrLf
    localOutput &= "<WebService(Namespace:=""" & _
        localURI & """)> _" & vbCrLf

    localOutput &= "Public Class " & t.Name & vbCrLf
    localOutput &= vbTab & _
        "Inherits System.Web.Services.WebService" _
        & vbCrLf & vbCrLf

    localObjectName = "o" & t.Name

    localAssemblyName = t.AssemblyQualifiedName.ToString

    i = InStr(localAssemblyName, ",")

    localAssemblyName = Left(localAssemblyName, i - 1)

    localOutput &= vbTab & "Private " & _
        localObjectName & " as New " & _
        localAssemblyName & "()" & vbCrLf

    localOutput &= "'Generate Constructor " & vbCrLf

    localOutput &= "Public Sub New()" & vbCrLf

     localOutput &= "End Sub" & vbCrLf

    localOutput &= "End Sub" & vbCrLf
            localOutput &= vbCrLf
    ' Methods

    For Each oMI In t.GetMethods()
        If CheckName(oMI.Name) Then
            bAtLeastOne = False
            localOutput &= "<WebMethod()> _" & vbCrLf
            localOutput &= "Public Function " & _
                oMI.Name & "("
            localMethodCall = _
                "Dim localReturn as " & _
            oMI.ReturnType.ToString & vbCrLf
            localMethodCall &= vbTab & "try" & vbCrLf
            localMethodCall &= vbTab & vbTab & _
                "localReturn = " & _
                localObjectName & "." & oMI.Name
            localMethodCall &= "("
            localTempString = ""
            For Each oParam In oMI.GetParameters
                If bAtLeastOne Then _
                    localTempString &= " _ " & _
                        vbCrLf & vbTab & ","
                    localTempString &= _
                        oParam.Name & " " & " as "
                    localTempString &= _
                        oParam.ParameterType.ToString
                    If bAtLeastOne Then _
                        localMethodCall &= ","
                    localMethodCall &= oParam.Name

                         If localTempString <> "" Then
                           bAtLeastOne = True
                           localOutput &= localTempString
                         End If
                   Next

                   localOutput &= ") as " & _
                            oMI.ReturnType.ToString & vbCrLf

                        localMethodCall &= ")" & vbCrLf

                        localMethodCall &= vbTab & _
                           "Catch Exc as Exception"  & vbCrLf
                        localMethodCall &= vbTab & vbTab & _
                            "Throw New Exception(""Error in "", _
                            Exc.InnerException)" & vbCrLf
                        localMethodCall &= vbTab & "End Try" & _
                            vbCrLf & vbCrLf

                        localMethodCall &= vbTab & "Return " & _
                            "localReturn" & vbCrLf

                        localOutput &= localMethodCall & vbCrLf

                        localOutput &= "End Function" & vbCrLf & vbCrLf

                    End If
            Next

            localOutput &= vbCrLf & vbCrLf

            localOutput &= "End Class" & vbCrLf

            Return localOutput
    End Function

    Function CheckName(ByVal MethodName As String) As Boolean
        Select Case MethodName
            Case "GetHashCode" : Return False
            Case "ToString" : Return False
            Case "Equals" : Return False
            Case "GetType" : Return False
            Case Else
                Return True
        End Select
    End Function
End Module

Now, let's look at the code that generates the Web Service code. The first line in the module creates a constant for the Web Service URI. This allows you to easily change the URI to the standard that your organization has adopted.

The GenerateWSHeader function outputs the header for the Web Service. The name of the class is passed to this function and is used to name the Web Service. The code isn't tricky; it's just a string handling function. The results are passed back from the function to generate the header line.

Now, let's take a look at generating the Web Service code, as shown in Figure 2. This is where the real work takes place. First, the variables are declared. This includes variables that are generally used for string handling but can also reference information about a method. For instance, to get its parameters or return value type, the variable oMI is used to reference the current method in the For Next loop. Similarly, oCN is used for a constructor's information.

The first few lines start outputting the code. As you can see, you simply build up the code statements and set the localOutput variable to that string:

localOutput = "Imports System.Web.Services" & vbCrLf
localOutput &= "<WebService(Namespace:=""" & _
    localURI & """)> _" & vbCrLf

For creating code that generates variables and references, you can set your coding standards. For instance, this line creates a variable with the name of the object reference that will be used in the Web Service:

localObjectName = "o" & t.Name

The next couple of lines set the assembly name and then extract the first entry to the left of the first comma:

localAssemblyName = t.AssemblyQualifiedName.ToString
i = InStr(localAssemblyName, ",")
localAssemblyName = Left(localAssemblyName, i - 1)
This is because the AssemblyQualifiedName method returns more information than I wanted in a comma-delimited string. To generate the correct name, you simply extract the part of the string to the left of the first comma:
localOutput &= vbTab & "Private " & _
    localObjectName & " as New " & _
    localAssemblyName & "()" & vbCrLf

The code currently outputs only a default constructor with no parameters. If you need to add custom code to the constructor, you must do it manually.

The first For Each loop creates the parameters for each method as it walks through the loop. The code also tags the method name with the WebMethod attribute. The CheckName function is called to determine if the method is one that should be output. For instance, you do not usually want to output the ToString method that is a default method of a class. You can see how the oMI variable is used to obtain the method's name, its parameters, and return type. For instance, to output the method's name, I simply use the following code:

localOutput &= "<WebMethod()> _" & vbCrLf
localOutput &= "Public Function " & _
    oMI.Name & "("

The code that is generated from this snippet will include the localReturn variable, which the Web methods use to contain the return value of the method that it calls.

The inner For Each loop (again in Figure 2) processes the parameters. This one was tricky because it must account for all parameters, their data types, and the correct syntax when multiple parameters are used.

Finally, the method's output parameter type is picked up by the following code and then it is set to the output string:

localOutput &= ") as " & _
  oMI.ReturnType.ToString  _
  & vbCrLf

The following code generates the error handling code for the Web method:

localMethodCall &= vbTab & "Catch Exc as Exception" _
    & vbCrLf
localMethodCall &= vbTab & vbTab & "Throw New Exception(""Error in "", _
    Exc.InnerException)" & vbCrLf
localMethodCall &= vbTab & "End Try" & vbCrLf & vbCrLf

The last bit of code in the outer For Each loop finishes the Web method by outputting the code to send the return value and generate the End Class statement.

For the current version of this utility, you must change the reference in the .asmx file to reflect the namespace of your project. For instance, the Class attribute generated by the utility was changed by adding the namespace for the application it was used in, like so:

<%@ WebService Language="vb"  Codebehind="Order.asmx.vb"
    Class="WSTesterForGeneratedWS.Order" %>

The tool is finished now, but I want to back up and think about the process for a moment. Once the code looked good, I put it into a Web Service project and looked for errors. Then I fixed each problem and regenerated the Web Service again. Then I tested, made changes, and tested again. When the Web Service showed no errors in Visual Studio® .NET, I ran it in test mode and tested each method. Using this process, it was fairly easy to walk through the coding and make sure it worked.

There is another way to generate coding using the .NET Framework's System.CodeDom class. This provides an alternate approach that I will demonstrate in next month's column. I also included a simple CodeDom sample (CodeDomSample.vb) in the download files for this month. The code has also been updated to fix a few bugs since this column was written.

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

Ken Spencerworks for 32X Tech (http://www.32X.com), where he provides training, software development, and consulting services on Microsoft technologies.


Basic Instincts: Implementing Callbacks with a Multicast Delegate

Basic Instincts
Implementing Callbacks with a Multicast Delegate
Ted Pattison

Contents
Implementing Callbacks with Delegates
Multicasting
Callbacks with a Multicast Delegate
Calling the GetInvocationList Method
Wrap-up

This month's column is a follow-up to the December 2002 installment in which I introduced the basic concepts and programming techniques associated with delegates. I am going to assume you have already read that column and that you are familiar with the fundamentals of programming delegates. For example, you should know how to define a delegate type, how to create a delegate object that's bound to a handler method, and how to execute the handler method by calling the delegate object's Invoke method.

This month I'll create a delegate-based design that involves callback notifications. I will show how delegates make it possible to create such a design in a loosely coupled fashion. After that, I will explain how delegates support binding a notification source to multiple handler methods through a feature known as multicasting.

Implementing Callbacks with Delegates

Imagine you are designing an application with a class named BankAccount that contains a method named Withdraw. Let's say you want to make it possible for another part of the application to react whenever a BankAccount object experiences a withdrawal of an amount greater than $5000. The starting point for your class might look something like this:

Class BankAccount
  Sub Withdraw(ByVal Amount As Decimal)
    If (Amount > 5000) Then
      '*** send notification to interested parties
    End If
    '*** perform withdrawal
  End Sub
End Class

In this example, a BankAccount object is going to act as a notification source. That means a BankAccount object must provide a way for a listener to express its interest in receiving notifications. In other words, a BankAccount object must allow a listener to register a handler method for a callback. Let's start by defining a new delegate type named LargeWithdrawHandler:

Delegate Sub LargeWithdrawHandler(ByVal Amount As Decimal)

Now it's time to modify the BankAccount class to act as a notification source. I can do this by adding two members. First, I'll add a private field named "handler" defined in terms of the delegate type LargeWithdrawHandler. Second, I will add a method named RegisterHandler that allows other code to register a delegate object to receive callback notifications:

Class BankAccount
  Private handler As LargeWithdrawHandler
  Sub RegisterHandler(ByVal handler As LargeWithdrawHandler)
    Me.handler = handler
  End Sub
  '*** other class members omitted
End Class

As you can see, the RegisterHandler method accepts a single parameter of type LargeWithdrawHandler. The implementation of RegisterHandler assigns this value to the field named handler so that a BankAccount object can track a delegate object and execute its handler method.

Once the RegisterHandler method has been called with a delegate object, any method within the BankAccount class can execute the handler method by calling Invoke on the registered delegate object. Here's how you execute the Invoke method from within the Withdraw method of the BankAccount class:

Sub Withdraw(ByVal Amount As Decimal)
  '*** send notifications if required
  If (Amount > 5000) AndAlso (Not handler Is Nothing) Then
    handler.Invoke(Amount)
  End If
  '*** perform withdrawal
End Sub

The Withdraw method conducts a check to make sure the handler field contains a valid reference rather than a value of Nothing. Remember that it will have a value of Nothing until there is a call to RegisterHandler, so you must prevent your code from attempting to execute the Invoke method on an uninitialized reference.

The BankAccount class has been written to send out a delegate-based notification whenever a withdrawal is made for an amount that exceeds $5000. Now let's create a handler method that can be wired up to respond to these notifications. Take a look at the class shown in the following code:

Class AccountHandlers
  Shared Sub GetApproval(ByVal Amount As Decimal)
    '*** block until manager approves withdrawal amount
  End Sub
  Shared Sub LogWithdrawToDB(ByVal Amount As Decimal)
    '*** write withdrawal info to a database
  End Sub
  Shared Sub LogWithdrawToFile(ByVal Amount As Decimal)
    '*** write withdrawal info to a log file
  End Sub
End Class
The GetApproval, LogWithdrawToDB, and LogWithdrawToFile methods have been written with the proper calling signatures so they can serve as handler methods for the notifications sent by a BankAccount object.

Now, let's write a simple application that ties everything together. First, the application must create a BankAccount object. Next, the application must create a delegate object that is bound to a target handler method such as GetApproval. The final step in hooking everything up is to call the RegisterHandler method, passing a reference to the delegate object.

Module MyApp
  Sub Main()
    '*** create bank account object
    Dim acc1 As New BankAccount()
    '*** create delegate object and register callback method
    acc1.RegisterHandler(AddressOf AccountHandlers.GetApproval)
    '*** do something that triggers callback
    acc1.Withdraw(5001)
  End Sub
End Module

This example doesn't use the New operator when creating a delegate object. Instead, it uses the more convenient shorthand syntax. Since the RegisterHandler method expects a parameter of type LargeWithdrawHandler, you can simply use the AddressOf operator followed by the method name, as shown in the code.

At this point, I have a simple application that performs a callback notification using a delegate. When the Main method calls the Withdraw method on the BankAccount object and passes a parameter value of 5001, the implementation of the Withdraw method uses the delegate object held by the handler field to execute the GetApproval method.

Note that this application provides a good example of a loosely coupled design. The BankAccount class doesn't know or care about what type of handler method is used. It would be very easy to replace the GetApproval handler method with the handler methods LogWithdrawToDB or LogWithdrawToFile. It would also be easy to create another method in a different class and use that one as a callback method instead. From this, you should be able to conclude that a delegate-based design can provide polymorphism in the same fashion as an interface-based design.

There is still one more important design issue that needs to be addressed. The current implementation of BankAccount can only provide callbacks to a single handler method. It would be better if the BankAccount class could be modified to provide callbacks to more than one handler method at a time. Fortunately, delegates provide built-in support for dealing with multiple handler methods through a feature known as multicasting.

Multicasting

Every delegate type has built-in support for dealing with multiple handler methods. Delegate types gain this support by inheriting from the MulticastDelegate class that's defined in the System namespace. The benefit of multicasting is that you can combine several handler methods in a list so they are all bound to a single delegate object. When Invoke is called on the delegate object, the MulticastDelegate class provides the code to execute every handler method in the list.

An example should help you see exactly how this works. Let's say you'd like to bind two different handler methods to a single delegate object. You can accomplish this by calling a shared method of the System.Delegate class, Combine. If you call the Combine method and pass two delegate objects, this method will return a new delegate object that is a multicast of the other two. Here's an example of taking the handler methods for two different delegate objects and combining them into a multicast delegate object:

'*** create two individual delegates
Dim handler1, handler2 As LargeWithdrawHandler
handler1 = AddressOf AccountHandlers.GetApproval
handler2 = AddressOf AccountHandlers.LogWithdrawToDB

'*** combine delegates into multicast delegate
Dim result As [Delegate] = [Delegate].Combine(handler1, handler2)

'*** convert reference to LargeWithdrawHandler type
Dim handlers As LargeWithdrawHandler
handlers = CType(result, LargeWithdrawHandler)

'*** execute handler methods in multicast list
handlers.Invoke(5001)

Please take a look at the square brackets that have been placed around the name of the [Delegate] class. They are required because Delegate is also a keyword. The brackets are used as escape characters to tell the Visual Basic® .NET compiler that you intend to use Delegate as a class name.

The call to Combine returns a reference to a newly created multicast delegate object. Note that the Combine method has a generic return type of Delegate that must be converted to the more specific delegate type, LargeWithdrawHandler. This is only required if you want to call the Invoke method.

Let's take a moment to discuss how multicast delegates are implemented. A multicast delegate is simply a linked list of delegate objects. The private implementation of each delegate object contains a field designed to hold a reference to the previous delegate object in the list. You might think it would be more intuitive if a delegate held a reference to the next delegate in the list as opposed to the previous delegate. However, the multicast delegate design uses the notion of the previous delegate because of the sequence in which the handler methods are executed. The reason that each delegate object tracks a previous delegate will be explained in more detail later in this column.

When you have created a multicast delegate that contains multiple handler methods, the delegate at the head of the list holds a reference to the previous delegate. That delegate also holds a reference to the previous delegate. For the delegate object at the tail of the list, the field for the previous delegate will have a value of Nothing. As you will see, the position in which a delegate object is placed in the list is important.

When you call Combine, it links two or more delegate objects together and returns a reference to the delegate object at the head of the list. Note that the previous example called the overloaded implementation of Combine that accepts two delegate parameters. This implementation of Combine creates a multicast list that places the delegate object passed as the second parameter at the head. For example, in the preceding code example the delegate at the head of the list is bound to the LogWithdrawToDB method. This delegate object contains a private field that references the previous delegate object that's bound to the GetApproval method.

Note that there is another overloaded version of Combine which accepts an array of delegate objects. When you pass an array of delegate objects to the Combine method, the method places the first delegate object in the array at position 0, at the head of the list. You can call whichever overloaded version of Combine you'd like. Just make sure you pay attention to how each delegate object is being placed in the list. You have control over which handler methods get executed first.

When you call Invoke on the delegate object that's at the head of the list, the MulticastDelegate class provides the code that's needed to enumerate through the list and execute the Invoke method of each delegate object in a chain. It's important to observe that the execution of individual handler methods proceeds in a serialized and synchronous fashion. You should also take note of which handler methods are executed first.

The delegate object at the head of a multicast list does not execute its handler method until after it has called the Invoke method on the previous delegate. This is why the multicast delegate design pattern refers to it as the previous delegate object as opposed to the next delegate object. You should see that control passes from the delegate object at the head of the list to the delegate object at the tail before any handler methods are executed.

The delegate object at the tail of a multicast list always executes its handler method first. Therefore, execution always occurs from back to front. You should observe that the delegate object at the head of the multicast list always executes its handler last. In my example involving a multicasting of two delegate objects, the GetApproval method is going to execute before the LogWithdrawToDB method.

Callbacks with a Multicast Delegate

Now let's revisit the example involving the BankAccount class and add support for multicasting. I'm going to modify the class implementation so that a BankAccount object can make callbacks to a list of handler methods. See the class definition in Figure 1.

Figure 1 BankAccount Class (Close Figure 1)
Class BankAccount
  Private handlers As LargeWithdrawHandler
  Sub RegisterHandler(ByVal handler As LargeWithdrawHandler)
   '*** add new handler to head of multicast list
    Dim NewList As [Delegate] = [Delegate].Combine(handlers, handler)
    handlers = CType(NewList, LargeWithdrawHandler)
  End Sub
  Sub Withdraw(ByVal Amount As Decimal)
    '*** send notifications if required
    If (Amount > 5000) AndAlso (Not handlers Is Nothing) Then
      handlers.Invoke(Amount)
    End If
    '*** perform withdrawal
  End Sub
End Class

First, you should notice that the handler field has been renamed to "handlers" to signify that the field can be used to hold a reference to a multicast delegate object. However, this is nothing more than a renaming issue since the field is still based on the LargeWithdrawHandler delegate type.

The implementation of the RegisterHandler method has also been updated to support multicasting. The implementation of RegisterHandler now calls the Combine method to add the new delegate object to the existing list of delegate objects. Note that the implementation of RegisterHandler passes the new delegate object as the second parameter in its call to Combine. That means the new delegate object will become the head of the list and will, therefore, be executed last in the chain. If you'd prefer, you could easily rewrite RegisterHandler to place a new handler method at the tail of the multicast list where it would execute before any handler method that was previously registered:

Sub RegisterHandler(ByVal handler As LargeWithdrawHandler)
  '*** add new handler to tail of multicast list
  Dim NewList As [Delegate] = [Delegate].Combine(handler, handlers)
  handlers = CType(NewList, LargeWithdrawHandler)
End Sub

You might have also observed that the Withdraw method did not require any modifications other than updating the name of the handler field to "handlers". The call to Invoke is made in the exact same way as before. This illustrates one of the most valuable aspects of using multicast delegates. A multicast delegate doesn't have to be concerned with how many target methods are bound to a delegate object. A notification source simply calls Invoke and every handler method is automatically executed.

Now that the BankAccount class has been updated to support multicasting, the application can be rewritten to register three different handler methods to respond to large withdrawal notifications, as shown in the following code:

Sub Main()
  '*** create bank account object
  Dim acc1 As New BankAccount()
  '*** create register handler methods
  acc1.RegisterHandler(AddressOf AccountHandlers.GetApproval)
  acc1.RegisterHandler(AddressOf AccountHandlers.LogWithdrawToDB)
  acc1.RegisterHandler(AddressOf AccountHandlers.LogWithdrawToFile)
  '*** do something that triggers callback
  acc1.Withdraw(5001)
End Sub

You should see from this design that multicasting allows you to place the handler methods so they execute in a predictable sequence. Figure 2 gives a high-level view of how things are laid out. The GetApproval method will execute first because it has been placed at the tail of the list. Next, the LogWithdrawFromDB method will execute. Since the LogWithdrawToFile method was registered last, it will be placed at the head of the list and, consequently, will execute last.

Figure 2 Multicast Delegate Executing Handlers
Figure 2 Multicast Delegate Executing Handlers

Figure 3 shows a complete application based on the design I just walked though. Before moving on, you should understand what's been created here with delegates: a loosely coupled design for implementing callbacks with support for multicasting. It would be easy to further customize this application by creating even more handler methods and using delegates to register them.

Figure 3 Delegate-based Design for Callback Notifications (Close Figure 3)
Imports System

Delegate Sub LargeWithdrawHandler(ByVal Amount As Decimal)

Class BankAccount
  Private handlers As LargeWithdrawHandler
  Sub RegisterHandler(ByVal handler As LargeWithdrawHandler)
    Dim NewList As [Delegate] = [Delegate].Combine(handlers, handler)
    handlers = CType(NewList, LargeWithdrawHandler)
  End Sub
  Sub Withdraw(ByVal Amount As Decimal)
    '*** send notifications if required
    If (Amount > 5000) AndAlso (Not handlers Is Nothing) Then
      handlers.Invoke(Amount)
    End If
    '*** perform withdrawal
  End Sub
End Class

Class AccountHandlers
  Shared Sub GetApproval(ByVal Amount As Decimal)
    '*** block until manager approval
  End Sub
  Shared Sub LogWithdrawToDB(ByVal Amount As Decimal)
    '*** write withdrawal info to database
  End Sub
  Shared Sub LogWithdrawToFile(ByVal Amount As Decimal)
    '*** write withdrawal info to log file
  End Sub
End Class

Module MyApp
  Sub Main()
    '*** create bank account object
    Dim acc1 As New BankAccount()
    '*** register callback methods
    acc1.RegisterHandler(AddressOf AccountHandlers.GetApproval)
    acc1.RegisterHandler(AddressOf AccountHandlers.LogWithdrawToDB)
    acc1.RegisterHandler(AddressOf AccountHandlers.LogWithdrawToFile)
    '*** do something that triggers callback
    acc1.Withdraw(5001)
  End Sub
End Module

Calling the GetInvocationList Method

In many cases, a notification source can simply call Invoke to execute all the target handler methods associated with a multicast delegate object. However, there are other times when you need more control. For example, you might need to determine how many target handler methods have been added to a multicast delegate list. You also might be required to write code that can gracefully deal with the exceptions thrown by handler methods in the list.

The Delegate class provides a public instance method named GetInvocationList. When you call this method on a multicast delegate, it returns an array of references to individual delegate objects. This array makes it possible to determine how many handler methods are currently bound to a multicast delegate:

'*** determine number of target handler methods
Dim HandlerCount As Integer = handlers.GetInvocationList().Length

A call to GetInvocationList also makes it relatively simple to enumerate through the individual delegate objects and explicitly execute their handler methods one at a time. Since a call to GetInvocationList returns an array of references to delegate objects, it makes it easy to structure your code to enumerate through the delegate objects using a For Each loop:

Sub Withdraw(ByVal Amount As Decimal)
  '*** send notifications if required
  If Not (Amount > 5000) AndAlso (Not handlers Is Nothing) Then
    Dim handler As LargeWithdrawHandler
    For Each handler In handlers.GetInvocationList()
      handler.Invoke(Amount)
    Next
  End If
  '*** perform withdrawal
End Sub

The code you've just seen doesn't really provide any more control than executing the Invoke method on the handlers field. So you might be asking why you would ever need to call GetInvocationList to enumerate through the delegate objects in the list. One reason is that you might need more control if one of the handler methods throws an exception during its execution.

Let's look at an example. Imagine you are holding onto a multicast delegate object that is bound to 10 handler methods. What happens if you call Invoke, and the seventh handler method throws an exception? The first six handler methods have already executed successfully. The exception thrown by the seventh handler causes the Invoke method to terminate unexpectedly. Therefore, the eighth, ninth, and tenth handler methods never execute at all.

The problem is that there's really no way to tell which handler methods executed successfully, which handler method failed, and which handler methods were never executed. You'll have more control if you restructure your code to explicitly call the Invoke method on each delegate object within a Try block:

Dim handler As LargeWithdrawHandler
For Each handler In handlers.GetInvocationList()
  Try
    handler.Invoke(Amount)
  Catch ex As Exception
    '*** deal with exception and continue
  End Try
Next

A second reason you might need to call GetInvocationList and enumerate through each delegate object individually is that it makes it possible to retrieve return values and output parameters from more than one handler method. If you call Invoke on a multicast delegate object and it involves an output parameter or a return value, the results you get are somewhat arbitrary. The output parameter and the return value you receive are the ones supplied by the last handler method to execute. Remember, that's the delegate object at the head of the list. However, you can capture a separate output parameter and return value for each handler method if you write the extra code to enumerate through the list and explicitly call Invoke on each delegate object.

Note that GetInvocationList returns an array that represents a snapshot in time. In other words, GetInvocationList returns the list delegate objects that were present at the time the method was called. If you add a new delegate object to a multicast delegate, an array generated with an earlier call to GetInvocationList will not be in sync. You must call GetInvocationList again to create a new array that represents the updated list of delegate objects.

Wrap-up

This concludes my two-part discussion of programming with delegates. As you have seen, a delegate is a programmable binding mechanism for implementing a callback between a notification source and one or more handler methods. Delegates provide an attractive means for implementing callbacks because they combine the type safety and polymorphic capabilities of interfaces with the efficiency and flexibility of function pointers.

Understanding the fundamentals of programming with delegates is a prerequisite to becoming an advanced user of Visual Basic .NET. I say this for two reasons. First, event handling in the .NET Framework is entirely based on delegates. If you really want to make the most of an event-driven application framework such as Windows® Forms or ASP.NET, you'd better be prepared to drop down to a lower level and program in terms of delegates when it's required. Delegates also provide the primary means for executing a method on a secondary thread in an asynchronous fashion.

Send your questions and comments for Ted to instinct@microsoft.com.

Ted Pattisonis an instructor and researcher at DevelopMentor (http://www.develop.com), where he co-manages the Visual Basic curriculum. He is the author of Programming Distributed Applications with COM and Microsoft Visual Basic 6.0 (Microsoft Press, 2000).


.NET Column: Safe Thread Synchronization

.NET
Safe Thread Synchronization
Jeffrey Richter

Contents
The Great Idea
Implementing the Great Idea
Using Monitor to Manipulate a SyncBlock
Synchronizing the Microsoft Way
Simplifying the Code with the C# Lock Statement
Synchronizing Static Members the Microsoft Way
Why the Great Idea isn't So Great
Unboxed Instances of Value Types

By far, the most common use of thread synchronization is to ensure mutually exclusive access to a shared resource by multiple threads. In the Win32® API, the CRITICAL_SECTION structure and associated functions offers the fastest and most efficient way to synchronize threads for mutually exclusive access when the threads are all running in a single process. The Microsoft® .NET Framework doesn't expose a CRITICAL_SECTION structure, but it does offer a similar mechanism allowing mutually exclusive access among a set of threads running in the same process. This mechanism is made possible by way of SyncBlocks and the System.Threading.Monitor class.

In this column, I'm going to explain how this common use of thread synchronization is exposed via the .NET Framework. Specifically, I'm going to explain the motivation for why SyncBlocks and Monitors were designed the way they are and how they work. Then, at the end of this column, I'm going to explain why this design is horrible and show you how to use this mechanism in a good, safe fashion.

The Great Idea

The .NET Framework employs an object-oriented programming structure. This means that developers construct objects and then call the type's members in order to manipulate it. Occasionally, these objects are manipulated by multiple threads, and to ensure that the object's state doesn't become corrupted, thread synchronization must be performed. While designing the .NET Framework, the folks at Microsoft decided to create a new mechanism that would make it easy for developers to synchronize an object.

Here's the basic idea: every object in the heap has a data structure associated with it (very similar to a Win32 CRITICAL_SECTION structure) that can be used for thread synchronization. Then, the framework class library provides methods that, when you pass in a reference to an object, use that data structure for thread synchronization purposes.

In Win32, an unmanaged C++ class with this design would look like the one in Figure 1. In essence, the .NET Framework provides every object with its own CRITICAL_SECTION-like field and takes over the responsibility of initializing and deleting it. The only thing the developer has to do is write some code to enter and leave the field as necessary in each method that required thread synchronization.

Figure 1 A CRITICAL_SECTION for Every Object (Close Figure 1)
class SomeType {
private:
   // The private CRITICAL_SECTION field
   // associated with each object
   CRITICAL_SECTION m_csObject;

public:
   SomeType() {
      // The constructor initializes the
      // object's CRITICAL_SECTION field
      InitializeCriticalSection(&m_csObject);
   }

   ~SomeType() {
      // The destructor deletes the
      // object's CRITICAL_SECTION field
      DeleteCriticalSection(&m_csObject);
   }

   void SomeMethod() {
      // In the methods, we use the object's
      // CRITICAL_SECTION field to synchronize
      // access to the object by multiple threads.

      EnterCriticalSection(&m_csObject);
      // Execute thread-safe code here...
      LeaveCriticalSection(&m_csObject);
   }

   void AnotherMethod() {
      // In the methods, we use the object's
      // CRITICAL_SECTION field to synchronize
      // access to the object by multiple threads.

      EnterCriticalSection(&m_csObject);
      // Execute thread-safe code here...
      LeaveCriticalSection(&m_csObject);
   }
};

Implementing the Great Idea

Now, obviously, associating a CRITICAL_SECTION field with every object in the heap is quite wasteful, especially since most objects never require thread-safe access. So, the .NET Framework team designed a more efficient way to offer the functionality just described. Here's how it works.

When the common language runtime (CLR) initializes, it allocates a cache of SyncBlocks. A SyncBlock is a chunk of memory that can be associated with any object as needed. This SyncBlock contains the same fields that you would find in a Win32 CRITICAL_SECTION structure.

Whenever an object is created in the heap, each object gets two additional overhead fields associated with it. The first overhead field, the MethodTablePointer, contains the memory address to the type's method table. Basically, this pointer makes it possible to obtain the type information about any object in the heap. In fact, when you call System.Object's GetType method internally, this method follows the object's MethodTablePointer field to determine what type the object is. The second overhead field, called the SyncBlockIndex, contains a 32-bit signed integer index into the cache of SyncBlocks.

When an object is constructed, the object's SyncBlockIndex is initialized to a negative value to indicate that it doesn't refer to any SyncBlock at all. Then, when a method is called to synchronize access to the object, the CLR finds a free SyncBlock in its cache and sets the object's SyncBlockIndex to refer to the SyncBlock. In other words, SyncBlocks are associated with an object on the fly when the object needs the synchronization fields. When no more threads are synchronizing access to the object, the object's SyncBlockIndex is reset to a negative number, and the SyncBlock is free to be associated with another object in the future.

Figure 2 Thread Synchronization in .NET
Figure 2 Thread Synchronization in .NET

For a concrete representation of this idea, take a look at Figure 2. In the CLR Data Structures section of the figure you can see that there is one data structure for every type that the system knows about; you can also see the set of SyncBlock structures. In the managed heap section of the figure you can see that three objects, ObjectA, ObjectB, and ObjectC, were created. Each object's MethodTablePointer field refers to the type's method table. From the method table, you can tell each object's type. So, we can easily see that ObjectA and ObjectB are instances of the SomeType type while ObjectC is an instance of the AnotherType type.

You'll notice that ObjectA's SyncBlockIndex overhead field is set to 0. This indicates that SyncBlock #0 is currently being used by ObjectA. On the other hand, ObjectB's SyncBlockIndex field is set to -1 indicating that ObjectB doesn't have a SyncBlock associated with it for its use. Finally, ObjectC's SyncBlockIndex field is set to 2 indicating that it is using SyncBlock #2. In the example I've presented here, SyncBlock #1 is not in use and may be associated with some object in the future.

So, logically, you see that every object in the heap has a SyncBlock associated with it that can be used for fast, exclusive thread synchronization. Physically, however, the SyncBlock structures are only associated with the object when they are needed and are disassociated from an object when they are no longer needed. This means that the memory usage is efficient. By the way, the SyncBlock cache is able to create more SyncBlocks if necessary so you shouldn't worry about the system running out of them if many objects are being synchronized simultaneously.

Using Monitor to Manipulate a SyncBlock

Now that you understand the SyncBlock infrastructure, let's examine how to lock an object. To lock or unlock an object you use the System.Threading.Monitor class. All of this type's methods are static. Here is the method you call to lock an object:

public static void Enter(object obj);
When you call the Enter method, it first checks to see if the specified object's SyncBlockIndex is negative and if it is, the method finds a free SyncBlock and records its index in the object's SyncBlockIndex. Once a SyncBlock is associated with the object, this method examines the specified object's SyncBlock to see if another thread currently owns the SyncBlock. If it is currently unowned, then the calling thread becomes the owner of the object. If, on the other hand, another thread owns the SyncBlock when Enter is called, then the calling thread is suspended until the currently owning thread gives up ownership of the SyncBlock.

If you want to code more defensively, then instead of calling Enter, you can call one of the following TryEnter methods:

public static Boolean TryEnter(object obj);

public static Boolean TryEnter(object obj,
    int millisecondsTimeout);

public static Boolean TryEnter(object obj,
    TimeSpan timeout);
The first version simply checks whether the calling thread can gain ownership of the object's SyncBlock and returns true if it is successful. The other two methods allow you to specify a timeout value indicating how long you'll allow the calling thread to sit around idly and wait for ownership. All the methods will return false if ownership cannot be obtained.

Once ownership is obtained, the code can access the object's fields safely. When finished, the thread should release the SyncBlock by calling Exit:

public static void Exit(object obj);

If the calling thread doesn't own the specified object's SyncBlock, then Exit will throw a SynchronizationLockException. Also note that a thread can own a SyncBlock recursively; every successful call to Enter/TryEnter must be matched by a corresponding call to Exit before the SyncBlock is considered unowned.

Synchronizing the Microsoft Way

Now let's look at Figure 3 for some sample code that shows how to use Monitor's Enter and Exit methods to lock and unlock an object. Note that the implementation of the property requires calls to Enter, Exit, and a temporary variable, dt. This is very important in order to prevent returning a corrupt value. This could happen if a thread calls PerformTransaction at the same time another thread has accessed the property.

Figure 3 Using Enter and Exit Methods (Close Figure 3)
class Transaction {

   // Private field holding the time of
   // the last transaction performed
   private DateTime timeOfLastTransaction;

   public void PerformTransaction() {
      // Lock this object
      Monitor.Enter(this);

      // Perform the transaction...

      // Record time of the most recent transaction
      timeOfLastTransaction = DateTime.Now;

      // Unlock this object
      Monitor.Exit(this);
   }

   // Public read-only property returning
   // the time of the last transaction
   public DateTime LastTransaction {
      get {
         // Lock this object
         Monitor.Enter(this);

         // Save the time of the last transaction
         // in a temporary variable
         DateTime dt = timeOfLastTransaction;

         // Unlock this object
         Monitor.Exit(this);

         // Return the value in the temporary variable
         return(dt);
      }
   }
}

Simplifying the Code with the C# Lock Statement

Because this pattern of calling Enter, accessing the protected resource, and then calling Exit is so common, the C# language offers special syntax to simplify the code. The two C# code fragments in Figure 4 are identical in their function, but the second one is simpler. Using the C# lock statement, you can simplify the Transaction class substantially. In particular, take a look at the new, improved LastTransaction property that is shown in Figure 5; the temporary variable is no longer necessary.

Figure 5 Transaction Class (Close Figure 5)
class Transaction {

   // Private field holding the time of
   // the last transaction performed
   private DateTime timeOfLastTransaction;

   public void PerformTransaction() {
      lock (this) {
         // Perform the transaction...

         // Record time of the most recent transaction
         timeOfLastTransaction = DateTime.Now;
      }
   }

   // Public read-only property returning
   // the time of the last transaction
   public DateTime LastTransaction {
      get {
         lock (this) {
            // Return the time of the last transaction
            return timeOfLastTransaction;
         }
      }
   }
}
Figure 4 Regular and Simple Lock and Unlock (Close Figure 4)
// Regular function
public void SomeMethod() {
   // Lock the object
   Object oTemp = this;
   Monitor.Enter(oTemp);
   try {
          // Access the object
         ...
         // Unlock the object
   }
   finally {
     Monitor.Exit(oTemp);
   }
   // Return
}

// Simple function
public void SomeMethod() {
   // Lock the object
   lock (this) {

      // Access the object
      ...
     // Unlock the object
   }

   // Return
}

In addition to this simplification, the lock statement ensures that Monitor.Exit is called, releasing the SyncBlock even if an exception occurs inside the try block. You should always use exception handling with thread synchronization mechanisms to ensure that locks are released properly. If you use the C# lock statement, the compiler writes the proper code for you automatically. By the way, Visual Basic® .NET has a SyncLock statement that does the same things as the C# lock statement.

Synchronizing Static Members the Microsoft Way

The Transaction class demonstrates how to synchronize access to an object's instance fields. But, what if your type defines a number of static fields and static methods that access these fields? In this case, you don't have an instance of the type in the heap and, therefore, there is no SyncBlock to be used or object reference to pass to Monitor's Enter and Exit methods.

As it turns out, the block of memory that contains a type's type descriptor is an object in the heap. Figure 2 doesn't show it, but the SomeType Type Descriptor and AnotherType Type Descriptor memory blocks are actually objects themselves and, as such, each has a MethodTablePointer field and a SyncBlockIndex field. This means that a SyncBlock can be associated with a type and a reference to the type object can be passed to the Monitor's Enter and Exit methods. In the version of the Transaction class shown in Figure 6, all of the members have been changed to static and the PerformTransaction method and LastTransaction property have been modified to show how Microsoft expects developers to synchronize access to static members.

Figure 6 New Transaction Class (Close Figure 6)
class Transaction {

   // Private field holding the time of
   // the last transaction performed
   private static DateTime timeOfLastTransaction;

   public static void PerformTransaction() {
      lock (typeof(Transaction)) {
         // Perform the transaction...

         // Record time of the most recent transaction
         timeOfLastTransaction = DateTime.Now;
      }
   }

   // Public read-only property returning
   // the time of the last transaction
   public static DateTime LastTransaction {
      get {
         lock (typeof(Transaction)) {
            // Return the time of the last transaction
            return timeOfLastTransaction;
         }
      }
   }
}

In the code for the method and property, you no longer see the this keyword being used since it can't be referenced in static members. Instead, I'm passing a reference to the type's type descriptor object to the lock statement. This reference is obtained using the C# typeof operator—this operator returns a reference to the specified type's type descriptor. In Visual Basic .NET, the same functionality is exposed via the GetType operator.

Why the Great Idea isn't So Great

As you can see, the idea of having a synchronization data structure logically associated with every object in the heap sounds like a great idea. But, in real life, this is actually a terrible idea. Let me explain why. Remember the unmanaged C++ code shown at the beginning of this column? If you were coding this yourself, would you ever mark the CRITICAL_SECTION field with public access? Of course not—that would be ridiculous! Making this field public allows any code in the application to manipulate the CRITICAL_SECTION structure. It would then become trivially simple for malicious code to deadlock any threads that used instances of this type.

Well, guess what—the SyncBlock is just like a public synchronization data structure associated with every object in the heap! A reference to any object at all can be passed to Monitor's Enter and Exit methods at any time by any piece of code. In fact, a reference to any type descriptor can be passed to Monitor's methods too.

The code in Figure 7 demonstrates how horrible this situation can be. Here, Main constructs an App object and then enters this object's lock. At some point, a garbage collection occurs (in this code, the garbage collection is forced), and when App's Finalize method gets called, it attempts to lock the object. But, the CLR's Finalize thread can't acquire the lock because the application's primary thread owns the lock. This causes the common language runtime's Finalizer thread to stop—no more objects (in the process which can include multiple AppDomains) can get finalized and no more finalizable objects will ever have their memory reclaimed from within the managed heap!

Figure 7 Threads Banging Heads (Close Figure 7)
using System;
using System.Threading;

class App {
   static void Main() {
      // Construct an instance of the App object
      App a = new App();

      // This malicious code enters a lock on
      // the object but never exits the lock
      Monitor.Enter(a);

      // For demonstration purposes, let's release the
      // root to this object and force a garbage collection
      a = null;
      GC.Collect();

      // For demonstration purposes, wait until all Finalize
      // methods have completed their execution - deadlock!
      GC.WaitForPendingFinalizers();

      // We never get to the line of code below!
      Console.WriteLine("Leaving Main");
   }

   // This is the App type's Finalize method
   ~App() {
      // For demonstration purposes, have the CLR's
      // Finalizer thread attempt to lock the object.
      // NOTE: Since the Main thread owns the lock,
      // the Finalizer thread is deadlocked!
      lock (this) {
         // Pretend to do something in here...
      }
   }
}

Fortunately, there is a solution to this problem. However, it means you must ignore the Microsoft design and recommendations. Instead, you must define a private System.Object field as a member of your type, construct the object, and then use the C# lock or Visual Basic .NET SyncLock statement passing in a reference to the private object. Figure 8 shows how to rewrite the Transaction class so that the object used for synchronization is private to the class object. Likewise, Figure 9 shows how to rewrite the Transaction class where all the members are static.

Figure 9 Transaction with Static Members (Close Figure 9)
class Transaction {

   // Private, static Object field
   // used purely for synchronization
   private static Object objLock = new Object();

   // Private field holding the time of
   // the last transaction performed
   private static DateTime timeOfLastTransaction;

   public static void PerformTransaction() {
      lock (objLock) {
         // Perform the transaction...

         // Record time of the most recent transaction
         timeOfLastTransaction = DateTime.Now;
      }
   }

   // Public read-only property returning
   // the time of the last transaction
   public static DateTime LastTransaction {
      get {
         lock (objLock) {
            // Return the time of the last transaction
            return timeOfLastTransaction;
         }
      }
   }
}
Figure 8 Transaction with Private Object (Close Figure 8)
class Transaction {

   // Private Object field used
   // purely for synchronization
   private Object objLock = new Object();

   // Private field holding the time of
   // the last transaction performed
   private DateTime timeOfLastTransaction;

   public void PerformTransaction() {
      lock (objLock) {
         // Perform the transaction...

         // Record time of the most recent transaction
         timeOfLastTransaction = DateTime.Now;
      }
   }

   // Public read-only property returning
   // the time of the last transaction
   public DateTime LastTransaction {
      get {
         lock (objLock) {
            // Return the time of the last transaction
            return timeOfLastTransaction;
         }
      }
   }
}

It seems odd to have to construct a System.Object object just for synchronization with the Monitor class. When you get right down to it, I feel that Microsoft designed the Monitor class improperly. It should have been designed so that you construct an instance of the Monitor type for each type that you intend to synchronize. Then, the static methods should have been instance methods that don't require the use of the System.Object parameter. This would have solved all these problems and would have substantially simplified the programming model for developers.

By the way, if you create complex types with many fields, your methods and properties may need to lock only a subset of the object's fields at any time. You can always lock specific fields by passing the specific field object to lock or pass to Monitor.Enter. Of course, I would only consider doing this if the fields are private (which I always recommend). If you have several fields that you want to lock together, you can either use one of the fields as the one you always pass to lock or Enter. Or, you can construct a System.Object object that you use for the sole purpose of locking a field set. The more finely grained your locking, the better performance and scalability your code will achieve.

Unboxed Instances of Value Types

Before I wrap up this column, I'd like to point out a synchronization bug that took me several hours to track down the first time I ran into it. This code snippet demonstrates the problem:

class AnotherType {

   // An unboxed Boolean value type
   private Boolean flag = false;

   public Boolean Flag {
      set {
         Monitor.Enter(flag); // Boxes flag and locks the object
         flag = value;        // The actual value is unprotected
         Monitor.Exit(flag);  // Boxes flag, attempts to unlock
                              // the object
      }
   }
}
You might be surprised to learn that in this code, no thread synchronization occurs! The reason is that flag is an unboxed value type, not a reference type. Instances of unboxed value types do not have the two overhead fields, MethodTablePointer and SyncBlockIndex. This means that an unboxed value type instance can't have a SyncBlock associated with it.

Monitor's Enter and Exit methods require a reference to an object on the heap. When C#, Visual Basic .NET, and many other compilers see code that is trying to pass an unboxed value type instance to a method that requires an object reference, they automatically generate code to box the instance. The boxed instance will have a MethodTablePointer and a SyncBlockIndex so the boxed version can be used for thread synchronization. However, a new boxed instance is created each time a method is called and therefore different objects are being locked and unlocked.

For example, in the last code snippet, when the Flag property's set property accessor method is called, it calls Monitor's Enter method. Enter requires a reference type and so flag is boxed and the pointer to the boxed version is passed to Enter. This boxed object's SyncBlock is now owned by the calling thread. If another thread were to access this property now, then flag would be boxed again making a new object with its own SyncBlock. In addition, the calls to Exit also box the passed value type.

As I said, it took me several hours to discover this problem. If you want to synchronize access to an unboxed value type instance, then you must allocate a System.Object object and use it for synchronization. The code in Figure 10 is the corrected code.

Figure 10 Now There's Synchronization (Close Figure 10)
class AnotherType {

   // An unboxed Boolean value type
   private Boolean flag = false;

   // A private Object field used to
   // synchronize access to the flag field
   private Object flagLock = new Object();

   public Boolean Flag {
      set {
         Monitor.Enter(flagLock);
         flag = value;
         Monitor.Exit(flagLock);
      }
   }
}

By the way, if you use the C# lock statement instead of calling Monitor's Enter and Exit methods directly, then the C# compiler will protect you from accidentally trying to lock a value type. When you pass an unboxed value type instance to the lock statement, the C# compiler produces an error. For example, if you try to pass a Boolean (bool in C#) to the lock statement, the following error is produced: error CS0185: 'bool' is not a reference type as required by the lock statement. The Visual Basic .NET compiler also reports the following error if you attempt to use an unboxed value type instance with its SyncLock statement: error BC30582: 'SyncLock' operand cannot be of type 'Boolean' because 'Boolean' is not a reference type.

Send your questions and comments for Jeff to dot-net@microsoft.com.

Jeffrey Richteris a cofounder of Wintellect (http://www.Wintellect.com), a training, debugging, and consulting firm specializing in .NET and Windows Technologies. He is the author of Applied Microsoft .NET Framework Programming (Microsoft Press, 2002) and several programming books on Windows.


C++ Q&amp;A: Progress Indicator in the Status Bar, International UI Terms

C++ Q&A
Progress Indicator in the Status Bar, International UI Terms
Paul DiLascia

Code download available at: CQA0301.exe (67KB)

Q I'm developing a C++/MFC app that sometimes has to load a large file and I'd like to display a progress indicator like the one in the standard progress control. I saw another app that did this in the status bar, which I thought was cool. How can I add a progress bar to my status bar? I tried various ways, but none of them worked.

Martin Issacson

A I'm not sure exactly what you tried, but I was able to build a little demo app with a progress control in the status bar using a fairly straightforward approach. The idea is to create the progress control as a child window of your status bar, then either hide or show it as needed to display progress.

PBAR is a normal MFC doc/view app that uses an edit view to display text files. When you open a file, PBAR simulates a lengthy load operation and displays its progress in the status bar (see Figure 1). I encapsulated the status-bar-with-progress-control in a new class, CProgStatusBar (see Figure 2), which I'll describe so you can use it in your own apps.

Figure 2 Status Bar with Progress Control (Close Figure 2)

ProgBar.h
////////////////////////////////////////////////////////////////
// MSDN Magazine — January 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP.
// Tab size=3.
//
#pragma once

//////////////////
// Status bar with progress control.
//
class CProgStatusBar : public CStatusBar {
public:
   CProgStatusBar();
   virtual ~CProgStatusBar();
   CProgressCtrl& GetProgressCtrl() {
      return m_wndProgBar;
   }
   void OnProgress(UINT pct);

protected:
   CProgressCtrl m_wndProgBar;          // the progress bar
   afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
   afx_msg void OnSize(UINT nType, int cx, int cy);
   DECLARE_MESSAGE_MAP()
   DECLARE_DYNAMIC(CProgStatusBar)
};
ProgBar.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — January 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP.
// Tab size=3.
//
#include "StdAfx.h"
#include "ProgBar.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

IMPLEMENT_DYNAMIC(CProgStatusBar, CStatusBar)
BEGIN_MESSAGE_MAP(CProgStatusBar, CStatusBar)
   ON_WM_CREATE()
   ON_WM_SIZE()
END_MESSAGE_MAP()

CProgStatusBar::CProgStatusBar()
{
}

CProgStatusBar::~CProgStatusBar()
{
}

/////////////////
// Status bar created: create progress bar too.
//
int CProgStatusBar::OnCreate(LPCREATESTRUCT lpcs)
{
   lpcs->style |= WS_CLIPCHILDREN;
   VERIFY(CStatusBar::OnCreate(lpcs)==0);
   VERIFY(m_wndProgBar.Create(WS_CHILD, CRect(), this, 1));
   m_wndProgBar.SetRange(0,100);
   return 0;
}

//////////////////
// Status bar was sized: adjust size of progress bar to same as first
// pane (ready message). Note that the progress bar may or may not be
// visible.
//
void CProgStatusBar::OnSize(UINT nType, int cx, int cy)
{
   CStatusBar::OnSize(nType, cx, cy); // call base class
   CRect rc;                          // rectangle
   GetItemRect(0, &rc);               // item 0 = first pane, "ready"
                                      // message
   m_wndProgBar.MoveWindow(&rc,FALSE);// move progress bar
}

//////////////////
// Set progress bar position. pct is an integer from 0 to 100:
//
//  0 = hide progress bar and display ready message (done);
// >0 = (assumed 0-100) set progress bar position
//
// You should call this from your main frame to update progress.
// (See Mainfrm.cpp)
//
void CProgStatusBar::OnProgress(UINT pct)
{
   CProgressCtrl& pc = m_wndProgBar;
   DWORD dwOldStyle = pc.GetStyle();
   DWORD dwNewStyle = dwOldStyle;
   if (pct>0)
      // positive progress: show prog bar
      dwNewStyle |= WS_VISIBLE;
   else
      // prog <= 0: hide prog bar
      dwNewStyle &= ~WS_VISIBLE;

   if (dwNewStyle != dwOldStyle) {
      // change state of hide/show
      SetWindowText(NULL);                               // clear old text
      SetWindowLong(pc.m_hWnd, GWL_STYLE, dwNewStyle);   // change style
   }

   // set progress bar position
   pc.SetPos(pct);
   if (pct==0)
      // display MFC idle (ready) message.
      GetParent()->PostMessage(WM_SETMESSAGESTRING, AFX_IDS_IDLEMESSAGE);
}
Figure 1 Showing Progress in the Status Bar
Figure 1 Showing Progress in the Status Bar

CProgStatusBar is derived from CStatusBar, the standard MFC status bar control. CProgStatusBar adds a progress control (CProgressCtrl) as a data member m_wndProgBar, and three methods of significance: OnCreate, OnSize, and OnProgress.

OnCreate, which receives control when the status bar is first created, creates and initializes the progress bar as a child window:

int CProgStatusBar::OnCreate(LPCREATESTRUCT lpcs)
{
   lpcs->style |= WS_CLIPCHILDREN;
   VERIFY(CStatusBar::OnCreate(lpcs)==0);
   VERIFY(m_wndProgBar.Create(WS_CHILD, CRect(), this, 1));
   m_wndProgBar.SetRange(0,100);
   return 0;
}
OnCreate adds WS_CLIPCHILDREN to the status bar style; this reduces screen flicker by telling Windows® not to paint areas of the status bar that lie under child windows. OnCreate then creates the progress control and sets its range to the interval [0, 100]. Note that I did not use WS_VISIBLE to create the progress bar because I want it to be hidden initially.

Whenever you add a child window to any window, you're responsible for resizing the child when the parent is resized. The place to do it, as usual, is in the parent window's WM_SIZE/OnSize handler:

// status bar was resized
void CProgStatusBar::OnSize(...)
{
   CStatusBar::OnSize(...);
   CRect rc;
   GetItemRect(0, &rc);
   m_wndProgBar.MoveWindow(&rc,FALSE);
}
CProgStatusBar::OnSize moves the progress bar to the desired position: in this case, the same position as the first status bar pane, the one used for the ready message and command prompts. Note that MoveWindow moves the window whether it's hidden or visible—so this code works even when the progress control is hidden.

So much for sizing. How do you actually show progress? CProgStatusBar::OnProgress is the method to call. Figure 2 shows the source. OnProgress takes a single UINT in the range 0 to 100, the percent progress made, with 0 meaning no pro-gress at all and 100 meaning it's done. If the progress is greater than zero, OnProgress shows the progress control and sets the bar position; if the progress is zero, OnProgress hides the progress control.

While it's often expedient to place a child control on top of an area that the parent window can paint, you always do so at the risk of paint problems. In particular, if you hide and show the progress bar as described, you'll discover a couple of glitches. For example, if there's any text already in the status bar's message pane, it will show through the progress indicator blocks, as shown in Figure 3. That's because the progress control assumes its back-ground is clear and paints only the colored portion of the progress bar. The easiest way to fix this is to call CStatusBar::SetWindowText(NULL) to clear the text before showing the progress control. (For a status bar, SetWindowText sets the text in pane zero—the first pane.) Figure 2 shows the details.

Figure 3 Paint Problem
Figure 3 Paint Problem

A similar glitch appears going in the opposite direction: namely, when you call OnProgress(0) to clear the progress. In that case, CProgStatusBar::OnProgress hides the progress bar. But now what about the "ready" message? When your app isn't doing anything, MFC displays the resource string AFX_IDS_IDLEMESSAGE in your status bar. (By default, this string is "Ready", but you can change it to anything you like in your RC file.) Personally, I've always thought the ready message is a little dopey—but if there's any time you'd like to inform the user that your app is now "ready," it would be after an operation so potentially lengthy that it requires a progress indicator. So how do you make MFC display the ready message in the status bar? Easy:

// in CProgStatusBar::OnProgress
// WM_SETMESSAGESTRING defined in <afxpriv.h>
GetParent()->PostMessage(WM_SETMESSAGESTRING,
  AFX_IDS_IDLEMESSAGE);
If you like, you can create a different ID and message like ID_DONE_LOADING for "Done Loading", and use them instead.

CProgStatusBar implements the status bar with progress control. How do you use it in your app? All you have to do is use CProgStatusBar instead of CStatusBar, then call CProgStatusBar::OnProgress whenever you want to show the progress. Figure 4 shows the details (to save space, only the relevant sections are shown—full source is available, as always, at the link at the top of this article). Creating the CProgStatusBar is the easy part—it works the same as for a vanilla status bar—but when and where do you call OnProgress?

Figure 4 Implementing the Progress Control Bar (Close Figure 4)

MainFrm.h
////////////////////////////////////////////////////////////////
// MSDN Magazine — January 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP.
// Tab size=3.
//
#include "ProgBar.h"

/////////////////
// Main frame window has menu manager, flatbar, status bar
//
class CMainFrame : public CFrameWnd {
public:
   CMainFrame();
   ~CMainFrame();

protected:
   CProgStatusBar m_wndStatusBar; // progress control status bar
   CToolBar       m_wndToolBar;   // if using toolbar

   virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
   afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
   afx_msg LRESULT OnProgress(WPARAM wp, LPARAM lp);
   DECLARE_MESSAGE_MAP()
   DECLARE_DYNCREATE(CMainFrame)
};
MainFrm.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — January 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP.
// Tab size=3.
//
#include "StdAfx.h"
#include "MainFrm.h"
#include "View.h"
#include "resource.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

static UINT indicators[] = {
   ID_SEPARATOR,           // status line indicator
   ID_INDICATOR_CAPS,
   ID_INDICATOR_NUM,
   ID_INDICATOR_SCRL,
};

////////////////////////////////////////////////////////////////
// CMainFrame
//
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
   ON_WM_CREATE()
   ON_MESSAGE(MYWM_PROGRESS,OnProgress)
END_MESSAGE_MAP()

CMainFrame::CMainFrame()
{
}

CMainFrame::~CMainFrame()
{
}

////////////////
// Override flicker-free drawing with no CS_VREDRAW and CS_HREDRAW. This
// has nothing to do with coolbars, but it's a good thing to do.
//
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
   cs.lpszClass = AfxRegisterWndClass(
      0,                                // no redraw
      NULL,                             // no cursor (use default)
      NULL,                             // no background brush
      AfxGetApp()->LoadIcon(IDR_MAINFRAME)); // app icon
   ASSERT(cs.lpszClass);
   return CFrameWnd::PreCreateWindow(cs);
}

//////////////////
// Create handler creates control bars.
//
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   VERIFY(CFrameWnd::OnCreate(lpCreateStruct) == 0);

   VERIFY(m_wndToolBar.Create(this));
   VERIFY(m_wndToolBar.LoadToolBar(IDR_MAINFRAME));
   m_wndToolBar.ModifyStyle(0, TBSTYLE_FLAT);

   // Create status bar and set indicators
   VERIFY(m_wndStatusBar.Create(this));
   VERIFY(m_wndStatusBar.SetIndicators(indicators,
      sizeof(indicators)/sizeof(UINT)));

   return 0;
}

///////////////////
// Handle progress message—any object can call send one.
//
LRESULT CMainFrame::OnProgress(WPARAM wp, LPARAM lp)
{
   m_wndStatusBar.OnProgress(wp); // pass to prog/status bar
   return 0;
}
Doc.h
////////////////////////////////////////////////////////////////
// MSDN Magazine — January 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP.
// Tab size=3.
//
class CMyDoc : public CDocument {
public:
   virtual void Serialize(CArchive& ar);

•••// standard doc class stuff not shown

};
Doc.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — January 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP.
// Tab size=3.
//
#include "StdAfx.h"
#include "Doc.h"
#include "resource.h"

••• // standard doc class stuff not shown

//////////////////
// Serialize function simulates slow-loading doc
//
void CMyDoc::Serialize(CArchive& ar)
{
   CWnd* pFrame = AfxGetMainWnd();
   if (!ar.IsStoring()) {
      // loading: simulate length operation by sleeping, but wake up
      // every 150 msec to notify frame of progress.
      //
      for (int pct=10; pct<=100; pct+=10) {
         Sleep(150);
         if (pFrame)
            pFrame->SendMessage(MYWM_PROGRESS, pct);
      }
   }
   if (pFrame)
      pFrame->SendMessage(MYWM_PROGRESS, 0);

   ((CEditView*)m_viewList.GetHead())->SerializeRaw(ar);
}

The exact approach will depend on your application. However, I suggest you follow an approach similar to the one I implemented in PBAR. Rather than exposing CProgStatusBar::OnProgress directly, PBAR's main window implements a special message, called MYWM_PROGRESS, that passes its WPARAM to CProgStatusBar::OnProgress.

// MYWM_PROGRESS defined in resource.h
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
   ON_MESSAGE(MYWM_PROGRESS,OnProgress)
END_MESSAGE_MAP()

// handle MYWM_PROGRESS
LRESULT CMainFrame::OnProgress(WPARAM wp, LPARAM lp)
{
   m_wndStatusBar.OnProgress(wp);
   return 0;
}

Now any object that wants to report progress can do so by sending a message to the main frame instead of calling the status bar directly. For example, in PBAR, the document's Serialize function simulates a lengthy load process by sleeping for 1.5 seconds but waking up every 150 milliseconds to report its progress (see Figure 4). Usually, you want low-level objects like documents to contain as little UI code as possible. Ideally, your document class should be able to operate with no UI (because you might someday want to use it in a service or command-line program)—though in practice few programmers observe this restriction religiously. In any case, it's always better to send a message to the frame than to expose the frame's internal members. To be safe, the document's Serialize function checks for the existence of the frame before sending it a message.

If you don't like sending window messages from your document, you could use the MFC view update mechanism instead. You could invent a "hint" code and a small structure to hold the progress/percent complete and call CDocument::UpdateAllViews with this information. Your view class's OnUpdate handler would then process this hint information by sending MYWM_PROGRESS to the frame. This is the cleanest way to propagate progress information from the document to view/frame.

CProgStatusBar assumes that you'll always report progress as an integer from 0 to 100 and also that you want the broken-look progress bar. If you want to change these settings, you can modify CProgStatusBar or call CProgStatusBar::GetProgressCtrl to access the progress control directly.

Q In your article ".NET GUI Bliss" in the November 2002 issue of MSDN Magazine, you described how to build a system that uses XML to code the UI. I really like this idea, but I want to go global. Is there some book that will tell me the foreign words for common terms and menu items like File, View, Edit, and so on?

Mark Desoura

A At first I was going to say: get a dictionary. But then I did a little poking around and found a resource that may be just what you want. If you look on the MSDN Library CD, under the section "User Interface Design and Development," there's a book called The Windows User Experience (also published by Microsoft® Press in hard copy) which has a reference at the end called "International Word List." This wonderful resource lets you select a language from a dropdown combobox to get a dictionary of common user interface terms in that language (see Figure 5). Amazing! From the word list, you can learn that Cancel in Czech is Storno, Minimize in Hungarian is Kis méret, and when your friend from Finland says helppokäyttötoiminnot, she means accessibility. Naturally, there's no substitute for a human translator, and dictionaries alone can't help you write help—but if you have a relatively simple application and all you want to do is translate your menus and dialogs, the International Word List is a start. The International Word List is really only intended to provide a standard lexicon for Windows-centric terms like Favorites, dialog box, and active window, not a substitute for human translators. Nor is the list completely up-to-date with the ever-changing Windows world. For example, you can find the Polish for the old Windows 98 phrase Network Neighborhood, but not the newfangled My Network Places found in Windows XP. Hasta Luego!

Figure 5 Translate User Interface Terms (Close Figure 5)

Send your questions and comments for Paul to cppqa@microsoft.com.

Paul DiLasciais a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at askpd@pobox.com or http://www.dilascia.com.


Resource File: Patterns &amp; Practices and the Developer Roadmap

Resource File
Patterns & Practices and the Developer Roadmap

Contents
Patterns & Practices
The Developer Roadmap
New from Microsoft Press

This page offers information on the most current Microsoft developer resources. This month we highlight a site for architectural guidance, the Developer Roadmap, and additional resources related to the topics covered in this issue of MSDN Magazine.

Patterns & Practices

If you are looking for technical architectural guidance, check out Microsoft patterns & practices. This site offers design practice guidelines for specific software needs utilizing multiple Microsoft products within the .NET Framework. Rather than offering finished wizards and solutions, the site provides patterns and principles, presenting a solid foundation from which you can then create your own solutions. Patterns & practices offers infrastructure guidance to design, build, and operate integrated solutions. Developers will be able to mix and match design patterns to suit their particular needs. Along with development solutions, the site also provides design patterns for software deployment.

The technical guides are divided into three categories: Reference Architecture, Reference Building Blocks, and Operational Practices.

Patterns & practices plans to use regular interviews with customers to determine their needs and provide design solutions to resolve them. Eventually customers will be able to submit their own design patterns to the site to share with others.

Plans are underway for a series of books to be published by Microsoft Press in conjunction with the patterns & practices group. The first book, on the subject of helping to protect the security of ASP.NET applications, is scheduled to be released in February.

The Developer Roadmap

In the latest .NET Show, Robert Hess interviews Thomas O'Grady and Kerry Carnahan about the .NET Developer Career Skills Roadmap. The Roadmap was designed to help enterprise developers discover the best ways to properly acquire the skills they need for developing applications in .NET. This tool provides a clear mapping of real-world career paths for developers, along with courseware for the most comprehensive .NET training. To learn more, see http://msdn.microsoft.com/theshow.

New from Microsoft Press

Debugging Applications for Microsoft .NET and Microsoft Windows by John Robbins (February 2003) covers debugging techniques for every .NET application scenario imaginable, including instructions for building your own .NET debugger.

Performance Testing Microsoft .NET Web Applications by the Microsoft ACE Team (October 2002) will teach you how to boost the performance of your Microsoft .NET-based applications. Written by the Microsoft team that tests hundreds of Web sites and Web-based apps, this book will help you take advantage of the best tools available for performance testing.

Additional Resources

For additional information on the topics covered in this issue, see the following:

.NET Framework General Reference: Performance Counters

Performance Considerations for Run-Time Technologies in the .NET Framework

Microsoft Platform SDK Documentation: Windows GDI

Visual Studio: Testing

Tools for Capacity Planning

Microsoft SQL Server 2000 Windows CE Edition 2.0

Developing Custom Windows Controls Using Visual Basic .NET