About This Article
I want to give people a basic idea of how plugins should work and what kinds of ways they can be made and used. (There will be no exact coding because that is very different in every case.)
What Are Plugins?
Plugins are pieces of code (often ATLs or .NET DLLs) that intergrate with a host application, by being in a certain folder or being registrated, and has (partial) control of the execution of the host program.
A very good example of a program that uses plugins is the Microsoft development environment. VB.NET, C++, Crystal Reports, and maybe other programs installed into that environment are basicly plugins. They are loaded when the environment is started and then they can do their thing (adding menu items, adding project types, loading itself...). MDE (Microsoft development environment) is a good example of a plugin-based application. When you should remove all the plugins (Crystal Reports, VB.NET...) and start it, it would just show an empty form.
Not all plugins are meant to change the whole host that much as in MDE. Sometimes, plugins are just for simple routines. For example, take milkshape3d. Milkshape3d is a 3d model editor; milkshape3d plugins can add new "save as.. filetypes".
What Are the Advantages of Plugins?
- They are very portable and easy to deploy. You have to put a DLL in the plugin folder, or register it with a .reg file.
- They are small. Plugins are very small, but can pack enormous power.
- And more, but that depends on the plugin framework that is used!
Making Your Own Plugin Framework
Making a plugin framework isn't hard. It is very easy, when you do it right. Before you can make it, you must make a plan for it. What has it got to do, what do I want it to let do... These are the plugin framework types you can consider:
- A small plugin framework that lets plugins add one or a few new options. For example, new menu items in the 'export as' menu.
- Medium scale plugin framework that lets the plugins add features to the host application, such as new forms, new menu items, and changing the host.
- Big scale plugin framework. This has to be totaly plugin based, which means that the host just loads the plugins, and then lets the plugins do what they have to do.
There is a big difference. Making a small plugin framework that just adds a new option in, for example, a menu is not hard to program, but it keeps plugins very limited. It also requires much less coding, and there aren't many things to go wrong.
Small Plugin Framework
Here is an example of a small plugin framework (note that I left big gaps; that's because it is meant as an example, written in VB 6).
'code for the mainform of an application sub LoadPlugins 'this will be the sub launched when the plugins have to load dim plugins as new collection plugins = getdllsfromfolder (app.path + "\plugins") 'I won't write down this function because it has nothing to do 'with plugins dim plugp as string dim plug as object for each plugp in plugins set plug = createobject (getbasename(plugp) + ".pluginmain") 'creates an instance of the pluginmain class in the plugin dll. plug.addmenuitems (me) 'will let the plugin be able to run the 'addmenu item sub in this form by 'passing the object reference next end sub sub addmenu(caption as string, key as string) 'this sub is run by a plugin when it wants to create a menu. redim preserve MNUPlugins(ubound(MNUPlugins)+1) set MNUPlugins(ubound(MNUPlugins) = new menuitem MNUPlugins.caption = caption MNUPlugins.tag = key 'this is the key used to let the plugin 'know that the menu item is pressed end sub public sub MNUPlugins_click(index as long) 'This is just a copy of the previous code for getting the 'plugins. This isn`t a good way to do it 'The best way is to store it in arrays dim plugins as new collection plugins = getdllsfromfolder (app.path + "\plugins") 'I won't write down this function because it has nothing to do 'with plugins dim plugp as string dim plug as object for each plugp in plugins set plug = createobject (getbasename(plugp) + ".pluginmain") 'creates an instance of the pluginmain class in the plugin DLL. plug.menuitempressed(MNUPlugins(index).tag) 'All plugins are warned that a menu item is pressed and the 'plugins which has made the menu item (which it can identify 'because it has left a key, which is now returned, can respond next end sub '----CODE FOR THE PLUGIN DLL's class pluginmain---- public sub addmenuitems(host as object) host.addmenu("Some menu caption", "somemnucpt") end sub public sub menuitempressed(menukey as string) select case menukey case "somemnucpt" msgbox "pressed" end select end sub
As you can see, that plugin framework does its job, but is very static.
Medium scale plugin framework
Here is the code of a plugin of FL Studio. FL Studio is a program by Micheal Houston and me and may not be copied in any way. (The code is in VB.NET 2003.) As you can see, the plugins have more room, but still can't do everything.
Imports System.Windows.Forms Public Class myPlugin Implements FLStudio.IFLStudioPlugin Private studio As FLStudio.IFLStudio Private myMenu As MenuItem Private myTab As TabPage Public Sub initialize(ByVal FLStudio As FLStudio.IFLStudio) _ Implements FLStudio.IFLStudioPlugin.initialize studio = FLStudio myMenu = New MenuItem("Test Plugin") studio.addMenu(myMenu) myTab = New TabPage("Test Tab") studio.addTab(myTab) End Sub Public Function pluginName() As String Implements _ FLStudio.IFLStudioPlugin.pluginName Return "myPlugin" End Function Public Function description() As String Implements _ FLStudio.IFLStudioPlugin.description Return "An example FLStudio Plugin" End Function Public Sub unload() Implements FLStudio.IFLStudioPlugin.unload studio.removeMenu(myMenu) studio.removeTab(myTab) End Sub Public ReadOnly Property MustLoad() As Boolean _ Implements FLStudio.IFLStudioPlugin.MustLoad Get Return False End Get End Property Public ReadOnly Property Invalid() As Boolean _ Implements FLStudio.IFLStudioPlugin.Invalid Get Return False End Get End Property Public Property configuration() As FLStudio.PluginConfiguration _ Implements FLStudio.IFLStudioPlugin.configuration Get End Get Set(ByVal Value As FLStudio.PluginConfiguration) End Set End Property End Class
There can be as many plugin classes in a dotnet assembly that is put in the plugin folder as you want. They will be recognized as a plugin when they use the IFLStudioPlugin interface. The plugin host's plugin loader isn't included. If you want to take a look at it, e-mail me firstname.lastname@example.org.
Big Scale Plugin Framework
A really good plugin framework should have the following things:
- DLL as plugin library. A DLL has to be seen as a plugin library and not as a plugin itself.
- Plugins must have priority and be overpowering. When there are two plugins with the same key, the one with the higher priority will survive. When building in that system, it will be very easy for plugin coders to even make mods in other plugins. This also makes it unnecessary to delete DLLs if there is a new version of a plugin. Just put the DLL in the plugin folder and the rest is done for you.
- The host does nothing except load the plugins. All coding has to be done in plugins. By also using plugin overpowering, the application will be very easy to maintain.
- Plugins should have total control of themselves and all other plugins. In that way, even the plugin loader can be 'modded' by plugins.
- Plugins should be able to share and change objects. The plugin host should have a storage system for objects that are shared by plugins. These 'shared-objects' can be, for example, forms. These shared objects should also have a priority and be able to be overpowered by other shared-objects.
- Plugins should be able to communicate with a message system. A plugin has to be able to send a message to all other plugins, where the other plugins can respond to. Or just to send that same message, but only let the plugin which returns the highest priority respond to that event/message.
I have made, with some other programmers, a few plugin frameworks that are based on this principle. The big advantage is flexibility; the disadvantage is the big chance that coders aren't going to use this right, and make it impossible for themselves.
The first one I made was in Visual Basic 6.0. It uses ActiveX DLLs as plugins. You can find the code, and download here.
The second one I made is made in Visual Basic.NET, and uses .NET assemblies as plugins. I will only give the interfaces used by the pluginframework host and of the plugins:
This code is copyrighted
'plugin interfaces, and classes used as interface. 'this is almost complete and shouldn't change in later versions Public Interface UMSPlugin 'info part Function getinfo() As Collection Sub setinfo(ByVal info As Collection) 'registration part and unload part Sub registerplugin(ByVal host As UMSHost) Sub unloadplugin() 'interaction part Function msgpriority(ByVal key As String) As Long Sub message(ByVal key As String, Optional ByVal _ parameters As Collection = Nothing) End Interface Public Interface UMSHost 'info part Function getinfo() As Collection 'plugins Overloads Sub AddPlugin(ByVal PluginInterface As UMSPlugin) Overloads Sub AddPlugin(ByVal Path As String, Optional ByVal _ PluginKey As String = "*") Overloads Sub AddPlugin(ByVal key As String, ByVal _ PluginInstance As Object, Optional ByVal priority _ As Long = 0) Function GetPlugin(ByVal key As String) As UMSPlugin Function GetPlugins() As Collection Sub RemovePlugin(ByVal key As String) 'shared objects Overloads Sub AddSharedObject(ByVal obj As Object,_ ByVal key As String, _ Optional ByVal priority _ As Long = 0, _ Optional ByVal extrainfo As _ Collection = Nothing) Overloads Sub AddSharedObject(ByVal sharedobject As SharedObject) Function GetSharedObject_Wrapper(ByVal key As String) _ As SharedObject Function GetSharedObject(ByVal key As String) As Object Function GetSharedObjects() As SharedObject() Sub RemoveSharedObject(ByVal key As String) 'Global values Overloads Sub AddGlobalValue(ByVal GlobalValue As GlobalValue) Overloads Sub AddGlobalValue(ByVal key As String, _ ByVal value As Object, _ Optional ByVal priority As Long = 0) Function GetGlobalValue(ByVal key As String) As GlobalValue Sub RemoveGlobalValue(ByVal key As String) 'plugin communication Sub Message(ByVal key As String, _ Optional ByVal parameters As Collection = Nothing, _ Optional ByVal prioritybound As Boolean = False) End Interface Public Class SharedObject 'info Public key As String Public priority As Long 'additional, for extra info about this shared object Public info As Collection 'object itself Public obj As Object End Class Public Class GlobalValue 'info Public key As String Public priority As Long Public value As Object End Class
A few other programmers and I are going to make a Universal Modding Studio with this plugin framework.
How Should a Plugin Work?
Here are some basic principles about how a plugin should work:
- Use others' plugin resources. Don't rewrite a whole, for example, INI parser hardcoded in a plugin. Use the existing one or make a new plugin for it. This will make all plugins use one parser which makes the plugins easier to maintain.
- Never depend to much on a plugin. Never try to depend to much on another plugin because that plugin can change in newer versions. Rather, try to use interfaces.