Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing an object to receive callback from a child AppDomain created within a process to Default AppDomain

Tags:

c#

ipc

appdomain

Sitation:

  1. I am creating a child app domain from my process to load an assembly.
  2. I am able to make calls to this AppDomain.
  3. I want to pass one object from my default process AppDomain to this newly created AppDomain, to receive callbacks from the assembly loaded in new AppDomain to my default AppDomain.

One way I found is to use the AppDomain.DoCallback method, however not sure how to get my host AppDomain in my child AppDomain?

Any body have any idea to achieve it?

like image 528
Jack Sparrow Avatar asked Mar 03 '16 12:03

Jack Sparrow


2 Answers

The general idea is to pass to the newly created domain an instance of a class that is derived from MarshalByRefObject class. It will guarantee that this object will be marshaled by reference and not by value. It means that a proxy will be passed to the new domain and not the original object (this proxy will be generated for you by .NET framework).

Later on when you call a method on this proxy, this call will be passed back to the original domain (a domain where the object was created). In other words a method will be executed in the original domain.

Here is a code that shows this idea:

public class Program
{
    private static void Main(string[] args)
    {
        var listener = new Listener();
        var otherDomain = AppDomain.CreateDomain("otherDomain");

        var instance = (Loader)otherDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(Loader).FullName);
        instance.Init(listener);
    }
}

[Serializable]
public class Loader 
    : MarshalByRefObject
{
    public void Init(Listener listener)
    {
        Console.WriteLine($"[{nameof(Init)}] Hello from {AppDomain.CurrentDomain.FriendlyName} domain");
        listener.Callback();
    }
}

[Serializable]
public class Listener 
    : MarshalByRefObject
{
    public void Callback()
    {
        Console.WriteLine($"[{nameof(Callback)}] Hello from {AppDomain.CurrentDomain.FriendlyName} domain");
    }
}

When you run this code you will get the following result:

[Init] Hello from otherDomain domain
[Callback] Hello from Sandbox.vshost.exe domain

It shows that Init method was executed in a new domain but a callback in the original one. Now comment 2 lines with : MarshalByRefObject and run the program one more time. This time Listener will be passed to the new domain by value and the result will be:

[Init] Hello from Sandbox.vshost.exe domain
[Callback] Hello from Sandbox.vshost.exe domain
like image 165
Michał Komorowski Avatar answered Sep 20 '22 01:09

Michał Komorowski


Simply create remote host object in the main AppDomain and pass it to the newly initialized child domain. Whenever the child want to send data to the host, use this remote host object.

// This class provides callbacks to the host app domain.
// As it is derived from MarshalByRefObject, it will be a remote object
// when passed to the children.
// if children are not allowed to reference the host, create an IHost interface
public class DomainHost : MarshalByRefObject
{
    // send a message to the host
    public void SendMessage(IChild sender, string message)
    {
        Console.WriteLine($"Message from child {sender.Name}: {message}");
    }

    // sends any object to the host. The object must be serializable
    public void SendObject(IChild sender, object package)
    {
        Console.WriteLine($"Package from child {sender.Name}: {package}");
    }

    // there is no timeout for host
    public override object InitializeLifetimeService()
    {
        return null;
    }
}

I suspect that the children object you create already implement an interface so you can reference them from the main domain without loading their actual type. On initialization you can pass them the host object so after the initialization you can do callbacks from the children.

public interface IChild
{
    void Initialize(DomainHost host);

    void DoSomeChildishJob();

    string Name { get; }
}

ChildExample.dll:

internal class MyChild : MarshalByRefObject, IChild
{
    private DomainHost host;

    public void Initialize(DomainHost host)
    {
        // store the remote host here so you will able to use it to send feedbacks
        this.host = host;
        host.SendMessage(this, "I am being initialized.")
    }

    public string Name { get { return "Dummy child"; } }

    public void DoSomeChildishJob()
    {
        host.SendMessage(this, "Job started.")
        host.SendObject(this, 42);
        host.SendMessage(this, "Job finished.")
    }
}

Usage:

var domain = AppDomain.CreateDomain("ChildDomain");

// use the proper assembly and type name.
// child is a remote object here, ChildExample.dll is not loaded into the main domain
IChild child = domain.CreateInstanceAndUnwrap("ChildExample", "ChildNamespace.MyChild") as IChild;

// pass the host to the child
child.Initialize(new DomainHost());

// now child can send feedbacks
child.DoSomeChildishJob();
like image 35
György Kőszeg Avatar answered Sep 21 '22 01:09

György Kőszeg