EternalWindows
WebBrowser コントロール / 概要説明

インターネットに接続してWebページを閲覧するという行為ほど、 多くのユーザーが慣れ親しんでいるものはないといえるでしょう。 こうした行為に必要なのは、Internet Explorerに代表されるブラウザと呼ばれるアプリケーションであり、 ユーザーは自分のニーズに合せて最適なブラウザを選択しようとします。 しかし、インターネット上のサービスの増加により、 現在ではユーザーが望むニーズというものが非常に多様化しています。 一昔前では、Webページが速く表示されればそれば問題ないと感じることもありましたが、 現在ではタブをサポートしているかや、ダウンロード機能が充実しているかなど、 ブラウザに求められる機能が非常に多くなっています。 よって、ユーザーのニーズをくみ取ったブラウザというのは、非常に需要がある開発対象といえるでしょう。 今回からは、こうしたブラウザの開発について見ていきます。

それでは初めに、今回作成するブラウザの外観を確認します。 ソースコードのダウンロードはこちらから行ってください。

基本的な使用方法は、通常のブラウザと特に変わりありません。 タブを切り替えれば表示されるWebページが変更されますし、 左横のバーからはお気に入りやフィードにアクセスできます。 検索については、右上のOpenSearchのウインドウから行うことが可能であり、 タブの下に存在するバンドオブジェクト(上図ではgoogleツールバー)からも可能な場合があります。 今回作成するブラウザはWindows VistaからサポートされているAPIを使用しているため、 Windows Vista以降の環境で実行できます。 また、バージョン6のコモンコントロールを使用しなければ、 レバーコントロールが正しく表示されないため、開発環境にマニフェストファイルを追加しておきます。

ブラウザを開発するにあたって、IEの機能を使用することになるかが1つの分かれ目になります。 IEの機能を使用する場合は、明示的にWebサーバーへHTTP要求を発行したり、HTMLを解析して描画したりする必要はなくなりますから、 開発に必要な作業が大幅に減る利点があります。 しかし、IEの機能を使用するとは具体的にどういうことなのでしょうか。 これは簡単にいえば、IEが使用しているshdocvw.dllを使用するということです。 このDLLはブラウザに必要なナビゲーションや履歴などの処理に加えて、 MSHTML(mshtml.dll)のホストも行っているため、HTMLをWebページとして表示することもできます。 つまり、このDLLを使用するようになれば、ブラウザとしての最低限の処理が手に入るということになります。 WebBrowser コントロールというのは、shdocvw.dllに実装されているCOMオブジェクトのことであり、 このオブジェクトを使用するブラウザが、俗にいわれるIEコンポーネントブラウザということになります。

WebBrowser コントロールを使用すればWebページを表示できるといっても、 それだけでブラウザが完成するようなことはありません。 たとえばIEのように、WebBrowser コントロールの機能をメニューから使用できなければなりませんし、 お気に入りやフィードを表示するためのバーも必要になります。 そして、このような動作を実現するためには、WebBrowser コントロール以外のAPIも理解しておかなければなりません。 次に、今回使用する主なAPIの一覧を示します。

実装 使用するAPI
ナビゲーション履歴の取得 ITravelLogStg
お気に入りメニューの表示 ITrackShellMenu
お気に入りバーの表示 INameSpaceTreeControl
OpenSearch処理 WinHTTP + XmlLite
フィード管理 Windows RSS Platform
URL履歴の取得 IUrlHistoryStg
データ管理 DPA(Dynamic Pointer Array)

DPAは、動的に作成されたデータを配列のように管理できるAPIであり、 取得したいデータにインデックスベースでアクセスできる利点があります。 このAPIは今回のブラウザの多く場面で使用されることになるため、この場で例を示します。

HDPA hdpa;

hdpa = DPA_Create(1);

for (i = 0; i < n; i++) {
	pData = HeapAlloc(...);
	DPA_InsertPtr(hdpa, i, pData); // データをDPAに追加
}

pData = DPA_GetPtr(hdpa, 2); // DPAから2番目のデータを取得

DPA_Createを呼び出せば、動的にサイズが更新される配列を作成することができます。 第1引数は配列を拡大する際の要素の単位であり、1で問題ありません。 戻り値のHDPA型が動的な配列を識別することになります。 DPA_InsertPtrを呼び出せば、第2引数のインデックスに第3引数のデータのポインタを格納することができ、 DPA_GetPtrを呼び出せば第2引数のインデックスに関連するデータを取得できます。 DPAを使用することにより、データをリスト構造などで管理する必要がなくなります。

最低限の実装

ブラウザのようなそれなりの規模のアプリケーションを開発していく前に、 WebBrowser コントロールの表示方法を先に知りたいという要望は多いと思われます。 次に、WebBrowser コントロールを使用した最低限の実装を示します。

#include <windows.h>
#include <shlobj.h>

class CWebBrowserHost : public IOleClientSite, public IOleInPlaceSite
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP SaveObject();
	STDMETHODIMP GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker **ppmk);
	STDMETHODIMP GetContainer(IOleContainer **ppContainer);
	STDMETHODIMP ShowObject();
	STDMETHODIMP OnShowWindow(BOOL fShow);
	STDMETHODIMP RequestNewObjectLayout();
	
	STDMETHODIMP GetWindow(HWND *phwnd);
	STDMETHODIMP ContextSensitiveHelp(BOOL fEnterMode);
	STDMETHODIMP CanInPlaceActivate();
	STDMETHODIMP OnInPlaceActivate();
	STDMETHODIMP OnUIActivate();
	STDMETHODIMP GetWindowContext(IOleInPlaceFrame **ppFrame, IOleInPlaceUIWindow **ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo);
	STDMETHODIMP Scroll(SIZE scrollExtant);
	STDMETHODIMP OnUIDeactivate(BOOL fUndoable);
	STDMETHODIMP OnInPlaceDeactivate();
	STDMETHODIMP DiscardUndoState();
	STDMETHODIMP DeactivateAndUndo();
	STDMETHODIMP OnPosRectChange(LPCRECT lprcPosRect);

	CWebBrowserHost();
	~CWebBrowserHost();
	int Run(HINSTANCE hinst, int nCmdShow);
	LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
	BOOL Create();

private:
	LONG         m_cRef;
	HWND         m_hwnd;
	IWebBrowser2 *m_pWebBrowser;
};

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

CWebBrowserHost *g_pWebBrowserHost = NULL;

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	int nResult = 0;

	OleInitialize(NULL);
	
	g_pWebBrowserHost = new CWebBrowserHost;
	if (g_pWebBrowserHost != NULL) {
		nResult = g_pWebBrowserHost->Run(hinst, nCmdShow);
		g_pWebBrowserHost->Release();
	}

	OleUninitialize();

	return nResult;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	return g_pWebBrowserHost->WindowProc(hwnd, uMsg, wParam, lParam);
}


// CWebBrowserHost


CWebBrowserHost::CWebBrowserHost()
{
	m_cRef = 1;
	m_hwnd = NULL;
	m_pWebBrowser = NULL;
}

CWebBrowserHost::~CWebBrowserHost()
{
}

STDMETHODIMP CWebBrowserHost::QueryInterface(REFIID riid, void **ppvObject)
{
	*ppvObject = NULL;

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IOleClientSite))
		*ppvObject = static_cast<IOleClientSite *>(this);
	else if (IsEqualIID(riid, IID_IOleWindow) || IsEqualIID(riid, IID_IOleInPlaceSite))
		*ppvObject = static_cast<IOleInPlaceSite *>(this);
	else
		return E_NOINTERFACE;

	AddRef();
	
	return S_OK;
}

STDMETHODIMP_(ULONG) CWebBrowserHost::AddRef()
{
	return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CWebBrowserHost::Release()
{
	return InterlockedDecrement(&m_cRef);
}

STDMETHODIMP CWebBrowserHost::SaveObject()
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker **ppmk)
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::GetContainer(IOleContainer **ppContainer)
{
	*ppContainer = NULL;

	return E_NOINTERFACE;
}

STDMETHODIMP CWebBrowserHost::ShowObject()
{
	return S_OK;
}

STDMETHODIMP CWebBrowserHost::OnShowWindow(BOOL fShow)
{
	return S_OK;
}

STDMETHODIMP CWebBrowserHost::RequestNewObjectLayout()
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::GetWindow(HWND *phwnd)
{
	*phwnd = m_hwnd;

	return S_OK;
}

STDMETHODIMP CWebBrowserHost::ContextSensitiveHelp(BOOL fEnterMode)
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::CanInPlaceActivate()
{
	return S_OK;
}

STDMETHODIMP CWebBrowserHost::OnInPlaceActivate()
{
	return S_OK;
}

STDMETHODIMP CWebBrowserHost::OnUIActivate()
{
	return S_OK;
}

