MSDN Japan Home >  MSDN Library Japan > 
ブラウザ開発  

Browser Helper Objects

望み通りのブラウザ

ディーノ エスポジート

はじめに

ときどき、特殊なバージョンのブラウザが必要になるという状況が生じることがあります。場合によっては、その要求に応えるために、WebBrowserコントロールをベースに、ボタンやラベルなど、ユーザー インターフェイスに必要なものをすべてそろえた完全なカスタム モジュールを開発することもあるでしょう。その場合は、「そのブラウザ」になら、新しい非標準機能を自由に追加できます。しかし、実際にできあがるのは、単なる新しい非標準ブラウザです。WebBrowserコントロールは、ブラウザの解析エンジンにすぎません。アドレス バー、ツールバー、履歴、ステータスバー、チャンネル、お気に入りの追加など、UI関連の処理が依然として残るわけです。したがってカスタム ブラウザを作成するには、2種類のコードを書かなければなりません。1つは、WebBrowserコントロールをMicrosoft(R)Internet Explorerのような本格的なブラウザに仕立てるコードです。そしてもう1つは、サポートしたい新しい機能を実装するコードです。しかし、そんなことをしなくてもInternet Explorerを簡単にカスタマイズする方法があるといいですよね。BHO(Browser Helper Objects)はまさにそのためのものです。

プログラムのカスタマイズ

これまで、プログラムの動作をカスタマイズする第1の方法は、サブクラス化による方法でした。この手法では、プログラムのウィンドウにおけるメッセージの処理方法を変えて、実際に異なる動作をさせることができます。使われる側のクラスは、何が起きているのかをほとんど知ることができないため、これは力ずくの方法と見なされてきましたが、長い間これが唯一の選択肢だったのです。

Microsoft Win32(R)APIが登場したことにより、プロセス間のサブクラス化は推奨されなくなり、コーディングも少しむずかしくなりました。しかし勇気のある方なら、ポインタを怖がりはしないでしょう。しかも、システム全体からアクセスできるフックを使い慣れているなら、コーディングも簡単とさえ言えるでしょう。ただし、いつでもそうとは限りません。いかにプログラミングが巧みであったとしても、重要なのは、個々のWin32プロセスはそれぞれ専用のアドレス空間で実行している点であり、プロセスの境界を越えるのは適切とは言えません。一方、問題を起こさずに、そのようにしなければならないこともあります。そして多くの場合、カスタマイズは、プログラム自身がもともと備えている特殊な機能であることもあるのです。

後者の場合、プログラムは、既知のあらかじめ決められているディスク ゾーンで追加用モジュールを探し、それをロードし、初期化した後、所定の処理を実行させます。これは、まさしく、Internet Explorerブラウザとそのヘルパー オブジェクトで起こっていることです。

Browser Helper Objectsとは何か

この点から見ると、Internet Explorerは、自分自身でメモリ空間を維持する他のWin32ベース プログラムとまったく変わりません。Browser Helper Objectsを使うと、コンポーネント、特に、Internet Explorerが起動するたびにロードするインプロセスCOM(コンポーネント オブジェクト モデル)コンポーネントを記述できます。このようなオブジェクトは、ブラウザと同じメモリ コンテキストで実行し、利用可能なウィンドウとモジュールを対象に任意のアクションを実行できます。BHOは、たとえば、GoBack、GoForward、DocumentCompleteといったブラウザの一般的なイベントを検出したり、ブラウザのメニューとツールバーを利用・変更したり、ウィンドウを作成して現在開いているページに関する追加情報を表示したり、メッセージとアクションを監視するためのフックをインストールしたりできます。要するにBHOは、ブラウザの領域に潜入するために送り出されたスパイとして働くのです。

BHOの核心に進む前に、もう少し明確にしなければならない要点が2つあります。第1に、BHOはブラウザのメイン ウィンドウと結び付いています。実際には、新しいブラウザ ウィンドウが作成されると同時に、オブジェクトの新しいインスタンスが作り出されるという意味です。BHOのインスタンスはすべて、ブラウザのインスタンスと生死を共にします。第2に、BHOはInternet Explorerバージョン4.0以上の中でしか存在しません。

Microsoft Windows(R)98、Windows 2000、Windows 95、Windows NT(R)version 4.0オペレーティング システムでActive Desktop(TM)Shell Update(シェルのバージョン4.71)を実行している場合は、エクスプローラもBHOをサポートします。これにはいくつかの意味があるのですが、詳しくは、後半でパフォーマンスについて検討し、BHOの影響を評価する段階で取り上げます。

