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

动态将 MSHTML 添加到应用程序中

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (12投票s)

2009年8月27日

CPOL

5分钟阅读

viewsIcon

32016

downloadIcon

718

Microsoft.mshtml 程序集并非在所有计算机上都存在。下面介绍如何让您的应用程序在需要时自动添加该程序集。

引言

在 Windows 窗体中嵌入 WebBrowser 控件已变得极其简单。但是,任何执行此操作的应用程序可能都希望操作该 WebBrowser 显示的内容。也许最基本的操作是挂钩到 DocumentComplete 事件;在您对页面进行任何操作之前,您需要知道页面 - DOM - 已准备好供您进行操作。

不幸的是,DocumentComplete 在所有系统上都不会触发!在网上搜索“DocumentComplete 不触发” - 这是一个令人惊讶的普遍问题。根本原因是 Microsoft.Mshtml 程序集并未在所有计算机上安装。更糟糕的是,Visual Studio 安装该程序集(一些 Office 应用程序也会),因此您在开发系统上永远不会看到此 bug。除了收不到 DocumentComplete 之外,没有 MSHTML,您将无法对 DOM 进行任何有用的操作,并且在尝试时会收到异常。

本文是我的解决方案。我创建了一个类,该类会捕获 Microsoft.mshtml 的缺失情况,并在需要时安装它,所有操作都不会打扰用户,因为用户可能不知道 MSHTML 是什么,更不用说 gacutil 了。

背景

本文需要您熟悉 WebBrowser 控件和 .NET 程序集的使用。

拥有/找到一台没有安装 MSHTML 的机器将非常有帮助。

Using the Code

附带的演示项目概述了该技术。该演示实现了一个带有嵌入式 WebBrowser 的简单窗体;加载时,浏览器会导航到 codeproject.com;当 DocumentComplete 触发时,我会显示一个 MessageBox

重要内容在 MshtmlAssemblyHelper.cs 中,但首先让我描述一些可以帮助您挂钩到该功能的项。

指示 CLR 使用正确版本的 MSHTML

首先,您需要告诉您的应用程序,您需要 MSHTML 版本 7.0.3300.0 或更高版本,这也能涵盖“MSHTML 未安装”的情况。这可以通过在您的 app.config 文件中添加条目来完成。

<runtime> 
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
   <dependentAssembly> 
    <assemblyIdentity name="Microsoft.mshtml" 
              publicKeyToken="b03f5f7f11d50a3a" 
              culture="neutral" />
        <bindingRedirect oldVersion="1.0.0.0-7.0.3299.9" 
           newVersion="7.0.3300.9" />
    </dependentAssembly> 
  </assemblyBinding> 
</runtime>

上面的 <runtime> 块将指示 CLR 使用 MSHTML v7.0.3300.0,如果找不到该版本(或更高版本,或任何 MSHTML 程序集),CLR 应该要求我的应用程序查找程序集 DLL。最后这一部分 - 程序集解析 - 是演示的核心技巧,将在下面详细讨论。上面 XML 中最相关的部分是 oldVersionnewVersion。我将 oldVersion 设置为低于 7.0.3300.0 的任何版本(实际上是任何版本)。newVersion 是我的目标版本:我已知的良好 MSHTML 程序集的版本,它随我的 VS2008 副本一起安装。

另外需要注意的是,我的项目中没有对 MSHTML 的引用。在演示中,CLR 加载 MSHTML 的意愿完全由 app.config 中的条目驱动。当然,任何希望操作 WebBrowser 中 DOM 的应用程序都需要添加对 MSHTML 的引用,但我特意在演示中省略了它,以突出 app.config 的作用。

ResolveEventHandler

System.ResolveEventHandler 是一个委托,当 CLR 需要您的帮助来查找程序集时,它会调用该委托。由于 app.config 中的条目,如果 CLR 在 GAC 中找不到已安装的 MSHTML 版本 >= 7.0.3300.0 的副本,CLR 将调用您的 ResolveEventHandler(s) 来允许您提供一个有效的程序集。如果您不提供有效的程序集,CLR 将抛出异常。

挂钩到 MshtmlAssemblyHelper

Program.cs 是样板代码,但有一个例外

DaveChambers.MshtmlAssemblyHelper.Initialize(); 

并且 Initialize 执行以下操作

AppDomain.CurrentDomain.AssemblyResolve += 
            new ResolveEventHandler(MyResolveEventHandler); 

它只是将我的方法 (MyResolveEventHandler) 添加到回调列表中。MyResolveEventHandler 只是检查是否正在请求 MSHTML,如果是,则获取我已知的良好 MSHTML 程序集的路径,加载它,然后将程序集返回给 CLR。

static private Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
    AssemblyInfo info = new AssemblyInfo(args.Name);
    if (info.Name.ToLower() != MshtmlAssemblyName.ToLower())
        return null;

    string assemblyPath = MshtmlAssemblyHelper.Install();
    Assembly assembly = Assembly.LoadFrom(assemblyPath);
    return assembly;
}

“特殊”操作由 Install() 执行。我之前提到了“我已知的良好 MSHTML 程序集”,现在我需要对此进行扩展。因为 MyResolveEventHandler() 被调用的最常见原因是在系统上不存在任何 MSHTML 副本,为了加载一个副本,您必须先将其安装在本地系统上。我的(低技术)解决方案是将我的 MSHTML 程序集的一个副本(在您的 C 盘搜索 Microsoft.mshtml.dll)放在一个网络文件夹中,然后让 MshtmlAssemblyHelper 将该文件从该目录复制到本地系统,然后通过 gacutil 进行安装。最后一步 - 使用 gacutil 进行安装 - 只是为了方便。您可以加载一个未安装的 MSHTML 副本,CLR 不会抱怨。安装的好处是下次运行应用程序时,CLR 将在 GAC 中找到 MSHTML,因此无需调用您的 ResolveEventHandler

请注意,我使用映射的网络驱动器来检索可安装的 DLL。我原来的应用程序是一个内部工具;因此,使用基于网络驱动器的检索机制是快速而显而易见的选择。如果您的应用程序要在网络外部署,您需要实现某种基于 Internet(例如 HTTP)的 DLL 检索机制。我特意在此省略了该功能,因为我想将重点放在 ResolveEventHandler 上,而不是网络部分。

测试代码

正如我在上面提到的,VS2008 会安装 MSHTML,因此 MyResolveEventHandler 在开发系统上永远不会被调用。但是,您可以通过修改 app.config 条目来强制 CLR 在您的系统上调用 MyResolveEventHandler

<bindingRedirect oldVersion="1.0.0.0-7.9.9999.9" newVersion="8.0.0.0" />

这会导致 CLR 认为您的有效 MSHTML 程序集是旧版本,从而强制调用 MyResolveEventHandler。然后您将返回 MSHTML v7.0.3300.0,CLR 会不喜欢(因为它太旧了),但这确实允许您调试您的 ResolveEventHandler。上述测试 bindingRedirect 的副本已注释掉,位于演示的 app.config 文件中。

历史

  • 2009 年 8 月 27 日:初始版本
© . All rights reserved.