Customizing the Microsoft .NET Framework Common Language Runtime

If you're writing an application involving multiple application domains, you'll almost certainly want to take advantage of a concept called an application domain manager. An application domain manager makes it easy to provide much of the infrastructure your application will need to manage multiple domains in a process. In particular, an application domain manager helps you do the following:

  • Implement a host runtime When you provide an application domain manager, the CLR takes care of loading an instance of your domain manager into each application domain that gets created in the process. This is exactly the behavior I talked about needing in a host runtime. In essence, your application domain manager serves as your host runtime.

  • Customize individual application domains An application domain manager enables you to intercept all calls that create application domains in the process. Each time a new application domain is being created, your domain manager is called to give you a chance to configure the new domains as you see fit. An application domain manager even enables you to reuse an existing domain instead of creating a new one when requested. In this way, you can control how many application domains exist in your process and what code runs in each of them. This role of an application domain manager is covered in detail in Chapter 6.

  • Call into managed code from your CLR host As described in Chapter 2 and 3, all CLR hosts contain some unmanaged code that is used to initialize and start the CLR. After the CLR is loaded into the process, the host needs to transition into managed code to run the application extensions the host supports. This is done by calling methods on your application domain manager through the CLR COM Interoperability layer from the unmanaged portion of your host.

Providing an application domain manager requires two steps. First you must implement a managed class that derives from System.AppDomainManager. Once your class is written, you must direct the CLR to use your domain manager for all application domains created in your process. The next few sections describe these steps in detail.

Creating an Application Domain Manager

The System.AppDomainManager class provides the framework you need to write an application domain manager. The implementation of System.AppDomainManager doesn't provide much functionality, but rather is intended to be used as a base class for writing application domain managers for different scenarios. Application domain managers play a key role in many aspects of multidomain applications. In addition to the introduction provided in this chapter, I use an application domain manager in Chapter 10 to customize the security settings for application domains and in Chapter 6 to customize how application domains are created. Table 5-1 describes the methods on System.AppDomainManager and where in the book they are discussed.

Table 5-1. The Methods of System.AppDomainManager

Method

Purpose

CreateDomain

As you'll see later in this chapter, application domains are created by calls to the static method System.AppDomain.Create-Domain. Each time this method is called, the CLR calls your application domain manager to enable you to customize the creation of the new application domain. I discuss this method in more detail in Chapter 6.

InitializeNewDomain

After a new application domain is created, the CLR calls InitializeNewDomain from within the newly created application domain. This method gives you a chance to set up any infrastructure you need within the domain. If you're writing a CLR host, you can use InitializeNewDomain to pass a pointer to your domain manager out to the unmanaged portion of your host. I show you how to do this later in this chapter when I describe how to associate a domain manager with a process using the CLR hosting APIs.

InitializationFlags

Each application domain manager has a set of flags that are used to control various aspects of how the domain manager is initialized. In the "Associating an Application Domain Manager with a Process" section later this chapter, I discuss how InitializationFlags is used to enable communication with a domain manager from unmanaged code

ApplicationActivator

The ApplicationActivator property is part of the new CLR infrastructure in .NET Framework version 2.0 that is used for activating application add-ins defined by a formal manifest. I don't cover activation using a manifest in this book. Details can be found in the .NET Framework SDK.

HostSecurityManager

This property enables you to return an instance of a HostSecurityManager class that the CLR will use to configure security for application domains as they are created. This property is covered in Chapter 10.

HostExecutionContextManager

This property enables you to return an instance of a HostExecutionContextManager class that the CLR will call when determining how context information flows across thread transitions. This property is not discussed further in this book. Refer to the .NET Framework SDK for more information on this property.

CreateDomainHelper

CreateDomainHelper is a protected static method that application domain managers can call to create new application domains. This method is typically called from a domain manager's implementation of CreateDomain. CreateDomainHelper is discussed in more detail in Chapter 6.

