Loading multiple CLR Runtimes (InProc SxS) – Sample Code


Introduction

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 Smile. 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
1.0 1.0 mscorwks, mscorsvr
1.1 1.1 mscorwks, mscorsvr
2.0 2.0 mscorwks
3.0 2.0  
3.5 2.0  
4.0 4.0 clr
4.5 4.0  

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:

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=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" which has a higher version "4.0.0.0" than the version "2.0.0.0" 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!

CLR2CLR4.zip

Comments (4)

  1. Sample failing with VS 2013 / .Net 4.5 says:

    Hello!

    I'm trying to use your approach outlined here to successfully migrate some code to .Net 4.5.1, while providing a bridge that lets some CLR2 clients (namely, some SSIS packages) call the code.  I downloaded the sample, but can't seem to get it to run.  When I run ConsoleApplication3 I get a 'Class Not Registered' error, as follows:

    Unhandled Exception: System.Runtime.InteropServices.COMException (0x80040154): C

    lass not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))

    As near as I can tell the class is registered correctly.  Any tips on resolving?  My apolgies for a noob question, but it's been ages since I had to deal with anything ATL or COM related and I think I suppressed most of those memories.

  2. Hello,

    based on past experience, I would think that REGDB_E_CLASSNOTREG can be trusted. If you are on a 64-bit OS, the first thing that I would check is the bitness (32/64) of the compiled COM dll, and the bitness of the .net process (Task Manager shows this information). They have to match, otherwise the COM dll can't be loaded into the process and you get REGDB_E_CLASSNOTREG because the COM runtime looks in the "wrong" view of the registry, where the COM component is not registered. You can easily tell in which view of the registry the component is registered by checking if the path of the key includes Wow6432Node

  3. Kreeg says:

    Thanks.  Got it figured out.  The issue ended up being that the CLR4 assembly being called wasn't in the bin folder for the Console application..

Skip to main content