Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Managed Extensibility Framework: Part 1

4.94/5 (26 votes)
23 Jun 2009CPOL9 min read 147.6K   529  
First in an article series intended to provide an introduction to Microsoft's Managed Extensibility Framework.

Introduction

This article series is intended to provide an introduction to Microsoft's Managed Extensibility Framework. Before showing the code snippets using the MEF framework, I would like to run through a case scenario where the need for MEF can be felt. I feel this will help in identifying the situations where MEF can be used effectively.

Background

Building reusable code into libraries and using them in applications is normal. In legacy Win32 applications, if a library is linked to an application, a linker is used to add a reference to that library in the PE header. When the runtime tries to load this application, a loader is used to perform the following operations:

  1. resolve all dependencies
  2. load them into memory
  3. map them to respective pointers
  4. then start the main routine

But in .NET world, with CLR and JIT compilation in the middle, things are slightly different. Now, the loader tries to load those libraries which are needed to execute just the Main method. As other methods are called from Main, the IL for those methods are JIT compiled on demand and loaded into memory. These aspects form the basic core for MEF.

Here is how it is defined officially:

"The Managed Extensibility Framework (MEF) is a new library in .NET that enables greater reuse of applications and components. Using MEF, .NET applications can make the shift from being statically compiled to dynamically composed. If you are building extensible applications, extensible frameworks and application extensions, then MEF is for you."

Take the middle part: "statically compiled to dynamically composed". It is all about that.

Case Scenarios

I would like to formulate a case where the need for MEF can be felt. Let us create a console application for this purpose.

  1. Create a simple console application. Name it "WOMEF" (without MEF).
  2. Replace the code in Program.cs with the following snippet:
C#
namespace WOMEF
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Enter 0 to quit, " + 
                              "any other number to continue");
            while (Console.ReadLine() != "0")
            {
                new Program().SayHelloToUser();
            }
        }
        public void SayHelloToUser()
        {
            Console.WriteLine("In SayHelloToUser");
            IWOMEFLib womeflib = new CWOMEFLib();
            womeflib.SayHello(Environment.UserDomainName + 
                              Environment.UserName);
            Console.ReadLine();
        }
    }
}

Let us define IWOMEFLib and CWOMEFLib in a library:

  1. Add a class library project to this solution by the name "WOMEFLib".
  2. Rename Class1.cs to CWOMEFLib.cs. Then, replace the code with the following snippet:
C#
using System;
namespace WOMEFLib
{
    public interface IWOMEFLib
    {
        int SayHello(string username);
    }
    public class CWOMEFLib : IWOMEFLib
    {
        #region IWOMEFLib Members
        public int SayHello(string username)
        {
            Console.WriteLine("\"" + "Say Hello to " + 
                              username + "\"");
            return 0;
        }
        #endregion
    }
}

Now, in the WOMEF project, add a project reference to the WOMEFLib project. Change the configuration to Release, build, and test the application. Nothing great about this. But, we are going to witness some interesting facts with these two assemblies.

The points that will be verified are:

  1. When the loader loads WOMEFLib.dll
  2. What happens if WOMEFLib.dll is missing when the application is starting
  3. When the SayHelloToUser method is JIT compiled
  4. What happens if WOMEFLib.dll is copied to the application directory while it is waiting for user input

In order to check these scenarios, we are going to view the method table and descriptors of the application WOMEFLib.exe in runtime.

We are going to use Windbg here. If you don't have it installed, download it here.

When the loader loads WOMEFLib.dll

