Starting with version 4, the CLR supports In-Process Side-by-Side (InProc SxS).
The topic is extensively treated. In particular, this blog post does a very good job in explaining why CLR InProc Sxs is useful, and which scenarios it addresses.
However I could not find any sample code that causes multiple CLRs to be loaded into a process. I needed such a scenario in order to figure out how to do debugging with Windbg and Sos when multiple CLRs are loaded into a process.
So, the first step for me was to create a solution that causes multiple runtimes to be loaded into a process. The following step will then be to do managed debugging in such a process. This is the topic of a post to come, however . Let’s get started and create the solution then.
Note: there isn’t a 1:1 mapping between the .Net Framework version and the CLR version, because different versions of the .Net Framework may use the same version of the CLR. Also, the name of the CLR dll has changed over different versions. All this is summarized in the following table:
and will be useful in the rest of this post. Only the CLR version is relevant for our purposes, so the “Target Framework” in Visual Studio projects will be just a means to actually set the CLR version. In this respect, multiple options are possible: for instance, .Net Framework 2.0, .Net Framework 3.0 and .Net Framework 3.5 all set the CLR version to 2.0. In the samples, we’ll be using the last .Net Framework version that uses a given CLR version (.Net Framework 3.5 for CLR 2.0, .Net Framework 4.5 for CLR 4.0) at the time of this writing.
Each assembly can reference one CLR, so obviously it’s not possible to load multiple CLRs in one process writing one assembly only,
It is tempting to think, however, that by having different assemblies in one process, each referencing a different version of the CLR, we end up loading multiple CLRs. Let’s try this way then.
Follow these steps:
Even before you build this solution, you’ll see this icon on the added reference:
Building gives an error that is pretty self-explanatory:
3: 2>------ Build started: Project: ConsoleApplication1, Configuration: Debug Any CPU ------
4: 2>C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1578,5): warning MSB3274: The primary reference "D:\CLR2CLR4\ClassLibrary1\bin\Debug\ClassLibrary1.dll" could not be resolved because it was built against the ".NETFramework,Version=v4.5" framework. This is a higher version than the currently targeted framework ".NETFramework,Version=v3.5".
5: 2>C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1578,5): warning MSB3258: The primary reference "D:\CLR2CLR4\ClassLibrary1\bin\Debug\ClassLibrary1.dll" could not be resolved because it has an indirect dependency on the .NET Framework assembly "mscorlib, Version=18.104.22.168, Culture=neutral, PublicKeyToken=b77a5c561934e089" which has a higher version "22.214.171.124" than the version "126.96.36.199" in the current target framework.
6: 2>D:\SR\CLR2CLR4\ConsoleApplication1\Program.cs(5,7,5,20): error CS0246: The type or namespace name 'ClassLibrary1' could not be found (are you missing a using directive or an assembly reference?)
At startup, CLR2 would be loaded, and the reference to an assembly targeting the CLR4 would force that assembly to be run in a previous version of the CLR. This is not supported.
Follow these steps (you can reuse the previous solution)
If you run the solution, everything works fine. A closer inspection, however, shows that only CLR4 is loaded into the process (you can attach a debugger to the process, or use Sysinternals’ Process Explroer, and check that clr.dll is loaded into the process, but mscorwks.dll is not).
These two tests have one thing in common: directly referencing types between different versions of the CLR does not cause multiple CLRs to be loaded. This is logical, because a direct reference between types implies that they live in the same AppDomain. Having different CLRs loaded, however, means that these CLRs live side-by-side in the same process, separate from each other. This means that every CLR has its own Garbage Collector, JIT Compiler, set of AppDomains, and in general all the supporting structures are separate. The obvious consequence is that two .Net objects referencing each other cannot live in different CLRs.
This excellent post has valuable information on how multiple CLRs can be loaded into a process (see the section “Our Solution Is Not…”):
To take advantage of in-proc SxS, a managed component must be activated by a native host and interact with its environment through a native interop layer such as COM interop and P/Invoke.
So we come up with the idea of a managed application targeting a version of the CLR, and a managed assembly, exposed through COM Interoperability, targeting a different version of the CLR. Follow these steps (you can reuse the previous solution):
The last step fails and displays a message box with this content:
2: Microsoft Visual Studio
4: A reference to 'ClassLibrary3' could not be added.
6: The ActiveX type library 'D:\CLR2CLR4\ClassLibrary3\bin\Debug\bin\Debug\ClassLibrary3.tlb' was exported from a .NET assembly and cannot be added as a reference.
8: Add a reference to the .NET assembly instead.
Basically, this means that it is not possible to use managed code from managed code through COM Interop.
Due to the previous limitation, we have to introduce a native intermediary in the chain of calls.
Follow these steps (you can reuse the previous solution):
Build and run the solution (startup project: ConsoleApplication3). Everything works fine and, if you look at the dlls loaded into the process (again, you can either attach a debugger or use Project Explorer), you’ll see that both mscorwks.dll (CLR2) and clr.dll (CLR4) are loaded into the project.
The final Visual Studio 2012 solution, including 7 projects, is attached to this post. Note that only 3 projects (ConsoleApplication3, ClassLibrary3 and ATLProject1) are needed to have multiple CLRs loaded in a process. The other projects (ConsoleApplication1, ClassLibrary1, ConsoleApplication2, ClassLibrary2) exemplify the non-working approaches.
Note: You would get the same effect (CLR2 and CLR4 loaded) by reversing the CLR versions in the projects. In other words, if ConsoleApplication3 had Target Framework version 4.5, and ClassLibrary3 had Target Framework version 3.5, the end result would still be to have CLR2 and CLR4 loaded in the process, only in the reverse order (CLR4 first, then CLR2).
In the next post, we’ll use this solution to show how managed debugging works with CLR InProc SxS. Stay tuned!