mshtml.tlbのようなタイプライブラリから取得する相互運用機能クラスには非常に深刻な問題があります。タイプライブラリインポーターは、タイプライブラリのコクラス定義から合成.NETクラスを生成します。それらはタイプ名から認識できます。これらは「クラス」で終わり、インターフェースタイプの名前で始まります。これらは役立つことを目的としており、COMコクラスを.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
//...
}
2つの間の不一致に注意してください。Oleviewから入手したものには、追加のインターフェイスIHtmlElement5とIHtmlHeadElement2があります。理由は簡単に説明できます。私のマシンにIE9がインストールされています。相互運用機能の定義は、mshtmlPIAから取得されました。これは古いバージョンで、おそらくIE7にまでさかのぼります。
恐ろしい問題は、新しいIHtmlElement5インターフェイスが継承リストに挿入されたことです。これは、少なくとも.NETコードに関する限り、非常に重大な間違いでした。純粋なCOMコードにはまったく関係ありません。これは、インターフェイスでのみ機能し、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);
(IHTMLHeadElement)の代わりに(IHTMLDOMNode)へのキャストを意図的に使用した場合、その下位のインターフェイスはすでにitem要素にアクセスするのに十分でした。