Jump to content
Xtreme .Net Talk

Plugin-Based Application - How to access random functions?


Recommended Posts

Posted

I'm using the tutorial from divil (http://www.divil.co.uk/net/articles/plugins/plugins.asp) to develop plugin-based applications but I'm having some problems that I don't have a clue on how to solve them...

 

For instance, in the interfaces plugin:

Public Interface IPlugin

   Sub Initialize(ByVal Host As IHost)

   ReadOnly Property Name() As String

   Function Calculate(ByVal int1 As Integer, ByVal int2 As Integer) As Double

End Interface

 

We are pre dictating that we only have a Sub called Initialize, a ReadOnly Property called Name and a Function called Calculate. However, I want the plugins written to my application to have their own functions (the many they want, public, private, whatever...) with their own names, than I want to be able to use them on my main application.

 

For one part of this, I though I could create a string array, listing the names of the public functions that the plugin author want to be used by the main application. But than the problem came... How can I have the plugin author create their own functions with their own names and allow me to use them in the main function, how can I do that?

Posted

System.Reflection

 

Using Reflection, you can inspect a type to find its members, and also invoke those members. The System.Reflection namespace contains the classes that implement this.

 

For example, the Type class has methods GetMethods and GetProperties which can be used to discover what members the type contains, and a method InvokeMember which can invoke members.

 

You might be interested in these threads:

 

Reflector Class - Reflection Made Easy

Is it possible for a Base Class to call a function in a Child Class?

Access class properties in code (property names in datatable)

 

Good luck :cool:

Never trouble another for what you can do for yourself.
  • Leaders
Posted

I can convert my reflector class that MrPaul linked to to VB, if you would like. Using reflection can actually be a bit tricky. I recommend that you investigate other possible solutions, however, even if you ultimately resort to reflection. For one thing, reflection is much slower than using an interface. For another, certain things such as method overloading and differing accessor accessibility can throw off reflection code that might normally work. One suggestion would be that your plug-in interface implement some sort of command interface. For example, something along the lines of:

Public Interface ICommandInterface
   Public Function GetCommands() As IEnumerable 'Or more specific or array type
   Public Function InvokeCommand(command As String, args As Object())
End Interface

Public Interface IPlugIn
   Inherits ICommandInterface
 
   'Declare your standard plugin interface members here
End Interface

The plug-in class would have to declare a method (GetCommands) that returns, say, a list of CommandInfo objects, each of which defines a command name and expected arguments, and it would have to declare a function (InvokeCommand) which executes a command. (This would be similar to the way commands are sent to the mciSendStringA() method of winmm.dll.) It might not be the best solution, and it would be difficult for beginners to implement, but it is really just an example of how it could be done without reflection.

[sIGPIC]e[/sIGPIC]
Posted
Sounds very odd to me. How will your application know what to do with the functions contained in the plug-ins once it knows what they are? If, as you seem to be suggesting in your title, you want to access random functions, can't you incorporate the randomness in your plug-ins rather than the application?
Posted (edited)

Maybe I chose wrong title, I didn't mean exactly random, what I meant is that one plugin may have 2 functions, another one may have 3 with completely different method names...

 

@marble_eater

Your method described earlier seems to be the best option, however, I'm having problems working with it, can you please provide a more complete example?

Edited by Nazgulled
Posted
Maybe I chose wrong title, I didn't mean exactly random, what I meant is that one plugin may have 2 functions, another one may have 3 with completely different method names...

 

In which case I'm baffled as to what you want this for. If you want all functions to be called in sequence, just do this in your plug-in.

Posted
It doesn't matter what I want this for, I want to do it and I know why I want it this way... And no, I don't want to call them in sequence I just want to call functions in the plugins. Functions that I don't know their names nor I want to specify them in the host interface cause I don't want to limit the plugins to a number of functions and/or names.
  • Leaders
Posted

If you explain how and why these functions will be called it is much easier to give a satisfactory answer. As for not being able to get reflection to work, I don't know where you might have run into problems and you did not explain. Reflection might be the answer you are looking for, I merely suggested you look into other options as well.

 

 

