Chapter 9. Domain-Neutral Assemblies

In Chapter 5, I discuss how application domains act as subprocesses by providing an isolation boundary around a logical application running in an operating system process. To guarantee this isolation, the CLR must make sure that all assemblies are scoped to a particular application domain. That is, all assemblies are loaded within the context of an application domain, and, once loaded, an assembly is not visible to code running in a different application domain. However, I’ve shown that applications that create multiple application domains often end up loading many of the same assemblies into each domain. For example, it’s often the case that many of the Microsoft .NET Framework libraries are loaded into every application domain created by an extensible application. Also, the host runtime assembly that contains the application’s application domain manager must be loaded into every application domain. In scenarios such as these, it’s unfortunate to load multiple copies of the same assembly into the same process, especially because much of the runtime information the CLR requires to run a statically compiled assembly could, in theory, be shared among all the application domains in the process. Runtime data such as the data structures the CLR uses to represent the assembly’s classes, the code the CLR jit compiles while executing the assembly, and so on generally don’t vary based on the application domain in which the assembly is loaded.

Domain-neutral code is a feature of the CLR that enables runtime data about an assembly to be shared among multiple application domains. The primary goal of domain-neutral code is to reduce the overall memory consumed by applications that use multiple application domains. This chapter describes how domain-neutral code affects extensible applications. I describe how domain-neutral code affects an application’s architecture and how to enable domain-neutral code using the APIs available in the .NET Framework and the CLR hosting interfaces. As you’ll see, however, you’ll want to understand some consequences of domain-neutral code before enabling the feature.

Domain-Neutral Assembly Architecture

As stated, the goal of domain-neutral code is to reduce the overall working set used by a process. To get a general idea of how this goal is accomplished, consider the scenario discussed earlier in which an extensible application loads several of the same .NET Framework assemblies and its host runtime assembly into every application domain. In this situation, only the add-in assemblies are specific to a particular application domain, as shown in Figure 9-1.

Extensible applications often load many of the same assemblies into each application domain.

Figure 9-1. Extensible applications often load many of the same assemblies into each application domain.

As you can see, separate copies of all the "common" assemblies are loaded into every application domain. This approach results in significant duplication of the same runtime data. For example, each application domain has a copy of the native code that is compiled as the assembly is executed. Clearly, this could be optimized in scenarios in which this code is the same across all application domains. To reduce this duplication, the runtime data for an assembly is shared across all application domains if that assembly is loaded domain neutral. In Figure 9-2, you can see that only a single copy of the .NET Framework assemblies and the host runtime assembly are loaded into the process. The runtime data, including the jit-compiled native code and the CLR data structures used to represent the assembly in memory, is shared among all the application domains in the process.

Domain-neutral assemblies are shared among all the application domains in a process.

Figure 9-2. Domain-neutral assemblies are shared among all the application domains in a process.

In many ways, this sharing of runtime data is analogous to the way the operating system shares static code pages for DLLs that are loaded by multiple processes.

It’s important to remember that only the runtime data created by the CLR, such as the native code for the assembly, is shared between application domains, not the instances of the types created by the assembly itself. For example, say an assembly contains a method that creates a data structure that maintains a relationship between customer identifiers and instances of Customer objects. Clearly, sharing data such as this across multiple application domains would violate application domain isolation.

Implications of Using Domain-Neutral Code

At this point, domain-neutral code sounds like a panacea. After all, with all the working set savings, why aren’t all assemblies loaded domain neutral? Not surprisingly, you should consider some implications of using domain-neutral code when deciding which (if any) assemblies to load domain neutral. These implications, which follow, are primarily related to performance, security, and flexibility:

  • Domain-neutral assemblies cannot be unloaded from a process.

  • Access to static member variables is slower.

  • Initialization of types is slower in some scenarios.

  • The set of security permissions granted to a domain-neutral assembly must be the same for every application domain.

  • If an assembly is loaded domain neutral, all of its dependencies must be as well.

