『 Google Chrome Frame をチェックしていたら、BHO ( Browser Helper Object ) が気になってきた 』 ので、C++、ATL を使って実装してみた
以前投稿した BHO に関する記事 『Google Chrome Frame をチェックしていたら、BHO ( Browser Helper Object ) が気になってきた』は、ほとんど Surface に関する記事になってしまったので、今回は寄り道せず、まじめに C++ で BHO ( Browser Helper Object ) の実装をしたいと思います。実装内容は、Internet Explorer のアドインとして組み込まれ、常に私のブログへ強制的に遷移させてしまうという、非常に悪質な モジュールです(笑)。
まず、ネイティブ 且つ COM である必要があるので、ATL ( Active Template Library ) で実装します。下図の通り、ATL Project を新規作成します。ここでは、プロジェクト名を『 BHO_Sample 』とします。( ああ、ATL とか懐かしすぎる.... )

Finish をクリックします。昔と比較すると、随分設定がなくなった気がしますが、気のせいでしょうか?スレッド モデル、インターフェイスとかの設定がウィザードにありませんでしたっけ?

まず、COM 開発の基本となる、インターフェース定義を行います。インターフェースの定義は、IDL ( Interface Definition Language ) を使用します。( C# とはかなり勝手が違いますね。くじけずに読んでください。 ) プロジェクト中に、BHO_Sample.idl というファイルが存在します。内容は下記の通りです。ATL の各種設定は、IDL ベースでしか出来なくなったのでしょうか?
// BHO_Sample.idl : IDL source for BHO_Sample
//
// This file will be processed by the MIDL tool to
// produce the type library (BHO_Sample.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
[
uuid(DBBF27F7-A2EE-46B5-A13C-6193BD0519C2),
version(1.0),
]
library BHO_SampleLib
{
importlib("stdole2.tlb");
};
import 宣言配下に、IDL を利用してインターフェースを定義します。uuid は、Visual Studio の GUID ツールなどを使用して生成してください。
// BHO_Sample.idl : IDL source for BHO_Sample
//
// This file will be processed by the MIDL tool to
// produce the type library (BHO_Sample.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(40BE7058-278F-4B78-9C8C-9FDBDCBC5495),
dual,
nonextensible,
helpstring("BHO インターフェース"),
pointer_default(unique)
]
interface IBHOSample : IDispatch{
};
[
uuid(06263513-A9E9-41BA-BF18-686378B887D3),
version(1.0),
]
library BHO_SampleLib
{
importlib("stdole2.tlb");
[
uuid(0E22B494-B0E4-4679-961B-B835CE873488)
]
coclass BHOSample
{
[default] interface IBHOSample;
};
};
上記のインターフェースを Visual Studio 上からコンパイルします。作成した IDL ファイルを右クリックし、コンパイルを選択します。 (実際には、MIDL でコンパイルしています。MIDL に関してはここを参照してください。) コンパイルが成功すると、BHO_Sample_i.c および BHO_Sample_i.h が生成されます。これらのファイルは、BHO 実装時に触ることはなく、 include で使用するだけです。IDL をコンパイルしたら、こんなものが生成されるんだなという認識程度で十分です(だと思います)。
※BHO_Sample_i.c
/* this ALWAYS GENERATED file contains the IIDs and CLSIDs */
/* link this file in with the server and any clients */
/* File created by MIDL compiler version 8.00.0603 */
/* at Thu Jan 02 05:52:50 2014
*/
/* Compiler settings for BHO_Sample.idl:
Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.00.0603
protocol : dce , ms_ext, c_ext, robust
error checks: allocation ref bounds_check enum stub_data
VC __declspec() decoration level:
__declspec(uuid()), __declspec(selectany), __declspec(novtable)
DECLSPEC_UUID(), MIDL_INTERFACE()
*/
/* @@MIDL_FILE_HEADING( ) */
#pragma warning( disable: 4049 ) /* more than 64k source lines */
#ifdef __cplusplus
extern "C"{
#endif
#include <rpc.h>
#include <rpcndr.h>
#ifdef _MIDL_USE_GUIDDEF_
#ifndef INITGUID
#define INITGUID
#include <guiddef.h>
#undef INITGUID
#else
#include <guiddef.h>
#endif
<長いので、以下省略>
※BHO_Sample_i.h
/* this ALWAYS GENERATED file contains the IIDs and CLSIDs */
/* link this file in with the server and any clients */
/* File created by MIDL compiler version 8.00.0603 */
/* at Thu Jan 02 05:52:50 2014
*/
/* Compiler settings for BHO_Sample.idl:
Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.00.0603
protocol : dce , ms_ext, c_ext, robust
error checks: allocation ref bounds_check enum stub_data
VC __declspec() decoration level:
__declspec(uuid()), __declspec(selectany), __declspec(novtable)
DECLSPEC_UUID(), MIDL_INTERFACE()
*/
/* @@MIDL_FILE_HEADING( ) */
#pragma warning( disable: 4049 ) /* more than 64k source lines */
/* verify that the <rpcndr.h> version is high enough to compile this file*/
#ifndef __REQUIRED_RPCNDR_H_VERSION__
#define __REQUIRED_RPCNDR_H_VERSION__ 475
#endif
#include "rpc.h"
#include "rpcndr.h"
#ifndef __RPCNDR_H_VERSION__
#error this stub requires an updated version of <rpcndr.h>
#endif // __RPCNDR_H_VERSION__
#ifndef COM_NO_WINDOWS_H
#include "windows.h"
#include "ole2.h"
#endif /*COM_NO_WINDOWS_H*/
#ifndef __BHO_Sample_i_h__
#define __BHO_Sample_i_h__
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
/* Forward Declarations */
#ifndef __IBHOSample_FWD_DEFINED__
#define __IBHOSample_FWD_DEFINED__
typedef interface IBHOSample IBHOSample;
#endif /* __IBHOSample_FWD_DEFINED__ */
#ifndef __BHOSample_FWD_DEFINED__
#define __BHOSample_FWD_DEFINED__
#ifdef __cplusplus
typedef class BHOSample BHOSample;
#else
typedef struct BHOSample BHOSample;
#endif /* __cplusplus */
<長いので、以下省略>
これで、BHO の実装準備が完了しました。次に、BHO のメイン部分の実装をします。プロジェクトにクラスの追加をします。下図の画面で Add ボタンをクリックします。

Class name に CBHOSample と入力し 、Finish をクリックします。.h file および .cpp file は 自動入力されます。

BHOSample.h を下記のように書き換えます。(前回のブログで紹介した、各ページのサンプルをみながら実装しました。) ここ( ATL COM オブジェクトの基本事項)を参照するとわかりやすいです。BHO では、さらに IObjectWithSiteImpl が非常に重要で、ブラウザとやりとりをするための、重要なメソッドが用意されています。
#pragma once
#include "resource.h" // メイン シンボル
#include "BHO_Sample_i.h" // IDLから生成された情報
#include <shlguid.h> // IID_IWebBrowser2、DIID_DWebBrowserEvents2
#include <exdispid.h> // DISPID_DOCUMENTCOMPLETE
using namespace ATL;
// IDispatchImplなどから派生したものを作成する
class ATL_NO_VTABLE CBHOSample :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CBHOSample, &CLSID_BHOSample>,
public IObjectWithSiteImpl<CBHOSample>,
public IDispatchImpl<IBHOSample, &IID_IBHOSample, &LIBID_BHO_SampleLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IDispEventImpl<1, CBHOSample, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1>
{
public:
CBHOSample()
{
}
// レジストリ登録の ATL マクロ
DECLARE_REGISTRY_RESOURCEID(IDR_BHO_SAMPLE)
// スレッドプールからオブジェクトを生成するため?
DECLARE_NOT_AGGREGATABLE(CBHOSample)
// COMのインターフェースマップの ATL マクロ
BEGIN_COM_MAP(CBHOSample)
COM_INTERFACE_ENTRY(IBHOSample)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IObjectWithSite)
END_COM_MAP()
// イベントシンクマップの ATL マクロ
BEGIN_SINK_MAP(CBHOSample)
SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, OnBeforeNavigate2)
END_SINK_MAP()
// 参照カウントが 0 になってもオブジェクトを削除されないようにする
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
// イベントシンクの登録および解除
STDMETHOD(SetSite)(IUnknown *pUnkSite);
// DWebBrowserEvents2 ハンドラー
void STDMETHODCALLTYPE OnBeforeNavigate2(IDispatch* pDisp, VARIANT* url, VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData, VARIANT* Headers, VARIANT_BOOL* Cancel);
private:
CComPtr<IWebBrowser2> m_spWebBrowser; // IWebBrowser2(ブラウザのコントロール) へのポインタをキャッシュ
BOOL m_fAdvised;
};
OBJECT_ENTRY_AUTO(__uuidof(BHOSample), CBHOSample)
BHOSample.cpp を下記のように書き換えます。 SetSite および OnBeforeNavigate2 の実装を行います。
#include "stdafx.h"
#include "BHOSample.h"
// SetSite の実装をします。
STDMETHODIMP CBHOSample::SetSite(IUnknown* pUnkSite)
{
if (pUnkSite)
{
// Internet Explorer 起動時の処理を記述します。
// IWebBrowser2(ブラウザのコントロール) へのポインタをキャッシュしておきます。
HRESULT hr = pUnkSite->QueryInterface(IID_IWebBrowser2, (void**)&m_spWebBrowser);
if (SUCCEEDED(hr))
{
// DWebBrowserEvents2 からのイベントシンクに登録します。
hr = DispEventAdvise(m_spWebBrowser);
if (SUCCEEDED(hr))
{
m_fAdvised = TRUE;
}
}
}
else
{
// Internet Explorer 終了時の処理を記述します。
// イベントシンクを解除します。
if (m_fAdvised)
{
DispEventUnadvise(m_spWebBrowser);
m_fAdvised = FALSE;
}
// リソースを解放します。
if (m_spWebBrowser) m_spWebBrowser.Release();
}
// 基本クラスに戻ります。
return IObjectWithSiteImpl<CBHOSample>::SetSite(pUnkSite);
}
// ブラウザでページを参照すると、常に私のブログ (http://ryuchan.hatenablog.com/) に誘導します。
void STDMETHODCALLTYPE CBHOSample::OnBeforeNavigate2(IDispatch* pDisp, VARIANT* url, VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData, VARIANT* Headers, VARIANT_BOOL* Cancel)
{
// 私のブログの場合は何もしません。
if (_tcsstr(url->bstrVal, _T("http://ryuchan.hatenablog.com/")))
{
// 今は何もしない。
}
// 私以外のサイトであれば、強制的に http://ryuchan.hatenablog.com/ へ遷移します。
else
{
CComPtr<IWebBrowser2> spWebBrowser;
HRESULT hr = pDisp->QueryInterface(IID_IWebBrowser2, reinterpret_cast<void**>(&spWebBrowser));
if (SUCCEEDED(hr))
{
*Cancel = VARIANT_TRUE;
CComVariant spVar;
MessageBox(0, L"http://ryuchan.hatenablog.com/に遷移します。", L"http://ryuchan.hatenablog.com/", MB_OK);
spWebBrowser->Stop();
spWebBrowser->Navigate(_T("http://ryuchan.hatenablog.com/"), &spVar, &spVar, &spVar, &spVar);
spWebBrowser.Release();
}
}
}
これで BHO の実装は完了です。C# になれてしまうと、C++ & ATL の実装は大変だなぁと思ってしまいます。.NETFramework での開発は本当に楽ですね。
最後に、生成されたアセンブリをレジストリに登録するため、rgs ファイル (レジストリ登録のための設定ファイル)を記述します。プロジェクト内に、BHO_Sample.rgs がありますので、そのファイルを下記のように変更します。各 uuid は BHO_Sample.idl の情報を元に設定をしてください。( .NETFramework だとレジストリの設定なんて意識しないもんなぁ。 )
HKCR
{
NoRemove CLSID
{
ForceRemove {0E22B494-B0E4-4679-961B-B835CE873488} = s 'BHOSample Class'
{
ForceRemove Programmable
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Apartment'
}
TypeLib = s '{06263513-A9E9-41BA-BF18-686378B887D3}'
Version = s '1.0'
}
}
}
HKLM
{
NoRemove SOFTWARE
{
NoRemove Microsoft
{
NoRemove Windows
{
NoRemove CurrentVersion
{
NoRemove Explorer
{
NoRemove 'Browser Helper Objects'
{
ForceRemove '{0E22B494-B0E4-4679-961B-B835CE873488}' = s 'BHOSample'
{
val 'NoExplorer' = d '1'
}
}
}
}
}
}
}
}
これで、今回の実装・設定は完了です。 ソリューションをビルドすると、BHO_Sample.dll が生成されます。
BHO_Sample.dll を BHO として Internet Explorer に認識させるには、コマンドプロンプトで下記のコマンドを実行します。
regsvr32 BHO_Sample.dll
BHO_Sample.rgs の定義通りに、レジストリが登録されます。レジストリエディタを使用すると、CLSID、インターフェース、ブラウザヘルパーオブジェクト が登録されているのがわかります。



