1

我目前正在使用 MS HTML 将 JavaScript 代码插入网站。

我参考了 Microsoft HTML 对象库并键入了此代码。

IHTMLDocument2 doc = BrowserHost.Document as HTMLDocumentClass;
IHTMLElement head = (IHTMLElement)
      ((IHTMLElementCollection)doc.all.tags("head")).item(null, 0);
IHTMLScriptElement scriptObject = 
      (IHTMLScriptElement)doc.createElement("script");
scriptObject.type = @"text/javascript";
scriptObject.text = TTS.TTSWebFactory.GetJavascript();
((HTMLHeadElementClass)head).appendChild((IHTMLDOMNode)scriptObject);

我在脚本的最后一行收到错误消息,这是消息。

Unable to cast COM object of type 'System._ComObject' to class type 
'mshtml.HTMLHeadElementClass'. COM components that enter the CLR and do not 
support IProvideClassInfo or that do not havae any iterop assembly registered 
will be wrapped in the _ComObject type. Instances of this type cannot be cast 
to any other class; however they can be cast to interfaces as long as the 
underlying COM component supports QueryInterface calls for the IID of the 
interface

我对 COM 没有太多经验,将最后一行保留在他的代码中很重要,任何人都可以帮助我理解这意味着什么以及我如何解决它?

4

1 回答 1

7

从 mshtml.tlb 这样的类型库中获得的互操作类存在一个非常严重的问题。类型库导入器从类型库中的 coclass 定义生成合成 .NET 类。您可以从它们的类型名称中识别它们,它们以“类”结尾并以接口类型的名称开头。它们旨在提供帮助,允许您将 COM coclass 视为 .NET 类。

然而,它们会导致非常严重的版本控制问题。当我使用 oleview.exe 查看我机器上的 mshtml.tlb 时,我看到了 HTMLHeadElement coclass 的这个定义:

[
  uuid(3050F493-98B5-11CF-BB82-00AA00BDCE0B),
  noncreatable
]
coclass HTMLHeadElement {
    [default] dispinterface DispHTMLHeadElement;
    [default, source] dispinterface HTMLElementEvents;
    [source] dispinterface HTMLElementEvents2;
    interface IHTMLElement;
    interface IHTMLElement2;
    interface IHTMLElement3;
    interface IHTMLElement4;
    interface IHTMLUniqueName;
    interface IHTMLDOMNode;
    interface IHTMLDOMNode2;
    interface IHTMLElement5;
    interface IHTMLDOMConstructor;
    interface IHTMLHeadElement;
    interface IHTMLHeadElement2;
};

当我在源代码中右键单击 mshtml.HtmlHeadElementClass 并选择 Go To Definition 时,我会看到:

public class HTMLHeadElementClass : DispHTMLHeadElement, HTMLHeadElement, 
   HTMLElementEvents_Event, IHTMLElement, IHTMLElement2, IHTMLElement3, 
   IHTMLElement4, IHTMLUniqueName, IHTMLDOMNode, IHTMLDOMNode2, 
   IHTMLHeadElement, IHTMLElementEvents2_Event {
   // Lots of members
   //...
}

注意两者之间的不匹配。我从 Oleview 得到的那个有额外的接口,IHtmlElement5 和 IHtmlHeadElement2。原因很简单,我的机器上安装了IE9。互操作定义来自 mshtml PIA。这是一个版本,可能可以追溯到 IE7。

可怕的问题是新的 IHtmlElement5 接口被插入到继承列表中。这是一个非常严重的错误,至少就 .NET 代码而言。纯 COM 代码根本不重要,它只适用于接口,并通过 QueryInterface 向 coclass 请求接口指针。然而,这对 .NET 包装类来说是致命的,所有通过 IHTMLDOMNode2 的方法都有错误的偏移量。因此,如果您调用 IHTMLHeadElement.appendChild 方法,那么您最终会调用错误的方法。

这是一个无法解决的问题。您可以卸载 PIA 并生成您自己的 interop.mshtml.dll 互操作库,它将在您的机器上正常工作。但它不会在另一台没有安装 IE9 的机器上正常工作。

但是有一个很好的解决方法,只是不要使用 XxxClass classes。仅使用接口类型。这应该类似于(在 Winforms 应用程序中测试):

        var doc = (IHTMLDocument2)webBrowser1.Document.DomDocument;
        var headItems = (IHTMLElementCollection)doc.all.tags("head");
        var scriptObject = (IHTMLScriptElement)doc.createElement("script");
        scriptObject.type = @"text/javascript";
        //scriptObject.text = TTS.TTSWebFactory.GetJavascript();
        var node = (IHTMLDOMNode)headItems.item(null, 0);
        node.appendChild((IHTMLDOMNode)scriptObject);

我故意使用转换为 (IHTMLDOMNode) 而不是 (IHTMLHeadElement) 的地方,因为那个较小的接口已经足以访问item元素。

于 2012-04-17T17:26:37.890 回答