先の記事 SHDocVw.dll と AxSHDocVw.dll の作り方と使い方 では、ActiveX の WebBrowser コントロール (shdocvw.dll) をホストする、AxHost クラス から派生するラッパーコントロールを Visual Studio で生成し、それ使って NewWindow2 イベントを利用する例を書きました。
それと比較するために、.NET Framework の WebBrowser(これも shdocvw.dll のマネージラッパー)を拡張して同様なことを行うコードを書いてみました。
かなり面倒で、最初は COM の相互運用の知識がなかったのでお手上げ状態でした。あちこちググって調べて、動くようになるまで 3 日ぐらいかかりました。(笑)
詳しくは以下のコードとそのコメントを参照してください。参考にしたページの一覧も書いておきます。(手抜きですみません)
WebBrowser.CreateSink メソッド
Extended .NET 2.0 WebBrowser Control
COM相互運用機能の利用
COM相互運用機能の利用 - パート2
Microsoft .NET/COM の移行と相互運用性
COM ラッパー
ランタイム呼び出し可能ラッパー
COM 相互運用性 - 第 1 部 : C# クライアント チュートリアル
方法: COM ソースによって発生したイベントを処理する
.NET の WebBrowser を拡張したクラス
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Reflection;
namespace WebBrowserExtended
{
public class MyWebBrowser : WebBrowser
{
// WebBrowser の AxtiveX への参照
private IWebBrowser2 axIWebBrowser2;
// WebBrowser の AxtiveX が作成されたとき呼び出される
[PermissionSet(SecurityAction.LinkDemand,
Name = "FullTrust")]
protected override void
AttachInterfaces(object nativeActiveXObject)
{
this.axIWebBrowser2 =
(IWebBrowser2)nativeActiveXObject;
base.AttachInterfaces(nativeActiveXObject);
}
[PermissionSet(SecurityAction.LinkDemand,
Name = "FullTrust")]
protected override void DetachInterfaces()
{
this.axIWebBrowser2 = null;
base.DetachInterfaces();
}
public object Application
{
get
{
if ((this.axIWebBrowser2 == null))
{
throw new
AxHost.InvalidActiveXStateException(
"Application",
AxHost.ActiveXInvokeKind.PropertyGet);
}
// この Application プロパティは COM の
// マネージラッパー
return this.axIWebBrowser2.Application;
}
}
public bool RegisterAsBrowser
{
get
{
if ((this.axIWebBrowser2 == null))
{
throw new
AxHost.InvalidActiveXStateException(
"RegisterAsBrowser",
AxHost.ActiveXInvokeKind.PropertyGet);
}
// この RegisterAsBrowser プロパティは
// COM のマネージラッパー
return this.axIWebBrowser2.RegisterAsBrowser;
}
set
{
if ((this.axIWebBrowser2 == null))
{
throw new
AxHost.InvalidActiveXStateException(
"RegisterAsBrowser",
AxHost.ActiveXInvokeKind.PropertySet);
}
// この RegisterAsBrowser プロパティは
// COM のマネージラッパー
this.axIWebBrowser2.RegisterAsBrowser = value;
}
}
// シンクオブジェクトへの参照
private MyWebBrowserEventSink sink;
// HTTP 通信の cookie とは関係ないので注意
private AxHost.ConnectionPointCookie cookie;
// シンクをサブスクライバ・リストに追加
[PermissionSetAttribute(SecurityAction.LinkDemand,
Name="FullTrust")]
protected override void CreateSink()
{
base.CreateSink();
if ((this.axIWebBrowser2 == null))
{
throw new AxHost.InvalidActiveXStateException(
"CreateSink",
AxHost.ActiveXInvokeKind.MethodInvoke);
}
this.sink = new MyWebBrowserEventSink(this);
this.cookie = new AxHost.ConnectionPointCookie(
this.axIWebBrowser2,
this.sink,
typeof(DWebBrowserEvents2));
}
// シンクのサブスクライブを解除
[PermissionSetAttribute(SecurityAction.LinkDemand,
Name="FullTrust")]
protected override void DetachSink()
{
if (cookie != null)
{
this.cookie.Disconnect();
this.cookie = null;
}
base.DetachSink();
}
// NewWindow2 イベントの定義
public event NewWindow2EventHandler NewWindow2;
// .NET 側の NewWindow2 イベントを発動するメソッド
protected virtual void
OnNewWindow2(NewWindow2EventArgs e)
{
if ((this.NewWindow2 != null))
{
this.NewWindow2(this, e);
}
}
// コネクションポイントからの呼び出しを受け取る
// クライアント・シンクのクラス定義
[ClassInterface(ClassInterfaceType.None)]
public class MyWebBrowserEventSink :
StandardOleMarshalObject, DWebBrowserEvents2
{
private MyWebBrowser browser;
public MyWebBrowserEventSink(MyWebBrowser browser)
{
this.browser = browser;
}
// COM ソースから発生したイベントから呼び出される
// メソッド
public void
NewWindow2(ref object ppDisp, ref bool cancel)
{
NewWindow2EventArgs e =
new NewWindow2EventArgs(ppDisp, cancel);
// .NET 側の NewWindow2 イベントを発動
this.browser.OnNewWindow2(e);
ppDisp = e.PpDisp;
cancel = e.Cancel;
}
}
}
// NewWindow2 イベントのハンドラのデリゲート
public delegate void NewWindow2EventHandler(object sender,
NewWindow2EventArgs e);
// NewWindow2 イベントハンドラ引数のクラス定義
public class NewWindow2EventArgs : EventArgs
{
public object PpDisp { get; set; }
public bool Cancel { get; set; }
public NewWindow2EventArgs(object ppDisp, bool cancel)
{
this.PpDisp = ppDisp;
this.Cancel = cancel;
}
}
// DWebBrowserEvents2 インターフェイスの NewWindow2 メ
// ソッド、IWebBrowser2 インターフェイスの Application
// プロパティと RegisterAsBrowser プロパティをインポー
// ト(つまり、マネージラッパーをコンパイル時に生成)。
// ComImport, InterfaceType, Guid 指定は必須らしい。
[ComImport,
InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D")]
public interface DWebBrowserEvents2
{
[DispId(0xfb)]
void NewWindow2(
[In, Out, MarshalAs(UnmanagedType.IDispatch)]
ref object ppDisp,
[In, Out, MarshalAs(UnmanagedType.VariantBool)]
ref bool Cancel);
}
[ComImport,
Guid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch), ]
public interface IWebBrowser2
{
[DispId(200)]
object Application
{
[return: MarshalAs(UnmanagedType.IDispatch)]
get;
}
[DispId(0x228)]
bool RegisterAsBrowser
{
get;
set;
}
}
}
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WebBrowserExtended
{
public partial class Form1 : Form
{
private MyWebBrowser browser;
public Form1()
{
browser = new MyWebBrowser();
InitializeComponent();
browser.Dock = DockStyle.Fill;
this.Controls.Add(browser);
browser.NewWindow2 +=
new NewWindow2EventHandler(browser_NewWindow2);
}
private void button1_Click(object sender, EventArgs e)
{
browser.Navigate(
"http://msdntestnew/159-HyperLinkToPdf.aspx");
}
private void browser_NewWindow2(object sender,
NewWindow2EventArgs e)
{
Form1 frmWB = new Form1();
// WebBrowser.AttachInterfaces メソッドは Visible
// プロパティを true にすると呼び出される。なので、
// ここで設定しないと RegisterAsBrowser プロパティ、
// Application プロパティで例外がスローされてしまう。
frmWB.Visible = true;
frmWB.browser.RegisterAsBrowser = true;
e.PpDisp = frmWB.browser.Application;
}
}
}