Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp 2005 Recipes (2006) [eng]

.pdf
Скачиваний:
48
Добавлен:
16.08.2013
Размер:
4.04 Mб
Скачать

68C H A P T E R 3 A P P L I C AT I O N D O M A I N S, R E F L E C T I O N, A N D M E TA D ATA

application domains stems from the interaction with .NET Remoting and the way objects traverse application domain boundaries.

All types fall into one of three categories: nonremotable, marshal-by-value (MBV), or marshal- by-reference (MBR). Nonremotable types cannot cross application domain boundaries and cannot be used as arguments or return values in cross-application domain calls. Recipe 3-4 discusses nonremotable types.

MBV types are serializable types. When you pass an MBV object across an application domain boundary as an argument or a return value, the .NET Remoting system serializes the object’s current state, passes it to the destination application domain, and creates a new copy of the object with the same state as the original. This results in a copy of the MBV object existing in both application domains. The content of the two instances are initially identical, but they are independent; changes made to one instance are not reflected in the other instance (this applies to static members as well). This often causes confusion as you try to update the remote object but are in fact updating the local copy. If you actually want to be able to call and change an object from a remote application domain, the object needs to be an MBR type.

MBR types are those classes that derive from System.MarshalByRefObject. When you pass an MBR object across an application domain boundary as an argument or a return value, the .NET Remoting system creates a proxy in the destination application domain that represents the remote MBR object. To any class in the destination application domain, the proxy looks and behaves like the remote MBR object that it represents. In reality, when a call is made against the proxy, the .NET Remoting system transparently passes the call and its arguments to the remote application domain and issues the call against the original object. Any results are passed back to the caller via the proxy. Figure 3-1 illustrates the relationship between an MBR object and the objects that access it across application domains via a proxy.

Figure 3-1. An MBR object is accessed across application domains via a proxy.

C H A P T E R 3 A P P L I C AT I O N D O M A I N S, R E F L E C T I O N, A N D M E TA D ATA

69

The Code

The following example highlights (in bold) the fundamental difference between creating classes that are passed by value (Recipe03_02MBV) and those passed by reference (Recipe03_02MBR). The code creates a new application domain and instantiates two remotable objects in it (discussed further in recipe 3-7). However, because the Recipe03_02MBV object is an MBV object, when it is created in the new application domain, it is serialized, passed across the application domain boundary, and deserialized as a new independent object in the caller’s application domain. Therefore, when the code retrieves the name of the application domain hosting each object, Recipe03_02MBV returns the name of the main application domain, and Recipe03_02MBR returns the name of the new application domain in which it was created.

using System;

namespace Apress.VisualCSharpRecipes.Chapter03

{

//Declare a class that is passed by value.

[Serializable]

public class Recipe03_02MBV

{

public string HomeAppDomain

{

get

{

return AppDomain.CurrentDomain.FriendlyName;

}

}

}

//Declare a class that is passed by reference.

public class Recipe03_02MBR: MarshalByRefObject

{

public string HomeAppDomain

{

get

{

return AppDomain.CurrentDomain.FriendlyName;

}

}

}

public class Recipe03_02

{

public static void Main(string[] args)

{

//Create a new application domain. AppDomain newDomain =

AppDomain.CreateDomain("My New AppDomain");

//Instantiate an MBV object in the new application domain. Recipe03_02MBV mbvObject =

(Recipe03_02MBV)newDomain.CreateInstanceFromAndUnwrap( "Recipe03-02.exe", "Apress.VisualCSharpRecipes.Chapter03.Recipe03_02MBV");

//Instantiate an MBR object in the new application domain. Recipe03_02MBR mbrObject =

70 C H A P T E R 3 A P P L I C AT I O N D O M A I N S, R E F L E C T I O N, A N D M E TA D ATA

(Recipe03_02MBR)newDomain.CreateInstanceFromAndUnwrap( "Recipe03-02.exe", "Apress.VisualCSharpRecipes.Chapter03.Recipe03_02MBR");

//Display the name of the application domain in which each of

//the objects is located.

Console.WriteLine("Main AppDomain = {0}", AppDomain.CurrentDomain.FriendlyName);

Console.WriteLine("AppDomain of MBV object = {0}", mbvObject.HomeAppDomain);

Console.WriteLine("AppDomain of MBR object = {0}", mbrObject.HomeAppDomain);

// Wait to continue.

Console.WriteLine("\nMain method complete. Press Enter."); Console.ReadLine();

}

}

}

