views:

183

answers:

4

Can an abstract class be used as the contract object between a 'Host' and a 'plugin'? The idea is that the plugin inherits the contract (we call it an adapter). We are also understanding that all participants in the framework must inherit MarshalByRefObject (MBRO). So, this is what we were thinking -

Host:

class Host : MarshalByRefObject
{
}

Contract:

public abstract class PluginAdapter : MarshalByRefObject
{
}

Plugin:

class myPlugin : PluginAdapter
{
}

All three exist in separate asm's. Our Host will create a new AppDomain for each plugin, and the PluginAdapter is created as follows:

{
    ObjectHandle instHandle = Activator.CreateInstance(
    newDomain, data.Assembly.FullName, data.EntryPoint.FullName);

    PluginAdapter adapter = (PluginAdapter)instHandle.Unwrap();
}

EDIT: where data is the concrete type of myPlugin.

We were wondering if this implementation of the framework would work. We have seen articles using an interface (IPlugin) for the plugin derivation, and a concrete class as the contract. Those articles would also say that an abstract class can be used, but no examples of that implementation given. Is it required that the contract be a concrete class?

EDIT: In this example by Richard Blewett - C# Reflection - he uses a much simpler implementation:

Contract:

public interface IPlugIn  
{  
    // do stuff  
}

Plugin:

public class PlugIn : MarshalByRefObject, IPlugIn  
{  
}

Now, if using an abstract class as the contract, the Plugin cannot inherit both the contract and MBRO. What, then, becomes the best implementation for a scalable plugin framework. Should we go ahead and implement remoting even though, initially, we are developing for single-machine operation? This project is expected to become distributed across a network, possibly across the Internet as well. We simply have not implemented Tcp yet because we are trying to get the basics of a plugin framework fully understood and operational.

Does it make sense to implement Tcp remoting on a single machine using loopback?

A: 

If data.EntryPoint.FullName refers to the concrete type myPlugin I don't see any reason why this wouldn't work (unless than having assembly loading issues in the other appdomain but that's a different issue).

Florian Doyon
Yes...added edit for clarification that `data.EntryPoint.FullName` refers to the concrete type `myPlugin`.
dboarman
+2  A: 

Provided "data.EntryPoint.FullName" is the full type name, the above code should work.

However, if you're trying to keep this type isolated in its own AppDomain, you should be careful here. By doing data.Assembly, you'll pull the assembly (and it's types) into your AppDomain, causing the types to be loaded in the executing AppDomain...

Reed Copsey
This does seem to 'mostly' work until we added events defined in the abstract contract. When the Host subscribes to the events, we get a TargetInvocationException where the assembly could not be resolved. So that may need to be another issue. We just wanted to start with the basic question "will this work"...
dboarman
+4  A: 

Abstract classes are better choices for this, imho. Its primarily because interfaces are harder to version. This blog post describes the issue you might find yourself having down the road if you don't use base classes. This rule doesn't only apply to plugins, btw.

About your design...

Plugins shouldn't extend MBRO. You should use your Host (which should extend MBRO) to marshall all calls across to your plugins, including handling plugin events. Its very easy to unintentionally load a plugin DLL into your main appdomain if you attempt to pull them across and use their proxies.

For example, if the plugin returns an IEnumerable for one of its methods, it may return an implementation of IEnumerable that is defined in the plugin assembly. If that doesn't extend MBRO the main appdomain will have to load the plugin assembly.


I've uploaded three projects dealing with appdomains here:

http://cid-f8be9de57b85cc35.skydrive.live.com/self.aspx/Public/NET%20AppDomain%20Tests/appdomaintests.zip

One is using callbacks across appdomains, the second is cross-appdomain event handling, and the third is a plugin example.

In the plugin example, an application defines a plugin interface (its a demo, not best practices!) and a plugin host. The app loads a plugin assembly raw from disk and passes it over to the plugin appdomain via the plugin host proxy, where it is loaded. The plugin host then instantiates the plugin and uses it. But when the host returns a type defined in the plugin assembly back over into the application appdomain, the plugin assembly is loaded into the main application domain, rendering the whole plugin thing pointless.

Best thing to avoid this is provide an abstract base class for plugins that is not marked serializable and does not extend MBRO, and to only bring back primitives or sealed types you define back across the boundary from the plugin domain.

NOTE: the projects are all 4.0 RC. You'll need this or above to run them. Otherwise you'll have to edit the project files by hand or rebuild them to get them running in b2 or 2008.

Will
From what we have experienced, if `myPlugin` does not extend MBRO, then we get an exception that it isn't serializable. We know that `[Serializable]` would effectively instance the class in the Host's appdomain rather than the plugin's appdomain.
dboarman
@dboarman you're getting that because you're pulling the plugin from the plugin domain BACK to the application domain. As I stated, when this happens you are endangering yourself. If you are trying to keep plugins in their own app domain, you must control every type communcated between app domains. Your plugin host must exist within the plugin domain. Types it sends back must only be those types you supply. To do otherwise risks loading plugin assemblies in your domain. Those "not serializable" exceptions are GOOD. They tell you you're doing something wrong--bringing their code over.
Will
@dboarman managing domains is a tricky process and takes some time and effort to get right. The easiest way to do it is to have a single type which you write that you unwrap in the plugin domain. This type is responsible for loading and managing plugins. Anything transmitted from the plugin domain must be a type you supply and they should be sealed to prevent plugins from returning their own versions. Creating a small scale prototype of your system can be very helpful for refining these techniques; I did that and learned a lot.
Will
In the 2nd edit I provided, the link to Richard Blewett's sample does show the plugin extending MBRO. His "host" and contract do not extend MBRO. I'm really trying to understand the difference and why one method would be chosen over another.
dboarman
@dboarman well, examples aren't always the best design or the best code. I have a decent test app that I used to test out some cross-domain scenarios. Respond back so I'll remember to upload it somewhere.
Will
@Will: I'd be grateful for something like that. We have a forum for our little team - it's on my profile. If you make an account I believe you can upload files.
dboarman
@dboarman I uploaded it to skydrive so anyone who reads this can grab it.
Will
@Will: I've grabbed it and reading with a keen eye...thx much.
dboarman
@dboarman np this stuff is confusing to begin with, and you're bound to get bitten by the various implementation "magics' that normally gets hidden from you by the CLR/compiler/framework.
Will
+1  A: 

You may want to take a look at MAF (the Managed Addin Framework) which is an extensibility framework built into .NET for doing Addins. It is similar (and older) than MEF (Managed Extensibility Framework), but has more options as far as keeping plugins in their own app domain, among other things.

Nick
I have looked at MAF, and Mono.Addins. We are targeting a broader base than Windows machines.
dboarman