BHOの最もシンプルな形態は、特定のレジストリ キーの下に登録されるCOMインプロセス サーバーです。Internet Explorerは、起動時にそのキーを探し、そこにCLSIDが保存されているオブジェクトをすべてロードします。Internet Explorerはそのオブジェクトを初期化して、オブジェクトに対して特定のインターフェイスを要求します。そのインターフェイスが見つかると、Internet Explorerは、提供されているメソッドを使って、そのIUnknownポインタをヘルパー オブジェクトに渡します。この処理の流れを図1に示します。


図1:Internet Explorerがヘルパー オブジェクトを探して、ロードする様子。BHOサイトは、通信を確立するためのCOMインターフェイス

ブラウザは、レジストリ内で一連のCLSIDを見つけ、それぞれについてインプロセスのインスタンスを作成できます。その結果、これらのオブジェクトはブラウザのコンテキストの中でロードされ、ブラウザに元々あるコンポーネントのように動作できます。しかし、Internet ExplorerがCOMベースであるという性質上、プロセス空間内にロードされることには、それほどメリットはありません。言い換えると、BHOは、構成要素となるウィンドウのサブクラス化や、スレッドにとってローカルなフックのインストールなど、潜在的に役立つことをいろいろ実行してくれるのも事実ですが、BHOがブラウザの中心的な処理から取り残されているのは明らかです。ブラウザのイベントをフックしたり、ブラウズのオートメーションを実現するには、ヘルパー オブジェクトは、特権的なCOMベースの通信チャンネルを開設する必要があります。このような理由から、BHOはIObjectWithSiteと呼ばれるインターフェイスを実装しなければなりません。実際Internet Explorerは、IObjectWithSiteを通じて、そのIUnknownインターフェイスへのポインタを渡すことになります。BHOは、今度はそれを格納して、IWebBrowser2、IDispatch、IConnectionPointContainerなど、より具体的なインターフェイスを問い合わせることができます。

BHOのもう1つの見方は、Internet Explorerのシェル拡張機能に関連します。みなさんもご存知のように、Windowsのシェル拡張機能はCOMインプロセス サーバーであり、Windows Explorerはこれを、ドキュメントに対して特定の操作を実行するときに、たとえばショートカットメニューを表示するときなどにロードします。COMインターフェイスをいくつか実装したCOMモジュールを書くことにより、新しい項目をショートカットメニューに追加し、それらを適切に処理することができます。シェル拡張機能もまた、Windows Explorerが見つけられるように登録しなければなりません。Browser Helper Objectsも同じパターンを踏襲します。唯一違う点は実装するインターフェイスです。そして、BHOをロードするきっかけとなるものが少しだけ違います。しかし実装に違いがあるにも関わらず、シェル拡張機能とBHOには、次の表に示すように共通の性質があります。

機能とシェル拡張機能 ブラウザ ヘルパーオブジェクト
Windows Explorerによってロードされる Internet Explorer(シェルのバージョン4.71以上ではエクスプローラも)
特定の種類のドキュメントに対するユーザーのアクション(右クリック)によって引き起こされる ブラウザのウィンドウが開いたとき
参照カウントが0になった数秒後にアンロードされる ロードするきっかけとなったブラウザ ウィンドウが閉じたとき
COMインプロセスDLLとして実装される COMインプロセスDLLとして
登録には、COMサーバーの通常のエントリと、シェル拡張機能のタイプとその対象となるドキュメントの種類に応じて、他のエントリが必要 COMサーバーの通常のエントリと、BHOと認識されるために必要なエントリ
必要なインターフェイスは、シェル拡張機能のタイプによって異なる IObjectWithSite

表1:シェル拡張子とBrowser Helper Objectsが共通の機能を実装する方法

シェル拡張機能に興味のある方は、MSDN Library OnlineまたはCDに掲載されている、初心者向け記事を参照してください。もう少し深く知りたい方は、私が最近出版した『Professional Shell Programming for Windows』(Wrox Press、1-861001-84-3)という本も参考にご覧ください。

ヘルパー オブジェクトのライフサイクル

先述の通り、BHOをサポートしているのはInternet Explorerだけではありません。シェルのバージョン4.71以上を実行している場合は、エクスプローラにもBHOをロードできます。つまり、独自のブラウザで、Webもローカル ディスクも、同じ操作で渡り歩くことができるということです。次の表は、現在入手可能な各種のシェルのバージョンと対応する製品の説明です。シェルのバージョン番号は、shell32.dllに格納されているバージョン情報に基づきます。

シェルのバージョン インストールされている製品
4.00 Internet Explorer4.0以前が搭載されている、あるいは搭載されていないWindows 95とWindows NT 4.0。

注 Shell Updateはインストールされていません。BHOはInternet Explorer 4.0によってサポートされます。

4.71 Active Desktop Shell Updateを組み込んだInternet Explorer 4.0 が搭載されているWindows 95とWindows NT 4.0。BHOはInternet Explorerとエクスプローラの両方でサポートされます。
4.72 Windows 98。BHOはInternet Explorerとエクスプローラの両方でサポートされます。
5.00 Windows 2000(旧称Windows NT 5.0)。BHOはInternet Explorerとエクスプローラの両方でサポートされます。

表2:各種シェルのバージョンにおけるBrowser Helper Objectsのサポート

Browser Helper Objectsは、ブラウザのメイン ウィンドウが開くときにロードされ、ウィンドウが破壊するとアンロードされます。ブラウザ ウィンドウをさらにいくつか開くと、その数だけBHOのインスタンスが作成されます。BHOは、ブラウザを起動するコマンド ラインに関係なくロードされます。たとえば、特定のHTMLページや特定のフォルダだけを単に見たい場合でさえ、BHOがロードされます。一般にBHOは、explorer.exeまたはiexplore.exeを実行するとロードされると考えることができます。フォルダの設定を[フォルダを開くたびに新しいウィンドウを開く(Open each folder in its own window)]にしておくと、BHOはフォルダを開くたびにロードされます。


図 2:この設定により、フォルダを開くたびに、explorer.exeの新しいインスタンスが実行し、登録されているBHOがロードされる

ただしこれは、デスクトップ上の[マイ コンピュータ(My Computer)]アイコンからフォルダを開くときのみ当てはまります。この場合、シェルは、別のフォルダに移動するたびにexplorer.exeを呼び出します。2つの区画に分かれているウィンドウでブラウズする場合は、このようにはなりません。実際、フォルダを変更すると、シェルはブラウザの新しいインスタンスを起動せず、単に埋め込みのビュー オブジェクトの別のインスタンスを作成するだけです。面白いことに、アドレス バーに名前を入力してフォルダを変更すると、エクスプローラの表示が単一か2つの区画かに関係なく、常に同じウィンドウでブラウズすることになります。

Internet Explorerの場合は、もっとシンプルです。iexplore.exeを複数回実行した場合に限り、複数個のコピーが生まれます。Internet Explorerから新しいウィンドウを開くときは、各ウィンドウは新しいプロセスを開始せずに新しいスレッドで複製されるので、BHOも再ロードされません。

しかしBHOの最も興味深い特徴は、それが極めて動的である点です。エクスプローラまたはInternet Explorerのウィンドウが開くたびに、ローダはインストールされているヘルパー オブジェクトのCLSIDをレジストリから読み取って処理します。したがって、ブラウザを次に開くまでの間にレジストリを編集すれば、前にブラウザを開いたときとは異なるBHOをロードさせることができます。つまりこれは、まったく新しいブラウザをゼロから書く代わりとなる、すばらしい方法なのです。Microsoft Visual BasicやMicrosoft Foundation Classes(MFC)フレーム ウィンドウにWebBrowserを埋め込むことができるのです。同時に、拡張性の高いブラウジング アプリケーションを整える可能性も与えてくれます。Internet Explorerのすべての機能が使えるだけでなく、必要に応じて必要なだけの機能を追加できます。

IObjectWithSiteインターフェイス

Browser Helper Objectsのこの高度な概要から、ある1つのコンセプトが明らかになります。それは、BHOが、Internet Explorerと、一定の状況下ではエクスプローラの任意の新しいインスタンスに、それ自身をアタッチできるダイナミック リンク ライブラリ(DLL)であるということです。このようなモジュールは、コンテナのサイトを通じてブラウザとやり取りができます。

一般に、「サイト」は、コンテナと、中身の各オブジェクトの間に位置する中間オブジェクトです。コンテナは、サイトを通じて、中身のオブジェクトの内容を管理する一方で、サイトはオブジェクトの内部の機能を外から利用できるようにします。コンテナとオブジェクトの間のサイト ベースの関係を成立させるには、コンテナ側にIOleClientSiteのようなインターフェイスを、オブジェクト側にIOleObjectのようなインターフェイスを実装する必要があります。コンテナは、IOleObjectのメソッドを呼び出すことにより、オブジェクトにそのホスト環境を知らせます。

コンテナがInternet Explorer(またはWeb対応のエクスプローラ)のときは、パフォーマンスの問題により、この通信形態は基本部分にまで縮小されます。オブジェクトは今度は、IObjectWithSiteと呼ばれるもっとシンプルで軽量なインターフェイスを実装しなければならなくなります。これは、メソッドを2つだけ提供します。

HRESULT SetSite(   IUnknown* pUnkSite)

ブラウザのIUnknownポインタを受け取ります。通常の実装は、このようなポインタを、後で使うために単に格納するだけです。

HRESULT GetSite(   REFIID riid,   void** ppvSite)

SetSite()を通じて一番最後に設定されたサイトから、指定のインターフェイスを受け取って返します。通常の実装では、指定されたインターフェイスを指す、以前に格納されたpUnkSiteポインタを問い合わせます。

このインターフェイスを実装することが、BHOに対して課せられている唯一厳密な条件です。前記の2つの関数について、E_NOTIMPLが返されるようなことがあってはなりません。インターフェイスをまったく実装しないか、そのメソッドを正しくコーディングするかのどちらかです。

Browser Helper Objectsを書く

Browser Helper Objectsは、COMインプロセス サーバーなので、これを作るためにActive Template Library(ATL)に勝るものはありません。ATLを選ぶべきもう1つの理由は、標準で十分な機能を持つIObjectWithSiteインターフェイスがすでに実装されている点です。さらに、ATL COMウィザードが元々サポートしている、定義済みのオブジェクトの中に、Internet Explorer Objectというオブジェクトがありますが、これがまさにBHOのオブジェクトのあるべき姿なのです。ATLのInternet Explorer Objectは、実際、シンプルなオブジェクトです。つまり、IUnknownと自己登録機能、さらにIObjectWithSiteをサポートする COMサーバーです。このオブジェクトを自分のATLプロジェクトに追加して、対応するクラスをCViewSourceという名前にすれば、ウィザードによって次のコードが生成されます。

class ATL_NO_VTABLE CViewSource :
	public CComObjectRootEx _
		<CComSingleThreadModel>,
	public CComCoClass<CViewSource, _
	 &CLSID_ViewSource>,
	public IObjectWithSiteImpl<CViewSource>,
	public IDispatchImpl<IViewSource, _
	 &IID_IViewSource,
			&LIBID_HTMLEDITLib>

見て分かる通り、ウィザードはすでに、IObjectWithSiteの基礎的な実装を提供するATLテンプレート クラスであるIObjectWithSiteImplからクラスを継承させています(Microsoft Visual Studio(R)98のATL\INCLUDEディレクトリにあるatlcom.h をご覧ください)。通常は、GetSite()メンバー関数をオーバーライドする必要はありません。その代わり、場合によってはSetSite()のコード化されている動作をカスタマイズしなければなりません。実際、ATLは単にIUnknownポインタをm_spUnkSiteと呼ばれるメンバー変数に格納するだけです。

本稿の残りの紙面を使って、高度で機能豊富なBHOの例について説明します。オブジェクトは自身をInternet Explorerだけにアタッチし、閲覧中のページのソース コードをテキスト ボックスに表示します。このコード ウィンドウは、ページを変更するたびに自動的に更新され、Internet Explorerが表示しているドキュメントがHTMLページでなければグレー色で空のウィンドウが表示されます。生のHTMLコードへの変更はすべて、直後にブラウザに反映されます。この種の魔法は、Dynamic HTML(DHTML)によって実現されます。コード ウィンドウを非表示にして、後でホット キーを使って再表示させることもできます。表示しているときは、図3に示すように適切にサイズ変更した状態で、デスクトップの作業領域全体をInternet Explorerと共有します。


図3:稼動中のBrowser Helper Objects。Internet Explorerにアタッチして、閲覧中のページのソース コードを表示する。入力をして内容を変更することもできる(保存はできない)

