?
Solved

C# .Net plugin architecture

Posted on 2009-04-20
21
Medium Priority
?
1,785 Views
Last Modified: 2013-12-17
I am trying to create a program that will incorporate DLL files in a type of "plugin" architecture, but I can't figure out how to implement.  Basically, I need the Main program to look for and load all Plugin files it finds, and then I obviously need them to interact with each other.  What is the best way to implement this as far as overhead goes, and how to I import the plugin architecture so that the Main program knows what to expect while still being able to reference the Public elements of the Main program itself from within the Plugin?  I have a working program right now, but it does not implement the plugin architecture I desire.  See the attached screenshot.  The "Collections" would each be their own DLL file.
screen.png
0
Comment
Question by:eslindsey
  • 13
  • 7
21 Comments
 
LVL 16

Accepted Solution

by:
CuteBug earned 2000 total points
ID: 24189973
0
 
LVL 6

Expert Comment

by:alex_paven
ID: 24214341
Also, you could have a look here (Addin architecture).
0
 

Author Comment

by:eslindsey
ID: 24328684
Sorry for the long absence guys, I've been swamped here at work.  CuteBug, I haven't worked much with Interfaces and Reflection, being relatively new to working in .Net.  And alex, thanks for the response.  Which of these solutions would be better for my application?  I like the idea of independent versioning; but I want to make sure that I don't try to implement a solution that is going to be more work than it's worth.  In other words, the way I was doing things before, everything was built into the same application so any Public members of the main (host) applications were directly accessible from the "plugin" (even though it wasn't really a plugin).  That made my job really easy and I'd like to maintain that level of accessibility but add the level of distinction that allows me to develop and build the host application and the plugins separately.  Unfortunately, I don't want to keep this question open for a few weeks while I investigate both of these options thoroughly.  Could you give me some pros and cons, and tell me if I'm going in the right direction with this?  Which of these two options is going to be easier for me to implement with my lack of experience in this area?
0
Visualize your virtual and backup environments

Create well-organized and polished visualizations of your virtual and backup environments when planning VMware vSphere, Microsoft Hyper-V or Veeam deployments. It helps you to gain better visibility and valuable business insights.

 

Author Comment

by:eslindsey
ID: 24360247
As a follow-up, I have examined the Add-In architecture, and I believe it is more complex than what I am really looking for.  At first, the Interfaces/Reflection approach looked very promising, but now I've run into a few roadblocks that I can't seem to get around.  Referring to the screenshot I posted in the original question, the host application would load the plugins dynamically and populate the "Collection" list seen dropped down.  It would then call various methods of the plugin based on the actions taken by the user.  The problem that I am running into is that the plugin would then need to access all of the public members of the host application--for example the "source list" (ListView on the left) and the "destination list" (ListView on the right) and several other objects and properties.  How can I do this, as it seems to me that using Interfaces and Reflection precludes the possibility of such open communication both ways.
0
 
LVL 16

Assisted Solution

by:CuteBug
CuteBug earned 2000 total points
ID: 24362855
Hi,
   What you can do is to have a member called Parent in each of your plugins. When you instantiate the plugin, pass a reference of your host application to your plugin to set the Parent member variable's value.

   Now, once ther Parent member of your plugin is properly instantiated, you can call the required public APIs.
0
 
LVL 16

Expert Comment

by:CuteBug
ID: 24362870
There is one more approach.

Let each of the plugins have Events to which the Host application would subscribe.
For e.g. in order to Fill the SourceList, the plugin has a FillSourceListEvent.

When this event is raised, the Host application would fill the source List with the Arguments send along with the event.
0
 

Author Comment

by:eslindsey
ID: 24370255
Thanks for the reply, CuteBug.  Your first option is EXACTLY what I am looking to accomplish.  That is how I have been doing it in the application I currently have working.  Unfortunately, once I try to build them as separate projects, it creates a circular dependency issue.  Visual Studio yells at me and says "A reference to '(host project)' could not be added. Adding this project as a reference would cause a circular dependency."
0
 

Author Comment

by:eslindsey
ID: 24370258
By the way, to get it working that way in my current application I had to build everything in the same project.  Defeats the purpose because now I have no plugin architecture.
0
 

Author Comment

by:eslindsey
ID: 24370310
OOH!  I think I got it.  I have to build the IPlugin and the Host application in the same project; the ACTUAL plugins are created as separate projects and I simply add the host/interface project as a reference.  Thus the main project can build successfully, and the plugin projects just build against the main assembly.  No circular references!
0
 

Author Comment

by:eslindsey
ID: 24371073
Ok here's my latest challenge.  Instead of using an Interface, I'm using an abstract class.  Other than that, I'm implementing in pretty much the same way as the second article in your original post.  So far everything works until I get down to the one part I had to modify because I'm not using an Interface.  That's the "object NewClass = ..." line below.  I'm assuming that this is the proper way for me to create an instance of a class from the assembly; only problem is every time I try it, it throws an exception TargetInvocationException with an InnerException of NullReferenceException.  I must be doing something wrong, but I can't seem to figure out the proper way to create a new instance of the class to add to my Collections List.

Here's the details of the exception:

System.Reflection.TargetInvocationException was unhandled
  Message="Exception has been thrown by the target of an invocation."
  Source="mscorlib"
  StackTrace:
       at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandle& ctor, Boolean& bNeedSecurityCheck)
       at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean fillCache)
       at System.RuntimeType.CreateInstanceImpl(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean fillCache)
       at System.Activator.CreateInstance(Type type, Boolean nonPublic)
       at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
       at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
       at System.Reflection.Assembly.CreateInstance(String typeName)
       at SuperTagger.Tagger..ctor() in C:\Users\Eric\Documents\Visual Studio 2008\Projects\SuperTagger\MacGyver's Super Tagger\Tagger.cs:line 39
       at SuperTagger.Program.Main() in C:\Users\Eric\Documents\Visual Studio 2008\Projects\SuperTagger\MacGyver's Super Tagger\Program.cs:line 18
       at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: System.NullReferenceException
       Message="Object reference not set to an instance of an object."
       Source="Tagger"
       StackTrace:
            at SuperTagger.TaggerCollection..ctor() in C:\Users\Eric\Documents\Visual Studio 2008\Projects\SuperTagger\MacGyver's Super Tagger\TaggerCollection.cs:line 130
            at SuperTagger.PromoOnly..ctor() in C:\Users\Eric\Documents\Visual Studio 2008\Projects\SuperTagger\PromoOnly\PromoOnly.cs:line 53
       InnerException:

        //List of collections
        private List<TaggerCollection> Collections = new List<TaggerCollection>();
 
        //Constructor
        public Tagger()
        {
            //Load Collection plugins
            string[] FileNames = Directory.GetFiles("Collections", "*.dll");
            foreach(string FileName in FileNames)
            {
                try
                {
                    Assembly DLL = Assembly.LoadFrom(FileName);
                    Type[] TypesFromDLL = DLL.GetTypes();
                    foreach(Type TypeFromDLL in TypesFromDLL)
                    {
                        if(TypeFromDLL.IsSubclassOf(typeof(TaggerCollection)))
                        {
                            if(TypeFromDLL.GetCustomAttributes(typeof(TaggerCollectionName), false).Length != 1)
                                throw new Exception("Collection Name attribute does not exist");
                            object NewClass = DLL.CreateInstance(TypeFromDLL.FullName);
                            Collections.Add((TaggerCollection)NewClass);
                        }
                    }
                }
                catch(Exception e)
                {
                    MessageBox.Show(e.Message);
                }
            }
        }

