65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (26投票s)

2009 年 6 月 23 日

CPOL

9分钟阅读

viewsIcon

148437

downloadIcon

551

本系列文章的第一篇,旨在介绍 Microsoft 的托管扩展性框架。

引言

本系列文章旨在介绍 Microsoft 的托管扩展性框架。在展示使用 MEF 框架的代码片段之前,我想先通过一个场景来阐述 MEF 的必要性。我认为这将有助于识别 MEF 可以有效使用的各种情况。

背景

将可重用代码构建成库并在应用程序中使用是很常见的。在传统的 Win32 应用程序中,如果一个库链接到一个应用程序,则使用链接器向 PE 头中的该库添加引用。当运行时尝试加载此应用程序时,加载器会执行以下操作:

  1. 解析所有依赖项
  2. 将它们加载到内存中
  3. 将它们映射到相应的指针
  4. 然后启动主例程

但在 .NET 世界中,由于 CLR 和 JIT 编译的介入,情况略有不同。现在,加载器会尝试加载仅执行 Main 方法所需的那些库。当从 Main 调用其他方法时,这些方法的 IL 会在需要时进行 JIT 编译并加载到内存中。这些方面构成了 MEF 的基本核心。

官方定义如下:

托管扩展性框架 (MEF) 是 .NET 中的一个新库,它能够更好地重用应用程序和组件。使用 MEF,.NET 应用程序可以从静态编译转向动态组合。如果您正在构建可扩展的应用程序、可扩展的框架和应用程序扩展,那么 MEF 就是为您准备的。”

取中间部分:“静态编译转向动态组合”。这一切都围绕着这一点。

场景分析

我想构建一个场景来体现 MEF 的必要性。为此,我们创建一个控制台应用程序。

  1. 创建一个简单的控制台应用程序。将其命名为“WOMEF”(不含 MEF)。
  2. 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

  1. 向此解决方案添加一个名为“WOMEFLib”的类库项目。
  2. 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,生成并测试应用程序。这没什么特别的。但是,我们将通过这两个程序集来见证一些有趣的事实。

我们将验证以下几点:

  1. 加载器加载 WOMEFLib.dll
  2. 应用程序启动时 WOMEFLib.dll 丢失会发生什么
  3. SayHelloToUser 方法被 JIT 编译时
  4. 当应用程序等待用户输入时,如果 WOMEFLib.dll 被复制到应用程序目录会发生什么

为了检验这些场景,我们将在运行时查看应用程序 WOMEFLib.exe 的方法表和描述符。

我们将在这里使用 Windbg。如果您尚未安装,请在此处 下载

加载器加载 WOMEFLib.dll 时

在此,我们将检查加载器何时加载 WOMEFLib.dll

  1. 启动 Windbg。
  2. 在 Windbg 中,点击 文件 -> 打开可执行文件。
  3. 导航到解决方案的 Release 文件夹并选择 WOMEF.exe
  4. Windbg 停在中断 3 处。按 F5 继续。
  5. ntdll!DbgBreakPoint:
    7c90120e cc              int     3
  6. 现在,应用程序启动并等待用户输入,显示消息“Enter 0 to quit, any other number to continue”(输入 0 退出,输入其他数字继续)。
  7. 在 Windbg 中,转到“调试”菜单,然后单击“中断”。这是中断应用程序运行时的理想时间。
  8. 现在,通过在 Windbg 中输入以下命令在 Windbg 中加载 sos.dll:".loadby sos.dll mscorwks"。
  9. ntdll!DbgBreakPoint:
    7c90120e cc              int     3
    Missing image name, possible paged-out or corrupt data.
    0:003> .loadby sos.dll mscorwks
  10. 现在,通过输入命令 "!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. 正如预期的那样,有三个应用程序域。在应用程序特定的应用程序域中,加载了三个程序集。第一个(mscorlib.dll)是一个相当标准的程序集。第二个(WOMEF.exe)是应用程序本身。第三个(WOMEFLib.dll)是引用的程序集。
  13. 因此,尽管我们还没有使用其中的任何类型,加载器还是加载了引用的程序集。我们仅在 Main 方法的第一行。此外,我们在 Main 方法中也没有使用 WOMEF.dll 中的任何类型。加载器加载此程序集是因为它存在于清单中。现在,让我们看看当我们启动 WOMEF.exe 时,如果此程序集不可用会发生什么。按 Control+C 终止应用程序。如果需要,关闭 Windbg 并重新打开。

应用程序启动时 WOMEFLib.dll 丢失会发生什么

在此,我们将检查应用程序启动时 WOMEFLib.dll 丢失会发生什么。将 WOMEFLib.dllRelease 文件夹移动到解决方案目录之外的另一个文件夹(例如桌面;不要复制,请剪切)。

  1. 启动 Windbg。
  2. 在 Windbg 中,点击 文件 -> 打开可执行文件。
  3. 导航到解决方案的 Release 文件夹并选择 WOMEF.exe
  4. Windbg 停在中断 3 处。按 F5 继续。
  5. ntdll!DbgBreakPoint:
    7c90120e cc              int     3
  6. 现在,应用程序启动并等待用户输入,显示消息:“Enter 0 to quit, any other number to continue”(输入 0 退出,输入其他数字继续)。
  7. 尽管其中一个依赖的程序集不可用,但应用程序在此之前运行正常。这引出了一个有趣的点。为什么加载器没有抱怨 WOMEFLib.dll 不可用?答案是 SayHelloToUser 还没有被 JIT 编译。我们将在下一节中验证这一点。按 Control+C 终止应用程序。如果需要,关闭 Windbg 并重新打开。

