Events between appdomains

  • Thread starter Thread starter mons
  • Start date Start date
M

mons

I want to receive events from another appdomain.

I use a separate appdomain as a sandbox and whant to know and have
control over which assemblies get loaded. I need to comunicate with a
library in the sanbox appdomain from a plugin in my other appdomain,
without my plugin beeing loaded into the sandbox. (the library is
linked to by the plugin AND loaded in the sandbox)

I first wrote an interface and stuck it in the library. This way I
could cast myself to the interface and send myself over to the sandbox
as a proxy and tie the events to the proxy and be fine... thought I.

Now this does not work since even when I send the AssemblyName
(contains full path and full name and everything) over to the sandbox
when loding the library, the libraries in the two appdomains are
treated as different and i get an invalide cast exeption :-(

Now I'm looking for a way around through using a suitable class in the
..NET framework. A class that act as a event router/callback-thing. A
class to which i can hook up my event and then send it over to the
sandbox and then hook up the events from their, and have my
communication link.

Is there such a class??

this whole thig seem largely over-complicatified. Any ideas as of how
to get an event from one appdomain across to another without linking in
other asemblies then those in the .Net Framework?
 
this whole thig seem largely over-complicatified. Any ideas as of how
to get an event from one appdomain across to another without linking
in other asemblies then those in the .Net Framework?

There's a whole namespace for that: System.Runtime.Remoting, you must use
it for inter-AppDomain communications. Google for "remoting events' and you'll
get tons of tips and sample code.

HTH,
Alexander
 
Thanx Alexander... but ther is a but

Remoting is exactly what I'm doing. Sending myself over as a proxy (or
deriving from MarshalByRefObject if you like).

The thing is when you send an object via remoting it is actually
serialized, sent to the other appdomain. That appdomain must then have
a loaded defenition of the type to instanciate first a real proxy
object and then a transparent proxyobject on which you can performe
operations and have them realy performed on the object in the first
appdomain.

If I dont want bouth assembies loaded in both appdomains, the standard
workaround is to put an interface in a third assembly and link
staticaly to that from the two comunicating assemblies. (standard a
server and a client with the interface in a shared dll).

Now I don't want to do that because one assemby is a general library
and should not link to anything other than .NET framework.