Here, we are going to check when the loader loads WOMEFLib.dll.

  1. Start Windbg.
  2. In Windbg, click on File -> Open executable.
  3. Navigate to the Release folder of the solution and select WOMEF.exe.
  4. Windbg stops with interrupt 3. Press F5 to continue.
  5. ASM
    ntdll!DbgBreakPoint:
    7c90120e cc              int     3
  6. Now, the application starts and waits for the user entry with the message "Enter 0 to quit, any other number to continue".
  7. In Windbg, go to Debug menu, and click on Break. This is the ideal time to break into the application as it is up and running.
  8. Now, load sos.dll in Windbg by entering the following command in Windbg: ".loadby sos.dll mscorwks".
  9. ASM
    ntdll!DbgBreakPoint:
    7c90120e cc              int     3
    Missing image name, possible paged-out or corrupt data.
    0:003> .loadby sos.dll mscorwks
  10. Now, dumpdomains by entering the command: "!dumpdomain".
  11. 0:003> !dumpdomain
    *** ERROR: Symbol file could not be found. 
        Defaulted to export symbols for C:\WINDOWS\Microsoft.NET\... -
    PDB symbol for mscorwks.dll not loaded
    --------------------------------------
    System Domain: 7a3bd058
    LowFrequencyHeap: 7a3bd07c
    HighFrequencyHeap: 7a3bd0c8
    StubHeap: 7a3bd114
    Stage: OPEN
    Name: None
    --------------------------------------
    Shared Domain: 7a3bc9a8
    LowFrequencyHeap: 7a3bc9cc
    HighFrequencyHeap: 7a3bca18
    StubHeap: 7a3bca64
    Stage: OPEN
    Name: None
    Assembly: 001b0628
    --------------------------------------
    Domain 1: 0016d298
    LowFrequencyHeap: 0016d2bc
    HighFrequencyHeap: 0016d308
    StubHeap: 0016d354
    Stage: OPEN
    SecurityDescriptor: 0016e5c0
    Name: WOMEF.exe
    Assembly: 001b0628 [C:\WINDOWS\assembly\GAC_32\mscorlib\
                        2.0.0.0__b77a5c561934e089\mscorlib.dll]
    ClassLoader: 001b06a8
    SecurityDescriptor: 001ae190
    Module Name
    033e1000 C:\WINDOWS\assembly\GAC_32\mscorlib\
             2.0.0.0__b77a5c561934e089\mscorlib.dll
    
    Assembly: 001b9800 [D:\My Documents\Visual Studio 2008\
                        Projects\MEF\WOMEF\WOMEF\bin\Release\WOMEF.exe]
    ClassLoader: 001b9e68
    SecurityDescriptor: 001b96c8
    Module Name
    00a72c5c D:\My Documents\Visual Studio 2008\Projects\MEF\
             WOMEF\WOMEF\bin\Release\WOMEF.exe
    
    Assembly: 001bcb30 [D:\My Documents\Visual Studio 2008\
              Projects\MEF\WOMEF\WOMEF\bin\Release\WOMEFLib.dll]
    ClassLoader: 001bbc98
    SecurityDescriptor: 001bcd28
    Module Name
    00a730c8 D:\My Documents\Visual Studio 2008\Projects\MEF\
             WOMEF\WOMEF\bin\Release\WOMEFLib.dll
  12. As expected, there are three app domains. In the application specific appdomain, three assemblies are loaded. The first one (mscorlib.dll) is a pretty standard one. The second one (WOMEF.exe) is the application itself. And, the third one (WOMEFLib.dll) is the referenced assembly.
  13. So, the loader loaded the referenced assembly though we have not yet used any type from it so far. We are just at the first line in the Main method. Also, we don't use any type from WOMEF.dll in the Main method. The loader loaded this assembly because it is there in the manifest. Now, let us see what happens if this assembly is not available when we start WOMEF.exe. Kill the application by pressing Control+C. If needed, close Windbg and reopen.

What happens if WOMEFLib.dll is missing when the application is starting

