In-Proc SxS and Migration Quick Start

In-Proc SxS and Migration Quick Start

  • Comments 1
 

This post is meant to help you understand what runtime in-process side-by-side is, how to think about it, how to use it, and how it affects application and component migration to the .NET 4 Runtime. This post is relevant to you if you use native runtime activation APIs, depend on specific runtime activation behaviors, or use mixed mode assemblies built with Visual Studio Managed Extensions for C++ v8 or v9, or if you’re just interested in how we handle pre-.NET 4 and .NET 4 code coexisting on a machine.

The problem

Prior to the .NET 4 Runtime, any given process was limited to loading only one runtime version, and was bound to that runtime for remainder of that process lifetime. In environments where independently authored components built and tested against different .NET Runtime versions can be loaded into a single process, this sometimes caused compatibility problems for the components that did not target the runtime that was loaded into the process.

Our Solution

A new feature in the .NET 4 Runtime, “in-process runtime side-by-side” (or “in-proc SxS” for short) describes the ability to load more than one .NET Runtime version into the same process and have them run “side by side”. This ability to load multiple runtimes into the same process gives us with the ability to provide the highest level of compatibility possible for environments in which multiple independent managed components are loaded by, and communicate with, a native layer. COM is the biggest example of a native layer through which managed code must communicate, and this means that in an environment in which multiple independently-authored managed COM components are activated, each may be loaded into the runtime for which it was built and tested, which maximizes compatibility. The upcoming release of Visual Studio Tools for Office (VSTO) will take advantage of this by enabling all managed office addins to be loaded within their targeted runtime.

Our Solution Is Not…

In-proc SxS does not affect managed assembly loading, such as Assembly.Load or loads due to assembly references – these scenarios continue to load the target assembly in the runtime where the load request was made. 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.

This may be easier to understand if you consider that that two runtimes loaded into a process operate completely independently; i.e., runtime X has no more knowledge about runtime Y than it does about any other native DLL loaded into a process. Each runtime has its own GC; each runtime creates, owns, and manages its own Application Domains (never the other way around), including Shared and Default domains for each; and each runtime interacts with unmanaged DLLs through interop layers.

How to Think About In-Proc SxS

Thinking about multiple runtimes in the same process without additional context isn’t all that helpful; in fact, it just leads to more questions: “How and when does this happen?”; and most importantly, “Does this affect me?”

Does In-Proc SxS Affect Me?

In-proc SxS was designed so that existing applications, components and libraries should be completely unaffected by an installation of the .NET 4 Framework - applications that were running against a pre-v4 framework will remain blissfully unaware of the install.

In addition, migrating a product to .NET 4 is unlikely to be affected by in-proc SxS if:

·         The product is a pure managed application;

·         The product is a pure managed library; or

·         The product is a pure managed COM component.

However, a product may be affected by in-proc SxS during migration if:

·         The product makes use of one or more deprecated APIs; or

·         The product contains, or takes a dependency on, a mixed mode assembly built with Visual Studio Managed Extensions for C++ version 8 or 9.

 

See the migration section below for more information.

Runtime Activation

For the small number of you who may be affected by in-proc SxS and need to gain a better understanding of it, the easiest way to understand how and where in-proc SxS fits into the managed world is to think about it as a fundamental change to our runtime activation model. Runtime activation describes the process by which the most appropriate runtime version is found, located, and loaded. A runtime request consists of a set of inputs, which can include (among others) an assembly (often the application’s EXE), an application configuration file (often found next to the application’s EXE), a version string, or any combination thereof, and runtime activation uses these inputs to determine the most suitable match from among the set of installed runtimes. Common examples are: managed EXE launch, CoCreateInstanceInstance of COM-visible managed types, and mixed-mode assemblies loaded from native code.

Old Activation Model

In prior releases, when in-proc SxS did not exist, our runtime activation policy had to take into consideration the reality that only one runtime could ever be loaded into a process. For self-contained managed applications the obvious choice was invariably the right one: activate the runtime that the application targets (derived either from metadata in the managed executable itself or from an application configuration file if one exists). And since virtually all managed application installers out there list the application’s target runtime as a pre-requisite, the application could count on the target runtime being installed. Unfortunately, runtime activation was not always as clear cut in other important scenarios, the most prevalent of which is the activation of managed COM components. In such scenarios, the chosen policy has always been to activate in the latest installed runtime when no runtime has yet been loaded – a policy we call “bind to latest.”

