Saturday, November 24, 2007

Directly Invoking methods in Seperate AppDomains

You can directly execute methods from one app domain (calling app domain) to another (the target app domain) without having the calling app domain load the types that will also be loaded in the target domain.

You need one common class that is referenced in both app domains. This common class will simply store the name of the assembly, class, method and parameters of the method. The common class will be created in the main app domain and sent to the target app domain. This class is a MarshalByValue type and will be serialized HOWEVER it has one special property that extends MarshalByRef. We will get back to why that is important later.

Step #1: Build the Target App Domain

AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = @"C:\TEST";
System.AppDomain targetDomain= System.AppDomain.CreateDomain("Test_Domain", System.AppDomain.CurrentDomain.Evidence, setup);

The code above creates a new AppDomain. Once created it is ready to execute any class that it's Assembly probing has access to. Obviously the code above is running inside the primary AppDomain or DefaultDomain or some other AppDomain. The point is there are at least 2 AppDomains in this scenario.

Step #2: Create the "common" InvocationRequest object

InvocationRequest request = new Invocation.InvocationRequest();
request.AssemblyName = "TestLibrary";
request.ClassName = "TestLibrary.Test";
request.MethodName = "Kill";
request.Singleton = true;

This InvocationRequest class is nothing more than a bunch of string properties and a method that implements the CrossAppDomainDelegate method signature. To implement this delegate you only need a method of void MethodName(). Lemme explain a bit more, this is the most important piece to understand. Assume I implement the CrossAppDomainDelegate with a method named Invoke(). Remember, the primary AppDomain creates the InvocationRequest instance it then sends this instance to the target AppDomain thru the use of the DoCallBack() method.

public class InvocationRequest
{
... getters/setters ////

public void Invoke()
{
// load and invoke any code you like...
// This code will run in the Target AppDomain
object invocationTarget = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(AssemblyName, ClassName);
Type type = invocationTarget.GetType();
System.Reflection.MethodInfo mi = type.GetMethod(methName);
object rtn_o = mi.Invoke(invocationTarget, Parameters.ToArray());
if (rtn_o != null)
{
this.Result.Value = rtnVal;
}
}
}

Step #3: Invoking inside the Target Domain:

targetDomain.DoCallBack( new CrossAppDomainDelegate( request.Invoke ));

This code is executed from the Main AppDomain (the Calling AppDomain). It will send the instance of the request object via Serialization to the other AppDomain. However, the Invoke() method will actually run in target domain context.

Step #4: Expose return values to the Calling AppDomain

The InvocationRequest class is serialized from the calling AppDomain to target AppDomain because it is a MBV (MarshalByValue) as are almost all objects. If we add a property to the InvocationRequest that extends MBR (MarshalByRef) then we can set it's value in the target domain and read it in the calling domain.

public class InvocationResult : MarshalByRefObject
{
private object _value;

public object Value
{
get { return _value; }
set { _value = value; }
}
}

If you added a property of type InvocationResult to the InvocationRequest class then you will now have the ability to share a common object amongst multiple AppDomains.

No comments: