2012-04-17 55 views
1

我目前正在使用MS HTML将JavaScript代码插入网站。MSHTML HTMLHeadElementClass COM错误

我提到了Microsoft HTML Object Library并输入了这段代码。

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太多的经验,并保持最后一行诠释他的代码是很重要的,任何一个可以帮助我理解这意味着什么,我该如何解决呢?

回答

6

从类型库(如mshtml.tlb)中获得的interop类存在非常严重的问题。类型库导入器根据类型库中的coclass定义生成合成.NET类。您可以从它们的类型名称中识别出它们,它们以“类”结尾,并以接口类型的名称开头。它们意味着很有帮助,使您可以将COM coclass视为.NET类。

但是它们会导致严重的版本控制问题。当我使用OLEView.exe这类看我MSHTML.tlb的机器上,然后我看到这个定义为HTMLHeadElement组件类:

[ 
    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并选择转到定义则我看到这个:

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代码根本无关紧要,它只能与接口一起工作,并且向coclass请求带有QueryInterface的接口指针。然而,对于.NET包装类,它是致命,所有通过IHTMLDOMNode2的方法都有错误的偏移量。所以如果你调用IHTMLHeadElement.appendChild方法,那么你最终会调用错误的方法。

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

有一个很好的解决方法可用,但是,只是不使用XxxClass类。只使用接口类型。这应该像这样(在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),因为较小的接口已经足够好访问项目元素。