Note Recipe 13-1 provides more details on creating serializable types, and recipe 10-16 describes how to create remotable types.

3-3. Avoid Loading Unnecessary Assemblies into Application Domains

Problem

You need to pass an object reference across multiple application domain boundaries; however, to conserve memory and avoid impacting performance, you want to ensure the common language runtime (CLR) loads only the object’s type metadata into the application domains where it is required (that is, where you will actually use the object).

Solution

Wrap the object reference in a System.Runtime.Remoting.ObjectHandle, and unwrap the object reference only when you need to access the object.

How It Works

When you pass an MBV object across application domain boundaries, the runtime creates a new instance of that object in the destination application domain. This means the runtime must load the assembly containing that type metadata into the application domain. Passing MBV references across intermediate application domains can result in the runtime loading unnecessary assemblies into application domains. Once loaded, these superfluous assemblies cannot be unloaded without unloading the containing application domain. (See recipe 3-9 for more information.)

The ObjectHandle class allows you to wrap an object reference so that you can pass it between application domains without the runtime loading additional assemblies. When the object reaches the destination application domain, you can unwrap the object reference, causing the runtime to load the required assembly and allowing you to access the object as normal.

C H A P T E R 3 A P P L I C AT I O N D O M A I N S, R E F L E C T I O N, A N D M E TA D ATA

71

The Code

The following code contains some simple methods that demonstrate how to wrap and unwrap a System.Data.DataSet using an ObjectHandle:

using System; using System.Data;

using System.Runtime.Remoting;

namespace Apress.VisualCSharpRecipes.Chapter03

{

class Recipe03_03

{

// A method to wrap a Dataset.

public ObjectHandle WrapDataSet(DataSet ds)

{

// Wrap the DataSet.

ObjectHandle objHandle = new ObjectHandle(ds);

// Return the wrapped DataSet. return objHandle;

}

// A method to unwrap a DataSet.

public DataSet UnwrapDataSet(ObjectHandle handle)

{

// Unwrap the DataSet.

DataSet ds = (System.Data.DataSet)handle.Unwrap();

// Return the wrapped DataSet. return ds;

}

}

}

3-4. Create a Type That Cannot Cross Application

Domain Boundaries

Problem

You need to create a type so that instances of the type are inaccessible to code in other application domains.

Solution

Ensure the type is nonremotable by making sure it is not serializable and it does not derive from the

MarshalByRefObject class.

How It Works

On occasion, you will want to ensure that instances of a type cannot transcend application domain boundaries. To create a nonremotable type, ensure that it isn’t serializable and that it doesn’t derive (directly or indirectly) from the MarshalByRefObject class. If you take these steps, you ensure that an

72C H A P T E R 3 A P P L I C AT I O N D O M A I N S, R E F L E C T I O N, A N D M E TA D ATA

object’s state can never be accessed from outside the application domain in which the object was instantiated—such objects cannot be used as arguments or return values in cross-application domain method calls.

Ensuring that a type isn’t serializable is easy because a class doesn’t inherit the ability to be serialized from its parent class. To ensure that a type isn’t serializable, make sure it does not have

System.SerializableAttribute applied to the type declaration.

Ensuring that a class cannot be passed by reference requires a little more attention. Many classes in the .NET class library derive directly or indirectly from MarshalByRefObject; you must be careful you don’t inadvertently derive your class from one of these. Commonly used base classes that derive from MarshalByRefObject include System.ComponentModel.Component, System.IO.Stream, System.IO.TextReader, System.IO.TextWriter, System.NET.WebRequest, and System.Net.WebResponse. (Check the .NET Framework SDK documentation on MarshalByRefObject. The inheritance hierarchy listed for the class provides a complete list of classes that derive from it.)