それでは、動作確認をしてみましょう。インターネットエクスプローラーを起動します。インターネットエクスプローラー起動直後、下部にメッセージが表示されますので、『有効にする』をクリックします。

ツール → アドイン管理 をクリックします。

今回作成した BHOSample Class が登録されています。

有効化された BHO を実際に動作させるには、インターネットエクスプローラーを再起動する必要があります。インターネットエクスプローラーを再起動すると下記のようなメッセージが表示されます。OK をクリックすると、私のブログへ強制的に遷移します。


このままでは、まともなブラウジングができないので、BHO のレジストリを解除しておきましょう。コマンドプロンプトで下記のコマンドを実行します。
regsvr32 /u BHO_Sample.dll
これで、私が実装した悪質な BHO_Sample.dll のレジストリは削除され、私のブログへ強制的に遷移されることもなくなります。以上で、BHO の実装・設定および動作確認が一通り完了しました。
今後は、『今更感』はありますが COM の仕組みについて投稿したいと思います。( BHO の実装より先にやったほうがよかったですね。) このような開発をする場合には、まだまだ ネイティブの COM 開発も現役ですね。Windows 開発者であれば、覚えておいて損はない気がします。
また寄り道です。全然関係ないですけど、Windows 8.1 タブレットの Lenovo Miix 2 8 いいですね。最近、Nexus 7 を購入したんですが、Windows 8.1 タブレットもほしくなりました。
Lenovo IdeaPad Miix2 8 (Atom Z3740/64GB/2GB/Win8.1/8型HD IPS/ブラック/Office H&B 2013) 59399891
- 出版社/メーカー: Lenovo
- 発売日: 2013/12/06
- メディア: Personal Computers
- この商品を含むブログ (13件) を見る
Venue 8 Pro も捨てがたい...
dynabook はどうなんだろうか。日本勢もがんばれー。
Acer もいいな。
Acer ICONIA W4-820/FP (Atom Z3740/2G/64G eMMC/8.0/Win8.1(32)/OFL2013) W4-820/FP
- 出版社/メーカー: 日本エイサー
- 発売日: 2013/12/13
- メディア: Personal Computers
- この商品を含むブログ (4件) を見る
Windows 8.1 タブレットほしいですねぇ。すいません、また話題がそれてしまいました。

