Post your questions and comments on the CLR Team Blog.
As we built the .NET Framework 4, one of the more difficult problems we faced was maintaining compatibility with previous releases while still adding new features and functionality. We followed strict processes requiring approval for any changes that might introduce compatibility issues—most were rejected—and ran a compatibility lab with hundreds of real applications to find any unintentional breaks.
But every time we fix a bug there’s the risk that some application depended on that wrong behavior. Sometimes applications take dependencies we have warned against, such as the behavior of private APIs or the description text of exceptions.
Since the .NET Framework was introduced, we’ve had a good solution for application compatibility: allow multiple versions of the .NET Framework to be installed on the same machine at the same time. This enables two different applications, built against two different versions and installed on one machine, to each run against the appropriate version.
This works fine when each application gets its own process, but add-ins are much more difficult problems. Imagine you are running a program such as Outlook that hosts COM add-ins, including managed COM add-ins, and you have two versions of the runtime—and add-ins built against each one—installed on your machine. Which runtime should you choose? Loading a newer add-in on an older runtime clearly is not going to work.
On the other hand, because of the high level of compatibility, an older add-in will usually run fine on a newer runtime. To give all add-ins the best chance of working, we always choose the latest runtime for managed COM activation. Even if you only have older add-ins installed, there is no way for us to know that when that add-in gets activated, so the latest runtime still gets loaded.
An unfortunate side effect of this activation policy is that when a user installs a new application with a new version of the runtime, completely unrelated applications that use managed COM add-ins, built against older versions, suddenly start running on a newer runtime and can fail.
For the .NET Framework 3.0 and 3.5, we solved this problem through an extremely strict policy: each release was additive and only added new
assemblies to the prior version with the same runtime underneath. This prevented any compatibility issues when installing them on a machine running the .NET Framework 2.0. This means that when you are running an app on the .NET Framework 3.5, you are really running it on the 2.0 runtime, with a few extra assemblies on top of it. However, it also means that we couldn’t innovate in the .NET 2.0 assemblies, which include key functionalities, such as the garbage collector, just in time (JIT) and base class libraries.
With the .NET Framework 4 we have implemented an approach that allows high compatibility, including never breaking existing add-ins,
and also lets us innovate in the core. We can now run both .NET 2.0 and .NET 4 add-ins in the same process, at the same time. We call this approach In-Process Side-by-Side, or In-Proc SxS.
While In-Proc SxS solves the most common compatibility issues, it doesn’t fix everything. In this column we’ll describe more about why we decided to build In-Proc SxS, how it works and which problems it doesn’t solve. For people writing normal applications or add-ins, In-Proc SxS mostly just works—the right things all happen automatically. For those of you who are writing hosts that can take advantage of In-Proc SxS, we’ll also describe the updated hosting APIs and provide some guidelines for using them.
Late in 2005 almost all high-level Microsoft executives were suddenly unable to check e-mail on any of their main machines. For no apparent reason, whenever they opened Outlook it would crash, restart and then crash again in a continuous loop. There were no recent updates to Outlook or anything else that seemingly could have been causing this. It was soon tracked down to a managed exception being thrown by a managed add-in. A friend of mine [“Mine” refers to column co-author Jesse Kaplan—Ed.] from the Visual Studio Tools for Office (VSTO) team—responsible for managed add-ins to Office—was sent to diagnose this problem on the machine of one of the most prominent victims of this bug: Ray Ozzie, who was chief technical officer at the time.
Once in Ray’s office, my friend was quickly able to determine that a beta version of the .NET Framework 2.0 had been deployed via an internal beta program, and he identified which Office add-in was causing the problem. As one of the compatibility PMs on the CLR team, I installed the add-in and took it from there.
We quickly determined what went wrong: the add-in had a race condition in which it started up nine different threads, and after starting each one it initialized the data the thread processed (see Figure 1). The coders got lucky with the timing, but once the .NET Framework 2.0 was installed, the add-in was automatically rolled forward to .NET 2.0, for the reasons I outlined above. But .NET 2.0 was slightly faster at starting threads, so the latent race condition started to surface consistently.
Figure 1 Code from the Office Add-In
Thread  threads = new Thread;
for (int i=0; i<9; i++)
Worker worker = new Worker();
threads[i] = new ThreadStart(worker.Work);
threads[i].Start(); //This line starts the thread executing
worker.identity =i; //This line initializes a value that
//the thread needs to run properly
This application failure drove home a hard lesson in compatibility: no matter how hard we try to avoid making behavior changes than can break applications, simple things such as a performance improvement can expose bugs in applications and add-ins that can cause them to fail when run against anything other than the runtime they were built and tested on. We realized that there was no way for us to evolve the platform in any meaningful way and ensure that applications like the one above can run perfectly on the latest version.
During our compatibility testing we came upon an application that ran fine if .NET 2.0 was installed after the application but failed if the application was installed on a machine that had both 1.1 (the version the application was built against) and 2.0. It took a while to figure out what was going on, but we tracked the problem down to a bit of code that was running inside the installer that was, again, floating forward to 2.0 and this time having problems finding the framework directory.
The detection logic was clearly fragile and actually wrong, as you can see here:
string sFrameworkVersion = System.Environment.Version.ToString();
string sWinFrameworkPath = session.Property["WindowsFolder"] +
sFrameworkVersion.Substring(0,8) + "\\";
But even after fixing that bug, the application still failed to execute properly after installing. Here’s the fix:
string sWinFrameworkPath = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
It turns out that the installer was looking for the framework directory in order to get a path to caspol.exe and give the app permission to run in that framework. It broke even after finding the path because it had just granted itself permission to run on the 2.0 CLR even though the application itself runs on the 1.1 CLR. Here’s the problem code:
System.Diagnostics.Process.Start(sWinFrameworkPath + "caspol.exe " + casPolArgs);
The core issue causing problems in all of these cases, as we came to understand, is that it was impossible to make any significant changes or additions to our platform and still ensure that the latest version could run any application as well as older versions did.
From the beginning, the .NET Framework tried to solve this problem by supporting side-by-side installations of multiple versions of the framework on a machine and having each application choose which version it wanted to run on.
Unfortunately the limitation of one runtime per process meant that for managed COM components and extensibility scenarios, where there were multiple independent apps running in the same process, there was no single choice that would work for every one. This limitation meant that some components were not going to get the runtime they wanted and that, regardless of how hard we tried to maintain compatibility, some percentage of them would break.
Our new ability to load multiple versions of the runtime in a process solves these problems.
To help you better understand some of the decisions we made and the detailed behavior we describe later in the column, it’s useful to discuss the guiding principles we held to while designing this feature.
All existing applications and add-ins should continue to run against the versions of the framework they were built and configured to run on and should not see the new version unless they specifically ask for it. This has always been the rule for managed applications, but now it also applies to managed COM add-ins and consumers of the runtime hosting APIs.
In addition to making sure applications run against the version of the runtime they were built with, we still need to make sure it is easy to transition to a newer runtime, so we have kept compatibility for the .NET Framework 4 as high as or higher than it was with .NET 2.0.
The .NET Framework 4 runtime—and all future runtimes—will be able to run in-process with one another. While we did not back-port this functionality to older runtimes (1.0 through 3.5), we did make sure that 4 and beyond will be able to run in-process with any single older runtime. In other words, you will be able to load 4, 5 and 2.0 in the same process, but you will not be able to load 1.1 and 2.0 in the same process. .NET Frameworks 2.0 through 3.5 all run on the 2.0 runtime and so have no conflicts with one another, as shown in Figure 2.
Figure 2 Will These Runtimes Load in the Same Process?
No existing applications or components should notice any difference when the .NET Framework 4 runtime is installed: they should continue to get whichever runtime they were built against. Applications and managed COM components built against .NET 4 will execute on the 4 runtime. Hosts that wish to interact with the 4 runtime will need to specifically request it.
End Users and System Administrators: You now can have confidence that when you install a new version of the runtime, either independently or with an application, it will have no impact on your machine, and all existing applications will continue to run as they did before.
Application Developers: In-Proc SxS has almost no impact on application developers. Applications have always defaulted to run against the version of the framework on which they were built and this has not changed. The only change in behavior we have made that impacts application developers is that we will no longer automatically run an application built against an older runtime on a newer version when the original version is not present. Instead we will prompt the user to download the original version and provide a link to make it easy to do so.
We still provide configuration options that allow you to indicate which versions of the framework you want your application to run against, so it is possible to run an older application on a newer runtime, but we won’t do it automatically.
Library Developers and Consumers: In-Proc SxS does not solve the compatibility problems faced by library developers. Any libraries directly loaded by an application—either via a direct reference or an Assembly.Load*—will continue to load directly into the runtime and AppDomain of the application loading it. This means that if an application is recompiled to run against the .NET Framework 4 runtime and still has dependent assemblies built against .NET 2.0, those dependents will load on the .NET 4 runtime as well. Therefore, we still recommend testing your libraries
against all version of the framework you wish to support. This is one of the reasons we have continued to maintain our high level of backward compatibility.
Managed COM Component Developers: In the past, these components would automatically run against the latest version of the runtime installed on the machine. Now, pre-.NET Framework 4 components will still get activated against the latest runtime (3.5 or earlier) and all newer components will be loaded against the version they were built on, as shown in Figure 3.
1.1, 3.5, 4.0, 5.0 installed
1.1, 4.0 loaded
3.5, 5.0 loaded
1.1, 3.5, 5.0 installed
*These components would fail to load in the past as well
**When you register components you can now specify a range of versions that they support and so you could configure this component to run against 5.0 or future runtimes if you have tested them.
Figure 3 Managed COM Components and Runtime Interoperability
Shell Extension Developers: Shell extension is a general name applied to a wide variety of extensibility points inside the Windows shell. Two common examples are extensions that add to the right-click context menu for files and folders and those that provide custom icons or icon overlays for files and folders.
These extensions are exposed via a standard COM model, and their defining characteristic is that they are loaded in-process with any application. It is this last bit, and the fact that only one CLR has been allowed per process, that caused problems for managed shell extensions. To elaborate:
These problems led us to officially recommend against—and not support—the development of in-process shell extensions using managed code. This was a painful choice for us and for our customers as you can see in this MSDN forum explaining the problem: http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/1428326d-7950-42b4-ad94-8e962124043e. Shell extensions are very popular and one of the last places where developers of certain types of applications are forced to write native code. Unfortunately, because of our limitation allowing only one runtime per process, we could not support them.
With the ability to have multiple runtimes in process with any other runtime, we can now offer general support for writing managed shell extensions—even those that run in-process with arbitrary applications on the machine. We still do not support writing shell extensions using any version earlier than .NET Framework 4 because those versions of the runtime do not load in-process with one another and will cause failures in many cases.
Developers of shell extensions, managed and native, still have to take special care and ensure that they are able to run in a wide variety of environments and work well with others. As we get closer to release to manufacturing (RTM), we will provide guidance and samples that will help you develop high-quality managed shell extensions that play well in the Windows eco-system.
Hosts of Managed Code: If you host managed code using native COM activation, you will not have to do anything special to work with multiple runtimes. You can simply activate components as you always did and the runtime will load them according to the rules listed in Figure 3.
If you’ve ever used any of our pre-.NET Framework 4 hosting APIs, you have probably noticed that they all assume that only one runtime will ever be loaded in the process. Therefore, if you host the runtime using our native APIs, you will need to modify your host to be In-Proc-SxS-aware. As part of our new approach of having multiple runtimes in a process, we have deprecated the old, single-runtime-aware hosting APIs and added a new set of hosting APIs designed to help you manage a multi-runtime environment. MSDN will have complete documentation for the new APIs, but they will be relatively easy to use if you have experience using the current ones.
One of the most interesting challenges we faced when developing In-Proc SxS was the question of how to update the behavior of the existing, single-runtime-aware hosting APIs. A range of options was available, but when following the principles laid out earlier in this column we were left with the following guideline: the APIs should behave such that when the .NET Framework 4 is installed on a machine, they return the exact behavior they did before. This means that they can only be aware of one runtime in each process and that even if you used them in a way that would have previously activated the latest runtime on the machine, they will only give you the latest runtime with a version earlier than 4.
There are still ways to “bind” these APIs to the .NET Framework 4 runtime by explicitly passing the 4 version number to them or configuring your application in a certain way, but, again, this will happen only if you specifically request the 4 runtime and not if you requested the “latest.”
In summary: code using the existing hosting APIs will continue to work when .NET Framework 4 is installed but will get a view of the process that sees only one runtime loaded. Also, to maintain this compatibility, they will usually be able to interact only with pre-4 versions. The details of which version is chosen for each of these older APIs will be available on MSDN, but the few rules above should help you understand how we determined these behaviors. If you want to interact with multiple runtimes, you will need to move to the new APIs.
C++/CLI Developers: C++/CLI, or managed C++, is an interesting technology that allows developers to mix both managed and native code in the same assembly and manage the transitions between the two largely without developer interaction.
Because of that architecture, there will be limits on how you use these assemblies in this new world. One of the fundamental problems is that if we allowed these assemblies to be loaded multiple times per process, we still would need to maintain isolation between both the managed and native data sections. That means loading both sections twice, which is not allowed by the native Windows loader. The full details of why we have the following restrictions are outside the scope of this column but will be available elsewhere as we get closer to RTM.
The basic restriction is that pre-.NET Framework 2.0-based C++/CLI assemblies can only load on the .NET 2.0 runtime. If you provide a 2.0 C++/CLI library and want it to be consumable from 4 and beyond, you need to recompile it with each version you want it to be loaded in. If you consume one of these libraries, you will either need to get an updated version from the library developer or, as a last resort, you can configure your application to block pre-4 runtimes from your process.
The Microsoft .NET Framework 4 is the most backward-compatible release of .NET yet. By bringing In-Proc SxS to the table, Microsoft guarantees that the simple action of installing .NET 4 will not break any existing application and that everything already installed on the machine will work as well as it did before.
End users will no longer have to worry that installing the framework—either directly or with an application that requires it—will break any of the applications already on the machine.
Enterprises and IT professionals can adopt new versions of the framework as quickly or as gradually as they wish without having to worry about different versions used by different applications conflicting with one another.
Developers can use the latest version of the framework to build their applications and will be able to reassure their customers that they will be safe to deploy.
Finally, hosts and add-in developers can be comfortable knowing that they will get the version of the framework they want, without impacting anyone else, so they can be confident their code will keep working even as new versions of the framework are installed.
Jesse Kaplan is the program manager of Managed/Native Interoperability for the CLR team at Microsoft. His past responsibilities include compatibility and extensibility.
Luiz Santos, formerly part of the CLR team, is a program manager in the SQL Connectivity team, where he is responsible for the ADO.NET Managed providers, including SqlClient, ODBCClient and OLEDBClient.
Thanks to the following technical experts for reviewing this article:Joshua Goodman, Simon Hall and Sean Selitrennikoff
More MSDN Magazine Blog entries >
Browse All MSDN Magazines
Subscribe to MSDN Flash newsletter
Receive the MSDN Flash e-mail newsletter every other week, with news and information personalized to your interests and areas of focus.