Open in new window

0
 
LVL 16

Expert Comment

by:CuteBug
ID: 24373323
You should use the System.Activator.CreateInstance() to create an instance.

object NewClass = System.Activator.CreateInstance(TypeFromDLL.FullName);

Open in new window

0
 

Author Comment

by:eslindsey
ID: 24377189
The method wasn't correct, none of the implementations of CreateInstance take a string.  The alternative in line 1 below throws the same NullReferenceException that I was getting before, and the one in line 2 throws the following:

System.IO.FileLoadException was unhandled
  Message="Could not load file or assembly '\"C:\\\\Users\\\\Eric\\\\Documents\\\\Visual Studio 2008\\\\Projects\\\\SuperTagger\\\\MacGyver's Super Tagger\\\\bin\\\\Debug\\\\Collections\\\\PromoOnly.dll\"' or one of its dependencies. The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047)"
  Source="mscorlib"
  FileName="\"C:\\\\Users\\\\Eric\\\\Documents\\\\Visual Studio 2008\\\\Projects\\\\SuperTagger\\\\MacGyver's Super Tagger\\\\bin\\\\Debug\\\\Collections\\\\PromoOnly.dll\""
  FusionLog=""
  StackTrace:
       at System.Reflection.AssemblyName.nInit(Assembly& assembly, Boolean forIntrospection, Boolean raiseResolveEvent)
       at System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
       at System.Activator.CreateInstance(String assemblyName, String typeName, Boolean ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, Evidence securityInfo, StackCrawlMark& stackMark)
       at System.Activator.CreateInstance(String assemblyName, String typeName)
       at SuperTagger.Tagger..ctor() in C:\Users\Eric\Documents\Visual Studio 2008\Projects\SuperTagger\MacGyver's Super Tagger\Tagger.cs:line 39
       at SuperTagger.Program.Main() in C:\Users\Eric\Documents\Visual Studio 2008\Projects\SuperTagger\MacGyver's Super Tagger\Program.cs:line 18
       at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException:

