Handling Add-ins at Run-time


The Mono.Addins API provides several classes to be used by add-in hosts for handling add-ins at run-time. Most of the operations can be done with the class Mono.Addins.AddinManager, which provides methods for:
  • initializing the add-in engine,
  • getting nodes from extension points,
  • subscribing to extension events.

Initialization of the add-in manager

The first thing an add-in host has to do before using any of the Mono.Addins classes is to initialize the add-in engine using the AddinManager.Initialize() method. It's a very simple operation:

public class TextEditorApplication
{
	public static void Main ()
	{
		// Initialize the add-in engine
		AddinManager.Initialize (".");
	}
}

This method has several optional parameters which specify the location of the application add-ins and other settings:

Parameter Description
configDir Directory where the add-in engine configuration information will be stored. If the path is relative, it is assumed to be relative to the application path. If no value is provided, the global location /etc/mono.addins will be used.
addinsDir Directory where to look for add-ins. If the path is relative, it is assumed to be relative to configDir. If not specified, the relative directory "addins" is used (that is, the directory will be configDir/addins
databaseDir Directory where to create the add-in cache database. If the path is relative, it is assumed to be relative to configDir. If not specified, the configDir path is used.


See Add-in discovery for more information about how add-ins are found and loaded.

Querying Extension Points

The AddinManager class offers several methods for getting extensions from an extension point. The most basic method is GetExtensionNodes, which can be used like this:

foreach (ExtensionNode node in AddinManager.GetExtensionNodes ("/TextEditor/StartupCommands")) {
	Console.WriteLine ("Command: " + node.Id);
}

It is important to notice that getting the extensions of a node won't normally result in loading any add-in. The add-in engine is able to extract the needed information from add-ins without having to load them.

The nodes returned from GetExtensionNodes will have the actual node type, depending on the node name that was used by add-ins to register them. So, for the StartupCommands commands extension (described in the previous chapter) it would be safe to do:

foreach (TypeExtensionNode node in AddinManager.GetExtensionNodes ("/TextEditor/StartupCommands")) {
	ICommand command = (ICommand) node.CreateInstance ();
	command.Run ();
}

Querying Type-bound Extension Points

The AddinManager class provides the method GetExtensionObjects (with several overloads) for querying extension points which are bound to types (that is, whose extension nodes are of type TypeExtensionNode). This method works like GetExtensionNode, but instead of returning a node, it casts the node to TypeExtensionNode and returns the result of calling CreateInstance (or optionally GetInstance).

For example, the previous GetExtensionNodes call might be simplified like this:

foreach (ICommand cmd in AddinManager.GetExtensionObjects ("/TextEditor/StartupCommands"))
	cmd.Run ();

If the extension point was declared using the TypeExtensionPointAttribute, you can specify a type reference instead of an extension path:

foreach (ICommand cmd in AddinManager.GetExtensionObjects (typeof(ICommand)))
	cmd.Run ();

Extension Object Lifetime

When querying and extension point, Mono.Addins provides three different ways of managing the creation of extension objects:
  • Shared objects: objects are created only once and shared in all queries. This is the default mode.
  • Non-shared objects: a new object is created for every query.
  • Shared by context: like Shared Objects, but only within a specific context.

By default, GetExtensionObjects returns shared objects.. The first time an extension point is queried, the add-in engine creates an instance of the extension objects to be returned. Those instances are cached, so the next time the extension point is queried, the same set of objects is returned. The GetExtensionObjects method has an optional reuseInstances parameter which can be set to False to get a set of non-shared objects from the extension point. For example:

// This first loop will create instances of each type registered in the extension point
foreach (ICommand cmd in AddinManager.GetExtensionObjects<ICommand> (true))
	cmd.Run ();

// The second loop will reuse and return the same instances created in the previous loop
foreach (ICommand cmd in AddinManager.GetExtensionObjects<ICommand> (true))
	cmd.Run ();

// This loop will create new instances, since the 'reuseInstances' parameter is set to false
foreach (ICommand cmd in AddinManager.GetExtensionObjects<ICommand> (false))
	cmd.Run ();

Objects can also be shared by context. Mono.Addins allows creating different Extension Contexts out of the extension model. Each extension context manages the object lifetime independently. Objects which are shared in one context will not be shared with other contexts. A context can be created using the AddinManager.CreateExtensionContext () method. This method returns an ExtensionContext instance, which has methods for querying extension points, just like AddinManager. For example:

// Create two different contexts
ExtensionContext ctx1 = AddinManager.CreateExtensionContext ();
ExtensionContext ctx2 = AddinManager.CreateExtensionContext ();

(...)

// Every time this loop is executed, it will process the same set of instances
foreach (ICommand cmd in ctx1.GetExtensionObjects<ICommand> (true))
	cmd.Run ();

// Every time this loop is executed, it will process the same set of instances,
// but the instances are not the same as the ones returned for ctx1
foreach (ICommand cmd in ctx2.GetExtensionObjects<ICommand> (true))
	cmd.Run ();

Extension Events

AddinManager offers several event sources to which add-in hosts can subscribe to be notified when an extension node is added or removed from an extension point. This may happen in two cases:
  • When an add-in is installed/uninstalled or enabled/disabled from inside a host application.
  • When there are extension nodes bound to conditions, and the status of those conditions change.

Using extension change events, it is possible to implement applications which can support dynamic activation and deactivation of add-ins at run-time.

There are two kind of extension change events:
  • AddinManager.ExtensionChanged event. This event is fired when any extension point in the add-in system changes. The event args object provides the path of the changed extension, although it does not provide information about what changed. Hosts subscribing to this event should get the new list of nodes using a query method such as AddinManager.GetExtensionNodes() and then update whatever needs to be updated.
  • AddinManager.AddExtensionNodeHandler (path, hanlder): hosts can call this method to be subscribed to an extension change event for a specific path. The event will be fired once for every individual node change. The event arguments include the change type (Add or Remove) and the extension node added or removed.

It is important to notice that when a host calls AddExtensionNodeHandler to register a handler, the handler will be called once for all nodes currently existing in the provided path. This behavior allows writing code like this:

class CommandManager
{
	// A list of commands
	ArrayList commands = new ArrayList ();

	public CommandManager ()
	{
		// Registers to node changes in the Commands path
		AddinManager.AddExtensionNodeHandler ("/TextEditor/Commands", OnExtensionChanged);
	}

	void OnExtensionChanged (object s, ExtensionNodeEventArgs args)
	{
		// Called when a node is added or removed
		ICommand command = (ICommand) args.ExtensionObject;
		if (args.Change == ExtensionChange.Add)
			commands.Add (command.Run ());
		else
			commands.Remove (command.Run ());
	}
}

In this example a CommandManager object subscribes to the extension change event for the path "/TextEditor/Commands". The idea is to keep all registered commands in a ''commands'' list. The class doesn't need to do any initialization of the commands list, because the call to AddExtensionNodeHandler will already fire an event of type ''Add'' for every existing node.

Last edited Feb 21, 2011 at 5:44 PM by slluis, version 7

Comments

Montellese Oct 10, 2010 at 12:26 PM 
In case anyone ran into the same problem as I did:

If you define a TypeExtensionPoint with a specific path you cannot simply use the GetExtensionObjects() methods which only take the extension type. If you define a path you also must provide it when using GetExtensionObjects().