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:
- resolve all dependencies
- load them into memory
- map them to respective pointers
- 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.
- Create a simple console application. Name it "WOMEF" (without MEF).
- Replace the code in Program.cs with the following snippet:
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:
- Add a class library project to this solution by the name "WOMEFLib".
- Rename Class1.cs to CWOMEFLib.cs. Then, replace the code with the following snippet:
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:
- When the loader loads WOMEFLib.dll
- What happens if WOMEFLib.dll is missing when the application is starting
- When the
SayHelloToUser
method is JIT compiled - 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.
- Start Windbg.
- In Windbg, click on File -> Open executable.
- Navigate to the Release folder of the solution and select WOMEF.exe.
- Windbg stops with interrupt 3. Press F5 to continue.
ntdll!DbgBreakPoint:
7c90120e cc int 3
- Now, the application starts and waits for the user entry with the message "Enter 0 to quit, any other number to continue".
- 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.
- Now, load sos.dll in Windbg by entering the following command in Windbg: ".loadby sos.dll mscorwks".
ntdll!DbgBreakPoint:
7c90120e cc int 3
Missing image name, possible paged-out or corrupt data.
0:003> .loadby sos.dll mscorwks
- Now, dumpdomains by entering the command: "!dumpdomain".
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
- 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.
- 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).
- Start Windbg.
- In Windbg, click on File -> Open executable.
- Navigate to the Release folder of the solution and select WOMEF.exe.
- Windbg stops with interrupt 3. Press F5 to continue.
ntdll!DbgBreakPoint:
7c90120e cc int 3
- Now, the application starts and waits for the user entry with the message: "Enter 0 to quit, any other number to continue".
- 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.
- Start Windbg.
- In Windbg, click on File -> Open executable.
- Navigate to the Release folder of the solution and select WOMEF.exe.
- Windbg stops with interrupt 3. Press F5 to continue.
ntdll!DbgBreakPoint:
7c90120e cc int 3
- Now, the application starts and waits for user entry with the message: "Enter 0 to quit, any other number to continue".
- 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.
- Now, load sos.dll in Windbg by entering the following command in Windbg: ".loadby sos.dll mscorwks".
ntdll!DbgBreakPoint:
7c90120e cc int 3
Missing image name, possible paged-out or corrupt data.
0:003> .loadby sos.dll mscorwks
- Now, dumpdomains by entering the command: "!dumpdomain".
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
- 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.
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
- 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.
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()
- 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
. - Now, let us break into the debugger again by clicking on the Break menu option of the Debug menu in Windbg.
- 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.
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()
- 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. - 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).
- Open the command prompt.
- Navigate to the Release folder of the solution and start WOMEF.exe.
- 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.
- Enter a non-zero value, and press Enter.
- 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:
Unhandled Exception: System.IO.FileNotFoundException:
Could not load file or assembly 'WOMEFLib, ..
- That means, even though the assembly is made available when it is actually being referred, the loader ignores to reload.
- 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:
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);
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
-