Now that I've discussed the role of an application domain manager, I want to dig into the details by writing one. Throughout the remainder of this chapter and into the next (Chapter 6), we'll build an application domain manager for a game application that simulates a sailboat race. The application will be a CLR host that enables third parties to write assemblies that represent individual sailboats in the race. These assemblies are the add-ins to the application. In particular, the application and its domain manager will have the following characteristics:

  • Each boat in the race will be loaded into its own application domain. We'll use application domains to ensure that individual boats are isolated from each other.

  • Our application domain manager will be the sole means used to communicate between application domains. Therefore, our domain manager will have more methods than just the ones defined in its base class, System.AppDomainManager. We'll add our own methods that enable us to interact with boats loaded in other application domains.

  • Our application domain manager will serve as the means for calling from the unmanaged portion of our host into managed code. So our domain manager will derive from an interface that we can call from unmanaged code using the CLR's COM Interoperability layer.

  • Only our domain manager will be loaded into the default application domain. The addins (the boats) will be loaded into application domains we create. I've chosen this design because the default application domain cannot be unloaded. Were we to load a boat into the default domain, we'd never be able to unload it without shutting down the process. The practice of loading only your host runtime into the default domain is very common among CLR hosts for exactly the reason we've chosen to use it in this example.

These characteristics of our boat race host can be seen in the architecture diagram shown in Figure 5-6.

Figure 5-6. BoatRace game architecture

The code for our application domain manager consists of one interface and one class. The interface contains a method we'll use to communicate between domain managers in different application domains and to communicate with the domain manager in the default domain from the unmanaged portion of our host. I created a separate interface rather than just adding additional methods directly to our domain manager class because we need to call through the interface from COM.[3] As expected, our application domain manager class derives both from this interface and from System.AppDomainManager. The initial interface and a skeletal class definition are shown here:

[3] The CLR's COM Interoperability layer can generate an interface automatically for you based on the class definition, but it's generally considered a better practice to define the interface explicitly. Automatically generated interfaces can result in versioning problems later when you add methods to a class.