Consider the following example to help understand why this policy was chosen for managed COM class activation. Let’s say that a native application X exposes an extensibility mechanism through COM, and two authors separately write managed extensions A and B that target .NET Frameworks v1.1 and v2.0 respectively, and that application X and both extensions are installed on machine Z with both .NET Frameworks v1.1 and v2.0 installed.

Say that, in the course of normal execution, application X CoCreateInstances component A. In this case it may be tempting to use the same activation policy as that of managed executables and pick the runtime that the component targets (v1.1). However, this choice would cause a later attempt to CoCreateInstance component B to fail, because the .NET Runtime v1.1 (to which the process has been locked) cannot load a v2.0 assembly.

To avoid this problem, the policy for managed COM component activation has always been to activate and load the component into the latest installed runtime, if a runtime has not yet been loaded. However, this policy created an impossibly high compatibility burden: we would need to provide 100% release-to-release compatibility, and to do so we would need to test every possible usage scenario for every managed application ever built on any previous .NET Runtime! So, while the entire .NET team will always set very high release-to-release compatibility requirements, we can all sleep a bit easier knowing that in-process side-by-side enables us to provide a much better compatibility model for our customers, insulating them from the small but inevitable accidental breaking changes that occur in a major product release.

New Activation Model

I’m sure you’ve guessed the fundamental change to our activation policy by now: starting with version 4, the .NET Runtime activation policy may now load the most appropriate runtime version for a given activation request, without regard for the set of runtime version(s) that may already be loaded into the process. The activation policy will no longer target the latest runtime on the machine as it did in previous versions (more details on this below). Again, there are some restrictions with when dealing with pre-v4 runtimes, but the above statement will hold true, without restriction, for all .NET Runtimes version 4 and higher.

Using the previous example, but with the small change that component A has been updated to target .NET 4, runtime activation policy is now free to activate component A in the v4 runtime and later activate component B in the v2 runtime. This provides the greatest compatibility by allowing components to run within their target runtime, without regard for the process environment into which it is loaded.

Now that we’ve covered the basics of the new in-proc SxS feature and activation model, let’s look at the necessary considerations when migrating an application, component or library to .NET 4 from an earlier runtime.

Migration

In the vast majority of cases it is simple to migrate an application to run on .NET Framework 4: if you are migrating an application at the source level, import your solution into Visual Studio 2010 and choose “.NET Framework 4” for the Target Framework in each project’s properties page, then recompile; if you are migrating an application without recompiling, create or update the application configuration file to target .NET 4.For example, the following is the recommended <startup> section to use when migrating an application that targets .NET 3.5 to also target .NET 4:

<?xml version ="1.0"?>

<configuration>

<startup>

<supportedRuntime version="v4.0"/>

<supportedRuntime version="v2.0.50727"/>

</startup>

</configuration>

Note that starting with .NET Framework 4, only a two-part version string is required, though three-part version strings are still accepted.

A migration decision tree is provided later in this section.

Limitations and Exceptions

When transitioning code originally targeting an in-proc-SxS-unaware runtime to an in-proc-SxS-aware runtime, some additional steps will need to be taken if your application contains one or more of the following:

·         calls to any of the hosting global static functions, from native and/or managed code;

·         calls to any of the Strong Naming Global Static Functions, from native and/or managed code;

·         native code that calls CoCreateInstance using any of the hosting CoClasses (CLSID_CorRuntimeHost, CLSID_CLRRuntimeHost, CLSID_TypeNameFactory, or ComCallUnmarshal), or is managed code that uses COM interop to access interfaces implemented by any of the same CLSIDs;

·         calls to CoCreateInstance, from native code, using any of the metadata CoClasses (CLSID_CorMetaDataDispenser, or CLSID_CorMetaDataDispenserRuntime), or managed code that uses COM interop to access interfaces implemented by any of the same CLSIDs;

·         calls to CoCreateInstance, from native code, using the CoClass of any COM-registered .NET Framework type;

·         a managed COM component (in the binary migration case only);

·         an assembly produced with the Visual Studio Managed Extensions for C++ compiler (otherwise known as a managed C++ assembly), or a dependency, either direct or indirect, on such an assembly.

Figure 1

The functionality in the above list was created for prior releases, and each lacks the ability to specify the target framework that is needed for in-proc SxS, and could not be retrofitted to do so. For the most part, all of these chose the same “bind to latest” activation policy as managed COM component activation.

Non-Impactful Install and “Latest Runtime” Activation Model