These points are described in more detail in the following sections.

Domain-Neutral Code Cannot Be Unloaded

Because the jit-compiled code and other CLR runtime data for a domain-neutral assembly is used by all application domains within a process, that assembly cannot be unloaded without shutting down the entire process. In most scenarios, the inability to unload a domain-neutral assembly is the biggest downside to using domain-neutral code. The fact that a domain-neutral assembly cannot be unloaded is the primary reason why the add-in assemblies in extensible applications aren’t typically loaded domain neutral. Instead, most extensible applications load only the .NET Framework assemblies and the assembly containing the application domain manager as domain neutral. These assemblies are used in every application domain anyway, so the inability to unload them doesn’t affect the overall design of the application.

Access to Static Member Variables Is Slower

To preserve the isolation properties of application domains, each domain must have a copy of the assembly’s static member variables. Otherwise, a change made to a static variable in one application domain would be visible in all other application domains. Because each application domain has its own copy of static variables, the CLR must maintain a lookup table that maps the application domain in which the static variable is accessed to the correct copy of the variable. Calling through this level of indirection is clearly slower than accessing the variable directly.

Initialization of Types Can Be Slower

When an assembly is loaded domain neutral, any aspect of its execution that might produce different results in different application domains must be factored out and isolated per domain. Access to static variables is an excellent example of this, as described in the previous section. Type initializers (also known as class constructors or static constructors) are another example. The CLR runs a type’s initializer in every application domain.

The CLR supports two different semantics for type initializers: precise and relaxed. The semantics for precise type initialization require that the initializer is run before the first access to any of the type’s static or instance fields, methods, or properties. In contrast, types that use relaxed initialization are only guaranteed to be initialized before the first access to one of the type’s static fields. Implementing precise initialization requires more runtime checks and more levels of indirection to maintain application domain isolation for domain-neutral assemblies, so the initialization of types requiring precise semantics will be slower if an assembly is loaded domain neutral. Even so, it’s unlikely that this difference in performance will have a substantial impact on your application unless you rely on type initializers to do significant amounts of work.

Note

Note

The choice of whether a type requires precise or relaxed initialization semantics is up to the language compiler you are using. In general, if a type initializer does nothing but initialize static variables, the compiler can emit a type initializer with relaxed semantics (beforefieldinit in MSIL). However, if a type initializer does more than just initialize static variables, precise semantics is required.

Security Policy Must Be Consistent Across All Application Domains

As I describe in Chapter 10, an application domain might define security policy that affects the set of security permissions granted to assemblies loaded in the domain. When an assembly is loaded domain neutral, the set of security permissions granted to the assembly must be the same for all application domains in the process. Ideally, the CLR would proactively check to make sure the grant set is consistent across application domains and fail to load the assembly if an inconsistency is found. Unfortunately, this is not the case. Instead, differences in the set of granted permissions show up as runtime errors, so it’s best to design your security policy at the same time you’re thinking about which assemblies to load domain neutral. In Chapter 10, I discuss the details of how to define security policy, including how to make sure your domainneutral assemblies are granted the same set of permissions in all domains.

The Set of Domain-Neutral Assemblies Must Form a Closure

The set of assemblies loaded domain neutral must form a complete closure. That is, all assemblies referenced by a domain-neutral assembly must also be loaded domain neutral. In theory, the CLR could enforce this restriction when an assembly is loaded by proactively locating all of its dependencies and checking whether you’ve specified they should be loaded domain neutral as well. However, the performance impact of proactively checking an assembly’s references in this fashion is prohibitive. Instead, the CLR enforces that the set of domain-neutral assemblies forms a closure at run time by checking each assembly as it is referenced. If a domain-neutral assembly makes a reference to an assembly that is not in the set of domainneutral assemblies, the CLR throws a FileLoadException, so it’s up to you to make sure that the assemblies you load domain neutral form a closed set. For this reason, it’s generally best to use domain-neutral loading only on assemblies that you can statically analyze ahead of time—that is, the set of assemblies you know you’ll load into your application versus the set of add-in assemblies that will be loaded into your application dynamically.

