Loading multiple CLR Runtimes (InProc SxS) – Sample Code
Starting with version 4, the CLR supports In-Process Side-by-Side (InProc SxS).
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:
|.Net Framework Version||CLR Version||CLR DLL|
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.
Easy approaches (and why they do not work)
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.
CLR2 referencing CLR4
Follow these steps:
- Create a new solution of type “Blank Solution” in Visual Studio 2012
- Add a console application (ConsoleApplication1), with Target Framework version 3.5
- Add a class library (ClassLibrary1), with Target Framework version 4.5
- Add a reference to ClassLibrary1 from ConsoleApplication1
- Reference a type in ClassLibrary1 from ConsoleApplication1
Even before you build this solution, you’ll see this icon on the added reference:
Building gives an error that is pretty self-explanatory:
1: 2: 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.
CLR4 referencing CLR2
Follow these steps (you can reuse the previous solution)
- Add a console application (ConsoleApplication2), with Target Framework version 4.5
- Add a class library (ClassLibrary2), with Target Framework version 3.5
- Add a reference to ClassLibrary2 from ConsoleApplication2
- Reference a type in ClassLibrary2 from ConsoleApplication2. Note that this time the reference is added without any warning
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.
A more serious approach (and why it still does not work)
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):
- Add a console application (ConsoleApplication3), with Target Framework version 3.5
- Add a class library (ClassLibrary3), with Target Framework version 4.5
- In ClassLibrary3, expose a .Net class through COM Interop. This requires using the ComVisible attribute and registering the assembly with COM through RegAsm.
- Build ClassLibrary3. Upon successful build, the regasm step creates the Type Library for use with COM Interop
- Add a COM Reference in ConsoleApplication3 to the Type Library generated by building ClassLibrary3
The last step fails and displays a message box with this content:
1: --------------------------- 2: Microsoft Visual Studio 3: --------------------------- 4: A reference to 'ClassLibrary3' could not be added. 5: 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. 7: 8: Add a reference to the .NET assembly instead. 9: --------------------------- 10: OK 11: ---------------------------
Basically, this means that it is not possible to use managed code from managed code through COM Interop.
The final solution
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):
- Add an ATL Project (ATLProject1) to the solution
- Add an ATL Simple Object to ATLProject1. This object just forwards calls to the object in ClassLibrary3 exposed through COM Interop
- Build ATLProject1
- Add a COM Reference in ConsoleApplication3 to the Type Library generated by building ATLProject1
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!