托管可扩展性框架:第 1 部分






4.94/5 (26投票s)
本系列文章的第一篇,旨在介绍 Microsoft 的托管扩展性框架。
引言
本系列文章旨在介绍 Microsoft 的托管扩展性框架。在展示使用 MEF 框架的代码片段之前,我想先通过一个场景来阐述 MEF 的必要性。我认为这将有助于识别 MEF 可以有效使用的各种情况。
背景
将可重用代码构建成库并在应用程序中使用是很常见的。在传统的 Win32 应用程序中,如果一个库链接到一个应用程序,则使用链接器向 PE 头中的该库添加引用。当运行时尝试加载此应用程序时,加载器会执行以下操作:
- 解析所有依赖项
- 将它们加载到内存中
- 将它们映射到相应的指针
- 然后启动主例程
但在 .NET 世界中,由于 CLR 和 JIT 编译的介入,情况略有不同。现在,加载器会尝试加载仅执行 Main
方法所需的那些库。当从 Main
调用其他方法时,这些方法的 IL 会在需要时进行 JIT 编译并加载到内存中。这些方面构成了 MEF 的基本核心。
官方定义如下:
“托管扩展性框架 (MEF) 是 .NET 中的一个新库,它能够更好地重用应用程序和组件。使用 MEF,.NET 应用程序可以从静态编译转向动态组合。如果您正在构建可扩展的应用程序、可扩展的框架和应用程序扩展,那么 MEF 就是为您准备的。”
取中间部分:“静态编译转向动态组合”。这一切都围绕着这一点。
场景分析
我想构建一个场景来体现 MEF 的必要性。为此,我们创建一个控制台应用程序。
- 创建一个简单的控制台应用程序。将其命名为“WOMEF”(不含 MEF)。
- 将 Program.cs 中的代码替换为以下代码片段:
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();
}
}
}
我们在一个库中定义 IWOMEFLib 和 CWOMEFLib
- 向此解决方案添加一个名为“WOMEFLib”的类库项目。
- 将 Class1.cs 重命名为 CWOMEFLib.cs。然后,将代码替换为以下代码片段:
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
}
}
现在,在 WOMEF 项目中,添加一个对 WOMEFLib 项目的项目引用。将配置更改为 Release,生成并测试应用程序。这没什么特别的。但是,我们将通过这两个程序集来见证一些有趣的事实。
我们将验证以下几点:
- 加载器加载 WOMEFLib.dll 时
- 应用程序启动时 WOMEFLib.dll 丢失会发生什么
SayHelloToUser
方法被 JIT 编译时- 当应用程序等待用户输入时,如果 WOMEFLib.dll 被复制到应用程序目录会发生什么
为了检验这些场景,我们将在运行时查看应用程序 WOMEFLib.exe 的方法表和描述符。
我们将在这里使用 Windbg。如果您尚未安装,请在此处 下载。
加载器加载 WOMEFLib.dll 时
在此,我们将检查加载器何时加载 WOMEFLib.dll。
- 启动 Windbg。
- 在 Windbg 中,点击 文件 -> 打开可执行文件。
- 导航到解决方案的 Release 文件夹并选择 WOMEF.exe。
- Windbg 停在中断 3 处。按 F5 继续。
- 现在,应用程序启动并等待用户输入,显示消息“Enter 0 to quit, any other number to continue”(输入 0 退出,输入其他数字继续)。
- 在 Windbg 中,转到“调试”菜单,然后单击“中断”。这是中断应用程序运行时的理想时间。
- 现在,通过在 Windbg 中输入以下命令在 Windbg 中加载 sos.dll:".loadby sos.dll mscorwks"。
- 现在,通过输入命令 "!dumpdomain" 来转储域。
- 正如预期的那样,有三个应用程序域。在应用程序特定的应用程序域中,加载了三个程序集。第一个(mscorlib.dll)是一个相当标准的程序集。第二个(WOMEF.exe)是应用程序本身。第三个(WOMEFLib.dll)是引用的程序集。
- 因此,尽管我们还没有使用其中的任何类型,加载器还是加载了引用的程序集。我们仅在
Main
方法的第一行。此外,我们在Main
方法中也没有使用 WOMEF.dll 中的任何类型。加载器加载此程序集是因为它存在于清单中。现在,让我们看看当我们启动 WOMEF.exe 时,如果此程序集不可用会发生什么。按 Control+C 终止应用程序。如果需要,关闭 Windbg 并重新打开。
ntdll!DbgBreakPoint:
7c90120e cc int 3
ntdll!DbgBreakPoint:
7c90120e cc int 3
Missing image name, possible paged-out or corrupt data.
0:003> .loadby sos.dll mscorwks
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
应用程序启动时 WOMEFLib.dll 丢失会发生什么
在此,我们将检查应用程序启动时 WOMEFLib.dll 丢失会发生什么。将 WOMEFLib.dll 从 Release 文件夹移动到解决方案目录之外的另一个文件夹(例如桌面;不要复制,请剪切)。
- 启动 Windbg。
- 在 Windbg 中,点击 文件 -> 打开可执行文件。
- 导航到解决方案的 Release 文件夹并选择 WOMEF.exe。
- Windbg 停在中断 3 处。按 F5 继续。
- 现在,应用程序启动并等待用户输入,显示消息:“Enter 0 to quit, any other number to continue”(输入 0 退出,输入其他数字继续)。
- 尽管其中一个依赖的程序集不可用,但应用程序在此之前运行正常。这引出了一个有趣的点。为什么加载器没有抱怨 WOMEFLib.dll 不可用?答案是
SayHelloToUser
还没有被 JIT 编译。我们将在下一节中验证这一点。按 Control+C 终止应用程序。如果需要,关闭 Windbg 并重新打开。
ntdll!DbgBreakPoint:
7c90120e cc int 3
SayHelloToUser 方法被 JIT 编译时
在此,我们将检查 SayHelloToUser 方法何时被实际 JIT 编译。将 WOMEFLib.dll 移回 Release 文件夹。
- 启动 Windbg。
- 在 Windbg 中,点击 文件 -> 打开可执行文件。
- 导航到解决方案的 Release 文件夹并选择 WOMEF.exe。
- Windbg 停在中断 3 处。按 F5 继续。
- 现在,应用程序启动并等待用户输入,显示消息:“Enter 0 to quit, any other number to continue”(输入 0 退出,输入其他数字继续)。
- 在 Windbg 中,转到“调试”菜单,然后单击“中断”。这是中断应用程序运行时的理想时间。
- 现在,通过在 Windbg 中输入以下命令在 Windbg 中加载 sos.dll:".loadby sos.dll mscorwks"。
- 现在,通过输入命令 "!dumpdomain" 来转储域。
- 现在,我们正好在
Main
方法的第一行。让我们使用命令:!dumpmodule -mt <module address 00a72c5c> 来获取程序集 WOMEF.exe 的方法表地址。您可以看到模块地址是红色的。 - 使用方法表地址,让我们使用命令:!dumpmt -md <method table 00a73010> 来获取模块描述。您可以看到 mt 地址是红色的。
- 在这里,我们可以观察到
SayHelloToUser
方法的 JIT 状态为 NONE。这意味着SayHelloToUser
还没有被 JIT 编译。让我们在 Windbg 中按 F5 运行应用程序,并在控制台中输入一个非零值。一旦您在控制台中输入一个非零值,CLR 就会编译SayHelloUser
,并加载它,同时引用 WOMEFLib.dll 中的SayHello
方法。现在,由于ReadLine
,控制台正在等待输入。 - 现在,让我们再次通过在 Windbg 的“调试”菜单中单击“中断”菜单选项来中断调试器。
- 让我们再次通过使用相同的命令:!dumpmt -md <method table 00a73010> 来检查方法描述的状态。您甚至可以在 Windbg 中按两次向上箭头来找到它。
- 现在,观察
SayHelloToUser
方法的状态为 JIT。这清楚地表明,如果用户在控制台窗口中输入零作为第一个选项,SayHelloToUser
方法将永远不会被 JIT 编译。因此,当控制进入while
循环时,CLR 开始 JIT 编译被调用的方法。 - 现在让我们看一个棘手的情况。假设我们不在 WOMEF.exe 启动时让 WOMEFLib.dll 可用,但我们将在应用程序等待用户第一次输入消息“Enter 0 to quit, any other number to continue”(输入 0 退出,输入其他数字继续)时复制此程序集。按 Control+C 终止应用程序。对于最后这种情况,我们不需要 Windbg。
ntdll!DbgBreakPoint:
7c90120e cc int 3
ntdll!DbgBreakPoint:
7c90120e cc int 3
Missing image name, possible paged-out or corrupt data.
0:003> .loadby sos.dll mscorwks
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
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
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()
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()
当应用程序等待用户输入时,如果 WOMEFLib.dll 被复制到应用程序目录会发生什么
在此,我们将检查当应用程序第一次等待用户输入消息“Enter 0 to quit, any other number to continue”(输入 0 退出,输入其他数字继续)时,WOMEFLib.dll 被复制到应用程序目录会发生什么。将 WOMEFLib.dll 从 Release 文件夹移动到解决方案目录之外的另一个文件夹(例如桌面;不要复制,请剪切)。
- 打开命令提示符。
- 导航到解决方案的 Release 文件夹并启动 WOMEF.exe。
- 应用程序启动并等待用户输入,显示消息:“Enter 0 to quit, any other number to continue”(输入 0 退出,输入其他数字继续)。此时,将 WOMEFLib.dll 移回 Release 文件夹。
- 输入一个非零值,然后按 Enter。
- 现在,当应用程序尝试编译
SayHelloToUser
时,它会尝试引用 WOMEFLib.dll 中的CWOMEF
类型,但无法解析程序集。它会抛出一个异常。 - 这意味着,即使在实际引用该程序集时将其提供,加载器也会忽略重新加载。
- 正是 MEF 试图解决的这个方面。
Unhandled Exception: System.IO.FileNotFoundException:
Could not load file or assembly 'WOMEFLib, ..
现有可能性
好的,有了这些背景,我希望您已经清楚 MEF 试图解决什么问题。但在查看 MEF 之前,让我们看看如何使用现有技术解决这个特定问题。使用 CLR 2.0,我们可以选择在运行时加载程序集。在运行时加载程序集后,我们可以创建其中的类型对象,甚至可以使用反射技术调用它们。这相当直接。我将提供一个可用的示例。对我们的解决方案进行以下更改。
在 WOMEF 项目中,删除对 WOMEFLib.dll 的引用。然后,将 Program.cs 中的代码替换为以下代码片段:
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;
}
}
}
在此,Invoke
方法在运行时加载程序集 (WOMEFLib),构造类对象 (CWOMEFLib
),并调用其中的一个方法 (SayHello
)。
在下一篇文章中,我们将看到 MEF 如何不仅简化此过程,还为这类场景提供更多有用的选项。
历史
-