EternalWindows
URL Monikers / 明示的なバインド

サーバー上に存在するHTMLファイルを受信しようとした場合、一般的にはWinINetかWinHTTPのどちらかのAPIを使用します。 これらのAPIはHTTPプロトコルの詳細を隠蔽しているため、アプリケーションがHTTPプロトコルの仕様を理解する必要がなくなる利点があります。 ただし、こうしたネットワークAPIを使用する場合においても、データの受信までの操作はそれなりに複雑です。 たとえば、サーバーへの接続を行う関数やHTTPリクエストを送信する関数などを呼ばなければならず、 ある程度のネットワーク的な操作はアプリケーションが意識する必要がありました。 これに対してURL Monikers(urlmon)は、COMにおけるモニカという概念に基づいてデータを取得するため、 ネットワーク的な操作は比較的見えにくい利点があります。 また、http:のような既知のプロトコルだけでなく、 sample:のような独自のプロトコルを含んだURLもサポートできます。

モニカというのは、オブジェクトを検索(正確にはバインド)するためのオブジェクトです。 たとえば、ファイルモニカというモニカは、ファイルパスを基にファイルを表すオブジェクトを取得し、 アプリケーションはそのオブジェクトを使用してファイルを操作します。 これに対して、URLからオブジェクトを取得するのはURLモニカであり、 CreateURLMonikerExを呼び出して作成します。

HRESULT CreateURLMonikerEx(      
  IMoniker *pMkCtx,
  LPCWSTR szURL,
  IMoniker **ppmk,
  DWORD dwFlags
);

pMkCtxは、pMkCtxはszURLにURLの一部を指定した場合に指定します。 szURLに完全なURLを指定した場合はNULLで構いません。 szURLは、データを取得したいURLを指定します。 ppmkは、IMonikerを受け取る変数のアドレスを指定します。 dwFlagsは、使用するURLパーサーの種類を示す定数を指定します。 URL_MK_LEGACYならば従来のCreateURLMonikerと同じパーサーが使用され、 URL_MK_UNIFORMならば更新されたパーサーが使用されます。

IMonikerを取得すれば、IMoniker::BindToStorageでIStreamを取得できます。 そして、IStream::ReadでHTMLの中身を取得できます。

#include <windows.h>
#include <urlmon.h>

#pragma comment (lib, "urlmon.lib")

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	IStream  *pStream;
	IBindCtx *pBindCtx;
	IMoniker *pMoniker;
	HRESULT  hr;
	WCHAR    szUrl[] = L"http://eternalwindows.jp/winbase/base/base00.html";
	WCHAR    szSaveFile[] = L"C:\\sample.html";
	DWORD    dwSize, dwRead, dwWriteByte;
	LPBYTE   lpData;
	STATSTG  stastg;
	HANDLE   hFile;

	hr = CreateURLMonikerEx(NULL, szUrl, &pMoniker, URL_MK_UNIFORM);
	if (FAILED(hr))
		return 0;

	CreateBindCtx(0, &pBindCtx);
	hr = pMoniker->BindToStorage(pBindCtx, NULL, IID_PPV_ARGS(&pStream));
	if (FAILED(hr)) {
		pBindCtx->Release();
		pMoniker->Release();
		return 0;
	}

	pStream->Stat(&stastg, STATFLAG_DEFAULT);
	dwSize = stastg.cbSize.LowPart;
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	pStream->Read(lpData, dwSize, &dwRead);

	hFile = CreateFile(szSaveFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	WriteFile(hFile, lpData, dwRead, &dwWriteByte, NULL);
	CloseHandle(hFile);

	MessageBox(NULL, TEXT("終了します。"), TEXT("OK"), MB_OK);

	pStream->Release();
	pBindCtx->Release();
	pMoniker->Release();

	return 0;
}

まず、CreateURLMonikerでURLモニカを作成します。 szUrlがダウンロード対象のURLであり、szSaveFileがダウンロード先とするファイルのパスです。 IMoniker::BindToStorageでURLのデータを受信するためのIStreamを取得できますが、 このためには第1引数にバインドコンテキストを指定します。 これは、CreateBindCtxで作成可能です。 IStream::Statで初期化できるSTATSTG.cbSizeにはストリーム内のデータのサイズが格納されているため、 このサイズ分だけバッファを確保してIStream::Readを呼び出します。 これにより、lpDataにHTMLの中身が格納されているはずなので、後はファイルに保存します。 なお、CreateURLMonikerExはURLの形式でない文字列を指定しても成功するため、 事前にIsValidURLで形式を確認するのも候補といえます。

