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

创建 .NET 公共语言运行时的宿主应用程序。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (15投票s)

2001 年 10 月 21 日

7分钟阅读

viewsIcon

203586

downloadIcon

1897

本文将介绍编写自定义宿主应用程序以运行 .NET 公共语言运行时的托管代码是多么容易。

创建 .NET 公共语言运行时的宿主

我们都见过 .NET 公共语言运行时 (CLR) 所支持的各种应用程序类型:Web 应用程序 (ASP.NET)、基于 GUI 的应用程序 (WinForms)、简单的基于控制台的应用程序以及 Internet Explorer 托管的托管 ActiveX 控件。

当我们使用 .NET 当前支持的语言编写面向“托管世界”的代码时,.NET 感知语言编译器会发出包含 CPU 无关的 IL 的程序集。这些程序集由 CLR 执行。

可以猜测,在 Windows 的非托管世界中,当从 Windows shell 调用可执行文件时,它必须是一个具有自身 4GB 地址空间的 WIN32 进程。因此,也可以猜测,当调用 .NET 应用程序时,在托管代码开始在 CLR 上执行之前,必须运行少量非托管代码来创建 CLR 所需的环境。这段代码称为 CLR 的“宿主”。

正是这个宿主初始化了托管的 .NET CLR 世界中的称为 AppDomain 的实体。AppDomain 是 CLR 表示隔离应用程序服务的(类似于操作系统任务)方式。一个或多个 AppDomain 可以在 CLR 中共存而不相互干扰,就像 WIN32 进程共享计算资源而不干扰一样。

作为 .NET 运行时一部分提供的标准 CLR 宿主包括:

ASP.NET

将运行时加载到处理 Web 请求的进程中。ASP.NET 为将在 Web 服务器上运行的每个 Web 应用程序创建一个 AppDomain。ASP.NET 应用程序实际上由 IIS Web 服务器的 ISAPI 过滤器 DLL 扩展加载和执行,该扩展接受与应用程序关联的 .aspx 文件的 HTTP 请求。

Internet Explorer

创建 AppDomain 来运行托管控件。.NET Framework 支持下载和执行基于浏览器的控件。它通过 MIME 过滤器使用 Internet Explorer 的可扩展性机制来为托管控件在网站上的托管创建 AppDomain。默认情况下,访问的每个网站都会创建一个 AppDomain。

Shell 可执行文件

每次从 shell 启动可执行文件时,都会调用运行时宿主代码以将控制权转移到运行时。(注意:.NET 可移植可执行文件 (PE) 文件在入口点包含非托管代码,用于设置运行时并将控制权转移到运行时,从而转移到其余的托管代码。自然,当可执行文件从 Windows shell 启动时,它会首先运行。)

.NET Framework 允许在非托管世界中轻松使用 CLR 下运行的托管组件:它会自动创建 COM 可调用包装器 (CCW)。考虑一个用 C# 编写的组件的以下示例。

using System;
using System.Reflection;
using System.Windows.Forms;
namespace Ranjeet.SimpleCLRHost
{
    public class HelloHostDemo
    {
        public void Hi() 
        {
            MessageBox.Show("YOO HOO from the Managed World!", 
                            "And now for this message");
        }
    }
}

要在非托管应用程序中将此(不太有用)的 C# 组件用作 COM 组件,您需要:

  1. 将组件编译为 .NET 程序集(DLL)。
  2. 使用命令行工具 regasm.exe 注册 DLL。这将生成一个等效的类型库供 COM 环境使用,并创建用于 `CoCreateInstance`/`CreateObject` 查找这些类型的传统 COM 注册表项 (HKCR/CLSID)。

但是,您是否曾经想过运行此工具时到底发生了什么?它会为 COM 环境生成一个等效的类型库,并在“HKCR\CLSID”下创建传统的 COM 注册表项,以便 CoCreateInstance 或 CreateObject 查找其中的类型。以下是注册表项的样子:

请注意,InprocServer32 部分的默认键指向 mscoree.dll?啊哈!!其他键显示 CLR 可以获取有关程序集名称和类型名称等托管组件属性的信息。

这就引出了一个问题:“mscoree.dllInprocServer32 部分之下做什么?” Mscoree.dll 是 Microsoft .NET 运行时执行引擎,它在非托管环境中宿主 .NET CLR,并为其公开通用的宿主 API。

这是 RegAsm 生成的 TypeLibrary。

