Runtime Host Sample

Open this sample in Visual Studio

This sample demonstrates how to host PostSharp in an application that performs runtime weaving. This would be the case for instance of an application server that weaves components before loading them in the server.

The sample explains how different application domain interact. It shows how to work with deep-first assembly processing and assembly name overwriting.

The characteristics of this sample host are:

Concept

When you develop an aspect or a plug-in, your code is instantiated and invoked by PostSharp. However, when you develop a PostSharp Host, your code instantiate and invoke PostSharp.

PostSharp comes with two hosts: the MSBuild task and the command-line utility. This sample illustrates how to develop a host that weaves assemblies at runtime, that is, to be precise, just before they are loaded in the application domain (AppDomain).

Application Domains

Three application domains live in the host process:

The Host Class

An application that hosts PostSharp should implement the IPostSharpHost interface. PostSharp calls back this interface when it needs to resolve an assembly reference (ResolveAssemblyReference method) or to know how an assembly should be processed (i.e. with which PostSharp project and which parameters; GetProjectInvocationParameters method), or when a message is emitted (OnMessage method).

In our sample, the IPostSharpHost interface is implemented by the Host class. Additionally, this class has an Execute method that sets up the client application domain and invoke PostSharp.

The LocalHost Class

PostSharp offers the possibility to have a host-defined class laying between PostSharp and the system application domain. This is useful if you want to react to some events, or perform assembly reference resolving, in the application domain of PostSharp. This enables you to have access to the complete object model of PostSharp. This host object is called local because it resides in the same application domain as PostSharp. The default implementation is PostSharpLocalHost. In this sample, we demonstrated how to override it. We defined the class LocalHost that registers to some event and simply log to the console. Event handlers are registered in the Initialize method.

The ClientDomainManager class

The ClientDomainManager class manages the client application domain. An instance of this object is created by the Host object just after it creates the client application domain. The principal role of this object is to handle the AppDomain.AssemblyResolve event so that it provides the assembly transformed by PostSharp instead of the unmodified assembly. 

In order to achieve this, there is a close cooperation between the Host and the ClientDomainManager objects. The ClientDomainManager object maintains a dictionary that associates an AssemblyName to a location on the file system. This file could be transformed by PostSharp or not; it is not relevant for ClientDomainManager. The Host object should call the ClientDomainManager.SetAssemblyLocation method after the assembly. Because we chose to weave all assemblies present in the same directory as the entry point assembly, and only them, it is sufficient to call the SetAssemblyLocation method once we have ordered the transformation of the assembly, that is, in the Host.GetInvocationParameters method.

Another role of the ClientDomainManager class it to invoke the entry point of the method level. This is nearly trivial, but one should not forget to set up the proper thread apartment. This is done by the Execute method.

Renaming Woven Assemblies

Remember that we use the AppDomain.AssemblyResolve event to tell the runtime engine to load our woven assembly instead of the initial, unmodified assembly. However, this even is only a fallback mechanism. That is, it is called only when the required assembly is not found using the default probing mechanism. So we cannot know with certitude that we will get the chance to have our event handler called.

In order to circumvent this issue, PostSharp renames woven assemblies (this feature is named assembly name overwriting). The result is that the default probing algorithm of the runtime engine always fails, so the AssemblyResolve event is always raised and we get always the opportunity to load our woven version of the assembly.

When PostSharp renames an assembly, it calls the RenameAssembly method of the Host object. This method, in turn, propagates the renaming in the ClientDomainManager object.