As for the example you requested, well, since I am such a nice guy, here is a bunch of code. Like I said, this isn't a good beginner project. I'm assuming that you are familiar with inheritance and interfaces (especially since you are writing a plug-in app), and that you know how to use the System.Activator class and the PropertyGrid control.

 

Since we will be creating command-based plug-in, we would want to write a command-based-plug-in-interface as a base for command-based plug-ins.

 

The commands will be invoked by a string (which specifies the name of the command) and if there are any arguments, they can be passed using a special class for arguments for that command. You will see what I mean by that in a moment. Here is our interface:

Interface ICommandInterface
   Function GetCommands() As CommandInfo() 'Gets an array of CommandInfo objects
   Function SendCommand(command As String, args As Object) As Object
End Interface

We need to declare a class that can describe a command:

Public Structure CommandInfo
   Public Name As String
   Public ArgType As System.Type
  
   Public Sub New(Name As String, ArgType As System.Type)
       Me.Name = Name
       Me.ArgType = ArgType
   End Sub
End Structure

Note the ArgType. This specifies the class that is used to pass arguments to a command. This is very similar to the way arguments are passed to event handlers: A click event receives an EventArgs object, and a MouseDown event receives a MouseEventArgs. A label edit in a list view receives a LabelEditEventArgs. Get it? You will see how it works soon.

 

Now, pretend we are making an graphics editing application which supports graphic filter plug-ins. We can actually use our ICommandInterface as is because it is flexible enough that we don't need to add any methods--everything can be done through the SendCommand method.

 

This is how a graphics filter plugin will work: the GetCommands function will return a list of commands that represent filters that the plug-in supports, and you call SendCommand to use a filter. Simple, no?

 

Here is our plug-in code:

Public Class BitmapFilterPlugIn
   Implements ICommandInterface

   Public Function GetCommands() As CommandInfo() Implements ICommandInterface.GetCommands
       'We don't have any filters yet, so return an empty array
       Return New CommandInfo(0) {}
   End Function

   Public Function SendCommand(ByVal command As String, ByVal args As Object) As Object Implements ICommandInterface.SendCommand
       'We don't have any filters yet, so do nothing and return nothing.
       Return Nothing
   End Function
End Class
[/vB]
It doesn't look like much yet, but we will add filters. First, we need to look at those argument objects I was talking about. Since all filters will work on Bitmap objects, we will need to pass bitmaps to our filters. Let's define a base  class for passing arguments to our filters.
[code=visualbasic]
Public Class FilterOptions
   Public TargetImage As Bitmap
End Class

Now, let's create a filter. We can do something simple, like Brightness. Generally you would pass a floating-point number to a brightness filter that represents the percentage of brightness the filter is to apply. Here is a class that we can pass to the Brightness filter:

Public Class BrightnessOptions
   Inherits FilterOptions

   'Create a property for a filter option
   Dim _Brightness As Single
   Public Property Brightness() As Single
       Get
           Return _Brightness
       End Get
       Set(ByVal value As Single)
           _Brightness = value
       End Set
   End Property
End Class

Now, let's incorporate that filter into our filter plug-in.

Public Class BitmapFilterPlugIn
   Implements ICommandInterface

   Public Function GetCommands() As CommandInfo() Implements ICommandInterface.GetCommands
       'Return an array that lists our commands and the arguments they require
       Return New CommandInfo() { _
           New CommandInfo("Brightness", GetType(BrightnessOptions)) _
       }
   End Function

   Public Function SendCommand(ByVal command As String, ByVal args As Object) As Object Implements ICommandInterface.SendCommand
       'Perform the appropriate action based on the command string
       Select Case command.ToUpper()
           Case "BRIGHTNESS"
               Brighten(CType(args, BrightnessOptions).TargetImage, _
                       CType(args, BrightnessOptions).Brightness)
       End Select

       ' Most likely, none of our filters will need to return anything
       Return Nothing
   End Function

   Public Sub Brighten(ByVal image As Bitmap, ByVal brightness As Single)
       'This function applies the filter
   End Sub
End Class

