Allow C # plugins to register on application hooks

I am creating a .NET-based application and want to allow a more extensible and pluggable design.

For simplicity, the application provides a set of operations and events:

  • DoSomething ()
  • DoOtherThing ()
  • Onerror
  • Onsuccess

I would suggest โ€œpluginsโ€ for loading and connecting to some of these operations (something like: when Event1 fires, start plugin1).

For example, run plugin1.HandleError () when the OnError event is fired.

This can be easily done with event subscription:

this.OnError += new Plugin1().HandleError(); 

The problem is that:

  • My application does not know the type "Plugin1" (this is a plugin, my application does not directly link).
  • Doing this will create an instance of the plugin until time, something I do not want to do.

In the "traditional" plug-in models, the application (the "client" of the plug-ins) downloads and executes the plug-in code at certain key points. For example, an image processing application when a specific operation is performed).

The control of when to create an instance of the plugin code and when to execute it is known to the client application.

In my application, the plugin itself must decide when it should execute ("The plugin should register in the OnError event").

Saving the plug-in "execution" code along with the "registration" code is a problem due to which the plug-in DLL will be loaded into memory during registration, which I want to prevent.

For example, if I add the Register () method to the plugin DLL, the DLL plugin will need to load into memory to call the Register method.

What could be a good design solution for this particular problem?

  • Lazy loading (or suggesting lazy / impatient loading) of plug-in DLL modules.
  • Providing plugins for managing various parts of the system / application to which they are connected.
+8
design c # plugins architecture
source share
4 answers

You are trying to solve a problem that does not exist. The mental image of all types loaded when your code calls Assembly.LoadFrom () is incorrect. The .NET infrastructure makes full use of Windows, which is an operational virtual memory system with requests on demand. And then some.

The ball rolls when you call LoadFrom (). As a result, the CLR creates a memory mapping file, the main abstraction of the operating system. It updates a bit of internal state in order to track the assembly, which is now in AppDomain, it is very insignificant. The MMF sets the memory mapping up, creating virtual memory pages that display the contents of the file. Just a small descriptor in the TLB processor. Nothing happens in the build file.

You will then use reflection to try to discover a type that implements the interface. This causes the CLR to read some of the assembly metadata from the assembly. At this point, page errors cause the processor to display the contents of some pages that span the assembly metadata section in RAM. A few kilobytes, possibly more if the assembly contains many types.

Then the just-in-time compiler goes into action to generate code for the constructor. This causes the processor to corrupt the page containing the IL constructor in RAM.

Etcetera. The basic idea is that the contents of the assembly are always read lazily only when necessary. This mechanism is no different for plugins; they work just like regular builds in your solution. The only difference is that the order is slightly different. First you load the assembly, and then immediately call the constructor. Unlike calling the type constructor in your code and CLR, load the assembly immediately. It takes so long.

+5
source share

What you need to do is find the path to the dll and then create an assembly object from it. From there you will need to get the classes that you want to get (for example, everything that implements your interface):

 var assembly = Assembly.Load(AssemblyName.GetAssemblyName(fileFullName)); foreach (Type t in assembly.GetTypes()) { if (!typeof(IMyPluginInterface).IsAssignableFrom(t)) continue; var instance = Activator.CreateInstance(t) as IMyPluginInterface; //Voila, you have lazy loaded the plugin dll and hooked the plugin class to your code } 

Of course, from here you can do whatever you want, use methods, subscribe to events, etc.

+4
source share

To load plugin collections, I lean toward the IoC container to load assemblies from the directory (I use StructureMap), although you can manually load them according to @Oskar's answer.

If you want to support loading plugins while your application is running, StructureMap can be "reconfigured", thus choosing new plugins.

For your applications, you can send events to the event bus. In the example below, the StructureMap structure allows you to find all registered event handlers, but you can use the usual old reflection or another IoC container:

 public interface IEvent { } public interface IHandle<TEvent> where TEvent : IEvent { void Handle(TEvent e); } public static class EventBus { public static void RaiseEvent(TEvent e) where TEvent : IEvent { foreach (var handler in ObjectFactory.GetAllInstances<IHandle<TEvent>>()) handler.Handle(e); } } 

Then you can create an event as follows:

 public class Foo { public Foo() { EventBus.RaiseEvent(new FooCreatedEvent { Created = DateTime.UtcNow }); } } public class FooCreatedEvent : IEvent { public DateTime Created {get;set;} } 

And process it (e.g. in your plugin):

 public class FooCreatedEventHandler : IHandle<FooCreatedEvent> { public void Handle(FooCreatedEvent e) { Logger.Log("Foo created on " + e.Created.ToString()); } } 

I would recommend this post to Shannon Deminic, which covers many of the problems with developing plug-in applications. This is what we used as the base for our own โ€œplugin managerโ€.

Personally, I would not download assemblies on demand. IMO, it is better to have a slightly longer launch time (even less problem in the web application) than users of the running application, who must wait for the necessary plug-ins to load.

+2
source share
+1
source share

All Articles