『 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 タブレットほしいですねぇ。すいません、また話題がそれてしまいました。