この例の重要なポイントは、Internet Explorerのブラウジング機能にアクセスしていることです。この機能は、WebBrowserコントロールのインスタンスにすぎません。この例は次の5つのステップに分けられます。

  1. オブジェクトをロードしているのが、Internet Explorerであるかエクスプローラであるかを検出する。
  2. WebBrowserオブジェクトを描画するIWebBrowser2インターフェイスを取得する。
  3. WebBrowserの特定のイベントを捕らえる。
  4. 閲覧中のドキュメントにアクセスして、HTMLドキュメントであることを確かめる。
  5. HTMLソース コードを表示したダイアログ ボックス ウィンドウを管理する。
最初のステップは、DllMain()コードの中で処理します。SetSite()はその代わり、WebBrowserオブジェクトへのポインタを取得する場所となります。上記のステップをもう少し詳しく見ていきましょう。ここで取り上げない情報については、MSDN Webサイトにあるソース コードを参照してください。

呼び出し元の検出

先述の通り、バージョン4.71以上のシェルを実行しているなら、Internet ExplorerとエクスプローラのどちらでもBHOを呼び出せます。この例では、特にHTMLページを対象とするヘルパー オブジェクトを設計しているので、エクスプローラとは何の関わりもありません。特定の呼び出し元にロードされたくないDLLは、呼び出し元を検出した後でDllMain()内でFalseを返すだけでロードを回避できます。API関数のGetModuleFileName()に、第1引数としてNULLを渡すと、呼び出し元モジュールの名前が返されます。第1引数には、名前を知りたいモジュールのハンドルを指定します。NULLを指定した場合、呼び出し元プロセスの名前が欲しいという意味になります。

if (dwReason == DLL_PROCESS_ATTACH)
{
TCHAR pszLoader[MAX_PATH];
GetModuleFileName(NULL, pszLoader, MAX_PATH);
_tcslwr(pszLoader);
if (_tcsstr(pszLoader, _T("explorer.exe")))
	return FALSE;
}

プロセスの名前が分かったら、それがエクスプローラならばロードを中止します。選択肢を絞り込みすぎるのは危険であることに注目してください。実際、他のプロセスが合理的な理由によってDLLをロードしようとするときに、拒否されてしまう可能性があります。そのような可能性の第1の候補として考えられるのが、オブジェクトの自動登録に使うregsvr32.exeプログラムです。テスト条件を変えて、たとえば、Internet Explorerの実行プログラムだけを対象とするようにしたとします。

if (!_tcsstr(pszLoader, _T("iexplore.exe"))) 

すると、もはやDLLは登録できなくなります。実際、regsvr32.exeがDLLをロードしてDllRegisterServer()関数を呼び出そうとすると、呼び出しは拒否されます。

WebBrowserとのやり取り

SetSite()メソッドは、BHOが初期化される場所であり、一度だけ起きるすべての処理を実行する場所でもあります。Internet Explorerを使って特定のURLへ移動したら、要求したドキュメントが完全にダウンロードされ、確実に初期化されるように、2つのイベントを待たなければなりません。この2つのイベントを受け取った時点で初めて、公開されているオブジェクト モデルを通して、そのコンテンツに(コンテンツがあれば)安全にアクセスできます。そのためには、2つのポインタを取得する必要があります。1つは、WebBrowserオブジェクトを描画するインターフェイスであるIWebBrowser2へのポインタです。もう1つは、イベント関連のポインタです。このモジュールは、ダウンロードの通知と、ドキュメント固有のイベントを受け取るためのイベント リスナーとしてブラウザに登録しなければなりません。ATLスマート ポインタを使って次のようにします。

CComQIPtr<IWebBrowser2, &IID_IWebBrowser2> m_spWebBrowser2;
CComQIPtr<IConnectionPointContainer,
		&IID_IConnectionPointContainer> m_spCPC;

ソース コードはコード サンプルAに示すとおりです。