STDMETHODIMP CWebBrowserHost::GetWindowContext(IOleInPlaceFrame **ppFrame, IOleInPlaceUIWindow **ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo)
{
	*ppFrame = NULL;
	*ppDoc = NULL;
	
	GetClientRect(m_hwnd, lprcPosRect);
	GetClientRect(m_hwnd, lprcClipRect);

	return S_OK;
}

STDMETHODIMP CWebBrowserHost::Scroll(SIZE scrollExtant)
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::OnUIDeactivate(BOOL fUndoable)
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::OnInPlaceDeactivate()
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::DiscardUndoState()
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::DeactivateAndUndo()
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::OnPosRectChange(LPCRECT lprcPosRect)
{
	return S_OK;
}

int CWebBrowserHost::Run(HINSTANCE hinst, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = ::WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CWebBrowserHost::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {

	case WM_CREATE:
		m_hwnd = hwnd;
		if (!Create())
			return -1;
		return 0;

	case WM_SIZE: {
		RECT              rc = {0, 0, LOWORD(lParam), HIWORD(lParam)};
		IOleInPlaceObject *pOleInPlaceObject;

		m_pWebBrowser->QueryInterface(IID_PPV_ARGS(&pOleInPlaceObject));
		pOleInPlaceObject->SetObjectRects(&rc, &rc);
		pOleInPlaceObject->Release();
		return 0;
	}

	case WM_DESTROY:
		if (m_pWebBrowser != NULL)
			m_pWebBrowser->Release();
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

BOOL CWebBrowserHost::Create()
{
	IOleObject *pOleObject;
	HRESULT    hr;
	MSG        msg;
	RECT       rc;
	BSTR       bstrUrl;
	VARIANT    varFlags, varTargetFrameName, varPostData, varHeaders;

	hr = CoCreateInstance(CLSID_WebBrowser, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pWebBrowser));
	if (FAILED(hr))
		return FALSE;
	
	m_pWebBrowser->QueryInterface(IID_PPV_ARGS(&pOleObject));
	pOleObject->SetClientSite(static_cast<IOleClientSite *>(this));

	SetRectEmpty(&rc);
	hr = pOleObject->DoVerb(OLEIVERB_INPLACEACTIVATE, &msg, static_cast<IOleClientSite *>(this), 0, m_hwnd, &rc);
	if (FAILED(hr)) {
		pOleObject->Release();
		m_pWebBrowser->Release();
		m_pWebBrowser = NULL;
		return FALSE;
	}
	
	bstrUrl = SysAllocString(L"http://www.yahoo.co.jp/");
	VariantInit(&varFlags);
	VariantInit(&varTargetFrameName);
	VariantInit(&varPostData);
	VariantInit(&varHeaders);
	m_pWebBrowser->Navigate(bstrUrl, &varFlags, &varTargetFrameName, &varPostData, &varHeaders);

	SysFreeString(bstrUrl);
	pOleObject->Release();

	return TRUE;
}

CWebBrowserHostというクラスのオブジェクトを作成しているのは、 WebBrowser コントロールを表示するためにインプレースアクティベーションという技術を使用するからです。 インプレースアクティベーションを行えば、WebBrowser コントロールのウインドウがアプリケーションのウインドウの子ウインドウになるのですが、 このためにはアプリケーション側が自身のオブジェクトをWebBrowser コントロールに渡さなければなりません。 オブジェクトが実装すべきインターフェースは、インプレースアクティベーションのためのIOleInPlaceSiteはもちろんのこと、 インプレースアクティベーションがOLEの埋め込みを基にしていることから、IOleClientSiteも実装していなければなりません。 ただし、各メソッドの処理は基本的に上記の通りで問題ないため、インターフェースについて深く理解しておく必要はありません。 インプレースアクティベーションを行うための具体的なコードはCreateの内部に記述されていますが、 これは後の節で取り上げます。

WebBrowser コントロールが持っているHTMLファイルの表示機能は、ブラウザ以外のアプリケーションにも大いに役立ちます。 アプリケーションのUIを記述したHTMLファイルを用意しておけば、 それにアクセスするだけでアプリケーションのUIが完成しますから、 GDIによる描画やコモンコントロールの配置などを行う必要はなくなります。 WebBrowser コントロールのウインドウは、IOleInPlaceObject::SetObjectRectsで任意の位置に表示できるため、 特定の部分だけHTMLファイルを使用するようなこともできます。



戻る