Partager via


Adapters

Geez, did I make you wait long enough!?! I have been remiss, hopefully I will have time to do this more regularly. Those of you with kids know how time can evaporate.

Anyway, I've promised a post on Adapters for months now, so here it is finally. As my colleague JackG said on my Proxies post, a picture is worth a thousand words, so here is a picture that pulls the whole story together.

So as you can see, I've drawn this picture so that the addin is calling into the host through the Proxy. Obviously (I hope its obvious anyway) there is at least one of these pipelines going the other way -- the host calling the addin through some contract. But in practice, the addin generally calls back to the host a lot more -- think Office addins. So there will be many proxies on the addin side, and many adapters sitting on top of many "real" host objects. So I drew the common case.

So the picture makes clear: the adapter implements the contract.  But it must do more: it must also derive from MarshalByRefObject so that it can be remoted. In general this means also overriding MarshalByRefObject.InitializeLifetimeService to manage the leasing of the object. I'm not going to delve too deeply into that implementation here, I'm sure there are several other posts on MarshalByRefObject in the blogosphere. But suffice it to say, the way the leasing is implemented in the MarshalByRefObject plays directly into the implementation of IContract.AcquireLifetimeToken and IContract.RevokeLifetimeToken.

There is a very simple implementation to this: return null from IntializeLifetimeService and have Acquire\RevokeLifetimeService do nothing. Returning null from InitializeLifetimeService gives the object an infinite lease, meaning it will live as long as the AppDomain lives. In many cases this is entirely appropriate, if the object is going to live as long as the application is running, this is a simple and robust implementation. But, of course, transient objects should only live as long as necessary to avoid unnecessary working set. This means understanding and dealing with Sponsors and Leases in the .NET Remoting system.

The good news is that we provide a default implementation of this in VSTA in ContractAdapterBase. Almost all of the built-in Adapters derive from ContractAdapterBase (there are a very few that don't, and most of these are directly involved in the implementation of Acquire and RevokeLifetimeToken themselves). It provides a robust implementation of the leasing system that keeps objects alive as long as they need to be, but no longer. And this implementation can be tweaked by overriding the default sponsor timeouts.

But I seem to have digressed a bit. I could do an entire article on Sponsors and Leases, and may someday (I don't want to promise anything, now, given how long it has taken for me to do this one). Let's talk about adapters.

This first thing you may think when looking at the picture above is, "is the adapter really necessary? It appears I could just implement the Contract on 'Real' Host Object and be done." Well, appearances can be deceiving.

It is certainly possible to implement contracts on "real" objects and do away with adapters, and in fact may be done. But we have found that in practice an adapter is more common than not. For one thing, the "real" host object might already be implemented -- you are adding extensibility after the fact. Another thing is that, well, there can be only one base class -- no multiple inheritance in .NET. So if your "real" object needs to derive from something that doesn't derive from MarshalByRefObject you are going to need an adapter. And practically, it is easier to simply keep your "real" implementation separate from your extensibility code. It just sort of falls out, you'll see.

Of course we provide a ton of default adapters in the VSTA code base. Another reason to use adapters is because you can just use ours...

I mentioned ContractAdapterBase already. We also provide two different implementations of IRemoteObjectContract and friends -- one that "adapts" managed objects and one that "adapts" COM objects. These are quite useful for getting up and going quickly, but there is a performance overhead here since everything goes through late-binding. In many cases the performance hit is worth taking -- its not that big and you are done quickly. But I usually recommend doing a mix of custom contracts and adapters with the generic ones. Target critical performance scenarios with the custom contracts, but let the generic adapters and contracts take care of the rest.