上記の方法ではIMoniker::BindToStorageが同期的に動作し、 BindToStorageが制御を返した時点で、データの受信は完了しています。 しかし、これはBindToStorageが制御を返すまでコードの進行がブロックされることも意味しますから、 場合によっては非同期に実行したいこともあるかもしれません。 そうした場合は、IBindStatusCallbackを実装したオブジェクトを用意し、 これをRegisterBindStatusCallbackに指定するようにします。 そうすると、データが使用可能になったタイミングなどでIBindStatusCallbackのメソッドが呼ばれます。

HRESULT RegisterBindStatusCallback(      
  IBindCtx *pbc,
  IBindStatusCallback *pbsc,
  IBindStatusCallback **ppbscPrevious,
  DWORD dwReserved
);

pbcは、CreateBindCtxで作成したIBindCtxを指定します。 pbscは、 ppbscPreviousは、 dwReservedは、予約されているため0を指定します。

URLによっては、特定のユーザーだけが閲覧できるように、認証情報を要求することがあります。 たとえば、次のURLにアクセスすると認証情報を要求するダイアログが表示されます。

http://eternalwindows.jp/network/winhttp/sec/sample.html

こうしたURLにアクセスする場合は、適切な認証情報をURLモニカに与える必要があります。 URLモニカは認証情報が必要になった場合にIAuthenticate::Authenticateを呼び出すため、 このメソッドを適切に実装すればよいでしょう。 IAuthenticateは、IBindStatusCallbackを実装したオブジェクトが実装します。

今回のプログラムは、URLモニカを使用してデータを受信します。

#include <windows.h>
#include <urlmon.h>

#pragma comment (lib, "urlmon.lib")

#define ID_DOWNLOAD 10

class CBindStatusCallback : public IBindStatusCallback, IAuthenticate
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP OnStartBinding(DWORD dwReserved, IBinding *pib);
	STDMETHODIMP GetPriority(LONG *pnPriority);
	STDMETHODIMP OnLowResource(DWORD reserved);
	STDMETHODIMP OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText);
	STDMETHODIMP OnStopBinding(HRESULT hresult, LPCWSTR szError);
	STDMETHODIMP GetBindInfo(DWORD *grfBINDF, BINDINFO *pbindinfo);
	STDMETHODIMP OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pformatetc, STGMEDIUM *pstgmed);
	STDMETHODIMP OnObjectAvailable(REFIID riid, IUnknown *punk);

	STDMETHODIMP Authenticate(HWND *phwnd, LPWSTR *pszUsername, LPWSTR *pszPassword);

	CBindStatusCallback();
	~CBindStatusCallback();
	
	void DoDownload(LPWSTR lpszUrl, LPWSTR lpszSaveFile);

private:
	LONG     m_cRef;
	IBindCtx *m_pBindCtx;
	IMoniker *m_pMoniker;
	IStream  *m_pStream;
	HANDLE   m_hFile;
	LPWSTR   m_lpszSaveFile;
};

HWND g_hwndListBox = NULL;

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, 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 CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static CBindStatusCallback *pBindStatusCallback = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		HMENU hmenu;

		hmenu = CreateMenu();

		InitializeMenuItem(hmenu, TEXT("ダウンロード(&D)"), ID_DOWNLOAD);
		
		SetMenu(hwnd, hmenu);

		g_hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		pBindStatusCallback = new CBindStatusCallback;

		return 0;
	}
	
	case WM_COMMAND: {
		int nId = LOWORD(wParam);

		if (nId == ID_DOWNLOAD) {
			WCHAR szUrl[] = L"http://eternalwindows.jp/winbase/base/base00.html";
			WCHAR szSaveFile[] = L"C:\\sample.html";

			pBindStatusCallback->DoDownload(szUrl, szSaveFile);
		}
		
		return 0;
	}
	
	case WM_SIZE:
		MoveWindow(g_hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

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

	default:
		break;

	}

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

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId)
{
	MENUITEMINFO mii;
	
	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = nId;
	mii.dwTypeData = lpszItemName;
	
	InsertMenuItem(hmenu, nId, FALSE, &mii);
}