I'm wondering if perhaps since the Assembly is already loaded, it can't load it again to instantiate the class?
object NewClass = Activator.CreateInstance(TypeFromDLL);
object NewClass = Activator.CreateInstance(DLL.Location, TypeFromDLL.FullName);

Open in new window

0
 

Author Comment

by:eslindsey
ID: 24377362
You know what, I don't think this is a problem with the implementation code.  I think this is the plugin itself throwing the exception (the NullReferenceException; the other one I don't know about)...  I just don't know how to troubleshoot since because I'm late-loading it completely bypasses the Visual Studio debugging process!
0
 
LVL 16

Expert Comment

by:CuteBug
ID: 24381190
I think the problem is with the PromoOnly.dll. As is evident from the error message, the DLL cannot be loaded.

use depends.exe (available at www.sysinternals.com) to find out the dependencies of the DLL.
0
 

Author Comment

by:eslindsey
ID: 24381253
But in fact the DLL IS loaded. The constructor appears to be throwing a NullReferenceException, as evidenced by the InnerException. I just don't know how to debug the PromoOnly project in the Visual Studio IDE given the late-loading nature of the plugin system.
0
 

Author Comment

by:eslindsey
ID: 24381313
By the way, PromoOnly.dll is my plugin. Its only direct dependency is the host project. Using two loading methods, the NullReferenceException is passed via the InnerException, which I am inclined to pursue. The error about not being able to find the DLL only came up when I tried roundabout methods of loading it separately after it had already been loaded. I wish I knew how to debug a DLL loaded at runtime through the IDE... I'm confident that would show me the error of my ways.  
0
 
LVL 16

Expert Comment

by:CuteBug
ID: 24381923
Yeah even I think the constructor of your DLL is throwing the exception, specially in the SuperTagger.TaggerCollection constructor.

You can debug the DLL.

Load the PromoOnly.dll project in Visual Studio.

Put a MessageBox.Show() code, before you call the Activator.CreateInstance();

Run your host application. Just before it creates the instance, it will popup the message box.

Attach your VS2008 to the Host application and then put a breakpoint within the constructor of PromoOnly.

Now click Ok on the messagebox.

The debug control will now come to your DLL's constructor.
0
 

Author Comment

by:eslindsey
ID: 24391397
CuteBug, thanks for all your assistance.  I have found the problem; the program was executing the static constructor of the TaggerCollection abstract class (which didn't have a problem), then executing the instance constructor of the abstract class (which WAS throwing the NullReferenceException), and only THEN was it trying to jump into the class constructor--which was where I had my initial breakpoint.  I didn't even have to do all the attaching of the VS IDE and everything to the host.  All I had to do was F10 my way through, line by line.  Sometimes it's the obvious solutions that escape me.  Anyways, I'll go ahead and post all this stuff so that hopefully someone else can also benefit from my mistakes.  Thanks again for all the help!
0
 

Author Closing Comment

by:eslindsey
ID: 31572514
Thanks CuteBug!
0
 

Author Comment

by:eslindsey
ID: 24391569
I can now confirm that ALL of the following work without throwing exceptions, now that I have debugged and gotten rid of the errors in the PromoOnly class AND the abstract TaggerCollection class it is based off of.
NewClass = DLL.CreateInstance(TypeFromDLL.FullName); //works
NewClass = Activator.CreateInstance(TypeFromDLL); //works
NewClass = Activator.CreateInstance(DLL.Location, TypeFromDLL.FullName); //works

Open in new window

0
 
LVL 16

Expert Comment

by:CuteBug
ID: 24393371
You are welcome :)
0

Featured Post

New feature and membership benefit!

New feature! Upgrade and increase expert visibility of your issues with Priority Questions.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

In my previous article (http://www.experts-exchange.com/Programming/Languages/.NET/.NET_Framework_3.x/A_4362-Serialization-in-NET-1.html) we saw the basics of serialization and how types/objects can be serialized to Binary format. In this blog we wi…
For those of you who don't follow the news, or just happen to live under rocks, Microsoft Research released a beta SDK (http://www.microsoft.com/en-us/download/details.aspx?id=27876) for the Xbox 360 Kinect. If you don't know what a Kinect is (http:…
Despite its rising prevalence in the business world, "the cloud" is still misunderstood. Some companies still believe common misconceptions about lack of security in cloud solutions and many misuses of cloud storage options still occur every day. …
Look below the covers at a subform control , and the form that is inside it. Explore properties and see how easy it is to aggregate, get statistics, and synchronize results for your data. A Microsoft Access subform is used to show relevant calcul…
Suggested Courses

864 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question