How to create a dynamic plugin
From OpenSimulator
(→Current state of Plugin loading) |
|||
Line 33: | Line 33: | ||
* automatically handles module sharing across application domains | * automatically handles module sharing across application domains | ||
* provides support for downloading assemblies from remote repositories | * provides support for downloading assemblies from remote repositories | ||
+ | |||
== Mono.Addin concepts == | == Mono.Addin concepts == | ||
Although effort has been made to make PluginLoader independent of the module loading mechanism, there are some concepts that should be understood when learning how to learn the system: | Although effort has been made to make PluginLoader independent of the module loading mechanism, there are some concepts that should be understood when learning how to learn the system: | ||
+ | |||
=== Separating Male and Female === | === Separating Male and Female === | ||
Line 49: | Line 51: | ||
In operation the consumer of a service, the host, requests that the system load all addins that claim to extend an extension point, and a list is returned to the consumer, who can choose to load any subset of those providers, the addins, into memory. | In operation the consumer of a service, the host, requests that the system load all addins that claim to extend an extension point, and a list is returned to the consumer, who can choose to load any subset of those providers, the addins, into memory. | ||
+ | |||
=== Describing an Addin === | === Describing an Addin === | ||
Line 60: | Line 63: | ||
# Filename of assemblies where the consumer interfaces or provider implementations can be found <br> this is needed to know which assemblies must be searched | # Filename of assemblies where the consumer interfaces or provider implementations can be found <br> this is needed to know which assemblies must be searched | ||
# Extension Point path and Fully Qualified .NET Class Name of the consumer interface or provider implementation <br> this is needed to load the actual classes into memory | # Extension Point path and Fully Qualified .NET Class Name of the consumer interface or provider implementation <br> this is needed to load the actual classes into memory | ||
+ | |||
+ | The way to tell a host manifest from an addin manifest is that host manifests will *always* contain an ''isroot=true'' attribute in the <Addin> tag. However this is complicated by the fact that an addin can act as host for another addin in a recursive manner. | ||
+ | |||
+ | |||
+ | === Building a model === | ||
+ | |||
+ | When the PluginLoader is first created within the host, Mono.Addins will search the directory the host was executed and search for all files that end in *.addin.xml. It uses the information to build a ''registry'' about the addins in addin-db-${VERSION}/ directory. The purpose of the registry is to collect all information once, then lazily load assemblies into memory only at the last moment when it is precisely known exactly which class is required by the user. | ||
+ | |||
+ | |||
+ | == How to make your own plugin == | ||
+ | |||
+ | # Decide on the service you wish to separate into provider and consumer. Ex: "LartMe" | ||
+ | # Create an extension point to identify that service. Ex: "/OpenSim/LartMe" | ||
+ | # Create an consumer interface that has the methods needed, and derive it from OpenSim.Framework.IPlugin. Ex: "OpenSim.ILart" | ||
+ | ## If that interface needs a constructor that takes parameters, you will also have to create a class derived from PluginInitialzerBase that can act as a [http://en.wikipedia.org/wiki/Closure_(computer_science) closure] for calling a parameterized constructor. See IApplicationPlugin for an example. Ex: "OpenSim.LartInitializer" | ||
+ | # Write a provider class that implements that interface. Ex: "OpenSim.Lart" | ||
+ | # Write a host manifest. See OpenSim.addin.xml as an example. | ||
+ | ## Ensure that /Addin@isroot=true | ||
+ | ## Ensure that /Addin@id is set to a unique name. Ex: "LartMe" | ||
+ | ## Ensure that /Addin/Runtime/Import@assembly exists for each assembly that either hosts the application (host.EXE) or defines an addin consumer interface (Lart.dll) used by the host | ||
+ | ## Ensure that /Addin/ExtensionPoint@path is set. Ex: "<ExtensionPoint path="/OpenSim/LartMe">..." | ||
+ | ## Ensure that /Addin/ExtensionPoint/ExtensionNode@name is set to "Plugin". This is a custom derived node type (OpenSim.Framework.PluginExtensionNode). | ||
+ | ## Ensure that /Addin/ExtensionPoint/ExtensionNode@type is set to "OpenSim.Framework.PluginExtensionNode" | ||
+ | ## Ensure that /Addin/ExtensionPoint/ExtensionNode@objectType is set to the fully qualified name of the consumer interface found in the above assembly import. Ex: "OpenSim.ILart" | ||
+ | # Write a addin manifest. See LoadRegionsPlugin.addin.xml as an example. | ||
+ | ## Ensure that /Addin/Runtime/Import@assembly exists for the assembly that implements the producer class. | ||
+ | ## Ensure that /Addin/Dependencies/Addin@id exists and is the same as the id of the host manifest. Ex: "Lart" | ||
+ | ## Ensure that /Addin/ExtensionPoint@path is set and is the same as that of the host manifest. Ex: "<ExtensionPoint path="/OpenSim/LartMe">..." | ||
+ | ## Ensure that /Addin/Extension/Plugin@type is set to the fully qualified name of the implementing provider class. Ex: "OpenSim.Lart" | ||
+ | ## Optionally, if you wish to discriminate on "provider" you can set /Addin/Extension/Plugin@provider | ||
+ | # When you need to load the provider for the consumer, create a PluginLoader of the type defined by the consumer interface. Ex: "new PluginLoader <ILart> ();" | ||
+ | ## If the provider requires a Initializer, it should be passed to the PluginLoader constructor. All plugins will be initialized using this initializer. Ex: "new PluginLoader <ILart> (new LartInitializer ("l4rt"));" | ||
+ | # Call PluginLoader.Load using the required extension point. Ex: "loader.Load ("/OpenSim/LartMe")" | ||
+ | |||
+ | <pre> | ||
+ | public interface ILart : IPlugin | ||
+ | { | ||
+ | void LartMe(); | ||
+ | } | ||
+ | |||
+ | //... | ||
+ | |||
+ | public class Lart : ILart | ||
+ | { | ||
+ | void LartMe () { // mystery } | ||
+ | } | ||
+ | </pre> |
Revision as of 01:12, 29 July 2008
Contents |
Current state of Plugin loading
The .NET runtime system has always made reflecting and introspecting of assemblies at runtime very easy to do, making dynamic loading of modules possible with very little effort.
This has unfortunately lead to a situation in OpenSim where historically module loading was hard-coded and done by hand, using copy-and-paste code. Clearly this is a suboptimal situation.
Effort was made to find a reusable, cross-platform dynamic assembly loader that could be used as-is, instead of reinventing our own. The only current candidate is Mono.Addins. MS's .NET runtime has its own Addin class, however as of this writing it is not yet implemented in Mono.
A class called PluginLoader has been created to thinly wrap Mono.Addins, and the assemblies loaded by this class have been standardized on the IPlugin interface hierarchy. With the exception of RegionModules, all dynamically loaded assemblies should be called Plugins, and inherit from this base class.
Currently the following interfaces have been converted to IPlugin:
- OpenSim.IApplicationPlugin
- OpenSim.Grid.GridServer.IGridPlugin
- OpenSim.Data.IGridDataPlugin
- OpenSim.Data.ILogDataPlugin
And the following Assemblies are being loaded by PluginLoader:
- OpenSim.ApplicationPlugins.LoadRegions.LoadRegionsPlugin
- OpenSim.ApplicationPlugins.Rest.Regions.RestRegionPlugin
- OpenSim.ApplicationPlugins.Rest.Inventory.RestHandler
- OpenSim.ApplicationPlugins.RemoteController.RemoteAdminPlugin
- OpenSim.Data.MySQL.MySQLGridData
- OpenSim.Data.MySQL.MySQLLogData
Why Mono.Addins
Mono.Addins offers features outside of being upstream maintained by the Mono project:
- logically splits up module interfaces from implementations in a cross platform way through the use of text-based "extension points" and "addin manifests"
- lazily loads assemblies only when finally necessary
- tracks module dependencies
- allows customize the addin manifest XML
- automatically handles module sharing across application domains
- provides support for downloading assemblies from remote repositories
Mono.Addin concepts
Although effort has been made to make PluginLoader independent of the module loading mechanism, there are some concepts that should be understood when learning how to learn the system:
Separating Male and Female
A good system provides a means of loosely coupling two connected parts with a common interface so that one can be changed easily without affecting the other.
The assembly that consumes a service is called a host, and the provider of a service is called an addin.
The common reference point that addins and hosts use to identify each other is called an extension point, which is essentially a hierarchical text "path". If a host asks for the addin(s) corresponding to extension point "/Foo", and an addin claims to implement "/Foo", then the two can be joined to make a functioning whole.
All OpenSim extension points start at the root "/OpenSim". If you wished to create a new service "Foo", you might choose to name it "/OpenSim/Foo".
In operation the consumer of a service, the host, requests that the system load all addins that claim to extend an extension point, and a list is returned to the consumer, who can choose to load any subset of those providers, the addins, into memory.
Describing an Addin
Clearly metadata must be associated with both host and addin, in our case this is an XML file that has the extension *.addin.xml, called a manifest.
In practice, the host manifest and addin manifest are so similar as to be almost exactly the same. The principle difference is that host manifests tell Mono.Addins where to find abstract interfaces, and addin manifests tell Mono.Addins where to find concrete implementations of those interfaces.
The manifest has 3 critical parts
- Name and Version information
this is used to check inter-addin dependencies - Filename of assemblies where the consumer interfaces or provider implementations can be found
this is needed to know which assemblies must be searched - Extension Point path and Fully Qualified .NET Class Name of the consumer interface or provider implementation
this is needed to load the actual classes into memory
The way to tell a host manifest from an addin manifest is that host manifests will *always* contain an isroot=true attribute in the <Addin> tag. However this is complicated by the fact that an addin can act as host for another addin in a recursive manner.
Building a model
When the PluginLoader is first created within the host, Mono.Addins will search the directory the host was executed and search for all files that end in *.addin.xml. It uses the information to build a registry about the addins in addin-db-${VERSION}/ directory. The purpose of the registry is to collect all information once, then lazily load assemblies into memory only at the last moment when it is precisely known exactly which class is required by the user.
How to make your own plugin
- Decide on the service you wish to separate into provider and consumer. Ex: "LartMe"
- Create an extension point to identify that service. Ex: "/OpenSim/LartMe"
- Create an consumer interface that has the methods needed, and derive it from OpenSim.Framework.IPlugin. Ex: "OpenSim.ILart"
- If that interface needs a constructor that takes parameters, you will also have to create a class derived from PluginInitialzerBase that can act as a closure for calling a parameterized constructor. See IApplicationPlugin for an example. Ex: "OpenSim.LartInitializer"
- Write a provider class that implements that interface. Ex: "OpenSim.Lart"
- Write a host manifest. See OpenSim.addin.xml as an example.
- Ensure that /Addin@isroot=true
- Ensure that /Addin@id is set to a unique name. Ex: "LartMe"
- Ensure that /Addin/Runtime/Import@assembly exists for each assembly that either hosts the application (host.EXE) or defines an addin consumer interface (Lart.dll) used by the host
- Ensure that /Addin/ExtensionPoint@path is set. Ex: "<ExtensionPoint path="/OpenSim/LartMe">..."
- Ensure that /Addin/ExtensionPoint/ExtensionNode@name is set to "Plugin". This is a custom derived node type (OpenSim.Framework.PluginExtensionNode).
- Ensure that /Addin/ExtensionPoint/ExtensionNode@type is set to "OpenSim.Framework.PluginExtensionNode"
- Ensure that /Addin/ExtensionPoint/ExtensionNode@objectType is set to the fully qualified name of the consumer interface found in the above assembly import. Ex: "OpenSim.ILart"
- Write a addin manifest. See LoadRegionsPlugin.addin.xml as an example.
- Ensure that /Addin/Runtime/Import@assembly exists for the assembly that implements the producer class.
- Ensure that /Addin/Dependencies/Addin@id exists and is the same as the id of the host manifest. Ex: "Lart"
- Ensure that /Addin/ExtensionPoint@path is set and is the same as that of the host manifest. Ex: "<ExtensionPoint path="/OpenSim/LartMe">..."
- Ensure that /Addin/Extension/Plugin@type is set to the fully qualified name of the implementing provider class. Ex: "OpenSim.Lart"
- Optionally, if you wish to discriminate on "provider" you can set /Addin/Extension/Plugin@provider
- When you need to load the provider for the consumer, create a PluginLoader of the type defined by the consumer interface. Ex: "new PluginLoader <ILart> ();"
- If the provider requires a Initializer, it should be passed to the PluginLoader constructor. All plugins will be initialized using this initializer. Ex: "new PluginLoader <ILart> (new LartInitializer ("l4rt"));"
- Call PluginLoader.Load using the required extension point. Ex: "loader.Load ("/OpenSim/LartMe")"
public interface ILart : IPlugin { void LartMe(); } //... public class Lart : ILart { void LartMe () { // mystery } }