Let’s discuss the specifics of the construction of the add-in, the isolation boundary and the pipeline in the add-in model – i.e. activation. I’ll be referring to the discovery and activation as discussed here and the segments of the add-in pipeline as discussed here. Let’s say you have discovered a single token for an add-in, and you have decided to activate it. To isolate the add-in from the host you chose to run it in its own new appdomain. What happens next when you call one of the Activate() methods on the add-in token? Let’s enumerate all of the steps that the system takes:
1. The appdomain is created with the permissions that you specified in the call to Activate().
2. The add-in assembly is loaded into that appdomain through a call to Assembly.LoadFrom(), passing in the location of the assembly. The add-in type will not be loaded in the host’s appdomain. In other uses this call can cause loader context issues if not used carefully, but we’ve taken steps to avoid them here. In fact, this method was created specifically to support add-in scenarios.
3. We use reflection to call the no-argument constructor for the add-in to create an instance in that appdomain. Naturally, since the add-in derives from the AddInBase class that is deployed in the AddInView assembly, this action causes the loader to load that assembly. At this point we have an instance of the add-in running in its own appdomain. Now we need to connect it to the host.
4. An instance of the AddInSideAdapter is constructed, passing in the add-in (typed as the add-in base) as the only argument to the constructor. (This adapter implements a Contract interface and derives from MarshalByRefObject through the intermediate ContractBase class. Therefore it can appear to live in both the host’s and the add-in’s appdomain—any calls to it from the host’s domain are proxied and executed in the add-in’s appdomain.)
5. The activation code passes the add-in adapter back to the host’s appdomain typed as the Contract that it implements.
6. An instance of the HostSideAdapter is constructed in the host’s appdomain, with the AddInSideAdapter being passed in as its only argument. The exact HostSideAdapter type is determined by examining the add-in token. This adapter derives from (or implements) the HostViewOfAddin that the host compiled against.
7. The activation code returns the host-side adapter to the host, typed as the HostViewOfAddin.
The host will sometimes do further initialization of the add-in through a separate call, passing in any needed arguments. Then it is up to the host and add-in to communicate as needed. Activation has completed.
Do the adapters need to be public? Actually, no. Though the normal rules of visibility would suggest that they would need to be public in order to be created and used by the activation code, the rules don’t apply in this case. That’s because we use reflection to invoke the constructor, and given enough permission, reflection can invoke non-public constructors. We didn’t want the adapters to need to be public because they exist as an implementation detail, not things to be documented and supported in themselves.
When the HostSideAdapter is constructed with a reference to the AddInSideAdapter, it does something right away that we wouldn’t need to do if all the objects were in the same appdomain. It needs to call the AcquireLifetimeToken() member, specified in the IContract interface, of that adapter. That is part of another big topic, lifetime management. We’ll discuss that in another post.
This is one way to activate an addin in this model. There are also some other different ways you could activate the same addin. You could choose to activate it in the same appdomain as the host. You could also choose to activate it in an entirely separate process, if isolation matters above all else. We’ll explore those options more in later posts.