A
HRESULT CViewSource::SetSite(IUnknown *pUnkSite) { // Retrieve and store the IWebBrowser2 pointer m_spWebBrowser2 = pUnkSite; if (m_spWebBrowser2 == NULL) return E_INVALIDARG; // Retrieve and store the IConnectionPointerContainer pointer m_spCPC = m_spWebBrowser2; if (m_spCPC == NULL) return E_POINTER; // Retrieve and store the HWND of the browser. Plus install // a keyboard hook for further use RetrieveBrowserWindow(); // Connect to the container for receiving event notifications return Connect(); }

IWebBrowser2インターフェイスへのポインタを取得するには、単にこれをクエリーするだけです。イベント処理の第1のステップであるIConnectionPointContainerについても同じことをします。SetSite()のコードはまた、ブラウザのHWNDも取得し、現在のスレッドにキーボード フックをインストールします。このHWNDは、後でInternet Explorerウィンドウの移動、サイズ変更を行うために使います。代わりにフックは、ユーザーの都合に合わせてコード ウィンドウを開いたり閉じたりするためのホット キーの役目を果たします。

ブラウザからのイベントの取得

新しいURLへ移動したら、ブラウザは主に2つのことを達成する必要があります。参照先のドキュメントのダウンロードと、そのためのホスト環境の準備です。つまり、ドキュメントのためのオブジェクト モデルを初期化し、外部での利用を可能にすることです。これは、ドキュメントの種類に応じて、そのドキュメントを扱うために登録されているMicrosoft ActiveX(R)サーバー アプリケーション(たとえば、.docファイルであればMicrosoft Word)をロードするか、ドキュメントの内容を分析して、それを描画するオブジェクトモデルの要素を埋めるいくつかの内部コンポーネントを初期化することになります。これは、DHTMLオブジェクト モデルを通じてコンテンツにアクセスできるHTMLページの場合に起きることです。ドキュメントが完全にダウンロードされると、DownloadCompleteイベントが発生します。この場合でも、そのオブジェクト モデルを通じてドキュメントの内容を管理することは必ずしも安全ではありません。代わりに、DocumentCompleteイベントが、すべてが完了し、ドキュメントの準備が整ったことを示します(DocumentCompleteは、対象URLに初めてアクセスしたときにだけ届きます。F5キーを押すか、[最新の情報に更新(Refresh)]ボタンをクリックした2回目以降は、DownloadCompleteイベントだけを受け取ります)。

ブラウザが発生させるイベントを受け取るために、BHOはIConnectionPointインターフェイスを介してブラウザと接続し、各種のイベントを処理する一連の関数を指すIDispatchテーブルを渡す必要があります。以前に取得したIConnectionPointContainerへのポインタを指定してFindConnectionPointメソッドを呼び出して、必要な外向きのインターフェイス(ここではDIID_DWebBrowserEvents2)のコネクション ポイント オブジェクトへのポインタを取得します。コード サンプルBに、接続がどのように起こるかを示します。BHOは、IConnectionPointのAdvise()メソッドを呼び出すことによって、イベントの通知を受信することに関心があることをブラウザに知らせます。COMのイベント処理メカニズムを考えると、これはすべて実際には、BHOがブラウザにそのIDispatchインターフェイスへのポインタを提供することを意味します。ブラウザはその後、IDispatchのInvoke()メソッドを呼び出し、第1引数としてイベントのIDを渡します。

HRESULT CViewSource::Invoke(DISPID dispidMember, REFIID riid,
   LCID lcid, WORD wFlags, DISPPARAMS* pDispParams,
   VARIANT* pvarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
  if (dispidMember == DISPID_DOCUMENTCOMPLETE) {
	   OnDocumentComplete();
	   m_bDocumentCompleted = true;
  }
  :
}

イベントが不要になったら、忘れずにブラウザから切断しなければなりません。これを忘れると、ブラウザ ウィンドウを閉じた後も、BHOはロックしたままになります(オブジェクトの再コンパイルも削除もできなくなります)。OnQuitイベントを受信した段階で切断するのがタイミングとしてはよいでしょう。

Documentオブジェクトの利用

この時点でBHOはInternet ExplorerのWebBrowserコントロールへの参照を持っており、ブラウザで発生するすべてのイベントを受信できるようにブラウザに接続されています。Webページが完全にダウンロードされ、正しく初期化されると、DHTMLドキュメント オブジェクト モデルを通じてようやくそのページにアクセスできるようになります。WebBrowserのDocumentプロパティは、ドキュメント オブジェクトのIDispatchへのポインタを返します。

CComPtr<IDispatch> pDisp;
HRESULT hr = m_spWebBrowser2->get_Document(&pDisp);

get_Document()メソッドが提供するのは、インターフェイスへのポインタだけです。IDispatchポインタの先に、本当にHTMLドキュメント オブジェクトがあることを確かめる必要があります。Visual Basicを使ったとすると、コードは次のようになります。

Dim doc As Object
Set doc = WebBrowser1.Document
If TypeName(doc)="HTMLDocument" Then
   ' Get the document content and display
Else
   ' Disable the display dialog
End If

ここで必要なのは、get_Document()から返されるIDispatchポインタの性質について知る手段です。Internet Explorerは単なるHTMLブラウザだけではなく、ActiveXドキュメント、つまり、ActiveXドキュメント サーバーの役目を果たすアプリケーションがある任意のドキュメントをホストする機能があります。そのため、表示されているドキュメントが本当にHTMLページであるという保証はありません。

1つの解決策は、URLの場所を見て、URLの拡張子を調べることです。しかし、Active Server Pages(ASP)や、HTMLページが直接的には指定されていないURLの場合はどうなるでしょう。また、aboutresのようなカスタム プロトコルを使っている場合はどうでしょう(カスタム プロトコルの詳細については、MIND誌の1999年1月号に掲載の私のコラム「Cutting Edge」をご覧ください)。

私は、先に示したVisual Basicコードによく似た、別のアプローチをとることにしました。IDispatchポインタが実際にHTMLドキュメントを参照しているならば、IHTMLDocument2インターフェイスを要求するクエリーは成功するはずだというのが基本的な考え方です。IHTMLDocument2は、HTMLページについてDHTMLオブジェクト モデルが公開しているすべての機能をまとめるインターフェイスです。次のコードは、処理方法を示します。

CComPtr<IDispatch> pDisp;
HRESULT hr = m_spWebBrowser2->get_Document(&pDisp);
CComQIPtr&lt;IHTMLDocument2, &amp;IID_IHTMLDocument2&gt; spHTML;
spHTML = pDisp;
if (spHTML) {
	// get the content of the document and display it
}
else {
	// disable the Code Window controls
}

spHTMLポインタは、IHTMLDocument2を要求するクエリーが失敗した場合はNULLになります。それ以外の場合、DHTMLオブジェクト モデルのメソッドとプロパティが使えます。

今度は、表示されたページのソース コードを得る方法が問題になります。幸い、これを解決するには、DHTMLの初歩的な知識があれば十分です。HTMLページがコンテンツのすべてをタグで囲むのと同様に、DHTMLオブジェクト モデルも、第1のステップとして、Bodyオブジェクトへのポインタを取得する必要があります。

CComPtr m_pBody;
hr = spHTML->get_body(&m_pBody);

面白いことに、DHTMLオブジェクト モデルでは、<HEAD>など、<BODY>よりも先にあるタグで囲まれている生のコンテンツについては何も教えてくれません。それらのコンテンツは、処理されてからいくつかのプロパティに格納されますが、オリジナルのHTMLファイルにある生のテキストを返すプロパティがありません。とは言っても、ここでは、ボディで得られる内容だけで十分です。<BODY>...</BODY>タグで囲まれたHTMLコードを取得するには、次のようにouterHTMLプロパティの内容をBSTR変数へ読み込む必要があります。

BSTR bstrHTMLText;
hr = m_pBody->get_outerHTML(&bstrHTMLText);

この段階で、コード ウィンドウにテキストを表示するために、ウィンドウを作成して、文字列をUnicodeからANSIに変換し、図3に示したように編集ボックスを設定します。コード サンプル3 に、このための完全なコードを示します。

B
HRESULT CViewSource::Connect(void) { HRESULT hr; CComPtr spCP; // Receives the connection point for WebBrowser events hr = m_spCPC->FindConnectionPoint(DIID_DWebBrowserEvents2, &spCP); if (FAILED(hr)) return hr; // Pass our event handlers to the container. Each time an event occurs // the container will invoke the functions of the IDispatch interface // we implemented. hr = spCP->Advise( reinterpret_cast<IDispatch*>(this), &m_dwCookie); return hr; }

このコードは、DocumentCompleteの通知に応えて実行するので、新しいページは即座に自動的に処理されます。DHTMLオブジェクト モデルのおかげで、ページの構造をその場で修正できますが、F5キーを押すか、ブラウザの[最新の情報に更新(Refresh)]ボタンをクリックしたとたんに変更内容がすべて失われます。DocumentCompleteイベントを処理してコード ウィンドウを最新の情報に更新することもできます(DownloadCompleteイベントは、DocumentCompleteよりも前に発生することに注目してください)。ですから、ページを初めてダウンロードするときに発生するDownloadCompleteは無視して、更新によって発生するときだけ、このイベントを扱うようにします。単純なBoolean型のメンバ、たとえばm_bDocumentCompletedなどを使えば、このような状況を簡単に区別できます。

コード ウィンドウの管理

現在のページのHTMLソース コードを表示するコード ウィンドウは、ATLのもう1つの基本要素、つまり、ATLオブジェクト ウィザードの[その他(Miscellaneous)]画面にある、ダイアログ ボックス ウィンドウです。WM_INITDIALOGメッセージに応えてこのウィンドウをサイズ変更し、デスクトップ作業領域、つまり画面全体からタスクバー(ドッキング位置に関係なく)の幅を除いた領域の一番下の部分を占めるようにします。

このウィンドウは、ブラウザの起動時に表示することも、表示しないこともできます。通常の設定では表示されますが、[Show window at startup]チェック ボックスをオフにすれば表示されません。 必要であればウィンドウを閉じることもできます。その場合は、F12キーを押せば、いつでも元どおりに開けます。F12キーが押されたことはSetSite()でインストールしたキーボード フックで検出します。

起動時の設定は、Microsoftのガイドラインに従ってレジストリに保存されます。このレジストリを読み書きするために、私はWin32関数の代わりに新しいShell Lightweight API(shlwapi.dll)を使い、関連するキーを開いたり閉じたりする手間を省きました。

DWORD dwType, dwVal;
DWORD dwSize = sizeof(DWORD);
SHGetValue(HKEY_CURRENT_USER, _T("Software\\MSDN\\BHO"),
   _T("ShowWindowAtStartup"), &dwType, &dwVal, &dwSize);

このDLLは、Internet Explorer 4.0とActive Desktopで導入され、Windows 98以降はシステム標準のコンポーネントになりました。これらの関数は、これに相当するWin32の関数と比べて直接的であり、1回の読み書きに適しています。 ヘルパー オブジェクトの登録 BHOはCOMサーバーであるため、COMサーバーとしてもBHOとしても登録しなければなりません。ATLウィザードを使うと、COMサーバーとして登録するために必要なレジストラ スクリプト コード(RGS)を生成できます。以下は、ヘルパー オブジェクトを正しくインストールするRGSコードです(CLSIDはサンプルのものを使っています)。

HKLM {
 SOFTWARE {
  Microsoft {	
   Windows {
    CurrentVersion {
     Explorer {
      'Browser Helper Objects' {
	    ForceRemove {1E1B2879-88FF-11D2-
		8D96-D7ACAC95951F}		
}}}}}}}

オブジェクトを登録解除するとキーを削除させるForceRemove句に注目してください。

Browser Helper Objectsキーの下に、インストールするすべてのヘルパー オブジェクトを登録します。このようなリストは、ブラウザにはキャッシュされないので、BHOのインストールとテストはすばやく簡単にできます。

まとめ

本稿では、Browser Helper Objectsを紹介しました。Browser Helper Objectsは、ブラウザのアドレス空間の内部に独自のコードを直接組み込む、比較的新しく、パワフルな手法です。みなさんがしなければならないことは、IObjectWithSiteインターフェイスをサポートするCOMサーバーを書くことです。この時点で、みなさんのモジュールは、規則を逸脱しない、ブラウザの正当な構成要素として機能します。本稿の中で作成したサンプルは、COM関連のイベント、Dynamic HTMLオブジェクト モデル、WebBrowserプログラミング インターフェイスなどについても触れましたが、これらはトピックの本題からはやや逸れているかもしれません。それでも、このサンプルを通じてBHOの威力を証明し、同時にみなさん自身のオブジェクトを作成する際の現実的な土台になっていると思います。ブラウザが何を表示しているかを知る必要がある場合は、イベントを完全に理解し、WebBrowserを熟知する必要があります。事前に知っていれば準備もできるというものです。最後に、BHOはエクスプローラでも役に立つことも憶えておいてください。WebBrowserのおかげでエクスプローラを自分のコードから操作できるのです。


ディーノ エスポジートは、ローマ在住の上級コンサルタントです。彼はAndersen Consultingに勤務しながら、COMとスクリプティングを専門に扱っています。彼の著書には『Visual C++ Windows Shell Programming』(WROX、1999年)があります。
ディーノへのメールはdesposito@infomedia.itまでお願いします。

©2007 Microsoft Corporation. All rights reserved. 使用条件 |商標 |プライバシー |日本での個人情報の取り扱い
Microsoft