EternalWindows
URL Monikers / ファイルとしてダウンロード

前節ではURLモニカを使用してIStreamを取得し、ストリームに格納されているデータをファイルとして保存することで、ダウンロードを再現しました。 この方法の大変なところは、データを処理するタイミングを受け取るためにIBindStatusCallbackが必要な点であり、 合せてストリームの中身を明示的に保存するという煩わしさも生じていました。 可能ならば、関数1つでダウンロードできるような直観的な操作をしたいものですが、 幸いにもそのような関数がURL Monikersは用意されています。 次に示すURLDownloadToFileはURLモニカの使用をラッピングしているため、 この関数を呼び出すだけでファイルのダウンロードが可能です。

URLDownloadToFile(NULL, szUrl, szSaveFile, 0, NULL);

第2引数にダウンロードしたいURLを指定し、第3引数にファイルとして保存するパスを指定します。 第1引数はNULLでよく、第4引数は予約されているため0を指定します。 第5引数はIBindStatusCallbackを指定できますが、NULLで問題ありません。 NULLの場合は関数が同期的に動作するため、 制御を返した時点でダウンロードは完了しているはずです。

特定のURLの中身をファイルとして保存するのではなく、IStreamで受け取りたい場合があるかもしれません。 このような場合は、URLOpenBlockingStreamを呼び出します。

IStream *pStream;

URLOpenBlockingStream(NULL, szUrl, &pStream, 0, NULL);

第2引数にダウンロードしたいURLを指定し、第3引数にIStreamを受け取る変数のアドレスを指定します。 それ以外の引数については、URLDownloadToFileと同様です。

今回のプログラムは、前節と同じくファイルをダウンロードしますが、 URLモニカによる明示的なバインドを行わず、バインドをラッピングしたURLDownloadToFileを呼び出しています。

#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();

private:
	LONG m_cRef;
};

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";
			
			URLDownloadToFile(NULL, szUrl, szSaveFile, 0, pBindStatusCallback);
		}
		
		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;
}

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;
}

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 (SUCCEEDED(hresult))
		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)
{
	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を実装していますが、 明示的にバインドを行うわけではないので、IMonikerやIBindCtxのようなメンバを持っていません。 URLDownloadToFileはデータを自分でダウンロードするため、データが使用可能になったことを示すIBindStatusCallbackが呼ばれることはありません。

WinINetの参照

URL MonikersはWinINetの上位層という位置づけになっているため、 内部的にはWinINetのAPIを呼び出しているものと考えられます。 アプリケーションがWinINetでなければ取得できない情報が必要になった場合は、 IBindStatusCallback::OnStartBindingに渡されるIBindingをメンバとして保存します。

STDMETHODIMP CBindStatusCallback::OnStartBinding(DWORD dwReserved, IBinding *pib)
{
	m_pBinding = pib;
	m_pBinding->AddRef();

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

	return S_OK;
}

このIBindingからは、WinINetのInternetQueryOptionを使用するためのIWinInetInfoと、 WinINetのHttpQueryInfoを使用するためのIWinInetHttpInfoを取得できます。 任意のメソッドでこうしたインターフェースを使用できたら便利であるため、メンバとして保存しています。

IBindStatusCallback::OnStopBindingはデータの受信が完了した場合に呼ばれますが、 この際にHTTPプロトコルのステータスコードを調べる例を示します。

STDMETHODIMP CBindStatusCallback::OnStopBinding(HRESULT hresult, LPCWSTR szError)
{
	TCHAR            szBuf[256];
	IWinInetHttpInfo *pHttpInfo;
	DWORD            dwStatusCode, dwSize, dwFlags;

	m_pBinding->QueryInterface(IID_PPV_ARGS(&pHttpInfo));

	dwStatusCode = 0;
	dwSize = sizeof(DWORD);
	dwFlags = 0;
	pHttpInfo->QueryInfo(HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &dwStatusCode, &dwSize, &dwFlags, NULL);

	wsprintf(szBuf, TEXT("OnStopBinding statuscode %d"), dwStatusCode);
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);

	pHttpInfo->Release();
	m_pBinding->Release();

	return S_OK;
}

保存しておいたIBindingからIWinInetHttpInfoを照会し、QueryInfoを呼び出しています。 このメソッドの引数については、HttpQueryInfoと同じ意味を持ちます。 スタータスコードを取得する場合はHTTP_QUERY_STATUS_CODEを指定し、 さらに数値として受け取りたいのであればHTTP_QUERY_FLAG_NUMBERも指定します。 返された値がHTTP_STATUS_OK(200)ならばデータの受信は成功したことを意味し、 HTTP_STATUS_NOT_FOUND(404)ならばファイルが見つからなかったことを意味します。



戻る