3-5. Load an Assembly into the Current Application Domain

Problem

You need to load an assembly at runtime into the current application domain.

Solution

Use the static Load method or the LoadFrom method of the System.Reflection.Assembly class.

Note The Assembly.LoadWithPartialName method has been deprecated in .NET 2.0. Instead, you should use the Assembly.Load method described in this recipe.

How It Works

Unlike Win32 where the referenced DLLs are loaded when the process starts, the CLR will automatically load the assemblies identified at build time as being referenced by your assembly only when the metadata for their contained types is required. However, you can also explicitly instruct the runtime to load assemblies. The Load and LoadFrom methods both result in the runtime loading an assembly into the current application domain, and both return an Assembly instance that represents the newly loaded assembly. The differences between each method are the arguments you must provide to identify the assembly to load and the process that the runtime undertakes to locate the specified assembly.

The Load method provides overloads that allow you to specify the assembly to load using one of the following:

A string containing the fully or partially qualified display name of the assembly

A System.Reflection.AssemblyName containing details of the assembly

A byte array containing the raw bytes that constitute the assembly

A fully qualified display name contains the assembly’s text name, version, culture, and public key token, separated by commas (for example, System.Data, Version=2.0.0.0, Culture=neutral,

C H A P T E R 3 A P P L I C AT I O N D O M A I N S, R E F L E C T I O N, A N D M E TA D ATA

73

PublicKeyToken=b77a5c561934e089). To specify an assembly that doesn’t have a strong name, use PublicKeyToken=null. You can also specify a partial display name, but as a minimum, you must specify the assembly name (without the file extension).

In response to the Load call, the runtime undertakes an extensive process to locate and load the specified assembly. The following is a summary; consult the section “How the Runtime Locates Assemblies” in the .NET Framework SDK documentation for more details:

1.If you specify a strong-named assembly, the Load method will apply the version policy and publisher policy to enable requests for one version of an assembly to be satisfied by another version. You specify the version policy in your machine or application configuration file using <bindingRedirect> elements. You specify the publisher policy in special resource assemblies installed in the global assembly cache (GAC).

2.Once the runtime has established the correct version of an assembly to use, it attempts to load strong-named assemblies from the GAC.

3.If the assembly is not strong named or is not found in the GAC, the runtime looks for applicable <codeBase> elements in your machine and application configuration files. A <codeBase> element maps an assembly name to a file or a uniform resource locator (URL). If the assembly is strong named, <codeBase> can refer to any location including Internet-based URLs; otherwise, <codeBase> must refer to a directory relative to the application directory. If the assembly doesn’t exist at the specified location, Load throws a System.IO.FileNotFoundException.

4.If no <codeBase> elements are relevant to the requested assembly, the runtime will locate the assembly using probing. Probing looks for the first file with the assembly’s name (with either a .dll or an .exe extension) in the following locations:

The application root directory

Directories under the application root that match the assembly’s name and culture

Directories under the application root that are specified in the private binpath

The Load method is the easiest way to locate and load assemblies but can also be expensive in terms of processing if the runtime needs to start probing many directories for a weak-named assembly. The LoadFrom method allows you to load an assembly from a specific location. If the specified file isn’t found, the runtime will throw a FileNotFoundException. The runtime won’t attempt to locate the assembly in the same way as the Load method—LoadFrom provides no support for the GAC, policies, <codebase> elements, or probing.

The Code

The following code demonstrates various forms of the Load and LoadFrom methods. Notice that unlike the Load method, LoadFrom requires you to specify the extension of the assembly file.

using System;

using System.Reflection; using System.Globalization;

namespace Apress.VisualCSharpRecipes.Chapter03