Domain-Neutral Code and Assembly Dependencies

The native code that the JIT compiler produces for an assembly can be shared across application domains only if that code is the same as the code that would be produced if the assembly were compiled in each application domain separately. An assembly’s references influence the native code that is generated because the JIT compiler must emit code to call from one assembly to another. The native code for a given assembly can be shared across application domains only if that assembly has exactly the same set of assembly references in every application domain. An assembly’s dependencies are statically recorded in metadata when the assembly is compiled. Those references will obviously be consistent in all application domains, but remember that an application domain might contain version policy that can cause a different version of a dependency to be loaded at run time, so any version policy that can affect the dependencies of a domain-neutral assembly must be the same in all application domains.

Each time an assembly you’ve specified as domain neutral is loaded into an application domain, the CLR checks to make sure that its set of dependencies is consistent with the other application domains already in the process. If an inconsistency is found, the native code for that assembly cannot be shared with the application domain in which the assembly is loaded. Instead, the CLR will generate a new copy of the native code specifically for that application domain. For example, consider a scenario in which you’ve specified that an assembly named Statistics should be loaded domain neutral. Statistics depends on an assembly called Probability. Statistics and version 5.0.0.0 of Probability have already been loaded by two application domains, as shown in Figure 9-3.

Native code can be shared only when an assembly’s dependencies are consistent.

Figure 9-3. Native code can be shared only when an assembly’s dependencies are consistent.

In this case, the native code for Statistics can be shared because Statistics references the same version of Probability in both application domains. (I’m assuming all of the other dependencies for Statistics are the same as well.) Now add another application domain to the scenario. This new application domain has version policy that redirects all references to Probability from version 5.0.0.0 to version 6.0.0.0. Because Statistics has a different set of dependencies in this new application domain, the native code that was previously generated in the process cannot be used. This situation is shown in Figure 9-4.

Native code cannot be shared if an assembly has a different set of dependencies.

Figure 9-4. Native code cannot be shared if an assembly has a different set of dependencies.

In Figure 9-4, you can see that a separate copy of the native code for Statistics has been created for use by the new application domain. As a result, you’ve lost the working set savings associated with sharing native code across application domains. However, there’s another downside to this situation as well. Because Statistics was specified to be domain neutral, it has all the properties of a domain-neutral assembly except the code sharing. That is, static variable access and type initialization will still be slower, security policy must be consistent with the other domains in the process, and so on. You can see in Figure 9-4 that even though the native code cannot be shared, a separate copy of the static variables of Statistics has been made in the new domain. In short, you’ve ended up with a worst-case scenario: not only have you lost the working set savings that you hoped to gain by loading Statistics domain neutral, but you are still left with the limitations associated with domain-neutral code.

Fortunately, you can avoid this situation by making sure that the version policy associated with the application domains in your process is consistent for those assemblies you wish to load domain neutral. The easiest way to ensure your version policy is consistent is to use an application domain manager to hook all calls to AppDomain.CreateDomain, as discussed in Chapter 6.

Specifying Which Assemblies Are Loaded Domain Neutral

You can use three sets of APIs to specify which assemblies you’d like to load domain neutral:

  • CorBindToRuntimeEx

  • The LoaderOptimization APIs

  • The CLR hosting interfaces

CorBindToRuntimeEx and the LoaderOptimization APIs were introduced in .NET Framework 1.0, whereas the hosting interfaces for working with domain-neutral assemblies are new to .NET Framework 2.0.

CorBindToRuntimeEx

CorBindToRuntimeEx is the function used to initialize the CLR in a process. The startupFlags parameter to CorBindToRuntimeEx is used to configure various aspects of the CLR, including which assemblies should be loaded domain neutral. The valid domain-neutral settings are given by the STARTUP_FLAGS enumeration from mscoree.idl:

typedef enum {
  // Other flags omitted...
  STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN = 0x1<<1,
  STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN = 0x2<<1,
  STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST = 0x3<<1,

  // Other flags omitted...
} STARTUP_FLAGS;

These flags offer very coarse options for controlling domain-neutral loading. Each flag is tailored for a specific scenario, as described in the following list:

  • STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN. The SINGLE_DOMAIN setting specifies that no assemblies are loaded domain neutral. As its name implies, this setting is geared toward applications that have only one application domain.

  • STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN. All assemblies are loaded domain neutral when you pass STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN to CorBindToRuntimeEx. This setting is best for those applications that always load the same set of assemblies into all application domains.

  • STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOSTThe MULTI_DOMAIN_HOST setting causes all assemblies with strong names to be loaded domain neutral. Assemblies with weak names are loaded into each application domain separately. This is the setting that .NET Framework 1.0 and .NET Framework 1.1 offer for extensible applications such as those I’ve been discussing throughout this book. This setting works great for extensible applications in which the host runtime has a strong name, but the add-in assemblies don’t (the .NET Framework assemblies all have strong names). Of course, it’s overly restrictive to assume that an add-in won’t have a strong name. To solve this problem, the CLR added support for specifying an exact list of assemblies to load domain neutral using the hosting interfaces. I discuss that approach later in this section.

Note

Note

Saying that "no assemblies are loaded domain neutral" when you specify STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN isn’t completely accurate. Regardless of the settings you specify, the CLR always loads mscorlib domain neutral. mscorlib is granted full trust by default security policy, and it doesn’t reference any other assemblies so it’s not subject to the security and version policy complications described here.

The following example uses CorBindToRuntimeEx to specify that all assemblies with strong names should be loaded domain neutral:

hr = CorBindToRuntimeEx(
  L"v2.0.41013",
  L"wks",
  STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST,
  CLSID_CLRRuntimeHost,
  IID_ICLRRuntimeHost,
  (PVOID*) &pCLR);

One advantage of specifying domain-neutral settings using the values from STARTUP_FLAGS is that you don’t have to worry about whether the set of assemblies you are supplying forms a full closure. Clearly, the set of no assemblies and the set of all assemblies form a closure. In addition, loading all assemblies with strong names domain neutral forms a closure, too, because an assembly with a strong name can only reference other assemblies with strong names—a reference from an assembly with a strong name to one with a weak name is not allowed.

The STARTUP_LOADER_OPTIMIZATION flags specify domain-neutral loading behavior for all application domains in the process. To specify different behavior per application domain, you must use the loader optimization API.

The Loader Optimization API

The loader optimization API is a managed API that enables you to set the same values for domain-neutral loading that CorBindToRuntimeEx does. The one difference is that the loader optimization API enables you to specify different values per application domain. The loader optimization API consists of the following components:

  • The System.LoaderOptimization enumeration

  • The System.LoaderOptimizationAttribute custom attribute

  • The LoaderOptimization property on System.AppDomainSetup

The LoaderOptimization enumeration defines the same values for managed code that the STARTUP_FLAGS enumeration does for unmanaged code, with the addition of a new value, NotSpecified:

public enum LoaderOptimization
{
    NotSpecified            = 0,
    SingleDomain            = 1,
    MultiDomain             = 2,
    MultiDomainHost         = 3
}

NotSpecified works the same as if you didn’t specify a value at all. Specifying NotSpecified at the process level defaults to SingleDomain. If specified at the application domain level, NotSpecified indicates that the process-wide settings should be used.

In terms of domain-neutral loading, the LoaderOptimizationAttribute is the managed equivalent to CorBindToRuntimeEx in that it enables you to specify the domain-neutral loading behavior for all application domains in the process. To specify the domain-neutral settings using the LoaderOptimizationAttribute, place the attribute on your application’s main method and pass a value from the LoaderOptimization enumeration to its constructor as shown in the following example:

[LoaderOptimization(LoaderOptimization.MultiDomainHost)]
static void Main(string[] args)
{
}

If you specify the LoaderOptimizationAttribute on a method other than main, the CLR simply ignores it.

Domain-neutral settings can also be specified on a per–application domain basis using the AppDomainSetup object passed to System.CreateDomain. AppDomainSetup has a property called LoaderOptimization that accepts a value from the System.LoaderOptimization enumeration. Note that it is possible to specify domain-neutral settings both at the process level (using the LoaderOptimizationAttribute or CorBindToRuntimeEx) and at the application domain level (using the LoaderOptimization property of AppDomainSetup). When domain-neutral settings are specified in both places, the value that is specific to the application domain takes precedence (unless the per-domain setting is NotSpecified, as described earlier). In other words, the process-wide settings are used only in those application domains for which a specific setting wasn’t provided.

Domain-Neutral Assemblies and the CLR Hosting Interfaces

In practice, the domain-neutral settings offered by CorBindToRuntimeEx and the loader optimization API were not granular enough for most extensible applications. In general, the most attractive option for extensible applications was to cause all assemblies with strong names to be loaded domain neutral. This worked great for the .NET Framework assembly and the application’s host runtime, but was suboptimal if an add-in with a strong name was loaded into the process. Such an add-in would be loaded as domain neutral, and therefore couldn’t be unloaded and was subject to the other restrictions described earlier in the chapter.

The CLR hosting interfaces in .NET Framework 2.0 solve this problem by letting CLR hosts supply an exact list of the assemblies to be loaded domain neutral. This list is supplied by implementing the GetDomainNeutralAssemblies method of IHostControl. GetDomainNeutralAssemblies returns the list of assemblies to load domain neutral in the form of a pointer to an ICLRAssemblyReferenceList interface, as shown in the following definition from mscoree.idl:

interface IHostControl : IUnknown
{
    // Other methods omitted...
    HRESULT GetDomainNeutralAssemblies(
        [out] ICLRAssemblyReferenceList **ppReferenceList);
}

Recall from Chapter 8 that ICLRReferenceAssemblyList pointers are obtained by passing an array of strings representing the assemblies in your list to the GetCLRAssemblyReferenceList method of ICLRAssemblyIdentityManager.

Although GetDomainNeutralAssemblies gives you the flexibility to load specific assemblies domain neutral, you must take on the burden of making sure the list you supply forms a complete closure. The only way to ensure you’re supplying a closed set is to use ildasm.exe or another tool to analyze each assembly’s dependencies statically. If the set of assemblies you supply doesn’t form a closure, you’ll see FileLoadExceptions at run time as discussed earlier.

The following sample provides an implementation of GetDomainNeutralAssemblies that returns a specific set of assemblies to load domain neutral. In this sample, I’ve taken the approach that most extensible applications use. Specifically, I’ve indicated that all of the .NET Framework assemblies and the assembly containing the host’s application domain manager (BoatRaceHostRuntime in this case) should be loaded domain neutral. This achieves the goal of sharing the jit-compiled code and CLR runtime data structures for the assemblies I expect to load into every application domain, while still allowing me to load strong-named add-ins into the process and unload them later.