// CBindStatusCallback


CBindStatusCallback::CBindStatusCallback()
{
	m_cRef = 1;
	m_pBindCtx = NULL;
	m_pMoniker = NULL;
	m_pStream = NULL;
	m_hFile = NULL;
}

CBindStatusCallback::~CBindStatusCallback()
{
}

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

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IBindStatusCallback))
		*ppvObject = static_cast<IBindStatusCallback *>(this);
	else if (IsEqualIID(riid, IID_IAuthenticate))
		*ppvObject = static_cast<IAuthenticate *>(this);
	else
		return E_NOINTERFACE;

	AddRef();

	return S_OK;
}


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

STDMETHODIMP_(ULONG) CBindStatusCallback::Release()
{
	if (InterlockedDecrement(&m_cRef) == 0) {
		delete this;
		return 0;
	}

	return m_cRef;
}

void CBindStatusCallback::DoDownload(LPWSTR lpszUrl, LPWSTR lpszSaveFile)
{
	IStream *pStream;
	HRESULT hr;

	hr = CreateURLMonikerEx(NULL, lpszUrl, &m_pMoniker, URL_MK_UNIFORM);
	if (FAILED(hr))
		return;

	CreateBindCtx(0, &m_pBindCtx);
	RegisterBindStatusCallback(m_pBindCtx, static_cast<IBindStatusCallback *>(this), NULL, 0);

	m_lpszSaveFile = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (lstrlenW(lpszSaveFile) + 1) * sizeof(WCHAR));
	lstrcpyW(m_lpszSaveFile, lpszSaveFile);
	
	m_pMoniker->BindToStorage(m_pBindCtx, NULL, IID_PPV_ARGS(&pStream));
}

STDMETHODIMP CBindStatusCallback::OnStartBinding(DWORD dwReserved, IBinding *pib)
{
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("OnStartBinding"));

	return S_OK;
}

STDMETHODIMP CBindStatusCallback::GetPriority(LONG *pnPriority)
{
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("GetPriority"));

	return E_NOTIMPL;
}

STDMETHODIMP CBindStatusCallback::OnLowResource(DWORD reserved)
{
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("OnLowResource"));
	
	return E_NOTIMPL;
}

STDMETHODIMP CBindStatusCallback::OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText)
{
	WCHAR szBuf[256];

	switch (ulStatusCode) {
	case BINDSTATUS_FINDINGRESOURCE:
		wsprintfW(szBuf, L"%sを検索中", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	case BINDSTATUS_CONNECTING:
		wsprintfW(szBuf, L"%sを接続中", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	case BINDSTATUS_SENDINGREQUEST:
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)L"リクエストの送信");
		break;
	case BINDSTATUS_MIMETYPEAVAILABLE:
		wsprintfW(szBuf, L"利用可能なMIMEタイプ %s", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	case BINDSTATUS_BEGINDOWNLOADDATA:
		wsprintfW(szBuf, L"%sをダウンロード開始", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	case BINDSTATUS_DOWNLOADINGDATA:
		wsprintfW(szBuf, L"%sをダウンロード中", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	case BINDSTATUS_ENDDOWNLOADDATA:
		wsprintfW(szBuf, L"%sをダウンロード終了", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	case BINDSTATUS_COOKIE_SENT:
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)L"クッキーをリクエストと共に送信");
		break;
	case BINDSTATUS_CACHEFILENAMEAVAILABLE:
		wsprintfW(szBuf, L"利用可能な一時ファイルまたはキャッシュ %s", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	default:
		wsprintfW(szBuf, L"status %d", ulStatusCode);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	}
	
	return S_OK;
}

STDMETHODIMP CBindStatusCallback::OnStopBinding(HRESULT hresult, LPCWSTR szError)
{
	TCHAR szBuf[256];

	if (m_pMoniker != NULL) {
		m_pMoniker->Release();
		m_pMoniker = NULL;
	}

	if (m_pBindCtx != NULL) {
		m_pBindCtx->Release();
		m_pBindCtx = NULL;
	}
	
	if (m_lpszSaveFile != NULL) {
		HeapFree(GetProcessHeap(), 0, m_lpszSaveFile);
		m_lpszSaveFile = NULL;
	}

	if (hresult == S_OK)
		lstrcpy(szBuf, TEXT("OnStopBinding"));
	else
		wsprintf(szBuf, TEXT("OnStopBinding (error %x)"), hresult);

	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);

	return S_OK;
}

STDMETHODIMP CBindStatusCallback::GetBindInfo(DWORD *grfBINDF, BINDINFO *pbindinfo)
{
	*grfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA | BINDF_GETNEWESTVERSION | BINDF_NOWRITECACHE;

	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("GetBindInfo"));

	return S_OK;
}

STDMETHODIMP CBindStatusCallback::OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pformatetc, STGMEDIUM *pstgmed)
{
	LPBYTE lpData;
	DWORD  dwWriteByte;
	DWORD  dwRead;

	if (grfBSCF & BSCF_FIRSTDATANOTIFICATION) {
		if (pstgmed->tymed == TYMED_ISTREAM) {
			m_pStream = pstgmed->pstm;
			m_pStream->AddRef();
			m_hFile = CreateFile(m_lpszSaveFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		}
		else
			return E_FAIL;
	}

	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	m_pStream->Read(lpData, dwSize, &dwRead);
	WriteFile(m_hFile, lpData, dwRead, &dwWriteByte, NULL);
	HeapFree(GetProcessHeap(), 0, lpData);

	if (grfBSCF & BSCF_LASTDATANOTIFICATION) {
		CloseHandle(m_hFile);
		m_pStream->Release();
		m_hFile = NULL;
		m_pStream = NULL;
	}

	return S_OK;
}

STDMETHODIMP CBindStatusCallback::OnObjectAvailable(REFIID riid, IUnknown *punk)
{
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("OnObjectAvailable"));

	return S_OK;
}

STDMETHODIMP CBindStatusCallback::Authenticate(HWND *phwnd, LPWSTR *pszUsername, LPWSTR *pszPassword)
{
	WCHAR szUserName[] = L"user";
	WCHAR szPassword[] = L"pass";

	*phwnd = NULL;
	*pszUsername = (LPWSTR)CoTaskMemAlloc(sizeof(szUserName));
	lstrcpyW(*pszUsername, szUserName);
	*pszPassword = (LPWSTR)CoTaskMemAlloc(sizeof(szPassword));
	lstrcpyW(*pszPassword, szPassword);
	
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("Authenticate"));

	return S_OK;
}

CBindStatusCallbackというクラスが、IBindStatusCallbackとIAuthenticateを継承しています。 このようなクラスが必要になるのは、URLモニカからの通知を受け取るためです。 メニューの「ダウンロード」が選択されると、DoDownloadという独自メソッドが呼ばれ、 ダウンロードが開始されます。

void CBindStatusCallback::DoDownload(LPWSTR lpszUrl, LPWSTR lpszSaveFile)
{
	IStream *pStream;
	HRESULT hr;

	hr = CreateURLMonikerEx(NULL, lpszUrl, &m_pMoniker, URL_MK_UNIFORM);
	if (FAILED(hr))
		return;

	CreateBindCtx(0, &m_pBindCtx);
	RegisterBindStatusCallback(m_pBindCtx, static_cast<IBindStatusCallback *>(this), NULL, 0);

	m_lpszSaveFile = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (lstrlenW(lpszSaveFile) + 1) * sizeof(WCHAR));
	lstrcpyW(m_lpszSaveFile, lpszSaveFile);
	
	m_pMoniker->BindToStorage(m_pBindCtx, NULL, IID_PPV_ARGS(&pStream));
}

CreateBindCtxでバインドコンテキストを作成し、 これをRegisterBindStatusCallbackに指定することで、 オブジェクト(CBindStatusCallback)をバインドコンテキストに登録します。 そして、このバインドコンテキストをIMoniker::BindToStorageに指定すれば、 バインドコンテキストに登録されたCBindStatusCallbackのメソッドが呼ばれます。 lpszSaveFileは後で必要になるため、メンバとして保存しています。

CBindStatusCallbackが実装しているIBindStatusCallbackには様々なメソッドがありますが、 呼ばれる順番は主に、GetBindInfo、 OnStartBinding、 OnProgress(1回以上)、OnDataAvailable(1回以上)、OnStopBindingになります。 OnStopBindingはバインドの終了時に呼ばれるため、バインドが失敗した場合はより早く呼ばれることもあります。 まず、GetBindInfoを確認します。

STDMETHODIMP CBindStatusCallback::GetBindInfo(DWORD *grfBINDF, BINDINFO *pbindinfo)
{
	*grfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA | BINDF_GETNEWESTVERSION | BINDF_NOWRITECACHE;

	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("GetBindInfo"));

	return S_OK;
}

grfBINDFは、バインド方法に関する定数を指定しますが、 基本的に上記の値を指定するのが一般的のようです。 ただし、BINDF_ASYNCHRONOUSだけを指定しても、IMoniker::BindToStorageは直ちに制御を返すようになりますし(ローカルファイルは同期的)、 キャッシュではなくインターネットにアクセスするように思えます。 pbindinfoは、バインドに関する追加情報を指定します。 追加情報がない場合は、特に何も指定しなくて構いません。

STDMETHODIMP CBindStatusCallback::OnStartBinding(DWORD dwReserved, IBinding *pib)
{
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("OnStartBinding"));

	return S_OK;
}

OnStartBindingは、バインドが開始されたことを通知するために呼ばれます。 IBindingは現在のバインドを識別するためのものですが、 必要ない場合は参照しなくてもよいでしょう。

STDMETHODIMP CBindStatusCallback::OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pformatetc, STGMEDIUM *pstgmed)
{
	LPBYTE lpData;
	DWORD  dwRead, dwWriteByte;

	if (grfBSCF & BSCF_FIRSTDATANOTIFICATION) {
		if (pstgmed->tymed == TYMED_ISTREAM) {
			m_pStream = pstgmed->pstm;
			m_pStream->AddRef();
			m_hFile = CreateFile(m_lpszSaveFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		}
		else
			return E_FAIL;
	}

	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	m_pStream->Read(lpData, dwSize, &dwRead);
	WriteFile(m_hFile, lpData, dwRead, &dwWriteByte, NULL);
	HeapFree(GetProcessHeap(), 0, lpData);

	if (grfBSCF & BSCF_LASTDATANOTIFICATION) {
		CloseHandle(m_hFile);
		m_pStream->Release();
		m_hFile = NULL;
		m_pStream = NULL;
	}

	return S_OK;
}

OnDataAvailableは、データが使用可能になった際に呼ばれます。 この場合はSTGMEDIUM構造体からIStreamを取得し、 dwSize分だけIStream::Readでデータを取得することになるでしょう。 最初に呼ばれた場合はgrfBSCFにBSCF_FIRSTDATANOTIFICATIONに含まれているため、 この際にデータを取得するための初期化を行います。 ストリームのハンドルは2回目以降のOnDataAvailableに必要ですし、 データはファイルに連続して保存したいため、ファイルハンドルもメンバに保存します。 grfBSCFにBSCF_LASTDATANOTIFICATIONが含まれる場合は、 これ以上が呼ばれることはないため、データを開放するようにします。

STDMETHODIMP CBindStatusCallback::OnStopBinding(HRESULT hresult, LPCWSTR szError)
{
	TCHAR szBuf[256];

	if (m_pMoniker != NULL) {
		m_pMoniker->Release();
		m_pMoniker = NULL;
	}

	if (m_pBindCtx != NULL) {
		m_pBindCtx->Release();
		m_pBindCtx = NULL;
	}
	
	if (m_lpszSaveFile != NULL) {
		HeapFree(GetProcessHeap(), 0, m_lpszSaveFile);
		m_lpszSaveFile = NULL;
	}

	if (hresult == S_OK)
		lstrcpy(szBuf, TEXT("OnStopBinding"));
	else
		wsprintf(szBuf, TEXT("OnStopBinding (error %x)"), hresult);

	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);

	return S_OK;
}