Now everything should come together. To use the plug-in, first you would call GetCommands(), which would return an array with one CommandInfo. That CommandInfo would tell you that there is a command named Brightness which requires a BrightnessOptions object.

 

We will list Brightness in our plug-in menu. When the user selects the Brightness filter, we will use the System.Activator class to create a BrightnessOptions object. We will assign the image the user is editing to the BrightnessOptions object's TargetImage field, and then we will put the BrightnessOptions object in a property grid where the user can edit the Brightness property (or whatever options there are for other filters). You will most likely want to show the property grid on a dialog form.

 

Finally, we call the SendCommand function, passing the string "Brightness" and our BrightnessOptions object. The plug-in calls the Brighten() function, which actually applies the filter, and that's it.

[sIGPIC]e[/sIGPIC]
Posted (edited)

But those things you are asking don't really matter, that's up to me and my application, I don't think it will really help me explaining how exactly my whole application will work and how the plugins system will work... And besides, I don't know exactly how the plugins system will work caus I've just thought on how to do it and I'm seeing if it's a viable solution, but I can't really test it cause I have the problem described on this thread.

 

Anyway, the application I'm coding has to do with network connection cards, to configure ips and such with the ability to have profiles and just apply one of them. I want to add the plugins system so users can extend the functionality of the profile selection to other stuff. For instance, the IE proxy, that's the first plugin I'm developing to release with the final version of the application. That plugin would have 2 functions, one to set proxy in the registry and the other to clear it. However, other plugins may do other stuff or may change proxies too on some other programas but may need more functions to be called or maybe just one. What I want to do, is to allow the plugins to have functions they want, then, "share" the ones that can be used by the main application. When creating a new profile, the user selects the plugins he wants to use, then, for each plugin, selects the functions that he wants to be called when that profile is applied. For instance, you would create a profile for "home" with the IE plugin, calling just the function to clear the proxy and another profile for "university" with the IE plugin, calling just the function to set the proxy in the registry (that's actually an example for the real use I'm going to give to my program).

 

I'm no begginer, but I'm not that average user either, I just now some stuff, maybe just a bit more than a begginer, but not too much... Maybe cause english is not my main language and there are many words in the documentation to describe stuff that I don't really understand... Shure I look up for words in the dictionary and google, shure I get the meaning of them, but sometimes that doesn't really help to understand the concept and the real meaning of them in context of conding in VB.NET. A bunch of things you just said, I don't understand that well... I didn't knew Interfaces exist nor what were they, until I read the tutorial by devil and still, even after reading msdn, I don't really get what exactly are they, what are they for and how exactly they work. I just know a bit and understand this and that, but the whole concept, no... I always had difficulties in this field, even in my home language, but I don't really read documentation in my language so, I don't think it's a valid point.

 

I'm going to read this whole code you posted, see if I can understand it and implement it on my project... I'll report back later! Thanks.

 

I'm sorry giving you this whole work...

Edited by Nazgulled
Posted (edited)

I've been trying to use your code in my test application but I've failed... I can't seem to understand enough to take this and apply it to my application.

 

Did you take a look at the personal message I've sent you? Did you opened the solutions inside the zip file? Do I really need all of the above code to do what I want? (just create a function2() in the plugin without implementing it in the interfaces)

Edited by Nazgulled
Posted

Consider delegates

 

You have to decide whether you want to place the responsibility for this functionality on the shoulders of the host application or on the plugins. In the case of the former, you need to look into reflection. marble_eater's code does the latter.

 

Do I really need all of the above code to do what I want? (just create a function2() in the plugin without implementing it in the interfaces)

 

The code marble_eater posted is fairly concise, and would form the basis of the system. If you wanted to extend BitmapFilterPlugIn with another method, you'd only need to add two more lines of code within SendCommand and alter the function list in GetCommands. That is not a lot of code. I can't see how you are able to judge the complexity of the code given without some basic concept of what it is you are trying to achieve and what basic steps are required - for example, knowing what the purpose of an interface is.

 

