Yes, doing PlugIns usually involves using either interfaces or base classes that the Plug-in client has to implement.
You want to be careful with this though, lest these interfaces become to complex. Just like anything keep it simple and create multiple interfaces rather than a single one and then have the application check whether the interface is implemented before making the the call to a particular plug in method.
The key issue you're facing probably is the fact that you typically don't have access to the bus object on a form because it's marked protected or private. So if you want this sort of functionality you'll need to expose this stuff as Public. No amount of interface work will let you get around that <g>... As an alternative you can pass information to the plug in from the form. You could have a method on the form that knows how to generically setup a plug in. A method say ConfigurePlugIn. When the Plugin is activated it can call this method and it gets passed the business object or objects or whatever you need to work with.
I just went through this exercise with our Web Monitor product (
http://www.west-wind.com/webmonitor/) which has a pretty simple plug-in architecture. Typically you want to support two kinds of interfaces - an interactive 'pop-up' type that can be activated from the menu and a hooked interface that gets invoked when some actions occur. For example, in Web Monitor the Add-in gets notified when a site is checked, when a log entry is written when an email is sent etc.
What I did is hook the hook interfaces with events which is the easiest usually. So you have some place where you load addins and hook up the events. The user either hooks up the events themselves or you use flags for it. For example:
public bool LoadAddIns(WebMonitorSiteList SiteList,WebMonitorMain IDE)
{
Assembly Assm;
this.ErrorMessage = "";
this.List.Clear();
if ( !Directory.Exists("addins") )
return true;
string[] Files = Directory.GetFiles("addins","*.dll");
Factory = new AppDomainObjectFactory();
foreach(string File in Files)
{
try
{
Assm = Assembly.LoadFrom(File);
}
catch(Exception ex)
{
this.ErrorMessage += ex.Message + "\r\n";
continue;
}
Type[] Types = Assm.GetTypes();
foreach(Type T in Assm.GetTypes() )
{
object[] arr = T.GetCustomAttributes( typeof(WebMonitorAddinAttribute),false);
if ( arr.Length == 0)
continue;
string Description = ((WebMonitorAddinAttribute) arr[0]).Description;
IWebMonitorAddin Addin = Factory.CreateObject(Directory.GetCurrentDirectory() + "\\" + File,T.FullName) as IWebMonitorAddin;
if (Addin == null)
{
this.ErrorMessage = this.ErrorMessage + "Couldn't load add-in " + File + "\r\n";
continue;
}
this.Add(File,Description,Addin);
if (IDE != null)
Addin.IDE = IDE;
Addin.OnInit(SiteList);
if ( (Addin.AddinEventType & AddinEventTypes.MessageEvents) != 0 )
SiteList.WebMonitorMessage += new WebMonitorSiteList.delWebMonitorMessage( Addin.OnMessage );
foreach(WebMonitorSite Site in SiteList.Sites)
{
if ( (Addin.AddinEventType & AddinEventTypes.AllSiteEvents) != 0 || (Addin.AddinEventType & AddinEventTypes.SiteHttpRequest) != 0)
Site.OnHttpRequest += new WebMonitorSite.WebMonitorSiteEventHandler( Addin.OnHttpRequest );
if ( (Addin.AddinEventType & AddinEventTypes.AllSiteEvents) != 0 || (Addin.AddinEventType & AddinEventTypes.SiteEmail) != 0)
Site.OnSendEmail += new WebMonitorSite.WebMonitorSiteEventHandler( Addin.OnSendEmail );
if ( (Addin.AddinEventType & AddinEventTypes.AllSiteEvents) != 0 || (Addin.AddinEventType & AddinEventTypes.SiteLogError) != 0)
Site.OnLogError += new WebMonitorSite.WebMonitorSiteEventHandler( Addin.OnLogError );
if ( (Addin.AddinEventType & AddinEventTypes.AllSiteEvents) != 0 || (Addin.AddinEventType & AddinEventTypes.SiteLogError) != 0)
Site.OnLogDetail += new WebMonitorSite.WebMonitorSiteEventHandler( Addin.OnLogDetail );
}
}
}
if (this.ErrorMessage != "")
return false;
return true;
}
You can see here I go through each objects that get hooked by the addin and hook up events that call back into the Add-in. All your Application code has to to do then is fire the events:
if (this.OnLogDetail != null)
this.OnLogDetatil(this);
If your app objects already fire events you won't have to really do anything else.
Note also that in the loader you can assign some sort of global object. In my case I'm passing an instance of the IDE which is the top level form, which in turn has references to all the rest of the application (ie. SiteList and individual Sites).
In your case that global reference might be the Mere Mortals App object or possibly a top level form that has references to the various business objects.
Building solid Add-ins and adin-in architectures can be tough though. You need to be able to do solid error trapping since your app is running foreign code and realistically you probably want to load add-ins into another AppDomain so they can be unloaded and/or refreshed.
+++ Rick ---
>I have a Windows Forms app that's extensible in that it looks for .dlls in a "Plug-In" folder, loads each assembly, iterates through the types, and if a type implements my interface, does stuff. Stuff it does includes grabbing a MenuItem to add to the app's "Plug-In" MenuItem.
>
>This works fine, but it's pretty much a one-way street. That is, the plug-in can't get information from the host. Just for kicks I added a set property to the interface so now the plug-in has a reference to the app's main form - you could do cute things like iterate through the app's MDI children and manipulate which form is activated, or any other public member.
>
>What I really want is to let a plug-in have access to objects in the app. Objects in this case would be data (Mere Mortals business objects) associated with an MDI form.
>
>What I've just started is another interface. I'm thinking my MDI child forms should implement the interface, then when the plug-in has a reference to a child form, could cast it to the new interface, and call, for example, .GetDataSet() or .GetBusinessOjbect().
>
>Is this the right direction to pursue? Any thoughts are appreciated.
>
>Mike