SayHelloToUser 方法被 JIT 编译时

在此,我们将检查 SayHelloToUser 方法何时被实际 JIT 编译。将 WOMEFLib.dll 移回 Release 文件夹。

  1. 启动 Windbg。
  2. 在 Windbg 中,点击 文件 -> 打开可执行文件。
  3. 导航到解决方案的 Release 文件夹并选择 WOMEF.exe
  4. Windbg 停在中断 3 处。按 F5 继续。
  5. ntdll!DbgBreakPoint:
    7c90120e cc              int     3  
  6. 现在,应用程序启动并等待用户输入,显示消息:“Enter 0 to quit, any other number to continue”(输入 0 退出,输入其他数字继续)。
  7. 在 Windbg 中,转到“调试”菜单,然后单击“中断”。这是中断应用程序运行时的理想时间。
  8. 现在,通过在 Windbg 中输入以下命令在 Windbg 中加载 sos.dll:".loadby sos.dll mscorwks"。
  9. ntdll!DbgBreakPoint:
    7c90120e cc              int     3
    Missing image name, possible paged-out or corrupt data.
    0:003> .loadby sos.dll mscorwks
  10. 现在,通过输入命令 "!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. 现在,我们正好在 Main 方法的第一行。让我们使用命令:!dumpmodule -mt <module address 00a72c5c> 来获取程序集 WOMEF.exe 的方法表地址。您可以看到模块地址是红色的。
  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. 使用方法表地址,让我们使用命令:!dumpmt -md <method table 00a73010> 来获取模块描述。您可以看到 mt 地址是红色的。
  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. 在这里,我们可以观察到 SayHelloToUser 方法的 JIT 状态为 NONE。这意味着 SayHelloToUser 还没有被 JIT 编译。让我们在 Windbg 中按 F5 运行应用程序,并在控制台中输入一个非零值。一旦您在控制台中输入一个非零值,CLR 就会编译 SayHelloUser,并加载它,同时引用 WOMEFLib.dll 中的 SayHello 方法。现在,由于 ReadLine,控制台正在等待输入。
  17. 现在,让我们再次通过在 Windbg 的“调试”菜单中单击“中断”菜单选项来中断调试器。
  18. 让我们再次通过使用相同的命令:!dumpmt -md <method table 00a73010> 来检查方法描述的状态。您甚至可以在 Windbg 中按两次向上箭头来找到它。
  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. 现在,观察 SayHelloToUser 方法的状态为 JIT。这清楚地表明,如果用户在控制台窗口中输入零作为第一个选项,SayHelloToUser 方法将永远不会被 JIT 编译。因此,当控制进入 while 循环时,CLR 开始 JIT 编译被调用的方法。
  21. 现在让我们看一个棘手的情况。假设我们不在 WOMEF.exe 启动时让 WOMEFLib.dll 可用,但我们将在应用程序等待用户第一次输入消息“Enter 0 to quit, any other number to continue”(输入 0 退出,输入其他数字继续)时复制此程序集。按 Control+C 终止应用程序。对于最后这种情况,我们不需要 Windbg。

当应用程序等待用户输入时,如果 WOMEFLib.dll 被复制到应用程序目录会发生什么

在此,我们将检查当应用程序第一次等待用户输入消息“Enter 0 to quit, any other number to continue”(输入 0 退出,输入其他数字继续)时,WOMEFLib.dll 被复制到应用程序目录会发生什么。将 WOMEFLib.dllRelease 文件夹移动到解决方案目录之外的另一个文件夹(例如桌面;不要复制,请剪切)。

  1. 打开命令提示符。
  2. 导航到解决方案的 Release 文件夹并启动 WOMEF.exe
  3. 应用程序启动并等待用户输入,显示消息:“Enter 0 to quit, any other number to continue”(输入 0 退出,输入其他数字继续)。此时,将 WOMEFLib.dll 移回 Release 文件夹。
  4. 输入一个非零值,然后按 Enter。
  5. 现在,当应用程序尝试编译 SayHelloToUser 时,它会尝试引用 WOMEFLib.dll 中的 CWOMEF 类型,但无法解析程序集。它会抛出一个异常。
  6. Unhandled Exception: System.IO.FileNotFoundException: 
              Could not load file or assembly 'WOMEFLib, ..
  7. 这意味着,即使在实际引用该程序集时将其提供,加载器也会忽略重新加载。
  8. 正是 MEF 试图解决的这个方面。

现有可能性

好的,有了这些背景,我希望您已经清楚 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 如何不仅简化此过程,还为这类场景提供更多有用的选项。

历史

-

© . All rights reserved.