const wchar_t *wszDomainNeutralAssemblies[] = {
    L"BoatRaceHostRuntime, PublicKeyToken=38c3b24e4a6ee45e",
    L"mscorlib, PublicKeyToken=b77a5c561934e089",
    L"System, PublicKeyToken=b77a5c561934e089",
    L"System.Xml, PublicKeyToken=b77a5c561934e089",
    L"System.Data, PublicKeyToken=b77a5c561934e089",
    L"System.Data.OracleClient, PublicKeyToken=b77a5c561934e089",
    L"System.Runtime.Remoting, PublicKeyToken=b77a5c561934e089",
    L"System.Windows.Forms, PublicKeyToken=b77a5c561934e089",
    L"System.Web, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.Drawing, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.Design, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.Runtime.Serialization.Formatters.Soap,
     PublicKeyToken=b03f5f7f11d50a3a",
    L"System.Drawing.Design, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.EnterpriseServices, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.DirectoryServices, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.Management, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.Messaging, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.Security, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.ServiceProcess, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.Web.Mobile, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.Web.RegularExpressions, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.Web.Services, PublicKeyToken=b03f5f7f11d50a3a",
    L"System.Configuration.Install, PublicKeyToken=b03f5f7f11d50a3a",
    L"Accessibility, PublicKeyToken=b03f5f7f11d50a3a",
    L"CustomMarshalers, PublicKeyToken=b03f5f7f11d50a3a",
    L"cscompmgd, PublicKeyToken=b03f5f7f11d50a3a",
    L"IEExecRemote, PublicKeyToken=b03f5f7f11d50a3a",
    L"IEHost, PublicKeyToken=b03f5f7f11d50a3a",
    L"IIEHost, PublicKeyToken=b03f5f7f11d50a3a",
    L"ISymWrapper, PublicKeyToken=b03f5f7f11d50a3a",
    L"Microsoft.JScript, PublicKeyToken=b03f5f7f11d50a3a",
    L"Microsoft.VisualBasic, PublicKeyToken=b03f5f7f11d50a3a",
    L"Microsoft.VisualBasic.Vsa, PublicKeyToken=b03f5f7f11d50a3a",
    L"Microsoft.VisualC, PublicKeyToken=b03f5f7f11d50a3a",
    L"Microsoft.Vsa, PublicKeyToken=b03f5f7f11d50a3a",
    L"Microsoft.Vsa.Vb.CodeDOMProcessor, PublicKeyToken=b03f5f7f11d50a3a",
    L"Microsoft_VsaVb, PublicKeyToken=b03f5f7f11d50a3a",
    L"mscorcfg, PublicKeyToken=b03f5f7f11d50a3a",
    L"vjswfchtml, PublicKeyToken=b03f5f7f11d50a3a",
    L"vjswfccw, PublicKeyToken=b03f5f7f11d50a3a",
    L"VJSWfcBrowserStubLib, PublicKeyToken=b03f5f7f11d50a3a",
    L"vjswfc, PublicKeyToken=b03f5f7f11d50a3a",
    L"vjslibcw, PublicKeyToken=b03f5f7f11d50a3a",
    L"vjslib, PublicKeyToken=b03f5f7f11d50a3a",
    L"vjscor, PublicKeyToken=b03f5f7f11d50a3a",
    L"VJSharpCodeProvider, PublicKeyToken=b03f5f7f11d50a3a",
};

// ...

HRESULT STDMETHODCALLTYPE CHostControl::GetDomainNeutralAssemblies(
                             ICLRAssemblyReferenceList **ppReferenceList)
{
   // Get a pointer to an ICLRAssemblyIdentityManager using a helper class
   // called CLRIdentityManager. This class was defined in Chapter 8.
   CCLRIdentityManager *pIdentityClass = new CCLRIdentityManager();
   ICLRAssemblyIdentityManager *pIdentityInterface =
    pIdentityClass->GetCLRIdentityManager();

   DWORD dwCount = sizeof(wszDomainNeutralAssemblies)/
    sizeof(wszDomainNeutralAssemblies[0]);

   // Call GetCLRAssemblyReferenceList passing in the array of
   // strings identifying the assemblies you'd like to load domain neutral.
   HRESULT hr = pIdentityInterface->
         GetCLRAssemblyReferenceList(wszDomainNeutralAssemblies,
                                     dwCount,
                                     ppReferenceList);

   pIdentityInterface->Release();
   delete pIdentityClass;
   return S_OK;
}

