I have the following code:
namespace ConsoleApplication
{
static void Main(string[] args)
{
Device device = new Device();
device.Command += new EventHandler<DeviceSpecialArgs>(device_Command);
}
public static void device_Command(Object source, DeviceSpecialArgs args)
{
Console.WriteLine("Command: {0}, Reguest: {1}", args.Command, args.Request);
}
}
}
I have to do the exact same thing but the assembly containing types Device and DeviceSpecialArgs need to be loaded at runtime. I know how to load the assembly with reflection but I find the event handling part puzzling:
namespace ConsoleApplication
{
static void Main(string[] args)
{
// Load the assembly
string dllPath = #"C:\Temp\Device.dll"
Assembly asm = Assembly.LoadFrom(dllPath);
// Instanciate Device
Type deviceType = asm.GetType("Device");
object device = Activator.CreateInstance(deviceType);
// How do I subscribe to the Command event?
}
// args would normally be a DeviceSpecialArgs but since that type is
// unknown at compile time, how do I prototype the handler?
public static void device_Command(Object source, ??? args)
{
Console.WriteLine("Command: {0}, Reguest: {1}", args.Command, args.Request);
}
}
How do I subscribe to the event using reflection? Also, how should I prototype the handler itself since the type of "args" is unknown at compile time? FYI, I'm C# 3 and .NET 3.5.
Firsly, look at the MAF.
Alternative way is to add to the first assembly a reference to the second one. Then create a couple of interfaces in the second assembly and make the classes in the first one to implement them:
public interface IDeviceSpecialArgs
{
string Command { get; }
string Request { get; }
}
public interface IDevice
{
event EventHandler<IDeviceSpecialArgs> Command;
}
The first assembly:
public sealed class DeviceSpecialArgs : EventArgs, IDeviceSpecialArgs
{
private readonly string command;
private readonly string request;
public string Command
{
get { return command; }
}
public string Request
{
get { return request; }
}
public DeviceSpecialArgs(string command, string request)
{
this.command = command;
this.request = request;
}
}
public class Device : IDevice
{
public event EventHandler<IDeviceSpecialArgs> Command;
...
}
In the second assembly simply cast newly instantiated objects to the corresponding interfaces:
IDevice device = Activator.CreateInstance(deviceType) as IDevice;
Now you can subscribe to the Command event, because it is declared in the IDevice interface:
device.Command += new EventHandler<IDeviceSpecialArgs>(device_Command);
EDIT: If you have no control over the assembly you are loading, please try the following code. It just creates a handler with the EventArgs type of the second argument and uses a reflection to get its properties:
internal class DeviceEvent
{
private readonly Type deviceType;
private readonly Type deviceSpecialArgsType;
public DeviceEvent()
{
// Load the assembly
const string dllPath = #"C:\Temp\Device.dll";
Assembly asm = Assembly.LoadFrom(dllPath);
// Get types
deviceType = asm.GetType("Device");
deviceSpecialArgsType = asm.GetType("DeviceSpecialArgs");
// Instantiate Device
object device = Activator.CreateInstance(deviceType);
// Subscribe to the Command event
deviceType.GetEvent("Command").AddEventHandler(device, (Delegate.CreateDelegate(typeof(EventHandler), GetType().GetMethod("Device_Command", BindingFlags.NonPublic))));
}
private void Device_Command(object sender, EventArgs e)
{
string command = deviceSpecialArgsType.GetProperty("Command", BindingFlags.Public).GetValue(e, null).ToString();
string request = deviceSpecialArgsType.GetProperty("Request", BindingFlags.Public).GetValue(e, null).ToString();
...
}
}
Related
So I have a dll file that generates people and data about them. Every time something changes the dll raises an event. I need to subscribe to the event and then process the data.
I have the following information about the dll
namespace PeopleGenerator
{
public class RawPeopleDataEventArgs : EventArgs
{
public RawPeopleDataEventArgs(List<string> peopleData)
{
PeopleData = peopleData;
}
public List<string> PeopleData { get; }
}
public interface IPeopleGenerator
{
event EventHandler<RawPeopleDataEventArgs> PeopleDataReady;
}
}
I have also been given info about a factory I can use to get an IPeopleGenerator object
namespace PeopleGenerator
{
public class PeopleGeneratorFactory
{
public static IPeopleGenerator CreatePeopleDataReceiver()
}
}
Now I have tried to make a subscription class
namespace Test
{
class EventSubscriber
{
public EventSubscriber(object o, RawPeopleDataEventArgs args)
{
List<string> listofpeople = args.PeopleData;
printList(listofpeople);
}
void printList(List<string> print)
{
print.ForEach(Console.WriteLine);
// More data processing to happen here
}
}
}
My problem is I cant't figure out how to start generating the data from the dll. with the factory class. My thought is something like this
static void Main(string[] args)
{
//generate a new object
EventSubscriber sub = new EventSubscriber(????);
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
You subscribe methods, not classes, modify your subscriber as following:
namespace Test
{
class EventSubscriber
{
public HandlePeople(object o, RawPeopleDataEventArgs args)
{
List<string> listofpeople = args.PeopleData;
printList(listofpeople);
}
void printList(List<string> print)
{
print.ForEach(Console.WriteLine);
// More data processing to happen here
}
}
}
And use instance of subscriber (and its method) to handle events:
static void Main(string[] args)
{
var recvr = PeopleGenerator.PeopleGeneratorFactory.CreatePeopleDataReceiver();
var subscriber = new EventSubscriber();
recvr.PeopleDataReady += new subscriber.Handle;
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
I have a plugin system where I use MarshalByRefObject to create isolated domains per plugin, so users can reload their new versions, as they see fit without having to turn off the main application.
Now I have the need to allow a plugin to view which plugins are currently running and perhaps start/stop a specific plugin.
I know how to issue commands from the wrapper, in the below code for example:
using System;
using System.Linq;
using System.Reflection;
using System.Security.Permissions;
namespace Wrapper
{
public class RemoteLoader : MarshalByRefObject
{
private Assembly _pluginAassembly;
private object _instance;
private string _name;
public RemoteLoader(string assemblyName)
{
_name = assemblyName;
if (_pluginAassembly == null)
{
_pluginAassembly = AppDomain.CurrentDomain.Load(assemblyName);
}
// Required to identify the types when obfuscated
Type[] types;
try
{
types = _pluginAassembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
types = e.Types.Where(t => t != null).ToArray();
}
var type = types.FirstOrDefault(type => type.GetInterface("IPlugin") != null);
if (type != null && _instance == null)
{
_instance = Activator.CreateInstance(type, null, null);
}
}
public void Start()
{
if (_instance == null)
{
return;
}
((IPlugin)_instance).OnStart();
}
public void Stop()
{
if (_instance == null)
{
return;
}
((IPlugin)_instance).OnStop(close);
}
}
}
So then I could, for example:
var domain = AppDomain.CreateDomain(Name, null, AppSetup);
var assemblyPath = Assembly.GetExecutingAssembly().Location;
var loader = (RemoteLoader)Domain.CreateInstanceFromAndUnwrap(assemblyPath, typeof(RemoteLoader).FullName);
loader.Start();
Of course the above is just a resumed sample...
Then on my wrapper I have methods like:
bool Start(string name);
bool Stop(string name);
Which basically is a wrapper to issue the Start/Stop of a specific plugin from the list and a list to keep track of running plugins:
List<Plugin> Plugins
Plugin is just a simple class that holds Domain, RemoteLoader information, etc.
What I don't understand is, how to achieve the below, from inside a plugin. Be able to:
View the list of running plugins
Execute the Start or Stop for a specific plugin
Or if this is even possible with MarshalByRefObject given the plugins are isolated or I would have to open a different communication route to achieve this?
For the bounty I am looking for a working verifiable example of the above described...
First let's define couple of interfaces:
// this is your host
public interface IHostController {
// names of all loaded plugins
string[] Plugins { get; }
void StartPlugin(string name);
void StopPlugin(string name);
}
public interface IPlugin {
// with this method you will pass plugin a reference to host
void Init(IHostController host);
void Start();
void Stop();
}
// helper class to combine app domain and loader together
public class PluginInfo {
public AppDomain Domain { get; set; }
public RemoteLoader Loader { get; set; }
}
Now a bit rewritten RemoteLoader (did not work for me as it was):
public class RemoteLoader : MarshalByRefObject {
private Assembly _pluginAassembly;
private IPlugin _instance;
private string _name;
public void Init(IHostController host, string assemblyPath) {
// note that you pass reference to controller here
_name = Path.GetFileNameWithoutExtension(assemblyPath);
if (_pluginAassembly == null) {
_pluginAassembly = AppDomain.CurrentDomain.Load(File.ReadAllBytes(assemblyPath));
}
// Required to identify the types when obfuscated
Type[] types;
try {
types = _pluginAassembly.GetTypes();
}
catch (ReflectionTypeLoadException e) {
types = e.Types.Where(t => t != null).ToArray();
}
var type = types.FirstOrDefault(t => t.GetInterface("IPlugin") != null);
if (type != null && _instance == null) {
_instance = (IPlugin) Activator.CreateInstance(type, null, null);
// propagate reference to controller futher
_instance.Init(host);
}
}
public string Name => _name;
public bool IsStarted { get; private set; }
public void Start() {
if (_instance == null) {
return;
}
_instance.Start();
IsStarted = true;
}
public void Stop() {
if (_instance == null) {
return;
}
_instance.Stop();
IsStarted = false;
}
}
And a host:
// note : inherits from MarshalByRefObject and implements interface
public class HostController : MarshalByRefObject, IHostController {
private readonly Dictionary<string, PluginInfo> _plugins = new Dictionary<string, PluginInfo>();
public void ScanAssemblies(params string[] paths) {
foreach (var path in paths) {
var setup = new AppDomainSetup();
var domain = AppDomain.CreateDomain(Path.GetFileNameWithoutExtension(path), null, setup);
var assemblyPath = Assembly.GetExecutingAssembly().Location;
var loader = (RemoteLoader) domain.CreateInstanceFromAndUnwrap(assemblyPath, typeof (RemoteLoader).FullName);
// you are passing "this" (which is IHostController) to your plugin here
loader.Init(this, path);
_plugins.Add(loader.Name, new PluginInfo {
Domain = domain,
Loader = loader
});
}
}
public string[] Plugins => _plugins.Keys.ToArray();
public void StartPlugin(string name) {
if (_plugins.ContainsKey(name)) {
var p = _plugins[name].Loader;
if (!p.IsStarted) {
p.Start();
}
}
}
public void StopPlugin(string name) {
if (_plugins.ContainsKey(name)) {
var p = _plugins[name].Loader;
if (p.IsStarted) {
p.Stop();
}
}
}
}
Now let's create two different assemblies. Each of them needs only to reference interfaces IPlugin and IHostController. In first assembly define plugin:
public class FirstPlugin : IPlugin {
const string Name = "First Plugin";
public void Init(IHostController host) {
Console.WriteLine(Name + " initialized");
}
public void Start() {
Console.WriteLine(Name + " started");
}
public void Stop() {
Console.WriteLine(Name + " stopped");
}
}
In second assembly define another plugin:
public class FirstPlugin : IPlugin {
const string Name = "Second Plugin";
private Timer _timer;
private IHostController _host;
public void Init(IHostController host) {
Console.WriteLine(Name + " initialized");
_host = host;
}
public void Start() {
Console.WriteLine(Name + " started");
Console.WriteLine("Will try to restart first plugin every 5 seconds");
_timer = new Timer(RestartFirst, null, 5000, 5000);
}
int _iteration = 0;
private void RestartFirst(object state) {
// here we talk with a host and request list of all plugins
foreach (var plugin in _host.Plugins) {
Console.WriteLine("Found plugin " + plugin);
}
if (_iteration%2 == 0) {
Console.WriteLine("Trying to start first plugin");
// start another plugin from inside this one
_host.StartPlugin("Plugin1");
}
else {
Console.WriteLine("Trying to stop first plugin");
// stop another plugin from inside this one
_host.StopPlugin("Plugin1");
}
_iteration++;
}
public void Stop() {
Console.WriteLine(Name + " stopped");
_timer?.Dispose();
_timer = null;
}
}
Now in your main .exe which hosts all plugins:
static void Main(string[] args) {
var host = new HostController();
host.ScanAssemblies(#"path to your first Plugin1.dll", #"path to your second Plugin2.dll");
host.StartPlugin("Plugin2");
Console.ReadKey();
}
And the output is:
First Plugin initialized
Second Plugin initialized
Second Plugin started
Will try to restart first plugin every 5 seconds
Found plugin Plugin1
Found plugin Plugin2
Trying to start first plugin
First Plugin started
Found plugin Plugin1
Found plugin Plugin1
Found plugin Plugin2
Trying to stop first plugin
Found plugin Plugin2
Trying to stop first plugin
First Plugin stopped
First Plugin stopped
Found plugin Plugin1
Found plugin Plugin2
Trying to stop first plugin
You can make a plugin ask it's host to perform these actions. You can pass to the RemoteLoader an instance of a MarshalByRefObject derived class that is created by the host. The RemoteLoader can then use that instance to perform any action.
You also can make the plugins communicate with each other by passing a suitable MarshalByRefObject from the host to each plugin. I'd recommend routing all actions through the host, though, because it's a simpler architecture.
I'm building an email-monitoring framework that I'll be using for a handful of users, so I'm building a class library to wrap everything in. I'm instantiating the configuration (sender, subject, last-received, ...) in a static class. Therefore, I have something like this.
public static class MyConfig
{
public static int Sender { get; set; }
// and so on and so forth
public static void BuildMyConfig(string theSender, string theRecipient, ...)
{
Sender = theSender;
// yada yada yada...
}
}
public class Monitoring
{
public delegate void DoSomethingWithEmail(EmailContents theContents);
public void StartMonitoring() {
//When I get an email, I call the method
DoSomethingWithEmail(theEmailWeJustGot);
}
}
Obviously, what we do with the email will be something completely different in each case. What I'm trying to is instantiate that delegate. Where would I do that? The MyConfig class and then invoke it from there as a static method? The instance of the Monitoring class?
An application would look like...
public class SpecificMonitor
{
Monitoring.BuildMyConfig("foo#bar.com", "bar#foo.com", ...);
Monitoring m = new Monitoring();
m.StartMonitoring();
//But where do I build the delegate method???
}
I've gotten compiling errors with every option I've tried so far. I've also tried overriding a method instead of using a delegate, using interfaces... but I think delegation is where it's at.
Thanks in advance!
Consistent with the rest of your design (although I do not necessarily agree that the design is great) you could allow for the callback to be set in the configuration class
public static class MyConfig
{
public static string Sender { get; set; }
public static DoSomethingWithEmail EmailReceivedCallback { get; set; }
public static void BuildMyConfig(string theSender, string theRecipient,
DoSomethingWithEmail callback)
{
Sender = theSender;
EmailReceivedCallback = callback;
}
}
// Make sure you bring the delegate outside of the Monitoring class!
public delegate void DoSomethingWithEmail(string theContents);
When an incoming email is acknowledged by your application you can now pass the email to the callback assigned to the configuration class
public class Monitoring
{
public void StartMonitoring()
{
const string receivedEmail = "New Answer on your SO Question!";
//Invoke the callback assigned to the config class
MyConfig.EmailReceivedCallback(receivedEmail);
}
}
Here is an example of usage
static void Main()
{
MyConfig.BuildMyConfig("...", "...", HandleEmail);
var monitoring = new Monitoring();
monitoring.StartMonitoring();
}
static void HandleEmail(string thecontents)
{
// Sample implementation
Console.WriteLine("Received Email: {0}",thecontents);
}
Define the constructor so that when people instantiate a Monitoring object, they must define the delegate:
public class Monitoring
{
public delegate void DoSomethingWithEmail(EmailContents theContents);
public Monitoring(Delegate DoSomethingWithEmail)
{
this.DoSomethingWithEmail = DoSomethingWithEmail;
}
public void StartMonitoring() {
//When I get an email, I call the method
DoSomethingWithEmail(theEmailWeJustGot);
}
}
Then pass in the delegate you want when you instantiate each Monitoring:
Monitoring m = new Monitoring(delegate(EmailContents theContents)
{
/* Do stuff with theContents here */
});
m.StartMonitoring();
I am trying to load an assembly during run-time and subscribe to its events. In my scenario the dll file has an ADD method that gets two integers as arguments and raises an event with a custom event argument that contains the sum.
Here is a part of my code to load the Dll file:
Assembly asm = Assembly.LoadFile(#"C:\Projects\Dll1.Dll");
Type typ = asm.GetType("DLL1.Class1", true, true);
var method = typ.GetMethod("add");
var obj = Activator.CreateInstance(typ);
EventInfo ev1 = typ.GetEvents()[0]; // just to check if I have the proper event
Type tDelegate = ev1.EventHandlerType; // just to check if I have the proper delegate
method.Invoke(obj, new object[] { 1, 0 });
But, I have no idea how to subscribe to the event raised by the assembly. Any help would be appreciated.
Added: example DLL source
namespace Dll1
{
public class Class1
{
int c = 0;
public void add(int a, int b)
{
c = a + b;
if (Added !=null)
Added(this, new AddArgs(c));
}
public delegate void AddHandler(object sender, AddArgs e);
public event AddHandler Added;
}
public class AddArgs : EventArgs
{
private int intResult;
public AddArgs(int _Value)
{
intResult = _Value;
}
public int Result
{
get { return intResult; }
}
}
}
Just take the ev1 you already have and call AddEventHandler like this:
ev1.AddEventHandler(obj, MyEventHandlerMethod);
however, you'll want to make sure you cleanup the handler by calling RemoveEventHandler so that garbage collection can occur.
ev1.RemoveEventHandler(obj, MyEventHandlerMethod);
Apologies had a typo...have edited...
I have a weird issue I am not sure about.
In one piece of code I have a class which is called as a singleton which has an event other classes can listen to, pretty straightforward by doing something like
Client.Instance.MyEvent += new EventHandler<EventArgs>(myHandler);
So if I have a generic class:
Class MyTest {
public MyTest() {
System.Console.WriteLine("In Constructor Registering Events");
Client.Instance.MyEvent += new EventHandler<EventArgs>(myHandler);
}
private void myHandler(object sender, EventArgs arg) {
System.Console.WriteLine("Got event!");
}
}
Now if i create the class like:
MyTest mC = new MyTest ();
Client.Instance.FireEvent();
I get the expected "In Constructor Registering Events" and "Got Event"
However if i create the class through Reflection, I do not.
Type mType = typeof(MyTest);
object mT = Activator.CreateInstance(mType);
Client.Instance.FireEvent();
All i get is "In Constructor Registering Events" but i DO NOT get the event fired message. whats going on here? Am i doing something incorrectly in my reflection calls?
Thanks -
I've just tested your claim and, with the proper type, it works the same whether the object is created using new or via reflection.
The following Working Demo can be tested here
public class Program
{
public static void Main(string[] args)
{
Client.Instance.MyEvent += delegate { Console.WriteLine("MY EVENT handled from Main"); };
MyTest mt = new MyTest();
Type mType = typeof(MyTest);
object reflectedMT = Activator.CreateInstance(mType);
Client.Instance.FireEvent();
}
}
public class Client {
private Client() {}
private static Client _inst = new Client();
public static Client Instance { get { return _inst; } }
public void FireEvent() { if(MyEvent != null) MyEvent(this, EventArgs.Empty); }
public event EventHandler<EventArgs> MyEvent;
}
public class MyTest {
public MyTest() {
System.Console.WriteLine("In Constructor Registering Events");
Client.Instance.MyEvent += new EventHandler<EventArgs>(myHandler);
}
private void myHandler(object sender, EventArgs arg) {
System.Console.WriteLine("Got event!");
}
}