当您想使用 RegAsmTlbExp 工具生成的类型库时:

  1. 使用生成的类型库中的 CoClass 的 GUID 调用 `CoCreateInstance`,该 GUID 会加载 mscoree.dll

  2. 方法 DllGetClassObject(由 CoCreateInstance 内部调用,传统 COM)由 mscoree.dll 导出。.NET 的 COM 互操作部分提供了一个标准的类工厂实现来创建任何 .NET Framework 类的实例。此类工厂将返回给调用者,调用者会调用 CreateInstance 方法,并传入请求的类型。在我们上面的示例中,HelloHostDemo 是一个具有单个方法 `Hi` 的类。由于没有声明显式接口,也没有使用高级类型库覆盖属性,类型库会在公开 NO dispids(默认情况下)后为 ClassInterface 生成类型。因此,IDispatch 是默认接口。

    这个接口指针就是类工厂对 CreateInstance 的调用返回的,也就是 CLR 在运行时伪造的 COM 可调用包装器(简称为 CCW)。

这就是 .NET 如何使用 mscoree.dll 在托管组件之上添加一个层,以便 COM 完全不知道有托管环境这样的东西。

现在我们已经解释了(喘口气!)CCW 如何用于在非托管环境中调用任何 .NET 组件,让我们来谈谈本文的重点:mscoree.dll 如何在被调用者的进程地址空间内加载 CLR,以及我们如何编写一个非常简单的 CLR 宿主。

这是我们简单的 CLR 宿主将要做的:

  1. 使用 CLR 的宿主 API 宿主公共语言运行时。
  2. 从 .NET 程序集中加载一个类型 - 在我们的示例中是 HelloMsgBox.dll 程序集中的 HelloHostDemo。(请参阅有关如何为 .NET Framework SDK, Beta2 构建示例的说明。)
  3. 将此类型检索为 IDispatch 指针,并使用该指针通过调用 GetIDsOfNames 来查找方法的 dispid(在我们的示例中是 Hi)。
  4. 使用 InvokeIDispatch 指针上并传入检索到的 dispid,就这样!

最重要的宿主 API 是 CorBindToRuntimeEx,正如 Microsoft 的 Steven Pratschner 所述,它更像是一个导出上述 API 的“shim”代码。

CorBindToRuntimeEx 接受 CLR 的版本号和其他可配置参数,并基于这些参数返回 ICorRuntimeHost 接口指针的实例。

宿主 API 允许自定义宿主微调一些 CLR 设置,例如:

  1. 要加载的 CLR 的版本
  2. 要加载的 CLR 的版本,分别是用于客户端应用程序和多处理器服务器场景的工作站或服务器版本。在多处理器计算机的情况下,服务器版本允许并行执行每个处理器的垃圾回收。
  3. 指定垃圾回收设置是并发还是非并发并发意味着垃圾回收在后台线程上进行,而不是在运行用户代码的线程上进行,因此对 UI 应用程序有利。非并发意味着垃圾回收在运行用户代码的线程上进行,主要用于服务器/无 UI 的应用程序。无论如何,在单处理器计算机上,非并发 GC 才会发生。
  4. 域中立设置,用于指定多个 AppDomain 中频繁使用的程序集的副本的加载方式,可以是每个 AppDomain 的不同副本,也可以是所有 AppDomain 的共享副本,后者是域中立的。

对该函数的调用如下:

// Retrieve a pointer to the ICorRuntimeHost interface
HRESULT hr = CorBindToRuntimeEx(
                 NULL,   //Retrieve latest version by default
                 L"wks", //Request a WorkStation build of the CLR
                 STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN | 
                 STARTUP_CONCURRENT_GC,
                 CLSID_CorRuntimeHost,
                 IID_ICorRuntimeHost,
                 (void**)&spRuntimeHost
                 );

ICorRuntimeHost 允许宿主指定其他可配置参数,例如:

  1. 对 CLR 的启动/停止控制,例如:

    spRuntimeHost->Start();
  2. 检索指向托管代码的默认 AppDomain 的指针,例如:

    spRuntimeHost->GetDefaultDomain(&pUnk);
  3. 检索指向 ICorConfiguration 的指针,通过它可以实现额外的微调(请参阅 CLR 相关问题的文档)。

看!在 SimpleCLRHost 示例中,我们会发现,在检索 COM 接口之前没有调用传统的 CoInitialize 函数,因此没有显式指定公寓。我对此有一个想法,但需要更多想法来确认/澄清我的怀疑。

要运行示例,请先运行 buildmanaged.bat,然后再生成并执行 SimpleCLRHost。

运行示例之前有几点需要注意:

  1. 该示例使用了头文件 mscoree.h,该文件通常位于 C:\PROGRAM FILES\ MICROSOFT.NET\FRAMEWORKSDK\INCLUDE 目录中,因此我选择通过 Visual Studio 中的工具->选项将其添加到我的包含路径列表中。
  2. 它使用了 MsCorLib.Tlb,它提供了程序中使用的接口定义,对于 CLR 的 beta2 版本,该文件通常位于 C:\WINNT\Microsoft.NET\Framework\v1.0.2914 路径下,所以我只是硬编码了路径。

参考文献

© . All rights reserved.