The assembly names you supply to indicate which assemblies to load domain neutral can be partial. That is, the PublicKeyToken, Version, and Culture elements are all optional, just as they are when you use one of the assembly loading APIs to load an assembly given a partial name. (See Chapter 7 for details on how to use the loading APIs.) If you omit a value for one of the optional elements, the CLR treats that element as a wildcard when determining whether an assembly should be loaded domain neutral. For example, the preceding code provides a value for PublicKeyToken but not for Version or Culture. In this case, the CLR will load domain neutral all versions and all cultures of the assembly with the given name and PublicKeyToken.

Most CLR hosts that specify an exact list of assemblies using GetDomainNeutralAssemblies do not also supply domain-neutral settings using CorBindToRuntimeEx or the loader optimization APIs. It is possible to do so, however. If you do specify domain-neutral settings using more than one of the techniques I’ve described in this chapter, the results are combined. The following list describes which assemblies are loaded domain neutral when you specify both a specific list and one of the values for either STARTUP_FLAGS or the LoaderOptimization enumeration:

  • If the host passes STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN (load no assemblies domain neutral), only the assemblies returned from GetDomainNeutralAssemblies would be loaded domain neutral.

  • If the host passes STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN (load all assemblies domain neutral), all assemblies would be loaded domain neutral, regardless of what is returned from GetDomainNeutralAssemblies.

  • If the host passes STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST (load only strong-named assemblies domain neutral), all strong-named assemblies plus those returned from GetDomainNeutralAssemblies would be loaded domain neutral.

In this section, I’ve described both managed and unmanaged APIs for specifying which assemblies to load domain neutral. As discussed earlier, however, the features available to you through the various APIs are not the same. Specifically,

  • The managed APIs enable you to specify domain-neutral settings per application domain, whereas the unmanaged APIs do not.

  • The unmanaged APIs enable you to provide a specific list of assemblies to load domain neutral, whereas the managed APIs do not.

In practice, I think you’ll find that returning a specific list of assemblies using GetDomainNeutralAssemblies more closely matches most application requirements than the coarse settings offered by either CorBindToRuntimeEx or the loader optimization APIs. In particular, GetDomainNeutralAssemblies enables you to implement the behavior most requested by extensible applications: the ability to load the .NET Framework assemblies and the host runtime assembly domain neutral while not loading any add-ins domain neutral regardless of whether they have a strong name.

Determining Whether an Assembly Has Been Loaded Domain Neutral

.NET Framework 2.0 introduces an API you can use to determine whether a given assembly was loaded domain neutral. The AssemblyIsDomainNeutral method on System.Diagnostics.Loader takes an assembly in the form of an instance of the System.Reflection.Assembly class and returns a boolean value indicating whether the assembly was loaded domain neutral, as shown in the following example:

using System;
using System.Text;
using System.Reflection;
using System.Diagnostics;

namespace DomainNeutralExample
{
   public class Program
   {
      [LoaderOptimization(LoaderOptimization.MultiDomainHost)]
      static void Main(string[] args)
      {
         Assembly a = Assembly.Load(";Alinghi");

         Console .WriteLine(a.FullName +
            " is loaded domain neutral: " +
            Loader .AssemblyIsDomainNeutral(a).ToString());
      }
   }
}

Summary

Domain-neutral code is a feature of the CLR aimed at reducing the overall working set required by processes consisting of multiple application domains. The working set is reduced by sharing the jit-compiled code and other CLR runtime data structures for a given assembly across application domains. However, there are some restrictions placed on assemblies loaded domain neutral. For example, a domain-neutral assembly might only reference other domain-neutral assemblies. In addition, an assembly’s dependencies must be exactly identical between application domains for the assembly’s native code to be shared. The set of assemblies to load domain neutral is specified by a set of APIs provided by the .NET Framework and the CLR hosting interfaces. The most flexible, and therefore the most widely used, of these APIs is the GetDomainNeutralAssemblies method on the IHostControl interface. GetDomainNeutralAssemblies enables a host to specify the exact set of assemblies to be loaded domain neutral.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.244.250