Why am I even talking about our “bind to latest” activation policy? Well, there’s a very good reason: one of the primary compatibility-related goals of the .NET 4 Framework is to ensure that its installation is non-impactful to pre-existing managed application or component. To achieve this, we have had to modify the “bind to latest” activation policy’s semantics in the .NET 4 release.

The “bind to latest” runtime activation model kicks in when no target runtime version information is provided with the activation request, such as through a managed assembly’s built-in version, a configuration file’s list of <supportedRuntime> entries, and/or an explicit version string passed into any of the native hosting APIs that accept them. In these cases, in previous versions of the runtime, “bind to latest” activation would pick the very latest .NET Runtime installed on the machine. This is also the activation model used for managed COM classes built against pre-v4 runtimes, for precisely the reasons stated in the earlier example.

Starting with the .NET 4 Runtime, this “bind to latest” activation policy has been modified so that, by default, it will only consider pre-v4 runtimes. By making this the default behavior, all pre-existing applications and components using this activation model will be unaffected by the installation of .NET 4.

Source Migration

If you intend to retarget your application or component to .NET 4 and rebuild, and it uses any of the functionality listed above, then you need to migrate to the equivalent functionality provided in the new .NET 4 Framework Hosting interfaces:

·         for calls to any of the hosting global static functions, use the corresponding methods provided by the new ICLRMetaHost, ICLRMetaHostPolicy and ICLRRuntimeInfo interfaces;

·         for calls to any of the Strong Naming Global Static Functions, use the corresponding methods provided by the new ICLRStrongName interface;

·         for instance creation of any of the hosting CoClasses, metadata CoClasses, or COM-visible Framework types, use ICLRRuntimeInfo::GetInterface.

Figure 2

Each ICLRRuntimeInfo and ICLRStrongName interface instance targets a specific runtime, and each method exposes functionality specific to, or provided by, that targeted runtime.

Binary Migration

If the preferred option of source migration and re-compilation is not possible, and the application or component also depends on any of the functionalities listed above, an application configuration extension is available to make migration possible in many cases. Adding this extension to the application’s configuration file will modify the “bind to latest” policy to cause it to bind to the same runtime as the application itself. The extension takes the form of an attribute named useLegacyV2RuntimeActivationPolicy, to be placed in the <startup> element of an application configuration file. The attribute’s valid values are “true” and “false”, of which “false” is the default. When set to “true”, this attribute instructs “bind to latest” runtime activation policy to consider all <supportedRuntime> entries in the application configuration file; when set to false, the policy considers only those <supportedRuntime> entries with a major version number of 2 or less. When a configuration file does not contain any <supportedRuntime> entries (or a deprecated <requiredRuntime> entry), then the version number contained in the executable image, which represents the runtime version against which the executable was built, is treated as an implicit <supportedRuntime> entry (roughly speaking).

Thus, if you were migrating a .NET 3.5 application to .NET 4, your configuration file might look like this:

<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0"/>
</startup>
</configuration>

Figure 3

Managed COM Component

To accommodate managed COM component authors (who in most cases cannot modify application configuration files), a separate mechanism is available: a semicolon delimited list of supported runtimes may be added to the component’s InprocServer32 registration. Specifically, add the string value “SupportedRuntimeVersions” to “HKCR\CLSID\<COM component clsid>\InprocServer32\<COM component version>”.

It is important to note that if a managed COM component depends on any of the items in Figure 1, then it must be migrated at the source level. This is because the COM component would need functionality equivalent to the new useLegacyV2RuntimeActivationPolicy configuration extension to work, which is a process-wide policy decision that must be made by the application author rather than by extensibility components.

If a managed COM component is activated through a reg-free manifest (which cannot be extended to contain a SupportedRuntimes entry), runtime activation will look for a configuration file next to the assembly containing the COM visible class. Note that a useLegacyV2RuntimeActivationPolicy entry will be ignored in this scenario.

Assemblies Built with Managed Extensions for Visual C++ Compiler

There are two additional limitations with managed C++ assemblies.

First, any dependency on a mixed-mode assembly built with a previous release of Visual C++ must be migrated using the useLegacyV2RuntimeActivationPolicy configuration file extension. This is because all such assemblies depend implicitly on the managed CRT, which in turn contains dependencies on functionality listed in Figure 1.This dependency has been removed in the upcoming release.

Second, all mixed-mode assemblies (including those built using this release of Visual C++) and all assemblies built with a previous release of Visual C++ may be loaded into at most one runtime per process, because they may contain process-global static image data that cannot be virtualized across runtimes, and/or because they depend on a prior release of the managed CRT. This limitation is virtually identical in nature to the current limitation restricting these types of assemblies to no more than one application domain at once.