Here, we are going to check what happens if WOMEFLib.dll is missing when the application is starting. Move WOMEFLib.dll from the Release folder to some other folder outside the solution directory (say to the Desktop; don't copy cut it).

  1. Start Windbg.
  2. In Windbg, click on File -> Open executable.
  3. Navigate to the Release folder of the solution and select WOMEF.exe.
  4. Windbg stops with interrupt 3. Press F5 to continue.
  5. ASM
    ntdll!DbgBreakPoint:
    7c90120e cc              int     3
  6. Now, the application starts and waits for the user entry with the message: "Enter 0 to quit, any other number to continue".
  7. Though one of the dependent assemblies is not available, the application runs normally till this point. This makes an interesting point. Why doesn't the loader complain that WOMEFLib.dll is not available? The answer is SayHelloToUser is not yet JIT compiled. And, we will verify this fact in the next section. Kill the application by pressing Control+C. If needed, close Windbg and reopen.

When the SayHelloToUser method is JIT compiled

Here, we are going to check when the SayHelloToUser method is actually JIT compiled. Move WOMEFLib.dll back to the Release folder.

  1. Start Windbg.
  2. In Windbg, click on File -> Open executable.
  3. Navigate to the Release folder of the solution and select WOMEF.exe.
  4. Windbg stops with interrupt 3. Press F5 to continue.
  5. ASM
    ntdll!DbgBreakPoint:
    7c90120e cc              int     3  
  6. Now, the application starts and waits for user entry with the message: "Enter 0 to quit, any other number to continue".
  7. In Windbg, go to Debug menu and click on break. This is the ideal time to break into the application as it is up and running.
  8. Now, load sos.dll in Windbg by entering the following command in Windbg: ".loadby sos.dll mscorwks".
  9. ASM
    ntdll!DbgBreakPoint:
    7c90120e cc              int     3
    Missing image name, possible paged-out or corrupt data.
    0:003> .loadby sos.dll mscorwks
  10. Now, dumpdomains by entering the command: "!dumpdomain".
  11. 0:003> !dumpdomain
    *** ERROR: Symbol file could not be found. 
        Defaulted to export symbols for C:\WINDOWS\Microsoft.NET\... -
    PDB symbol for mscorwks.dll not loaded
    --------------------------------------
    System Domain: 7a3bd058
    LowFrequencyHeap: 7a3bd07c
    HighFrequencyHeap: 7a3bd0c8
    StubHeap: 7a3bd114
    Stage: OPEN
    Name: None
    --------------------------------------
    Shared Domain: 7a3bc9a8
    LowFrequencyHeap: 7a3bc9cc
    HighFrequencyHeap: 7a3bca18
    StubHeap: 7a3bca64
    Stage: OPEN
    Name: None
    Assembly: 001b0628
    --------------------------------------
    Domain 1: 0016d298
    LowFrequencyHeap: 0016d2bc
    HighFrequencyHeap: 0016d308
    StubHeap: 0016d354
    Stage: OPEN
    SecurityDescriptor: 0016e5c0
    Name: WOMEF.exe
    Assembly: 001b0628 [C:\WINDOWS\assembly\GAC_32\mscorlib\
                        2.0.0.0__b77a5c561934e089\mscorlib.dll]
    ClassLoader: 001b06a8
    SecurityDescriptor: 001ae190
    Module Name
    033e1000 C:\WINDOWS\assembly\GAC_32\mscorlib\
                2.0.0.0__b77a5c561934e089\mscorlib.dll
    
    Assembly: 001b9800 [D:\My Documents\Visual Studio 2008\
                        Projects\MEF\WOMEF\WOMEF\bin\Release\WOMEF.exe]
    ClassLoader: 001b9e68
    SecurityDescriptor: 001b96c8
    Module Name
    "00a72c5c" D:\My Documents\Visual Studio 2008\Projects\
               MEF\WOMEF\WOMEF\bin\Release\WOMEF.exe
    
    Assembly: 001bcb30 [D:\My Documents\Visual Studio 2008\
                        Projects\MEF\WOMEF\WOMEF\bin\Release\WOMEFLib.dll]
    ClassLoader: 001bbc98
    SecurityDescriptor: 001bcd28
    Module Name
    00a730c8 D:\My Documents\Visual Studio 2008\Projects\MEF\
             WOMEF\WOMEF\bin\Release\WOMEFLib.dll
  12. Now, we are just at the first line of the Main method. Let us get the address of the method table of the assembly WOMEF.exe by using the command: !dumpmodule -mt <module address 00a72c5c>. You can see the module address is in red color.
  13. 0:003> !dumpmodule -mt 00a72c5c
    Name: D:\My Documents\Visual Studio 2008\Projects\MEF\
          WOMEF\WOMEF\bin\Release\WOMEF.exe
    Attributes: PEFile
    Assembly: 001b9780
    LoaderHeap: 00000000
    TypeDefToMethodTableMap: 00a700c0
    TypeRefToMethodTableMap: 00a700cc
    MethodDefToDescMap: 00a70128
    FieldDefToDescMap: 00a70138
    MemberRefToDescMap: 00a7013c
    FileReferencesMap: 00a701a4
    AssemblyReferencesMap: 00a701a8
    MetaData start address: 004020c0 (1864 bytes)
    
    Types defined in this module
    
        MT    TypeDef Name
    ------------------------------------------------------------
    "00a73010" 0x02000002 WOMEF.Program
    
    Types referenced in this module
    
        MT    TypeRef Name
    ------------------------------------------------------------
    03650508 0x01000001 System.Object
    03654258 0x01000012 System.Console
    036508ec 0x01000013 System.String
    00a73484 0x01000016 WOMEFLib.IWOMEFLib 
  14. Using the method table address, let us get the module descriptions by using the command: !dumpmt -md <method table 00a73010>. You can see that the mt address is in red color.
  15. 0:003> !dumpmt -md 00a73010
    EEClass: 00a712f4
    Module: 00a72c5c
    Name: WOMEF.Program
    mdToken: 02000002  (D:\My Documents\Visual Studio 2008\
                        Projects\MEF\WOMEF\WOMEF\bin\Release\WOMEF.exe)
    BaseSize: 0xc
    ComponentSize: 0x0
    Number of IFaces in IFaceMap: 0
    Slots in VTable: 7
    --------------------------------------
    MethodDesc Table
     Entry MethodDesc      JIT Name
    035a6a70   03424934   PreJIT System.Object.ToString()
    035a6a90   0342493c   PreJIT System.Object.Equals(System.Object)
    035a6b00   0342496c   PreJIT System.Object.GetHashCode()
    036172f0   03424990   PreJIT System.Object.Finalize()
    00a7c019   00a73008     NONE WOMEF.Program..ctor()
    00de0070   00a72ff0      JIT WOMEF.Program.Main(System.String[])
    00a7c015   00a72ffc     "NONE" WOMEF.Program.SayHelloToUser() 
  16. Here, we can observe the JIT status of the SayHelloToUser method as NONE. This means that SayHelloToUser is not yet JIT compiled. Let us run the application by pressing F5 in Windbg and entering a non-zero value in the console. As soon as you enter a non-zero value in the console, the CLR compiles SayHelloUser, and loads it with a reference to the SayHello method in WOMEFLib.dll. Now, the console waits for the input because of ReadLine.
  17. Now, let us break into the debugger again by clicking on the Break menu option of the Debug menu in Windbg.
  18. Let us check the status of the method description again by using the same command: !dumpmt -md <method table 00a73010>. You can even use the up arrow twice in Windbg to get there.
  19. 0:003> !dumpmt -md 00a73010
    EEClass: 00a712f4
    Module: 00a72c5c
    Name: WOMEF.Program
    mdToken: 02000002  (D:\My Documents\Visual Studio 2008\Projects\
                           MEF\WOMEF\WOMEF\bin\Release\WOMEF.exe)
    BaseSize: 0xc
    ComponentSize: 0x0
    Number of IFaces in IFaceMap: 0
    Slots in VTable: 7
    --------------------------------------
    MethodDesc Table
     Entry MethodDesc      JIT Name
    035a6a70   03424934   PreJIT System.Object.ToString()
    035a6a90   0342493c   PreJIT System.Object.Equals(System.Object)
    035a6b00   0342496c   PreJIT System.Object.GetHashCode()
    036172f0   03424990   PreJIT System.Object.Finalize()
    00a7c019   00a73008     NONE WOMEF.Program..ctor()
    00de0070   00a72ff0      JIT WOMEF.Program.Main(System.String[])
    00de00d0   00a72ffc      "JIT" WOMEF.Program.SayHelloToUser()
  20. Now, observe the status of the SayHelloToUser method as JIT. This makes it clear that the SayHelloToUser method would have never been JIT compiled if a zero is entered in the console window as the first option. So, when the control goes inside the while loop, the CLR started JIT compiling the called method.
  21. Let us see a tricky case now. Assume that we wont make WOMEFLib.dll available to WOMEF.exe when WOMEF.exe is starting, but we will copy this assembly while the application is waiting for user input with the message "Enter 0 to quit, any other number to continue" for the first time. Kill the application by pressing Control+C. For this last case, we don't need Windbg.

What happens if WOMEFLib.dll is copied to the application directory while it is waiting for user input

Here, we are going to check what happens if WOMEFLib.dll is copied to the application directory while it is waiting for user input for the first time. Move WOMEFLib.dll from the Release folder to some other folder outside the solution directory (say to Desktop; don't copy cut it).

  1. Open the command prompt.
  2. Navigate to the Release folder of the solution and start WOMEF.exe.
  3. Application starts and waits for user entry with the message "Enter 0 to quit, any other number to continue". At this point, move WOMEFLib.dll back to the Release folder.
  4. Enter a non-zero value, and press Enter.
  5. Now, when the application tries to compile SayHelloToUser, it tries to refer the type CWOMEF in WOMEFLib.dll and fails to resolve the assembly. It comes out with an exception:
  6. Unhandled Exception: System.IO.FileNotFoundException: 
              Could not load file or assembly 'WOMEFLib, ..
  7. That means, even though the assembly is made available when it is actually being referred, the loader ignores to reload.
  8. It is this particular aspect that MEF tries to solve.

Existing possibilities

OK, with that background, I hope you are clear on what MEF is trying to solve. But before looking at MEF, let us see how we can solve this particular problem with existing technologies. With CLR 2.0, we have an option of loading the assembly at run time. After loading the assembly at run time, we can create objects of the types defined in it and we can even invoke them using Reflection techniques. This is quite straightforward. I will just provide a working sample. Do the following changes to our solution.

In the WOMEF project, remove the reference to WOMEFLib.dll. Then, replace the code in Program.cs with the following snippet:

C#
using System;
using System.Reflection;
namespace WOMEF
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Enter 0 to quit, any other number to continue");
            while (Console.ReadLine() != "0")
            {
                new Program().SayHelloToUser();
            }
        }
        
        public void SayHelloToUser()
        {
            Console.WriteLine("In SayHelloToUser");
            InvokeMethod("WOMEFLib", "CWOMEFLib", "SayHello", new object[] { 
                Environment.UserDomainName+Environment.UserName });
            Console.ReadLine();
        }

        public object InvokeMethod(string assemblyName, string className, 
                                   string methodName, object[] parameters)
        {
            System.Type[] parametersTypes;
            int parametersCount = 0;
            object returnObject = null;
            try
            {
                Type type = System.Reflection.Assembly.LoadFrom(assemblyName + 
                               ".dll").GetType(assemblyName + "." + className);
                parametersTypes = new System.Type[parameters.GetUpperBound(0) + 1];
                foreach (object parameter in parameters)
                {
                    if (parameter != null)
                        parametersTypes[parametersCount] = parameter.GetType();

                    parametersCount++;
                }
                
                MethodInfo mi = type.GetMethod(methodName, parametersTypes);
                ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);

                object objectinstance = ci.Invoke(null);
                //Invoke 
                returnObject = mi.Invoke(objectinstance, parameters);

            }
            catch (TargetException tex)
            {
                throw tex;
            }
            
            return returnObject;
        }
    }
}

Here, the Invoke method loads the assembly (WOMEFLib) at run time, constructs the class object (CWOMEFLib), and invokes a method (SayHello) in it.

In my next post, we will see how MEF not only simplifies this process but also provides many more useful options for this kind of scenarios.

History

-

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)