Flylib.com

Books Software

 
 
 

3.6 Execute an Assembly in a Different Application Domain


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.

Solution

Call the ExecuteAssembly method of the AppDomain object that represents the application domain, and specify the name of an executable assembly.

Discussion

If you have an executable assembly that you want to load and run in an application domain, the ExecuteAssembly 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 (see recipe 13.10) 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.Runtime.InteropServices.COMException . 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 following example demonstrates the use of the ExecuteAssembly method to load and run an assembly. The ExecuteAssemblyExample class creates an AppDomain and executes itself in that AppDomain using the ExecuteAssembly method. This results in two copies of the ExecuteAssemblyExample assembly loaded into two different application domains.

using System;

public class ExecuteAssemblyExample {

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("ExecuteAssemblyExample.exe", 
                null, args);
        }

        // 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);
        }
    }
}



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 or CreateInstanceFrom method of the AppDomain object that represents the target application domain.

Discussion

The ExecuteAssembly method discussed in recipe 3.6 is straightforward to use, but when you are developing sophisticated applications that make use of application domains, you are likely to want more control over the loading of assemblies, instantiation of types, and the invocation of 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 use 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 into the application domain using the process described in recipe 3.5 for the Assembly.LoadFrom method.

Tip  

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.

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 the loading of assemblies, creating further application domains, cleaning up prior to the deletion of the application domain, or enumerating program elements (something you can't normally do from outside an application domain).

The following example demonstrates the use of 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.Specialized;

// A common interface that all plug-ins must implement.
public interface IPlugin {
    void Start();
    void Stop();
}

// A simple IPlugin implementation to demonstrate the PluginManager
// controller class.
public class SimplePlugin : IPlugin {

    public void Start() {
        Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + 
            ": SimplePlugin starting...");
    }

    public void Stop() {
        Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + 
            ": SimplePlugin stopping...");
    }
}

// The controller class, which manages the loading and manipulation
// of plug-ins in its application domain.
public class PluginManager : MarshalByRefObject {

    // A ListDictionary to hold keyed references to IPlugin instances.
    private ListDictionary plugins = new ListDictionary();

    // Default constructor.
    public PluginManager() {}

    // Constructor that loads a set of specified plug-ins on creation.
    public PluginManager(ListDictionary pluginList) {

        // Load each of the specified plug-ins.
        foreach (string plugin in pluginList.Keys) {

            this.LoadPlugin((string)pluginList[plugin], plugin);
        }
    }

    // Load the specified assembly and instantiate the specified
    // IPlugin implementation from that assembly.
    public bool LoadPlugin(string assemblyName, string pluginName) {

        try {

            // Load the named private assembly.
            Assembly assembly = Assembly.Load(assemblyName);

            // Create the IPlugin instance, ignore case.
            IPlugin plugin = 
                (IPlugin)assembly.CreateInstance(pluginName, true);

            if (plugin != null) {

                // Add new IPlugin to ListDictionary
                plugins[pluginName] = plugin;

                return true;

            } else {
                return false;
            }
        } catch {
            return false;
        }
    }

    public void StartPlugin(string plugin) {

        // Extract the IPlugin from the ListDictionary and call Start.
        ((IPlugin)plugins[plugin]).Start();
    }

    public void StopPlugin(string plugin) {

        // Extract the IPLugin from the ListDictionary and call Stop.
        ((IPlugin)plugins[plugin]).Stop();
    }

    public ArrayList GetPluginList() {

        // Return an enumerable list of plug-in names. Take the keys
        // and place them in an ArrayList, which supports marshal-by-value.
        return new ArrayList(plugins.Keys);
    }
}

public class CreateInstanceExample {

    public static void Main() {

        // Create a new application domain.
        AppDomain domain1 = AppDomain.CreateDomain("NewAppDomain1");

        // Create a PluginManager in the new application domain using 
        // the default constructor.
        PluginManager manager1 = 
            (PluginManager)domain1.CreateInstanceAndUnwrap(
            "CreateInstanceExample", "PluginManager");

        // Load a new plug-in into NewAppDomain1.
        manager1.LoadPlugin("CreateInstanceExample", "SimplePlugin");

        // Start and stop the plug-in in NewAppDomain1.
        manager1.StartPlugin("SimplePlugin");
        manager1.StopPlugin("SimplePlugin");

        // Create a new application domain.
        AppDomain domain2 = AppDomain.CreateDomain("NewAppDomain2");

        // Create a ListDictionary containing a list of plug-ins to create.
        ListDictionary pluginList = new ListDictionary();
        pluginList["SimplePlugin"] = "CreateInstanceExample";

        // Create a PluginManager in the new application domain and 
        // specify the default list of plug-ins to create.
        PluginManager manager2 = 
            (PluginManager)domain1.CreateInstanceAndUnwrap(
            "CreateInstanceExample", "PluginManager", true, 0,
            null, new object[] {pluginList}, null, null, null);

        // Display the list of plug-ins loaded into NewAppDomain2.
        Console.WriteLine("Plugins in NewAppDomain2:");
        foreach (string s in manager2.GetPluginList()) {
            Console.WriteLine(" - " + s);
        }

        // Wait to continue
        Console.ReadLine();
    }
}