When you create an application that allows you to add add-ins during runtime, you will need to deal with certain issues – for example, how to find the add-ins, and how to solve versioning issues so that the hosting application and the add-in can progress independently. There are several ways to resolve these issues. In this section, you read about the issues of add-ins and how the architecture of MAF solves them:
- Issues with add-ins
- Pipeline architecture
Issues with Add-Ins
Creating a hosted application that dynamically loads assemblies that are added at a later time has several issues that must be dealt with, as shown in the table that follows.
Now let’s look at the architecture of MAF and how this framework solves these issues, The design of MAF was influenced by these goals:
- It should be easy to develop add-ins,
- Finding add-ins during runtime should be performant.
- Developing hosts should be an easy process as well, but not as easy as developing add-ins.
- The add-in and the host application should progress independently.
The MAF architecture is based on a pipeline of seven assemblies. This pipeline solves the versioning issues with add-ins. Because the assemblies from the pipeline have a very light dependency, it is possible that the contract, the hosting, and the add-in applications progress with new versions completely independent of one another.
It shows the pipeline of the MAF architecture. In the center is the contract assembly. This assembly contains a contract interface that lists methods and properties that must be implemented by the add-in and can be called by the host. Left of the contract is the host side, and on the right, the add-in side. In the figure you can see the dependencies between the assemblies. The host assembly shown leftmost does not have a real dependency to the contract assembly; the same is true of the add-in assembly. Both do not really implement the interface that is defined by the contract. Instead, they just have a reference to a view assembly. The host application references the host view; the add-in references the add-in view. The views contain abstract view classes that define methods and properties as defined by the contract.
It shows the relationship of the classes from the pipeline. The host class has an association with the abstract host view class and invokes its methods, The abstract host view class is implemented by the host adapter. Adapters make the connection between the views and the contract. The add-in adapter implements the methods and properties of the contract. This adapter contains a reference to the add-in view and forwards-calls from the host side to the add-in view. The host adapter class defines a concrete class that derives from the abstract base class of the host view to implement the methods and properties. This adapter includes a reference to the contract to forward calls from the view to the contract.
With this model it is possible that the add-in side and the host side emerge completely independent. Just the mapping layer needs to adapt. For example, if a new version of the host is done that uses completely new methods and properties, the contract can still stay the same and only the adapter needs to change. It is also possible that a new contract is defined. Adapters can change, or several contracts can be used in parallel.
How can new add-ins be found for the hosting application? The MAF architecture uses a predefined directory structure to find add-ins and the other assemblies of the pipeline. The components of the
pipeline must be stored in these subdirectories:.
All these directories with the exception of the Addlns directory directly contain the assembly of the specific part of the pipeline. The Addlns directory contains subdirectories for every add-in assembly. With add-ins, it is also possible to store them in directories that are completely independent of the other pipeline components.
The assemblies of the pipeline are not just loaded dynamically to get all the information about the add-in using reflection. With many add-ins, this would increase the startup time of the hosting application. Instead, MAF uses a cache with information about the pipeline components. The cache is created by the program installing the add-in or by the hosting application if the hosting application write-access to the directory of the pipeline .
The cache information about the pipeline components is created by invoking methods of the AddlnStore class. The method Update () finds new add-ins that are not already listed with the store files. The Rebuild () method rebuilds the complete binary store file with information about the add-ins.
The following table lists the members of the AddlnStore class.
Activation and Isolation
The FindAddIns () method of the.AddInStore class returns a collection of AddInToken objects that represent an add-in. With the AddInToken class, you can access information about the add-in such as name, description, publisher, and version. You can activate the add-in by using the Activate () method. The following table lists properties and methods of the AddlnToken class.
One add-in can break the complete application. You may have seen Internet Explorer crash because of a failing add-in. Depending on the application type and the add-in type, you can avoid this by letting the add-in run within a different application domain or within a different process. MAF gives you several options here. You can activate the add-in in a new application domain or a new process. The new application domain might also have restricted permissions.
The Activate () method of the AddInToken class has several overloads where you can pass the environment into which the add-in should be loaded. The different options are listed in the following table.
The type of application may restrict the choices you have. WPF add-ins currently do not support crossing processes. With Windows Forms, it is not possible to have Windows controls connected across different application domains.
Let’s get into the steps of the pipeline when the Acti va te () method of an AddlnToken is invoked:
- The application domain is created with the permissions specified.
- The assembly of the add-in is loaded into the new application domain with the Assembly .LoadFrom () method
- The default constructor of the add-in is invoked by using reflection. Because the add-in derives from the base class that is defined with the add-in view, the assembly of the view is loaded as well.
- Next, an instance of the add-in side adapter-Is-constructed. The instance of the add-in is passed to the ..constructor of the adapter, S0 the adapter can connect the contract to the add-in. The add-in. adapter derives from the base class’MarshalBYRefObj ect, so it can be invoked across application domains .
- The activation code returns a proxy to the add-in side adapter to the application domain of the” hosting application. Because the add-in adapter implements the contract interface, the proxy contains methods and properties of the contract interface.
- An instance of the host side adapter is constructed in the application domain of the hosting application. The proxy of the add-in side adapter is passed to the constructor. The activation finds the type of the host-side adapter from the add-in token.
The host side adapter is returned to the hosting application.
Contracts define the boundary between the host side and the add-in side. Contracts are defined with an interface that needs to derive from the base interface IContract. The contract should be well-thought in that it supports flexible add-in scenarios as needed.
Contracts are not versionable and may not be changed so that previous add-in implementations can still run in newer hosts. New versions are created by defining a new contract.
There’s some restriction on the types you can use with the contract. The restriction exists because of versioning issues and also because application domains are crossed from the hosting application to the add-in. The types need to be safe and versionable, and able to pass it across the boundaries (application domain or cross-process) to pass it between hosts and add-ins.
Possible types that can be passed with a contract are:
- Primitive types
- Other contracts
- Serializable system types
- Simple seriaIizable custom types that consists of primitive types, contracts, and do not have an implementation.
The members of the IContract interface are explained in the following table.
Contract interfaces are defined in the namespaces System. Addln. Contract, System. Addln .Con’tract. Collections, and System, Addln. Contract. Automation. The following table lists contract interfaces that you can use With a contract:
How long does an add-in need to be loaded? How long is it used? When is it possible to unload the application domain? There are several options to resolve this. One option is to use reference counts. Every use of the add-in increments the reference count. If the reference count decrements to zero, the add-in can be unloaded. Another option is to use the garbage collector. If the garbage collector runs, and . there’s no more reference to an object, the object is the target of garbage collection .. NET Remoting is using a leasing mechanism and a sponsor to keep objects alive. As soon as the leasing time ends, sponsors are asked if the object should stay alive.
With add-ins, there’s a specific issue for unloading add-ins because they can run in different application domains and also in different processes. The garbage collector cannot work across different processes. MAF is using a mixed model for lifetime management. Within a single application domain, garbage collection is used. Within the pipeline an implicit sponsorship is used, but reference counting is available from the outside to control the sponsor.
Let’s consider a scenario where the add-in is loaded into a different application domain. Within the host application, the garbage collector cleans or the host view and the host side adapter when the reference is not needed anymore. For the add-in side, tne contract defines the methods AcquireLifetimeToken ( l and RevokeLifetimeToken (l to increment and decrement the reference count of the sponsor. These methods do not just increment and decrement a value which could lead to release an object too early if one party would call the revoke method too often. Instead, AcquireLifetimeToken (l returns an identifier for the lifetime token, and this identifier must be used to invoke the RevokeLifetimeToken ( l method. So these methods are always called in pairs.
Usually you do not have to deal with invoking the AcquireLifetimeToken () and RevokeLifetimeToken () methods. Instead you can use the ContractHandle class that invokes AcquireLifetimeToken () in the constructor and RevokeLifetimeToken () in the finalizer.
In scenarios where the add-in is loaded in a new application domain, it is possible to get rid of the loaded code when the add-in is not needed anymore. MAF uses a simple model to define one add-in as the owner of the application domain to unload the application domain if this add-in is not needed anymore. An add-in is the owner of the application domain if the application domain is created when the add-in is activated. The application domain is not unloaded automatically if it was created .previously.
The class ContractHandle is used in the host side adapter to add a reference count to the add-in. The members of this class are explained in the following table.
Versioning is a very big issue with add-ins. The host application is developed further as are the add-ins. , One requirement for an add-in is that it should be possible that a new version of the host application can still load old versions of add-ins. The other direction should work as well: older hosts should run newer versions of add-ins. But what if the contract changes?
System.AddIn is completely independent from the implementation of the host application and add-ins. This is done with a pipeline concept that consists of seven parts.