Now I already have the library loaded in the two appdomains so I
thought putting the interface in there but this causes problems with
..NET seeing the same assembly as two different ones and wouln't let you
cast. And I am again out of luck :-(

/mons
 
I'm confused... Why are you getting invalid cast exception? I created a small
sample which loads System.Data assembly ("library" in your setup) to another
AppDomain and creates a SqlConnection object. If you put a breakpoint and
look at conn variable in a call stack you'll see that it has System.Runtime.Remoting.TransparentProxy
type. And no casting exceptions...

System.Data assembly in my case is what is shared between the host AppDoman
and sandbox AppDomain. In your case it would be what you call "library".
Obviously, System.Data.dll knows nothing about my code, yet events work.
I simply don't see where's the problem here.

Can you please give us more info on what's going wrong?

Alexander


using System;
using System.Reflection;
using System.Data.SqlClient;

class MainClass
{
public static void Main(String[] args)
{
AppDomain sandbox = AppDomain.CreateDomain("sandbox");

SqlConnection conn = (SqlConnection)sandbox.CreateInstanceAndUnwrap("System.Data,
Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, Custom=null",

"System.Data.SqlClient.SqlConnection");

Console.WriteLine("conn variable is of type " + conn);

conn.StateChange += new System.Data.StateChangeEventHandler(conn_StateChange);

conn.ConnectionString = "Server=.;User ID=sa;Password=password;Database=Orchard";

conn.Open();
conn.Close();

AppDomain.Unload(sandbox);

try
{
Console.WriteLine(conn.State);
}
catch (AppDomainUnloadedException ex)
{
Console.WriteLine(ex.Message);
}


}

public static void conn_StateChange(object sender, System.Data.StateChangeEventArgs
e)
{
Console.WriteLine("Was: " + e.OriginalState.ToString() + "\nNow: " + e.CurrentState.ToString());

}
}
 
This is an interesting read!! Please find the below article to learn more about sending events across AppDomains.
blog.vcillusion.co.in/sending-events-through-application-domain-boundary/

An application domain is an isolated environment where applications execute. In other words, it's a partition in an operating system process where one or more applications reside.

1. AppDomain allows us to load DLL at runtime.
2. For communication between 'AppDomain' boundary, the types should be Serializable.
3. Derive from class MarshalByRefObject which enables access to objects across application domain boundaries in applications that support remoting.
4. Full name of DLL assembly is used to load it into AppDomain. For now, we place it in the same folder as the main program.

In this section, we detailed how to achieve sending and receiving events through Application Domain boundary. Here we are using the shared universal library with interfaces already known to us, and two separate publisher and subscriber DLLs loaded on runtime and fire events across domains.

For understanding, we use four separate projects.

1. **EventsCommon (Class Library Project)**
It defines standard interfaces for Publisher and Subscriber classes, and the Main Class uses it to create interface objects.


namespace EventCommons
{
using System;

/// <summary>
/// Common Interface for Publisher
/// </summary>
public interface IEventCommonGenerator
{
/// <summary>
/// Name Generator with <see cref="Action{T}"/> accepts string and return void
/// </summary>
event Action<string> NameGenerator;

/// <summary>
/// Fire Events
/// </summary>
/// <param name="input"></param>
void FireEvent(string input);
}

/// <summary>
/// Common Interface for Subscriber
/// </summary>
public interface IEventCommonCatcher
{
/// <summary>
/// Print Events executed
/// </summary>
/// <returns></returns>
string PrintEvents();

/// <summary>
/// Subscribe to Publisher's <see cref="IEventCommonGenerator.NameGenerator"/> event
/// </summary>
/// <param name="commonGenerator"></param>
void Subscribe(IEventCommonGenerator commonGenerator);
}
}


2. **EventsPublisher (Class Library Project)**
It references EventCommon project and implements Publisher related Interface IEventCommonGenerator from EventCommon.

namespace EventsPublisher
{
using EventCommons;
using System;

/// <summary>
/// Implements <see cref="IEventCommonGenerator"/> from <see cref="EventCommons"/>
/// </summary>
[Serializable]
public class EventsGenerators : IEventCommonGenerator
{
/// <summary>
/// Fires Event
/// </summary>
/// <param name="input"></param>
public void FireEvent(string input)
{
this.NameGenerator?.Invoke(input);
}

/// <summary>
/// Event for Publisher
/// </summary>
public event Action<string> NameGenerator;
}
}


3. **EventsSubscriber (Class Library Project)**
It references EventCommon project and implements Subscriber-related Interface IEventCommonCatcher from EventCommon.


namespace EventsSubscriber
{
using System;
using System.Collections.Generic;
using EventCommons;

/// <summary>
/// Implements <see cref="IEventCommonCatcher"/> from <see cref="EventCommons"/>
/// </summary>
[Serializable]
public class EventsCatcher : IEventCommonCatcher
{
/// <summary>
/// Initializes object of <see cref="ReceivedValueList"/> and <see cref="EventsCatcher"/>
/// </summary>
public EventsCatcher()
{
this.ReceivedValueList = new List<string>();
}

/// <summary>
/// Subscribes to the Publisher
/// </summary>
/// <param name="commonGenerator"></param>
public void Subscribe(IEventCommonGenerator commonGenerator)
{
if (commonGenerator != null)
{
commonGenerator.NameGenerator += this.CommonNameGenerator;
}
}

/// <summary>
/// Called when event fired from <see cref="IEventCommonGenerator"/> using <see cref="IEventCommonGenerator.FireEvent"/>
/// </summary>
/// <param name="input"></param>
private void CommonNameGenerator(string input)
{
this.ReceivedValueList.Add(input);
}

/// <summary>
/// Holds Events Values
/// </summary>
public List<string> ReceivedValueList { get; set; }

/// <summary>
/// Returns Comma Separated Events Value
/// </summary>
/// <returns></returns>
public string PrintEvents()
{
return string.Join(",", this.ReceivedValueList);
}
}
}


4. **CrossDomainEvents (Main Console Application)**
It loads EventsPublisher into Publisher AppDomain and EventsSubscriber into Subscriber AppDomain, Subscribes Events of Publisher AppDomain into Subscriber AppDomain and Fires the event.

using System;

namespace CrossDomainEvents
{
using EventCommons;

class Program
{
static void Main()
{
// Load Publisher DLL
PublisherAppDomain.SetupDomain();
PublisherAppDomain.CustomDomain.Load("EventsPublisher, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
var newPublisherGenerator = PublisherAppDomain.Instance as IEventCommonGenerator;

// Load Subscriber DLL
SubscriberAppDomain.SetupDomain(newPublisherGenerator);
SubscriberAppDomain.CustomDomain.Load("EventsSubscriber, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
var newSubscriberCatcher = SubscriberAppDomain.Instance as IEventCommonCatcher;

// Fire Event from Publisher and validate event on Subscriber
if (newSubscriberCatcher != null && newPublisherGenerator != null)
{
// Subscribe Across Domains
newSubscriberCatcher.Subscribe(newPublisherGenerator);

// Fire Event
newPublisherGenerator.FireEvent("First");

// Validate Events
Console.WriteLine(newSubscriberCatcher.PrintEvents());
}

Console.ReadLine();
}
}

/// <summary>
/// Creates Publisher AppDomain
/// </summary>
public class PublisherAppDomain : MarshalByRefObject
{

public static AppDomain CustomDomain;
public static object Instance;

public static void SetupDomain()
{
// Domain Name EventsGenerator
CustomDomain = AppDomain.CreateDomain("EventsGenerator");
// Loads EventsPublisher Assembly and create EventsPublisher.EventsGenerators
Instance = Activator.CreateInstance(CustomDomain, "EventsPublisher, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "EventsPublisher.EventsGenerators").Unwrap();
}
}

/// <summary>
/// Creates Subscriber AppDomain
/// </summary>
public class SubscriberAppDomain : MarshalByRefObject
{

public static AppDomain CustomDomain;
public static object Instance;

public static void SetupDomain(IEventCommonGenerator eventCommonGenerator)
{
// Domain Name EventsCatcher
CustomDomain = AppDomain.CreateDomain("EventsCatcher");
// Loads EventsSubscriber Assembly and create EventsSubscriber.EventsCatcher
Instance = Activator.CreateInstance(
CustomDomain,
"EventsSubscriber, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
"EventsSubscriber.EventsCatcher").Unwrap();
}
}

}


**Note:**
We need to ensure that the EventsSubscriber.dll and EventsPublisher.dll are present in the same folder as CrossDomainEvents.exe. It can be done using XCOPY command in Publisher and Subscriber projects to paste DLL in CrossDomainEvents Project Output Directory.
 
Last edited by a moderator:
Back
Top