OnStopBindingは、バインドが終了した場合に呼ばれます。 この時点になるとモニカやバインドコンテキストは不要であるため、開放しても問題ありません。 hresultがS_OKでない場合はエラーが原因でバインドが失敗したと考えられるため、その値を表示しています。 エラーの値はINET_E_XXXの形式が多く、たとえばINET_E_OBJECT_NOT_FOUND(0x800c0006)である場合は、 オブジェクトが存在しないことを意味します。

STDMETHODIMP CBindStatusCallback::Authenticate(HWND *phwnd, LPWSTR *pszUsername, LPWSTR *pszPassword)
{
	WCHAR szUserName[] = L"user";
	WCHAR szPassword[] = L"pass";

	*phwnd = NULL;
	*pszUsername = (LPWSTR)CoTaskMemAlloc(sizeof(szUserName));
	lstrcpyW(*pszUsername, szUserName);
	*pszPassword = (LPWSTR)CoTaskMemAlloc(sizeof(szPassword));
	lstrcpyW(*pszPassword, szPassword);
	
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("Authenticate"));

	return S_OK;
}

AuthenticateはIAuthenticateのメソッドであり、認証が必要なURLへアクセスする場合に呼ばれます。 pszUsernameとpszPasswordに正しいユーザー名とパスワードを指定すればバインドは続行されますが、 正しくない場合はバインドが失敗してOnStopBindingが呼ばれます。 文字列を格納するバッファは、CoTaskMemAllocで確保している必要があります。 ダイアログが表示されるようにしたい場合は、phwndにウインドウハンドルを指定し、 pszUsernameとpszPasswordにNULLを指定します。 GetBindInfoでBINDF_NO_UIを返していない場合は、ダイアログが表示されるはずです。

URIの操作

IE7からのurlmon.dllには、URIを操作するための関数が追加されています。 これらの関数は、URIを文字列ではなく、IUriというインターフェースを指定します。

#include <windows.h>
#include <urlmon.h>

#pragma comment (lib, "urlmon.lib")

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	IUri    *pUri;
	BSTR    bstr;
	HRESULT hr;
	
	hr = CreateUri(L"http://eternalwindows.jp/winbase/base/base00.html", Uri_CREATE_ALLOW_RELATIVE, 0, &pUri);
	if (FAILED(hr))
		return 0;
	
	pUri->GetHost(&bstr);
	MessageBoxW(NULL, bstr, L"OK", MB_OK);

	pUri->Release();
	SysFreeString(bstr);

	return 0;
}

CreateUriを呼び出すことによって、URIを表すIUriを取得できます。 インターフェースからはURIの一部分を取得でき、 たとえばGetHostならばドメインの部分を取得できます。 IUriをベースにURLモニカを作成する場合は、CreateURLMonikerEx2を呼び出します。

ベースとなるURIを基に、新しいURIを作成したいことがあるかもしれません。 このような場合は、CoInternetCombineIUriを呼び出します。

#include <windows.h>
#include <urlmon.h>

#pragma comment (lib, "urlmon.lib")

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	IUri    *pUriBase, *pUriRelative, *pUriCombine;
	BSTR    bstr;
	HRESULT hr;
	
	hr = CreateUri(L"http://eternalwindows.jp/", Uri_CREATE_ALLOW_RELATIVE, 0, &pUriBase);
	if (FAILED(hr))
		return 0;
	
	hr = CreateUri(L"winbase/base/base00.html", Uri_CREATE_ALLOW_RELATIVE, 0, &pUriRelative);
	if (FAILED(hr)) {
		pUriBase->Release();
		return 0;
	}
	
	hr = CoInternetCombineIUri(pUriBase, pUriRelative, 0, &pUriCombine, 0);
	if (FAILED(hr)) {
		pUriRelative->Release();
		pUriBase->Release();
		return 0;
	}

	pUriCombine->GetDisplayUri(&bstr);
	MessageBoxW(NULL, bstr, L"OK", MB_OK);

	pUriCombine->Release();
	pUriRelative->Release();
	pUriBase->Release();
	SysFreeString(bstr);

	return 0;
}

CoInternetCombineIUriによって、第1引数と第2引数を連結したURIが第4引数に返ります。 GetDisplayUriの結果から、URIが確かに連結していることが分かります。



戻る