Anyway, as far as you have described the purpose of this feature, I would recommend against both reflection and this SendCommand approach. Instead, I suggest the GetCommands function should return a collection of delegates along with their names, and then the host application can invoke them as required. On a more advanced level, the application can also inspect the delegates to determine the number and types of parameters.

 

Good luck :cool:

Never trouble another for what you can do for yourself.
Posted

Delegates example

 

I hope this doesn't confuse you further. This is an example of using delegates to perform this.

 

First, define the signature of any methods you want to expose, and again define the plugin interface. In this example, the interface only contains the GetMethods method. Other members can be added in line with your application requirements. You could also define different delegates for different parameter combinations and return types, with suitable changes to GetMethod.

 

'Defines members a plugin MUST implement
Public Interface IPluginInterface
   Function GetMethods() As PluginDelegate()
End Interface

'Defines the signature of any custom plugin methods
Public Delegate Sub PluginDelegate()

 

Now, for a simple plugin, the IEPlugin described in your previous posts. The implementation of GetMethods returns a list of available methods - in this case just SetProxy. Note that the signature of SetProxy must match PluginDelegate.

 

'Has just one callable method - SetProxy
Public Class IEPlugin
   Implements IPluginInterface

   'Returns a list of callable methods
   Public Function GetMethods() As PluginDelegate() Implements IPluginInterface.GetMethods
       Return New PluginDelegate(0) {AddressOf SetProxy}
   End Function

   Public Sub SetProxy()
       'Set proxy in registry or whatever
   End Sub
End Class

 

Now, how a more complex class might look, containing three methods instead of one.

 

'Has three callable methods - MethodOne, MethodTwo, MethodThree
Public Class MoreComplexPlugin
   Implements IPluginInterface

   'Returns a list of callable methods
   Public Function GetMethods() As PluginDelegate() Implements IPluginInterface.GetMethods
       Return New PluginDelegate(2) {AddressOf MethodOne, AddressOf MethodTwo, AddressOf MethodThree}
   End Function

   Public Sub MethodOne()
       'Do a little dance
   End Sub
   Public Sub MethodTwo()
       'Make a little love
   End Sub
   Public Sub MethodThree()
       'Get down tonight
   End Sub
End Class

 

Finally, the code in the host application which calls the GetMethods method on a plugin then inspects the returned delegates to find their names. The delegates are then stored in a dictionary to be accessed by name later - in the CallPluginMethod method.

 

'Host app
Public Class HostApplication

   'Holds all methods we can use: key = Typename.Methodname
   Private m_methods As Dictionary(Of String, PluginDelegate)

   'Constructor
   Public Sub New()
       m_methods = New Dictionary(Of String, PluginDelegate)
   End Sub

   'Gets all methods from plugin and adds to dictionary
   Public Sub GetPluginMethods(ByVal plugin As IPluginInterface)

       Dim methods As PluginDelegate()
       Dim ptype As Type

       'Get type of plugin
       ptype = DirectCast(plugin, Object).GetType()

       'Get methods
       methods = plugin.GetMethods()
       For Each pd As PluginDelegate In methods
           'Get name of method and add to m_methods
           m_methods.Add(ptype.Name & "." & pd.Method.Name, pd)
       Next
   End Sub

   'Invokes a plugin method
   Public Sub CallPluginMethod(ByVal pluginname As String, ByVal methodname As String)

       Dim fullname As String
       Dim plugmethod As PluginDelegate

       'Find the method
       fullname = pluginname & "." & methodname
       plugmethod = m_methods(fullname)

       If (plugmethod IsNot Nothing) Then
           'Found method, call it!
           plugmethod()
       Else
           'Method was not found
       End If

   End Sub


End Class

 

Good luck :cool:

Never trouble another for what you can do for yourself.
Posted
just create a function2() in the plugin without implementing it in the interfaces

 

Why not use attributes to define "callable" methods? NUnit is a good example of what I am talking about. You would still use reflection to grab the details, but you wouldn't be bound by an interface definition.

Posted

Hum... You didn't confuse me... Although I don't know exactly what are delegates, I took your example and implemented it correctly (I guess) in my test application. Everything seems to be working the way I want it...

 

Sorry giving you guys all this work in help me out...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...