Advances in .NET Type system: Type Equivalence Demo

I just finished my PDC session at Los Angeles where we introduced Type Embedding support in the compilers and Type Equivalence support in the CLR. Some people I talked to after the session were very enthusiastic about the whole thing and I did get comments like “this is the best thing in this conference”. This was very encouraging and exciting. Thanks!

On the other hand there were some points in the talk where people just assumed it is all about COM Interop. Couple of folks started leaving the room before I started showing the Type Equivalence being applied to entirely managed models. I’ve got to tell you that people leaving in the middle of your presentation makes for one of the worst feelings presenter could have (Jesse Kaplan actually shared with me his bad experiences during presentations and his seemed worse). As a result the rest of the presentation on type equivalence did go only south from there and not as many people as I would like to got it. Now I would like to rectify the situation and take some time to go through the Type Equivalence demo in this post.

The download of the demo sources is attached. To run it you will need to run Visual Studio 10 CTP – if you do not have one – get it here https://go.microsoft.com/fwlink/?LinkId=129231

Again, the goal of the demo is to show how you can achieve loose type coupling and version resiliency in a completely managed extensible application. I will show that by placing interfaces into a “compile-time only” programmability assembly and by linking the types from this assembly into your add-ins using new “Embed Interop Types” compilation option – you can achieve true version independence. This means that your add-in can run against any version of the host application – it will be both backward and forward compatible.

So, let’s get to it. Supposedly you already do have VS 10 CTP installed, you have downloaded the attached ConsoleApp solution and opened it in Visual Studio. And now you are asking yourself the very relevant question “what the heck is this thing in front of me?”.

Let’s start the discussion with ConsoleApp.Host project. It is actually a very simple application that does a very simple thing – it reads text from the console and sends lines one-by-one to its add-ins. So, here is the relevant code from Program.cs

static void Main(string[] args)
{
    Application app = new Application();
    LoadAddIns(app);

    string line = Console.ReadLine();
    while (line != "quit")
    {
        app.OnReadLine(line);
        line = Console.ReadLine();
    }

    UnloadAddIns();
}

Obviously, the Host application knows to do two things – load add-ins and quit. The real functionality though is not contained in ConsoleApp.Host project – it is the add-ins that do make this application interesting. The add-ins, however, do need an Object Model they could talk to.

So, let’s first talk about the OM exposed to the add-ins. It consists of interfaces and is located in Interfaces.cs file in ConsoleApp.Programmability project. The most important interface there is IApplication interface. This interface is handed to the add-in as part of the initial hand-shake which is happening when add-in is loaded and its implementation of IEntryPoint.Startup is called by the host (see the ConsoleApp.Host.Program.LoadAddIn method)

Here is how the IApplication interface looks like:

     /// <summary>
    /// This interface represents the top-level object in the host's OM
    /// It will be passed to the add-in during add-ins initialization
    /// </summary>
    [ComImport]
    [Guid("2885E362-5C29-4481-89DD-1E58F3770132")]
    public interface IApplication
    {
        int Version { get; }

        //
        // methods available in version 1
        //

        void WriteLine(string message);
        // TODO: we should have an event here instead of 
        // TODO: Register/Unregister pattern
        // TODO: but compiler currently errors out for events
        // public event ReadLineEventHandler ReadLine; 
        void RegisterReadLineCallback(IReadLineCallback callback);
        void UnregisterReadLineCallback(IReadLineCallback callback);

        //
        // methods available in version 2
        //

#if VERSION2
        ConsoleColor ForegroundColor { get; set; }
#endif
    }

There are a couple of things to note. Take a look at the toolbar and take a notice of the active configuration for your solution.

image

Your active configuration should be set to V1. If this is the case you will see ForegroundColor property appear grayed out since this functionality will only be available at Version 2 of the Host Applicaiton.

Other than that IApplication exposes quite simple functionality. In Version1 of the host application add-ins can ask the host application for its version number (IApplication.Version property) , write messages using IApplication.WriteLine method and also subscribe to read-line callbacks using RegisterReadLineCallback/UnregisterReadLineCallback methods.

We also are trying to enable locally embedding types from ConsoleApp.Programmability assembly exactly the same way types contained in Interop Assemblies are embedded for “No Primary Interop Assemblies” scenarios. To achieve this we need to make ConsoleApp.Programmability assembly looks like an Interop Assembly (remember that compilers only embed types from Interop Assemblies) by placing attributes in several key spots throughout the code in Interfaces.cs. In particular, each interface needs to be attributed with ComImport and Guid attributes. In addition ConsoleApp.Programmability assembly does have assembly level Guid and ImportedFromTypeLib attributes:

 [assembly:Guid("35E86F71-1587-4453-A8AB-65A6EA5EAE42")]
[assembly:ImportedFromTypeLib("ConsoleApp.Programmability")]

Next thing is to actually take a look at the ConsoleApp.AddIn.Echo project. This project represents an add-in. ConsoleApp’s add-ins do need to implement IEntryPoint interface and they also need to reply to IReadLineCallback calls coming from the host. The implementation for the add-in is very minimal and looks this:

namespace ConsoleApp.AddIn.Echo
{
    public class AddIn : IEntryPoint, IReadLineCallback
   {
        IApplication app;

        public void Startup(IApplication app)
        {
            this.app = app;

            app.WriteLine("AddIn.Echo is starting up ... ");
            app.WriteLine(" Host object type - "+ app.GetType().AssemblyQualifiedName);
            app.WriteLine(" Entry point type - "+ typeof(IEntryPoint).AssemblyQualifiedName);

            app.RegisterReadLineCallback(this);
        }

        public void Shutdown()
        {
            app.WriteLine("AddIn is shutting down ... ");
        }

        public void ReadLine(IReadLineEventArgs args)
        {
            string message = args.Message;

            if (message == null)
                return;

            string echoCmd = "echo ";

            if (message.StartsWith(echoCmd))
            {
                app.WriteLine(message.Substring(echoCmd.Length));
            }
        }
    }
}

Probably no special explanation is required for this code – notice though that ReadLine callback recognizes lines starting with ‘echo ‘ and prints back the rest of the line.

Also notice, that the reference to ConsoleApp.Programmabilty assembly is marked with “Embed Interop Types”=”true” (aka the EIT switch)

image

So, now is the time to compile the solution. For Version1 configuration this will result in 3 projects being compiled – ConsoleApp.Programmability, ConsoleApp.Host and ConsoleApp.AddIn.Echo when both ConsoleApp.Host and CosoleApp.AddIn.Echo do reference programmability assembly using the EIT switch.

Now, if you run the application that was compiled you will notice that Echo add-in did get loaded and is running and functioning (just type ‘echo Hello PDC’ to see the echo command in action). Also, by attaching the debugger and checking out the Modules window, you can verify that ConsoleApp.Programmability assembly is not even loaded into the process! This means that host and add-in can talk to each other w/o sharing a common type!

The secret here is the new CLR 4.0 Type Equivalence support. During the compilation local copies of types contained in programmability assembly were embedded into both ConsoleApp.Host and ConsoleApp.AddIn.Echo assemblies and the host and the add-in are able to talk to each other using those local copies of IEntryPoint and IApplication interfaces. Since both copies do have the same Guid attribute – they are considered to be equivalent by the CLR. Casts and calls through equivalent interfaces are executed as if they are casts/calls on the interface the object derives from!

Next step in the demo is to change the solution configuration option to Version2.

image

Switching to Version2 configuration compiles the Host and Programmability projects with VERSION2 conditional compilation symbol defined. If you search through the source code for the occurrences of “#if VERSION2” directives throughout the solution you will see a number of places where v2 of the ConsoleApp.Host is different from the previous version.

In particular, Host’s OM now has new API – IApplication.ForegroundColor. Notice, that we have added this new property at the end of the existing interface. The placement at the end  is very important and is done in order to maintain IApplication’s vtable compatibility with IApplication interface from v1 (IMPORTANT: you can only add new methods at the end of the existing interface and never-never in the middle or at the beginning)

Another interesting place to look at is the implementation of IApplication.Version property. v1 implementation of of Application.Host returns value of 1 here, while v2 implementation returns value of 2. This is done to allow add-ins to perform “adaptive coding”. In particular, if they know the host is of a lower version than expected they could “disable” some of its functionality. e.g. here is the adaptive code from ConsoleApp.AddIn.Color add-in:

if (app.Version < 2)
{
    app.WriteLine("'color' command is not supported on this version of the ConsoleApp");
    return;
}

Now, compile Version2 of the host and notice that Color add-in is loaded and can execute various color commands.

image

Again, using the debugger you can easily verify that since Application.Programmability assembly has been linked in using the EIT flag – this assembly is actually not loaded into the memory.

So, next step is to try version resiliency in action. What I suggest you is to manually copy the ConsoleApp.AddIn.Echo.dll from Version1\addins folder to Version2\addins folder and run the Version2 of the host. You will notice that Echo add-in is loaded alongside the Color add-in and is functioning as expected. This shows that the v2 application is backward compatible and can load add-ins compiled against the old version of the OM.

image

Now, lets try to go in the other direction and now copy ConsoleApp.AddIn.Color.dll from Version2\addins folder to Version1\addins folder and start the v1 version of the host application. You will again notice that both Color and Echo add-ins were loaded, but when trying to execute the color command – we will get an error saying "'color' command is not supported on this version of the ConsoleApp”. This is adaptive coding recognize the color command being executed on a version of Host where it is not applicable.

Below is the screenshot that I took. It demonstrated that add-ins compiled against future version of host OM can still be executed on previous versions of the host application. This is known as forward compatibility and is possible in the VS 10 CTP version of the CLR because of the Type Equivalence support.

image

I hope you followed this demo through to this point. We have seen how Type Equivalence enables loose type coupling and version resiliency for managed applications. From here it should be pretty trivial to imply how easier the managed extensions deployment experience has become.

I also encourage you to use tools like ildasm, reflector and managed debugger to better understand how the whole thing does fit together. I also will be glad to hear feedback /answer question that are posted as comments to this post.

TypeEquivalenceDemo.zip