{

class Recipe03_05

{

public static void ListAssemblies()

{

//Get an array of the assemblies loaded into the current

//application domain.

Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

74 C H A P T E R 3 A P P L I C AT I O N D O M A I N S, R E F L E C T I O N, A N D M E TA D ATA

foreach (Assembly a in assemblies)

{

Console.WriteLine(a.GetName());

}

}

public static void Main()

{

//List the assemblies in the current application domain. Console.WriteLine("**** BEFORE ****");

ListAssemblies();

//Load the System.Data assembly using a fully qualified display name. string name1 = "System.Data,Version=2.0.0.0," +

"Culture=neutral,PublicKeyToken=b77a5c561934e089"; Assembly a1 = Assembly.Load(name1);

//Load the System.Xml assembly using an AssemblyName.

AssemblyName name2 = new AssemblyName(); name2.Name = "System.Xml"; name2.Version = new Version(2, 0, 0, 0);

name2.CultureInfo = new CultureInfo(""); //Neutral culture. name2.SetPublicKeyToken(

new byte[] {0xb7, 0x7a, 0x5c, 0x56, 0x19, 0x34, 0xe0, 0x89}); Assembly a2 = Assembly.Load(name2);

//Load the SomeAssembly assembly using a partial display name. Assembly a3 = Assembly.Load("SomeAssembly");

//Load the assembly named c:\shared\MySharedAssembly.dll. Assembly a4 = Assembly.LoadFrom(@"c:\shared\MySharedAssembly.dll");

//List the assemblies in the current application domain. Console.WriteLine("\n\n**** AFTER ****");

ListAssemblies();

//Wait to continue.

Console.WriteLine("\nMain method complete. Press Enter."); Console.ReadLine();

}

}

}

3-6. Execute an Assembly in a Different

Application Domain

Problem

You need to execute an assembly in an application domain other than the current one.

C H A P T E R 3 A P P L I C AT I O N D O M A I N S, R E F L E C T I O N, A N D M E TA D ATA

75

Solution

Call the ExecuteAssembly or ExecuteAssemlyByName (in .NET 2.0) method of the AppDomain object that represents the application domain, and specify the filename of an executable assembly.

How It Works

If you have an executable assembly that you want to load and run in an application domain, the

ExecuteAssembly or ExecuteAssemblyByName method provides the easiest solution. The ExecuteAssembly method provides four overloads. The simplest overload takes only a string containing the name of the executable assembly to run; you can specify a local file or a URL. Other ExecuteAssembly overloads allow you to specify evidence for the assembly (which affect code access security) and arguments to pass to the assembly’s entry point (equivalent to command-line arguments).

The ExecuteAssembly method loads the specified assembly and executes the method defined in metadata as the assembly’s entry point (usually the Main method). If the specified assembly isn’t executable, ExecuteAssembly throws a System.MissingMethodException. The CLR doesn’t start execution of the assembly in a new thread, so control won’t return from the ExecuteAssembly method until the newly executed assembly exits. Because the ExecuteAssembly method loads an assembly using partial information (only the filename), the CLR won’t use the GAC or probing to resolve the assembly. (See recipe 3-5 for more information.)

The ExecuteAssemblyByName method provides a similar set of overloads and takes the same argument types, but instead of taking just the filename of the executable assembly, you pass it the display name of the assembly. This overcomes the limitations inherent in ExecuteAssembly as a result of supplying only partial names. Again, see recipe 3-5 for more information on the structure of assembly display names.

The Code

The following code demonstrates how to use the ExecuteAssembly method to load and run an assembly. The Recipe03-06 class creates an AppDomain and executes itself in that AppDomain using the ExecuteAssembly method. This results in two copies of the Recipe03-06 assembly loaded into two different application domains.

using System;

namespace Apress.VisualCSharpRecipes.Chapter03