Migration Decision Tree

Use the following decision tree to decide the correct migration path for your component in the most common scenarios.

decision_tree

Binary Migration Examples

Application: Target v2 and v4

Create an application configuration file with the following contents, where the highlighted portion is required only if the application depends on functionality from Figure 1:

<configuration>

<startup useLegacyV2RuntimeActivationPolicy="true">

<supportedRuntime version="v4.0"/>

<supportedRuntime version="v2.0.50727"/>

</startup>

</configuration>

 

As always, supportedRuntime entries are considered to be listed in decreasing preference; if the .NET 2 Runtime is preferred, the entries should be reversed.

Application: Target v4 Only

Use the same application configuration file as above, but remove the v2-specific supportedRuntime entry.

Managed COM Component: Target v2 and v4

If you authored a managed COM component built against the .NET 2 Runtime, with a CLSID of {DFEEABC2-17FF-4ce6-B25B-A35F7A156C68} and a version of 6.2.0.0, and you wanted the component to run within either the .NET 2 Runtime or the .NET 4 Runtime (in that order of preference), the registry update (in RegEdit syntax) would be:

[HKCR\CLSID\{DFEEABC2-17FF-4ce6-B25B-A35F7A156C68}\InprocServer32\6.2.0.0]

"SupportedRuntimeVersions"="v2.0.50727;v4.0"

 

If your component was registered without the assembly version portion of the registry key name, you would remove “\6.2.0.0” from the key name.

Or, if your managed COM component is activated through registry-free COM, you would place an application configuration file (should we coin the term component configuration file?) next to the managed COM assembly with contents similar to the example above (without the highlighted portion, and with the supportedRuntime entries reversed).

Troubleshooting

Instantiating Managed COM Components From Managed Code

If your application instantiates and uses COM components through the .NET Runtime’s managed interop layer, and one of those COM components happens to be managed, then there is a small chance that after migrating your application you might run into an error similar to this:

Error: Unable to cast COM object of type 'System.__ComObject' to interface type 'X'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{12345678-1234-1234-1234-123456789012}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

 

This error can occur as a result of the following two combined conditions:

1.       The managed COM component is registered against a different .NET Runtime version from that of the instantiating caller, causing it to be activated in another runtime than that of the instantiating caller.

2.       The interface is not marked with System.Runtime.InteropServices.ComVisibleAttribute, with a value of true.

This scenario worked in the past because of a runtime optimization: when the .NET Runtime notices that a COM type it instantiates has a managed implementation, and that it was instantiated into the same Application Domain as the instantiating caller, it bypasses the managed interop layer and returns to the caller the managed object itself. In prior .NET Framework releases (where in-proc SxS was not available), all managed COM types and their managed clients were commonly loaded into the same Application Domain, allowing the implementing managed object to be returned, and in turn allowing the interface cast succeed without being marked as ComVisible.

Migrating an application can easily create condition 1; if the application also relies on condition 2 (which is much less common), then the above error will be encountered. There are a couple of solutions available:

1.       Get an updated version of the managed COM type that properly declares the interface as ComVisible.

2.       Update the application configuration file to use the useLegacyV2RuntimeActivationPolicy extension.

Loading Managed C++ Assemblies Built Against Previous .NET Runtime Versions

Attempting to load a managed C++ assembly that was built with an earlier compiler into the .NET 4 Runtime when the application has not been configured with the useLegacyV2RuntimeActivationPolicy configuration file extension will result in the following error:

System.IO.FileLoadException: Mixed mode assembly is built against version 'v2.0.50727' of the runtime and cannot be loaded in the 4.0 runtime without additional configuration information.

Note: the error is not 100% correct, as it specifies that only mixed mode assemblies are subject to this problem; it should say “Managed C++” instead of “Mixed mode”, as managed C++ assemblies compiled with /clr:pure will also encounter this error.

It is possible to correct this issue with either of the following:

1.       Recompile the assembly with the .NET 4 managed C++ compiler.

2.       Update the application to use the useLegacyV2RuntimeActivationPolicy configuration file extension.

Leave a Comment
  • Please add 3 and 3 and type the answer here:
  • Post
  • I'm using Wix with a ComPlus action that is calling cocreateinstance on System.EnterpriseServices. However, I want the cocreateinstance to invoke the v4 rather the default v2 instance. Reading the above post it sounds like I'd have to change the loading order of System.EnterpriseServices in the registry but changing the behaviour of a framework component sounds dangerous. Do I have a choice?

Page 1 of 1 (1 items)