Throughout the Whidbey product cycle, we’ve tried hard to make sure that the new version of the Compact Framework we’re building is capable of running the applications you’ve built with version 1.0 unchanged. That is, our goal is to make sure you can run your existing applications on Whidbey without recompiling them or making any source code changes whatsoever. However, regardless of how much attention we pay to compatibility, it is virtually impossible to avoid making breaking changes between major releases of a platform. As software evolves, new features are added, bugs are fixed, security vulnerabilities are identified or existing designs are re-done such that breaking changes are inevitable. History has shown this trend time and time again.
In addition to maintaining compatibility with our own previous releases, the .Net CF team has an additional dimension to compatibility based on its relationship to the full .Net Framework. In particular, because .Net CF strives to be a “strict subset” of the full .Net Framework, and because one of our design goals is to allow all .Net CF applications to run against the full Framework, we must carefully consider the impact of any change we make that causes us to deviate from the full .Net Framework. In some cases the requirements to both maintain backwards compatibility with our current releases and the desire to remain compatible with the desktop are in conflict.
As the Whidbey product cycle nears completion we’ve stepped up our efforts to do our best to ensure that existing applications will run on Whidbey. In this post, I’ll describe the infrastructure we’ve built, and the processes we’re using to make sure we maintain the highest degree of compatibility that is practical. To a large degree, the amount of compatibility we can provide is directly related to the number of existing applications we can run on Whidbey before it ships. To that end, please download Beta2 of Whidbey when it becomes available (or one of the Community Technology Previews) and run your existing applications unchanged and without a recompile and report any problems you find (more on this later).
In terms of compatibility, there are two primary scenarios we’re concerned with:
1. Applications that consist entirely of assemblies built with .Net CF 1.0.
2. Applications that will be built with .Net CF Whidbey, but that use some assemblies built with .Net CF 1.0. This scenario would occur, for example, if you build a Whidbey application that uses some third party library or control built against .Net CF 1.0
The primary mechanism we’re providing to ensure your existing applications run on Whidbey is something we’ve been calling version coercion internally. In addition, we do offer the ability to install both .Net CF 1.0 and Whidbey side-by-side as an extreme fallback mechanism. The rest of this post describes these two techniques and how they relate. First, though, let’s take a brief look at what I mean by “breaking change”.
Strictly speaking, a breaking change is any change made to .Net CF that would adversely affect an application built against version 1.0 when running on Whidbey. In my mind, breaking changes fall into two categories: 1) changes to the public API surface (which are easy to find), 2) other changes that affect how an application runs (which are harder to find).
Examples of breaking changes made to the public API surface include changing an existing method signature, removing a method from a type, making a public type internal, throwing a different exception that we used to and so on. These changes are easy to find – we have a set of Reflection-based tools we use internally to ensure that the set of API’s we expose don’t change between builds of .Net CF (other than adding new APIs, which is a compatible change). These tools also make sure that our API set doesn’t diverge from that of the full .Net Framework.
Finding the “other changes” is much harder. In many cases we find these only through diligence on the part of the developer making the change, through extensive internal testing or through customer bug reports filed during the Betas. Examples of these sorts of breaking changes include:
· Changes in the order in which events are fired
· Changes to default form or control sizes, colors or other user-visible behavior
· Bug fixes that result in more (or less) user code getting called than in V1.0 (like finalizers getting run when they didn’t use to)
· Changes to the way input parameters are parsed, and which inputs are considered valid
· Changes in how assembly version numbers are matches.
· The effect of unhandled exceptions on the application domain or process
We’re currently in the process of documenting every breaking change we know about between version 1.0 and Whidbey. When we’re done, we’ll make this list publicly available to give you a sense for the types of changes we’ve made that are considered breaking. By making this list public we hope both to keep you better informed, and to solicit your help in determining which changes are likely to have the biggest impact on existing applications.
Now that we’ve seen what breaking changes are, let’s take a look at what we’re doing to make sure that these changes don’t break existing applications. This is where version coercion comes in.
Given the list of known breaking changes, and the knowledge of which version of .Net CF was used to build the application we’re running, we can decide whether the application should get the “1.0 behavior” or the “Whidbey behavior” for each breaking change. The process of determining which behavior the application should see is what we’ve termed version coercion. Similar systems have been in place for years in other products, including Windows.
Version coercion is very simple. There are essentially two pieces:
1. Determine which version of .Net was used to build the application we’re about to run. Every .Net assembly contains the version number of the CLR that was used by the compiler when the assembly was built. We extract this information from the application’s main executable at startup and store it in the process.
2. At each point in the code where we have a known breaking change, we look at which version was used to build the application. If the application was built with 1.0, we “revert” to the 1.0 behavior for the breaking change. If the application was built with Whidbey, we provide the new behavior.
Let’s take a look at a simple example to see how this works. One of the breaking changes we’ve made in Whidbey is the order in which a form’s OnLoad event is called relative to the time the form is made visible. In version 1.0, the OnLoad event was called before the form was made visible while in Whidbey OnLoad is called after the form is made visible. This can affect applications that do drawing or load other forms during the OnLoad event. While running an application we’ll look to see if it was built against .Net CF 1.0. If so, we’ll fire the OnLoad event before making the form visible. If the application was built with Whidbey, we’ll make the form visible first, then fire the OnLoad event.
The behavior I just described works great for the scenario in which the application’s executable and all its dependencies were built against .Net CF 1.0. However, recall there’s another scenario to consider – the case in which a Whidbey executable uses an assembly built with .Net CF 1.0. Because the executable was built with Whidbey, all assemblies running as part of that application will get Whidbey behavior for all breaking changes. This default behavior won’t work if one of the assemblies built with 1.0 is affected by a breaking change. What’s needed is a way to force .Net CF to give the application the 1.0 behavior for breaking changes even though the main executable was built against Whidbey. We accomplish this by allowing an entry to be made in the application’s configuration file specifying that 1.0 behavior is desired for all breaking changes. Here’s an example of such a configuration file:
<compatibilityversion major=“1” minor=”0” />
What about Side-by-Side?
If you’ve worked much with the full .Net Framework, you’re likely familiar with the concept of side-by-side - the ability to have multiple versions of the full .Net Framework installed on the same machine. One of the primary goals of side-by-side is to ensure that new versions of the full .Net Framework don’t break existing applications. There are two aspects of side-by-side that enable this goal:
1. If the version of the full .Net Framework used to build your application is installed on the machine, that version will be used, regardless of which other versions (including a newer one) are present.
2. If you chose to run your application with a different version than the one your application was built with, you can use a configuration file to redirect your application back to a different, more compatible version.
In Whidbey, .Net CF supports the concept of side-by-side as well. However, we view it as a feature that should only be needed in very rare circumstances. To begin with, side-by-side isn’t as practical on devices as it is on desktop machines for reasons related to size, ROM installs and so on. Also, the presence of version coercion in .Net CF largely alleviates the need for side-by-side if we do a good job of identifying all breaking changes and doing sufficient testing. Given the presence of version coercion, side-by-side should only be needed in the case where we’ve missed a breaking change that affects your application and you truly do need to revert to the exact version of .Net CF that was used to build your application.
“Promoting” Your Application to Whidbey
If the version of .Net CF that you used to build your application is present on the device, we’ll use that version even if a newer version is installed. This means that if you install Whidbey on a device that already has .Net CF 1.0, the applications you’ve built with 1.0 will continue to use that version – they are not promoted to Whidbey by default. To get an application you built with .Net CF 1.0 to run on Whidbey in this situation, you must use an application configuration file to specify which version of .Net CF should be used to run your application.
The only tricky part in authoring the configuration file is to determine the version number for the Whidbey build of .Net CF. You’ll need the major, minor and build portions of the version number for the CLR included with the build of Whidbey you are using. The easiest way to find this version number is to run cgacutil.exe from the \windows directory on your device. Clicking on cgacutil.exe in the shell will tell you which versions of .Net CF are installed on your device. When run on a device that has both version 1.0 and a recent build of Whidbey installed, cgacutil.exe will display the following string:
In this case, the major, minor and build numbers for Whidbey are 2, 0 and 4365 respectively. Authoring the configuration file is now easy – just use the <supportedRuntime> element to indicate that you’d like your application to run with Whidbey. Here’s a sample:
<?xml version="1.0" encoding="utf-8"?>
<supportedRuntime version="v2.0.4365" />
Testing, Testing and More Testing…
As described, a key to ensuring a high degree of compatibility between release of .Net CF is application compatibility testing. Internally, we try to get all applications we can and run regular tests runs against them to make sure we haven’t broken compatibility. The larger our library of applications, the greater coverage we can achieve. To that end, please download Beta2 when it becomes available (or any of the Community Tech Previews that are available now), run your V 1.0 application against it and report and problems to us through the MSDN Product Feedback Center (http://lab.msdn.microsoft.com/productfeedback/Default.aspx ), by posting to the .Net Compact Framework newsgroup dotnet.framework.compactframework(http://www.msdn.microsoft.com/newsgroups/default.aspx?dg=microsoft.public.dotnet.framework.compactframework&lang=en&cr=US ), or by commenting on this blog entry.
This posting is provided "AS IS" with no warranties, and confers no rights.