{

class Recipe03_06

{

public static void Main(string[] args)

{

//For the purpose of this example, if this assembly is executing

//in an AppDomain with the friendly name "NewAppDomain", do not

//create a new AppDomain. This avoids an infinite loop of

//AppDomain creation.

if (AppDomain.CurrentDomain.FriendlyName != "NewAppDomain")

{

// Create a new application domain.

AppDomain domain = AppDomain.CreateDomain("NewAppDomain");

//Execute this assembly in the new application domain and

//pass the array of command-line arguments. domain.ExecuteAssembly("Recipe03-06.exe", null, args);

}

76C H A P T E R 3 A P P L I C AT I O N D O M A I N S, R E F L E C T I O N, A N D M E TA D ATA

//Display the command-line arguments to the screen prefixed with

//the friendly name of the AppDomain.

foreach (string s in args)

{

Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + " : " + s);

}

// Wait to continue.

if (AppDomain.CurrentDomain.FriendlyName != "NewAppDomain")

{

Console.WriteLine("\nMain method complete. Press Enter."); Console.ReadLine();

}

}

}

}

Usage

If you run Recipe03-06 using the following command:

Recipe03-06 Testing AppDomains

you will see that the command-line arguments are listed from both the existing and new application domains:

NewAppDomain : Testing

NewAppDomain : AppDomains

Recipe03-06.exe : Testing

Recipe03-06.exe : AppDomains

3-7. Instantiate a Type in a Different Application

Domain

Problem

You need to instantiate a type in an application domain other than the current one.

Solution

Call the CreateInstance method or the CreateInstanceFrom method of the AppDomain object that represents the target application domain.

How It Works

The ExecuteAssembly method discussed in recipe 3-6 is straightforward to use, but when you are developing sophisticated applications that use application domains, you are likely to want more control over loading assemblies, instantiating types, and invoking object members within the application domain.

The CreateInstance and CreateInstanceFrom methods provide a variety of overloads that offer fine-grained control over the process of object instantiation. The simplest overloads assume the use

C H A P T E R 3 A P P L I C AT I O N D O M A I N S, R E F L E C T I O N, A N D M E TA D ATA

77

of a type’s default constructor, but both methods implement overloads that allow you to provide arguments to use any constructor.

The CreateInstance method loads a named assembly into the application domain using the process described for the Assembly.Load method in recipe 3-5. CreateInstance then instantiates a named type and returns a reference to the new object wrapped in an ObjectHandle (described in recipe 3-3). The CreateInstanceFrom method also instantiates a named type and returns an ObjectHandle-wrapped object reference; however, CreateInstanceFrom loads the specified assembly file into the application domain using the process described in recipe 3-5 for the Assembly.LoadFrom method.

AppDomain also provides two convenience methods named CreateInstanceAndUnwrap and CreateInstanceFromAndUnwrap that automatically extract the reference of the instantiated object from the returned ObjectHandle object; you must cast the returned object to the correct type.

Caution Be aware that if you use CreateInstance or CreateInstanceFrom to instantiate MBV types in another application domain, the object will be created, but the returned object reference won’t refer to that object. Because of the way MBV objects cross application domain boundaries, the reference will refer to a copy of the object created automatically in the local application domain. Only if you create an MBR type will the returned reference refer to the object in the other application domain. (See recipe 3-2 for more details about MBV and MBR types.)

A common technique to simplify the management of application domains is to use a controller class. A controller class is a custom MBR type. You create an application domain and then instantiate your controller class in the application domain using CreateInstance. The controller class implements the functionality required by your application to manipulate the application domain and its contents. This could include loading assemblies, creating further application domains, cleaning up prior to deleting the application domain, or enumerating program elements (something you cannot normally do from outside an application domain). It is best to create your controller class in an assembly of its own to avoid loading unnecessary classes into each application domain. You should also be careful about what types you pass as return values from your controller to your main application domain to avoid loading additional assemblies.

The Code

The following code demonstrates how to use a simplified controller class named PluginManager. When instantiated in an application domain, PluginManager allows you to instantiate classes that implement the IPlugin interface, start and stop those plug-ins, and return a list of currently loaded plug-ins.

using System;

using System.Reflection; using System.Collections;

using System.Collections.Generic; using System.Collections.Specialized;

namespace Apress.VisualCSharpRecipes.Chapter03

{

// A common interface that all plug-ins must implement. public interface IPlugin

{

void Start(); void Stop();

}

Соседние файлы в предмете Программирование на C++