namespace BoatRaceHostRuntime { public interface IBoatRaceDomainManager { Int32 EnterBoat(string assemblyFileName, string boatTypeName); } public class BoatRaceDomainManager : AppDomainManager, IBoatRaceDomainManager { // Implementation of EnterBoat and other methods omitted for now. } }

Associating an Application Domain Manager with a Process

Now that I've sketched out what a simple application domain manager looks like, we need to associate our application domain manager class with the process we're hosting. In this way, the CLR will create an instance of our domain manager in each new application domain for us. In the .NET Framework version 2.0 release of the CLR, all application domains in the process contain the same application domain manager type. In future releases it might be possible to have different application domain managers for different application domains.

You can use either of two approaches to associate your domain manager type with a process: you can specify the name of your type using either the CLR hosting APIs or a set of environment variables. The next two sections describe these two approaches.

The CLR Hosting APIs

CLR hosts use the ICLRControl::SetAppDomainManagerType method to associate an application domain manager with a process. Recall from Chapter 2 that you get a pointer to the ICLRControl interface by calling the GetCLRControl method on the ICLRRuntimeHost pointer returned from CorBindToRuntimeEx. SetAppDomainManagerType takes two string parameters: one that gives the identity of the assembly containing the application domain manager type and one that gives the name of the type itself. Here's the signature of ICLRControl::SetAppDomainManagerType from mscoree.idl:

interface ICLRControl: IUnknown { HRESULT SetAppDomainManagerType( [in] LPCWSTR pwzAppDomainManagerAssembly, [in] LPCWSTR pwzAppDomainManagerType); }

If we compiled our domain manager into an assembly with the identity

BoatRaceHostRuntime, Version=1.0.0.0, PublicKeyToken=5cf360b40180107c, culture=neutral

the following code snippet would initialize the CLR and establish our BoatRaceHostRuntime.BoatRaceDomainManager class as the application domain manager for the process:

int main(int argc, wchar_t* argv[]) { // Initialize the CLR using CorBindToRuntimeEx. This gets us // the ICLRRuntimeHost pointer we'll need to call Start. ICLRRuntimeHost *pCLR = NULL; HRESULT hr = CorBindToRuntimeEx( L"v2.0.41013, L"wks", STARTUP_CONCURRENT_GC, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*) &pCLR); // Start the CLR. hr = pCLR->Start(); // Get a pointer to the ICLRControl interface. ICLRControl *pCLRControl = NULL; hr = pCLR->GetCLRControl(&pCLRControl); // Call SetAppDomainManagerType to associate our domain manager with // the process. pCLRControl->SetAppDomainManagerType( L BoatRaceHostRuntime, Version=1.0.0.0, PublicKeyToken=5cf360b40180107c, culture=neutral, L"BoatRaceHostRuntime.BoatRaceDomainManager" ); // rest of main() omitted... }

Calling an Application Domain Manager from Unmanaged Code

One of the roles of an application domain manager is to serve as the entry point into managed code for CLR hosts. To make this initial transition into managed code, the unmanaged portion of the host must have an interface pointer to the instance of the application domain manager that has been loaded into the default application domain. Given this pointer, we can then call from unmanaged code to managed code using COM interoperability. Obtaining a pointer to an instance of an application domain manager in unmanaged code requires two steps. First, the application domain manager must notify the CLR that it would like a pointer to a domain manager sent out to unmanaged code. This is done by setting the InitializationFlags property on System.AppDomainManager to DomainManagerInitializationFlags.RegisterWithHost. Next, the unmanaged portion of the host must provide an implementation of IHostControl::SetAppDomainManager to receive the pointer. The relationship between these two steps is shown in Figure 5-7.

Figure 5-7. Passing a pointer to an application domain manager to unmanaged code

Step 1: Setting Initialization Flags to Register With Host

By setting InitializationFlags to RegisterWithHost, you make the unmanaged hosting code in the process aware of the new instance of your application domain manager, and thus that a new application domain has been created. A convenient place to set InitializationFlags is from your domain manager's implementation of InitializeNewDomain. When a new application domain is created, the CLR creates a new instance of your application domain manager in the new domain and calls its InitializeNewDomain method. This gives your domain manager a chance to initialize any state in the new domain as discussed in Chapter 6. Our BoatRaceDomainManager sets InitializationFlags from InitializeNewDomain as shown in the following code snippet:

public class BoatRaceDomainManager : AppDomainManager, IBoatRaceDomainManager { public override void InitializeNewDomain(AppDomainSetup appDomainInfo) { InitializationFlags = DomainManagerInitializationFlags.RegisterWithHost; } }

Step 2: Implement IHostControl::SetAppDomainManager

When you set InitializationFlags to RegisterWithHost, the CLR calls the SetAppDomainManager method on your implementation of IHostControl to pass information about the instance of your application domain manager out to the unmanaged portion of your host. Recall from Chapter 2 that IHostControl is one of the CLR hosting interfaces implemented by the host. Here's the signature for IHostControl:: SetAppDomainManager from mscoree.idl:

interface IHostControl : IUnknown { // other methods omitted... HRESULT SetAppDomainManager( [in] DWORD dwAppDomainID, [in] IUnknown* pUnkAppDomainManager); }

SetAppDomainManager has two parameters. The first is a numerical identifier for the application domain that has just been created. Whenever a new domain is created, the CLR generates a unique identifier and assigns it to the domain. This identifier is used to interact with the application domain in various places throughout the CLR hosting interfaces. For example, you can use the unique identifier passed to SetAppDomainManager to later call ICLRRuntimeHost::UnloadAppDomain to unload the application domain from the process. You can determine an application domain's unique identifier in managed code using the Id property on the System.AppDomain class.

The second parameter to SetAppDomainManager is a COM interface pointer to the new instance of your application domain manager. The type of this pointer is IUnknown. Clearly, we'll need a pointer to a more specific interface to get real work done. Recall that our application domain manager (BoatRaceDomainManager) is derived from an interface we defined in C# called IBoatRaceDomainManager. This interface has the specific methods we need to interact with the host runtime we implemented in managed code. To call these methods we need to obtain this interface from the IUnknown that was passed to SetAppDomainManager. We do this using the standard COM QueryInterface mechanism. Before we can call QueryInterface, however, we must have a definition of IBoatRaceDomainManager in unmanaged code. The easiest way to get an unmanaged definition of an interface you've defined in managed code is to use the tlbexp.exe tool that ships in the .NET Framework SDK. This tool takes a managed assembly as input and generates a COM type library that can be used to interact with managed classes through COM. Running tlbexp.exe on our BoatRaceHostRuntime assembly (the assembly containing our application domain manager) generates a type library called BoatRaceHostRuntime.tlb. We can use the #import directive in C++ to import our type library so we have access to the COM definition of IBoatRaceDomainManager:

#import <BoatRaceHostRuntime.tlb> using namespace BoatRaceHostRuntime;

Now we have all we need to use QueryInterface to get a pointer of type IBoatRaceDomainManager as shown in the following sample implementation of SetAppDomainManager:

HRESULT STDMETHODCALLTYPE CHostControl::SetAppDomainManager( DWORD dwAppDomainID, IUnknown *pUnkAppDomainManager) { HRESULT hr = S_OK; IBoatRaceDomainManager *pDomainManager = NULL; hr = pUnkAppDomainManager->QueryInterface(__uuidof(IBoatRaceDomainManager), (PVOID*) &pDomainManager); // pDomainManager can now be used to access the new instance of // BoatRaceDomainManager. return hr; }

In hosts involving multiple application domains, the CLR calls IHostControl::SetAppDomainManager multiple timesonce for every domain that is created in the process. Most hosts find it useful to save the data needed to access the application domain that the CLR passes to SetAppDomainManager. As I mentioned earlier, the application domain's unique identifier is used to interact with the domain in various places throughout the hosting APIs. In addition, no APIs provided enable you to enumerate the application domains in the process other than the CLR debugging APIs that were described earlier in the chapter as part of the AppDomainViewer sample. So, the most convenient way to get a list of all application domains in the process is to build up the list yourself by saving the identifiers and interface pointers you obtain from SetAppDomainManager.

You might also find it useful to explicitly save the interface pointer to the instance of the application domain manager that exists in the default application domain. It is likely that the unmanaged portion of your CLR host will interact mostly with the instance of your application domain manager that is loaded in the default application domain. That application domain manager will then do most of the work needed to create other domains and run the application's add-ins in those domains. This is the approach taken with our boat race example, as shown in Figure 5-7.

The following class is a sample implementation of IHostControl that both maintains a list of all application domain managers (and hence application domains) created in the process and explicitly identifies the application domain manager for the default domain. The list of application domain managers is stored in a Standard Template Library (STL) map that relates the unique identifier for an application domain to the interface pointer used to interact with its application domain manager. Here's the class definition for our sample implementation of IHostControl:

typedef map<DWORD, IBoatRaceDomainManager *> DomainMap; class CHostControl : public IHostControl { public // IHostControl HRESULT STDMETHODCALLTYPE GetHostManager(REFIID riid, void **ppObject); HRESULT STDMETHODCALLTYPE SetAppDomainManager(DWORD dwAppDomainID, IUnknown *pUnkAppDomainManager); HRESULT STDMETHODCALLTYPE GetDomainNeutralAssemblies( ICLRAssemblyReferenceList **ppReferenceList); // IUnknown virtual HRESULT STDMETHODCALLTYPE QueryInterface(const IID &iid, void **ppv); virtual ULONG STDMETHODCALLTYPE AddRef(); virtual ULONG STDMETHODCALLTYPE Release(); CHostControl(); virtual ~CHostControl(); IBoatRaceDomainManager *GetDomainManagerForDefaultDomain(); DomainMap &GetAllDomainManagers(); private: long m_cRef; IBoatRaceDomainManager *m_pDefaultDomainDomainManager; DomainMap m_Domains; };

As you can see from the class definition, CHostControl stores both the pointer to the domain manager in the default application domain and the map of application domain identifiers to interface pointers in the private member variables m_pDefaultDomainDomainManager and m_Domains. The class also provides the public methods GetDomainManagerForDefaultDomain and GetAllDomainManagers that enable the rest of the host to access the data stored in the class.

The following implementations of SetAppDomainManager, GetDomainManagerForDefaultDomain, and GetAllDomainManagers show how both the map and the variable holding the pointer to the default application domain are populated and returned.

HRESULT STDMETHODCALLTYPE CHostControl::SetAppDomainManager( DWORD dwAppDomainID, IUnknown *pUnkAppDomainManager) { HRESULT hr = S_OK; // Each time a domain gets created in the process, this method is // called. We keep a mapping of domainIDs to pointers to application // domain managers for each domain in the process. This map is handy for // enumerating the domains and so on. IBoatRaceDomainManager *pDomainManager = NULL; hr = pUnkAppDomainManager->QueryInterface( __uuidof(IBoatRaceDomainManager), (PVOID*) &pDomainManager); assert(pDomainManager); m_Domains[dwAppDomainID] = pDomainManager; // Save the pointer to the default domain for convenience. We // initialize m_pDefaultDomainDomainManager to NULL in the // class's constructor. The first time this method is called // is for the default application domain. if (!m_pDefaultDomainDomainManager) { m_pDefaultDomainDomainManager = pDomainManager; } return hr; } IBoatRaceDomainManager* CHostControl::GetDomainManagerForDefaultDomain() { // AddRef the pointer before returning it. if (m_pDefaultDomainDomainManager) m_pDefaultDomainDomainManager->AddRef(); return m_pDefaultDomainDomainManager; } DomainMap& CHostControl::GetAllDomainManagers() { return m_Domains; }

Now that we've built a host control object that enables us to get interface pointers to instances of our application domain manager, let's take a look at a small sample that shows how to call through those pointers into managed code from a CLR host. Recall that the interface we use to interact with our domain managers, IBoatRaceDomainManager, has a method called EnterBoat that takes the name of the assembly and the type that implement one of our application's add-ins. The following sample CLR host uses the host control object to get a pointer to the instance of our application domain manager in the default application domain. It then calls the EnterBoat method to add a boat to our simulated boat race.

# include "stdafx.h" #include "CHostControl.h" int main(int argc, wchar_t* argv[]) { // Start the CLR. Make sure .NET Framework version 2.0 is used. ICLRRuntimeHost *pCLR = NULL; HRESULT hr = CorBindToRuntimeEx L"v2.0.41013, L"wks", STARTUP_CONCURRENT_GC, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*) &pCLR); assert(SUCCEEDED(hr)); // Create an instance of our host control object and "register" // it with the CLR. CHostControl *pHostControl = new CHostControl(); pCLR->SetHostControl((IHostControl *)pHostControl); // Start the CLR in the process. hr = pCLR->Start(); assert(SUCCEEDED(hr)); // Get a pointer to our AppDomainManager running in the default domain. IBoatRaceDomainManager *pDomainManagerForDefaultDomain = pHostControl->GetDomainManagerForDefaultDomain(); assert(pDomainManagerForDefaultDomain); // Call into the default application domain to enter a boat in the race. pDomainManagerForDefaultDomain->EnterBoat("StevensBoat", "J29.ParthianShot"); // Clean up. pDomainManagerForDefaultDomain->Release(); pHostControl->Release(); return 0; }

Environment Variables

You can associate your application domain manager with a process using a set of environment variables instead of through the CLR hosting APIs. Setting the environment variables requires less code, but you lose the ability to interact with your domain manager from unmanaged code. Specifically, if you haven't implemented the IHostControl interface, the CLR cannot call out to your unmanaged code to give you a pointer to the domain manager. As a result, specifying an application domain manager using a configuration file is useful only if your extensible application is written completely in managed code or if you're writing a CLR host but don't need to interact with your domain manager from unmanaged code.

The environment variables you need to set to associate your application domain manager with a process are called APPDOMAIN_MANAGER_ASM and APPDOMAIN_MANAGER_TYPE. APPDOMAIN_MANAGER_ASM must be set to the fully qualified name of the assembly containing your application domain manager type, whereas APPDOMAIN_MANAGER_TYPE must be set to the name of the type within that assembly that implements your domain manager. For example, if we were to associate our BoatRaceDomainManager type with a process, the two environment variables would have the following values:

APPDOMAIN_MANAGER_ASM=BoatRaceHostRuntime, Version=1.0.0.0, PublicKeyToken=5cf360b40180107c, culture=neutral APPDOMAIN_MANAGER_TYPE= BoatRaceHostRuntime.BoatRaceDomainManager

Setting these values in an unmanaged CLR host is straightforwardjust call the Win32 API SetEnvironmentVariable any time before you start the CLR using ICLRRuntimeHost::Start. The following example uses SetEnvironmentVariable to set APPDOMAIN_MANAGER_ASM and APPDOMAIN_MANAGER_TYPE to the values described previously:

int main(int argc, wchar_t* argv[]) { // Start the CLR. Make sure .NET Framework 2.0 is used. ICLRRuntimeHost *pCLR = NULL; HRESULT hr = CorBindToRuntimeEx L"v2.0.41013 L"wks", STARTUP_CONCURRENT_GC, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*) &pCLR); assert(SUCCEEDED(hr)); // Use Win32's SetEnvironmentVariable to set up the domain manager. SetEnvironmentVariable(L"APPDOMAIN_MANAGER_TYPE", L"BoatRaceHostRuntime.BoatRaceDomainManager"); SetEnvironmentVariable(L"APPDOMAIN_MANAGER_ASM", L"BoatRaceHostRuntime, Version=1.0.0.0, PublicKeyToken=5cf360b40180107c, culture=neutral"); // Start the CLR. hr = pCLR->Start(); assert(SUCCEEDED(hr)); // The rest of the host's code is omitted. }

If your application is written entirely in managed code, setting APPDOMAIN_MANAGER_ASM and APPDOMAIN_MANAGER_TYPE isn't as straightforward. You can't set these environment variables from within your process as we did in the earlier unmanaged example because by the time your managed code is running, it's too late. The CLR honors the environment variables only if they are set before the default application domain is created. So you must write a small bootstrap application that creates a process to run your real application. Values for APPDOMAIN_MANAGER_ASM and APPDOMAIN_MANAGER_TYPE can be passed into the new process as it's created. The following example shows how you might accomplish this using the Process and ProcessStartInfo classes from System.Diagnostics:

using System; using System.Diagnostics; namespace AppDomainManagerProcess { class Launch { [STAThread] static void Main(string[] args) { ProcessStartInfo mdProcessInfo = new ProcessStartInfo("BoatRaceHost.exe"); mdProcessInfo.EnvironmentVariables["APPDOMAIN_MANAGER_ASM"] = "BoatRaceHostRuntime, Version=1.0.0.0, PublicKeyToken=5cf360b40180107c, culture=neutral"; mdProcessInfo.EnvironmentVariables["APPDOMAIN_MANAGER_TYPE"] = "BoatRaceHostRuntime.BoatRaceDomainManager"; mdProcessInfo.UseShellExecute = false; Process.Start(mdProcessInfo); } } }

Creating Application Domains

Application domains are exposed in the .NET Framework programming model through the System.AppDomain class. System.AppDomain has a static method called CreateDomain you can use to create a new application domain. There are several different flavors of AppDomain.CreateDomain, ranging from a simple method that creates a domain with just a friendly name to methods that create domains with custom security evidence or configuration properties. The signatures for CreateDomain are as follows:

public static AppDomain CreateDomain(String friendlyName) public static AppDomain CreateDomain(String friendlyName, Evidence securityInfo) public static AppDomain CreateDomain(String friendlyName, Evidence securityInfo, AppDomainSetup info)

The parameters to CreateDomain are described in Table 5-2.

Table 5-2. The Parameters to AppDomain.CreateDomain

Parameter

Description

friendlyName

A textual name to associate with the domain.

The contents of this string are completely up to the creator of the domainthe CLR doesn't require specific formats or conventions to be followed.

This name is typically used to display information about application domains in user interfaces. For example, AppDomainViewer uses a domain's friendly name in its tree view. Similarly, some debuggers use friendly names to display application domains when attached to a process.

A domain's friendly name can be accessed using the friendlyName property. Once an application domain is created, its name cannot be changed.

securityInfo

Security evidence to be associated with the domain. This parameter is optional in that you can pass null, causing the CLR not to set any evidence.

Earlier in the chapter, I talked about two ways to customize the Code Access Security for an application domainprovide a domain-level policy tree and limit the permissions granted to an assembly using application domain evidence. This parameter is the way you set that evidence.

Much more information about domain evidence is provided in Chapter 10.

info

An object of type System.AppDomainSetup that contains all the configuration properties for the domain. Using this object to configure an application domain is a broad subject in and of itself. A complete description of how to use AppDomainSetup is provided in Chapter 6.

Choosing which flavor of CreateDomain to call is dictated by how much you need to configure the domain upfront. For example, several configuration properties are exposed by AppDomainSetup that can be specified only when the domain is created (more on this in Chapter 6). I typically find that the CreateDomain that takes a friendly name, security evidence, and an AppDomainSetup object is the most useful. When a high degree of application domain configuration is required, this is the only flavor of CreateDomain that gives you full access to all the configuration options. For scenarios in which such customization is not required, any of its three parameters can be set to null.

Calling CreateDomain returns a new instance of System.AppDomain that you use to start interacting with the domain. The following code snippet shows a typical call to CreateDomain. Note that for now I'm passing null for both the security evidence and AppDomainSetup parameters. As described, I cover the details of those parameters in upcoming chapters.

using System; AppDomain ad = AppDomain.CreateDomain("Stevenpr Domain", null, null);

Note

For completeness, it's worth noting that there is a fourth flavor of CreateDomain that takes just a few of the configuration properties exposed through AppDomainSetup:

public static AppDomain CreateDomain(String friendlyName, Evidence securityInfo, String appBasePath, String appRelativeSearchPath, bool shadowCopyFiles)

This flavor of CreateDomain is not all that useful in my opinion. It was implemented before the AppDomainSetup object was and is kept in place for backward-compatibility reasons. As described, using AppDomainSetup is the preferred way to configure an application domain as it is being created.

Typically, one of the first things you'll want to do after creating a new domain is load an assembly into it. If you're writing an extensible application, it's likely that the assembly you load into the new domain will be an add-in. Recall from earlier discussions about application domain isolation that you typically want the code that loads your add-ins to be running in the domain in which the add-in is to be loaded. Otherwise, a proxy to the new extension would have to be returned across the application domain boundary, thereby requiring the assembly containing the extension to be loaded into multiple domains. The simplest, most efficient way to load an assembly from within its target domain is to take advantage of the host runtime, or application domain managers, discussed in the previous section. Because the CLR automatically creates an instance of an application domain manager in each new application domain, you must simply pass information about which assembly to load into the new domain manager and let it take care of the loading. Remember, too, that this practice also results in a clean design because all communication between application domains is done through the domain managers.

System.AppDomain has a property called DomainManager that returns the instance of the application domain manager for the given application domain. After a new domain is created, you can use this property to get its domain manager with which you can start interacting with the new domain. To see how this works, let's return to our boat race example and look at the implementation of the EnterBoat method. EnterBoat is responsible for creating a new application domain and loading the new boat into that domain. The implementation of BoatRaceDomainManager.EnterBoat is as follows:

public class BoatRaceDomainManager : AppDomainManager, IBoatRaceDomainManager { // Other methods omitted... public Int32 EnterBoat(string assemblyFileName, string boatTypeName) { // Create a new domain in which to load the boat. AppDomain ad = AppDomain.CreateDomain(boatTypeName, null, null); // Get the instance of BoatRaceDomainManager running in the // new domain. BoatRaceDomainManager adManager = (BoatRaceDomainManager)ad.DomainManager; // Pass the assembly and type names to the new domain so the // assembly can be loaded. adManager.InitializeNewBoat(assemblyFileName, boatTypeName); return ad.Id; } }

EnterBoat first creates a new application domain for the boat by calling AppDomain.CreateDomain. After the domain is created, AppDomain.DomainManager is used to get the instance of the domain manager in the new domain. Finally, EnterBoat calls a method on BoatRaceDomainManager called InitializeNewBoat, passing the name of the assembly containing the boat and the name of the type that represents the boat. InitializeNewBoat then takes this information and uses the methods on the System.Reflection.Assembly class to load the assembly into the new domain. The discussion of how to load assemblies is broad enough to warrant an entire chapter. I cover this topic in detail in Chapter 7.

Note

You might recall from looking at Table 5-1 that System.AppDomainManager also has a method called CreateDomain. This method is not intended to be called explicitly. Instead, as you'll see in Chapter 6, the CLR calls AppDomainManager.CreateDomain (or its overrides) whenever a new application domain is created using AppDomain.CreateDomain. This gives the domain manager a chance to configure the new domain as it is being created.

Note

If you're familiar with the CLR hosting APIs that were provided in the first two versions of the CLR (.NET Framework version 1.0 and .NET Framework version 1.1), you might recall that the ICorRuntimeHost interface includes methods that enable you to create application domains directly from unmanaged code. These methods returned an interface pointer through which you could interact directly with the new domain using COM interoperability. The ability to create an application domain from unmanaged code was occasionally useful, but in practice most hosts created their domains in managed code because the programming model was simpler and tended to perform better because the amount of communication between unmanaged and managed code was less. To encourage this model, and to reduce the number of duplicate ways to do the same thing, Microsoft has removed the ability to create application domains directly from unmanaged code in .NET Framework version 2.0.

    Категории