EternalWindows
URL Monikers / インターネットセッションの使用

前節では、Asynchronous Pluggable Protocolsに準拠したDLLを作成し、それをレジストリに登録しました。 この方法の魅力は、独自プロトコルを全てのアプリケーションに対応させられる点であり、 どのようなアプリケーションでもURL Monikersに独自プロトコルを指定すれば、 DLLをロードさせて独自のデータを返すことができました。 今回はこのようなグローバルな方法ではなく、 現在のアプリケーションで使用されるプロトコルを検出する方法を取り上げます。 この方法を理解していれば、そのアプリケーション内でのみ独自プロトコルをサポートしたり、 既存プロトコルの処理を変更したりできるようになります。

現在のアプリケーションで使用されるプロトコルを検出するためには、 一時的なインターネットセッションというものを作成し、 そこにプロトコルを検出するオブジェクトを登録しなければなりません。 インターネットセッションを表すIInternetSessionは、CoInternetGetSessionで取得できます。

STDAPI CoInternetGetSession(
  DWORD dwSessionMode,
  IInternetSession **ppIInternetSession,
  DWORD dwReserved
);

dwSessionModeは、予約されているため0を指定します。 ppIInternetSessionは、IInternetSessionを受け取る変数のアドレスを指定します。 dwReservedは、予約されているため0を指定します。

IInternetSessionを取得したら、プロトコルを検出するオブジェクトをRegisterNameSpaceで登録できます。

HRESULT IInternetSession::RegisterNameSpace(
  IClassFactory *pCF,
  REFCLSID rclsid,
  LPCWSTR pwzProtocol,
  ULONG cPatterns,
  const LPCWSTR *ppwzPatterns,
  DWORD dwReserved
);

pCFは、IClassFactoryを実装したクラスオブジェクトを指定します。 このオブジェクトのIClassFactory::CreateInstanceでは、IInternetProtocolを実装したオブジェクトを作成します。 rclsidは、IInternetProtocolを実装したオブジェクトを識別するCLSIDを指定します。 実際のところ、このCLSIDが意味をもつことはないと思われますが、 CLSID_NULLは指定するべきではないとされています。 pwzProtocolは、検出したいプロトコルを表す文字列を指定します。 たとば、httpの使用を検出したい場合はL"http:"を指定します。 cPatternsは、使用されないため0を指定します。 ppwzPatternsは、使用されないためNULLを指定します。 dwReservedは、予約されているため0を指定します。

次に、IInternetSession::RegisterNameSpaceを呼び出す例を示します。

pClassFactory = new CClassFactory;
CoInternetGetSession(0, &pInternetSession, 0);
pInternetSession->RegisterNameSpace(pClassFactory, CLSID_InternetProtocol, L"http", 0, NULL, 0);

まず、CoInternetGetSessionでIInternetSessionを取得します。 RegisterNameSpaceを呼び出すにはクラスオブジェクトが必要になるため、 CClassFactoryという型のオブジェクトを事前に作成しています。 第2引数のCLSID_XXXはGuidGen.exeなどで取得した値を指定しますが、 これがどこか別の場所で使用されるようなことはないと思われます。 第3引数は検出したいプロトコルを示す文字列であり、 http:を指定していることから、httpプロトコルの使用を検出したいことを意味します。 検出が不要になった場合は、IInternetSession::UnregisterNameSpaceを呼び出すようにします。

今回のプログラムは、URL Monikersでhttpプロトコルが使用される場合にそれを検出し、HTTP通信を行います。 言い換えれば、httpプロトコルをサポートする既定のオブジェクトと同じような動作を行います。 まず、ヘッダーファイル(protocol.h)を示します。

class CInternetProtocol : public IInternetProtocol
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP Start(LPCWSTR szUrl, IInternetProtocolSink *pOIProtSink, IInternetBindInfo *pOIBindInfo, DWORD grfPI, HANDLE_PTR dwReserved);
	STDMETHODIMP Continue(PROTOCOLDATA *pProtocolData);
	STDMETHODIMP Abort(HRESULT hrReason, DWORD dwOptions);
	STDMETHODIMP Terminate(DWORD dwOptions);
	STDMETHODIMP Suspend();
	STDMETHODIMP Resume();

	STDMETHODIMP Read(void *pv, ULONG cb, ULONG *pcbRead);
	STDMETHODIMP Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition);
	STDMETHODIMP LockRequest(DWORD dwOptions);
	STDMETHODIMP UnlockRequest();
	
	BOOL ReportProtocolInfo(LPCWSTR lpszUrl);
	HRESULT GetProtocolData(LPCWSTR lpszUrl);
	BOOL GetMimeTypeFromHeader(LPWSTR lpHeader, DWORD dwHeaderSize, LPWSTR lpszMimeType);
	LPWSTR GetUrl();
	void ResetData();
	static DWORD WINAPI ThreadProc(LPVOID lpParameter);

	CInternetProtocol();
	~CInternetProtocol();
	
private:
	LONG                  m_cRef;
	DWORD                 m_dwBindFlags;
	IInternetProtocolSink *m_pProtocolSink;
	DWORD                 m_dwTotalSize;
	LPBYTE                m_lpData;
	DWORD                 m_dwReadSize;
	BOOL                  m_bCacheAvailable;
	BOOL                  m_bComplete;
	LPWSTR                m_lpszUrl;
};

class CClassFactory : public IClassFactory
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
	STDMETHODIMP LockServer(BOOL fLock);
};

const CLSID CLSID_InternetProtocol = {0xd897eccd, 0xd0d1, 0x4775, {0xa6, 0xa9, 0xb6, 0x0, 0x13, 0x7f, 0xb9, 0xa9}};

続いて、ソースファイル(GUI関連)を示します。

#include <windows.h>
#include <urlmon.h>
#include "protocol.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;
	static IClassFactory       *pClassFactory = NULL;
	static IInternetSession    *pInternetSession = 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;

		pClassFactory = new CClassFactory;
		CoInternetGetSession(0, &pInternetSession, 0);
		pInternetSession->RegisterNameSpace(pClassFactory, CLSID_InternetProtocol, L"http", 0, NULL, 0);

		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();
		if (pInternetSession != NULL) {
			pInternetSession->UnregisterNameSpace(pClassFactory, L"http:");
			pInternetSession->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;
}

最後に、ソースファイル(IInternetProtocol関連)を示します。

#include <windows.h>
#include <wininet.h>
#include "protocol.h"

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


// CInternetProtocol


CInternetProtocol::CInternetProtocol()
{
	m_cRef = 1;
	m_pProtocolSink = NULL;
	m_lpData = NULL;
	m_lpszUrl = NULL;
	ResetData();
}

CInternetProtocol::~CInternetProtocol()
{
}

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

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IInternetProtocolRoot) || IsEqualIID(riid, IID_IInternetProtocol))
		*ppvObject = static_cast<IInternetProtocol *>(this);
	else
		return E_NOINTERFACE;

	AddRef();

	return S_OK;
}

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

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

	return m_cRef;
}

STDMETHODIMP CInternetProtocol::Start(LPCWSTR szUrl, IInternetProtocolSink *pOIProtSink, IInternetBindInfo *pOIBindInfo, DWORD grfPI, HANDLE_PTR dwReserved)
{
	m_pProtocolSink = pOIProtSink;
	m_pProtocolSink->AddRef();

	m_lpszUrl = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (lstrlenW(szUrl) + 1) * sizeof(WCHAR));
	lstrcpyW(m_lpszUrl, szUrl);
	
	pOIBindInfo->GetBindInfo(&m_dwBindFlags, NULL);

	if (m_dwBindFlags & BINDF_ASYNCHRONOUS)
		CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, (LPVOID)this, 0, NULL);
	else
		ReportProtocolInfo(szUrl);

	return S_OK;
}

STDMETHODIMP CInternetProtocol::Continue(PROTOCOLDATA *pProtocolData)
{
	return S_OK;
}

STDMETHODIMP CInternetProtocol::Abort(HRESULT hrReason, DWORD dwOptions)
{
	return S_OK;
}

STDMETHODIMP CInternetProtocol::Terminate(DWORD dwOptions)
{
	ResetData();

	return S_OK;
}

STDMETHODIMP CInternetProtocol::Suspend()
{
	return E_NOTIMPL;
}

STDMETHODIMP CInternetProtocol::Resume()
{
	return E_NOTIMPL;
}

STDMETHODIMP CInternetProtocol::Read(void *pv, ULONG cb, ULONG *pcbRead)
{
	HRESULT hr;

	if (m_bCacheAvailable) {
		*pcbRead = m_dwTotalSize;
		return S_FALSE;
	}

	if (m_bComplete)
		return S_FALSE;

	if (m_dwTotalSize - m_dwReadSize < cb) {
		cb = m_dwTotalSize - m_dwReadSize;
		hr = S_FALSE;
		m_bComplete = TRUE;
	}
	else
		hr = S_OK;

	CopyMemory(pv, m_lpData + m_dwReadSize, cb);
	*pcbRead = cb;
	m_dwReadSize += cb;

	return hr;
}

STDMETHODIMP CInternetProtocol::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition)
{
	return E_NOTIMPL;
}

STDMETHODIMP CInternetProtocol::LockRequest(DWORD dwOptions)
{
	return S_OK;
}

STDMETHODIMP CInternetProtocol::UnlockRequest()
{
	return S_OK;
}

BOOL CInternetProtocol::ReportProtocolInfo(LPCWSTR lpszUrl)
{
	DWORD   dwNoCacheFlag = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
	HRESULT hrReport;
	WCHAR   szMimeType[256];
	BOOL    bAlreadyMimeTypeReport = FALSE;
	
	if ((m_dwBindFlags & dwNoCacheFlag) != dwNoCacheFlag) {
		DWORD                       dwSize;
		LPINTERNET_CACHE_ENTRY_INFO lpInfo;
		
		m_bCacheAvailable = TRUE;

		GetUrlCacheEntryInfo(lpszUrl, NULL, &dwSize);
		if (GetLastError() == ERROR_FILE_NOT_FOUND) {
			hrReport = GetProtocolData(lpszUrl);
			if (hrReport != S_OK) {
				m_pProtocolSink->ReportResult(hrReport, 0, NULL);
				return FALSE;
			}
			bAlreadyMimeTypeReport = TRUE;
			GetUrlCacheEntryInfo(lpszUrl, NULL, &dwSize);
		}

		if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
			lpInfo = (LPINTERNET_CACHE_ENTRY_INFO)HeapAlloc(GetProcessHeap(), 0, dwSize);
			GetUrlCacheEntryInfo(lpszUrl, lpInfo, &dwSize);
		}
		else {
			m_pProtocolSink->ReportResult(E_FAIL, 0, NULL);
			return FALSE;
		}

		if (!bAlreadyMimeTypeReport && GetMimeTypeFromHeader(lpInfo->lpHeaderInfo, lpInfo->dwHeaderInfoSize, szMimeType))
			m_pProtocolSink->ReportProgress(BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE, szMimeType);

		m_dwTotalSize = lpInfo->dwSizeLow;
		m_pProtocolSink->ReportProgress(BINDSTATUS_CACHEFILENAMEAVAILABLE, lpInfo->lpszLocalFileName);

		HeapFree(GetProcessHeap(), 0, lpInfo);
		hrReport = S_OK;
	}
	else
		hrReport = GetProtocolData(lpszUrl);

	if (hrReport == S_OK) {
		m_pProtocolSink->ReportData(BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION | BSCF_DATAFULLYAVAILABLE, m_dwTotalSize, m_dwTotalSize);
		m_pProtocolSink->ReportResult(S_OK, 0, NULL);
	}
	else
		m_pProtocolSink->ReportResult(hrReport, 0, NULL);

	return TRUE;
}

HRESULT CInternetProtocol::GetProtocolData(LPCWSTR lpszUrl)
{
	HINTERNET       hSession, hConnect, hRequest;
	URL_COMPONENTSW urlComponents;
	WCHAR           szHostName[256], szUrlPath[256];
	DWORD           dwReadByte;
	LPBYTE          lpPrev = NULL;
	WCHAR           szContentType[256];
	DWORD           dwLength;
	BYTE            buf[1];
	
	hSession = InternetOpenW(L"Asynchronous Pluggable Protocol", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
	if (hSession == NULL)
		return E_FAIL;

	ZeroMemory(&urlComponents, sizeof(URL_COMPONENTSW));
	urlComponents.dwStructSize     = sizeof(URL_COMPONENTSW);
	urlComponents.lpszHostName     = szHostName;
	urlComponents.lpszUrlPath      = szUrlPath;
	urlComponents.dwHostNameLength = sizeof(szHostName) / sizeof(WCHAR);
	urlComponents.dwUrlPathLength  = sizeof(szUrlPath) / sizeof(WCHAR);

	if (!InternetCrackUrlW(lpszUrl, lstrlenW(lpszUrl), 0, &urlComponents)) {
		InternetCloseHandle(hSession);
		return INET_E_INVALID_URL;
	}
	
	m_pProtocolSink->ReportProgress(BINDSTATUS_FINDINGRESOURCE, lpszUrl);
	m_pProtocolSink->ReportProgress(BINDSTATUS_CONNECTING, lpszUrl);
	
	hConnect = InternetConnectW(hSession, szHostName, urlComponents.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
	if (hConnect == NULL) {
		InternetCloseHandle(hSession);
		return INET_E_CANNOT_CONNECT;
	}

	hRequest = HttpOpenRequestW(hConnect, L"GET", szUrlPath, NULL, NULL, NULL, 0, 0);
	if (hRequest == NULL) {
		InternetCloseHandle(hConnect);
		InternetCloseHandle(hSession);
		return INET_E_DATA_NOT_AVAILABLE;
	}
	
	m_pProtocolSink->ReportProgress(BINDSTATUS_SENDINGREQUEST, lpszUrl);

	if (!HttpSendRequest(hRequest, NULL, 0, NULL, 0)) {
		InternetCloseHandle(hRequest);
		InternetCloseHandle(hConnect);
		InternetCloseHandle(hSession);
		return INET_E_DATA_NOT_AVAILABLE;
	}
	
	dwLength = sizeof(szContentType);
	HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_TYPE, szContentType, &dwLength, NULL);
	m_pProtocolSink->ReportProgress(BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE, szContentType);

	dwLength = sizeof(DWORD);
	HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &m_dwTotalSize, &dwLength, NULL);
	m_lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, m_dwTotalSize);
	InternetReadFile(hRequest, m_lpData, m_dwTotalSize, &dwReadByte);
	InternetReadFile(hRequest, buf, 1, &dwReadByte);

	InternetCloseHandle(hRequest);
	InternetCloseHandle(hConnect);
	InternetCloseHandle(hSession);

	return S_OK;
}

BOOL CInternetProtocol::GetMimeTypeFromHeader(LPWSTR lpHeader, DWORD dwHeaderSize, LPWSTR lpszMimeType)
{
	DWORD  i, j;
	WCHAR  szType[] = L"Content-Type: ";
	DWORD  dwTypeLen = lstrlenW(szType);
	LPWSTR lpsz = lpHeader;
	DWORD  dw = dwHeaderSize - dwTypeLen;
	
	for (i = 0; i < dw; i++) {
		for (j = 0; j < dwTypeLen; j++) {
			if (lpsz[i + j] != szType[j])
				break;
		}
		if (j == dwTypeLen) {
			lpsz = &lpsz[i + dwTypeLen];
			lstrcpynW(lpszMimeType, lpsz, 10);
			return TRUE;
		}
	}
	
	return FALSE;
}

LPWSTR CInternetProtocol::GetUrl()
{
	return m_lpszUrl;
}

void CInternetProtocol::ResetData()
{
	m_dwBindFlags = 0;
	m_dwTotalSize = 0;
	m_dwReadSize = 0;
	m_bCacheAvailable = FALSE;
	m_bComplete = FALSE;
	
	if (m_pProtocolSink != NULL) {
		m_pProtocolSink->Release();
		m_pProtocolSink = NULL;
	}

	if (m_lpData != NULL) {
		HeapFree(GetProcessHeap(), 0, m_lpData);
		m_lpData = NULL;
	}
	
	if (m_lpszUrl != NULL) {
		HeapFree(GetProcessHeap(), 0, m_lpszUrl);
		m_lpszUrl = NULL;
	}
}

DWORD WINAPI CInternetProtocol::ThreadProc(LPVOID lpParameter)
{
	CInternetProtocol *p = (CInternetProtocol *)lpParameter;

	p->ReportProtocolInfo(p->GetUrl());
	
	return 0;
}


// CClassFactory


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

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory))
		*ppvObject = static_cast<IClassFactory *>(this);
	else
		return E_NOINTERFACE;

	AddRef();
	
	return S_OK;
}

STDMETHODIMP_(ULONG) CClassFactory::AddRef()
{
	return 2;
}

STDMETHODIMP_(ULONG) CClassFactory::Release()
{
	return 1;
}

STDMETHODIMP CClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject)
{
	CInternetProtocol *p;
	HRESULT           hr;
	
	*ppvObject = NULL;

	if (pUnkOuter != NULL)
		return CLASS_E_NOAGGREGATION;

	p = new CInternetProtocol();
	if (p == NULL)
		return E_OUTOFMEMORY;

	hr = p->QueryInterface(riid, ppvObject);
	p->Release();

	return hr;
}

STDMETHODIMP CClassFactory::LockServer(BOOL fLock)
{
	return S_OK;
}

インターネットセッションを使用する場合においても、DLLを作成する場合と共通する処理は多くあります。 前節同様、IInternetProtocolを実装したクラスを定義することになりますし、 最初に呼ばれるメソッドはIInternetProtocol::Startです。

STDMETHODIMP CInternetProtocol::Start(LPCWSTR szUrl, IInternetProtocolSink *pOIProtSink, IInternetBindInfo *pOIBindInfo, DWORD grfPI, HANDLE_PTR dwReserved)
{
	m_pProtocolSink = pOIProtSink;
	m_pProtocolSink->AddRef();

	m_lpszUrl = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (lstrlenW(szUrl) + 1) * sizeof(WCHAR));
	lstrcpyW(m_lpszUrl, szUrl);
	
	pOIBindInfo->GetBindInfo(&m_dwBindFlags, NULL);

	if (m_dwBindFlags & BINDF_ASYNCHRONOUS)
		CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, (LPVOID)this, 0, NULL);
	else
		ReportProtocolInfo(szUrl);

	return S_OK;
}

IInternetProtocolSinkはプロトコルの処理状況を伝えるために必要であるため、メンバとして保存しています。 szUrlはプロトコルを含んだURLが格納されていますが、これも後で必要になるためメンバとして保存しています。 IInternetBindInfo::GetBindInfoを呼び出しているのは、 URL Monikersを使用したアプリケーションがIBindStatusCallback::GetBindInfoで返したバインドフラグを取得するためです。 この値にBINDF_ASYNCHRONOUSが含まれる場合は、プロトコルに関する処理を非同期に行わなければならないため、 CreateThreadで作成したスレッドでReportProtocolInfoを呼び出すようにしています。 ReportProtocolInfoという自作メソッドの目的は、プロトコルの処理状況を伝えることです。

BOOL CInternetProtocol::ReportProtocolInfo(LPCWSTR lpszUrl)
{
	DWORD   dwNoCacheFlag = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
	HRESULT hrReport;
	WCHAR   szMimeType[256];
	BOOL    bAlreadyMimeTypeReport = FALSE;
	
	if ((m_dwBindFlags & dwNoCacheFlag) != dwNoCacheFlag) {
		DWORD                       dwSize;
		LPINTERNET_CACHE_ENTRY_INFO lpInfo;
		
		m_bCacheAvailable = TRUE;

		GetUrlCacheEntryInfo(lpszUrl, NULL, &dwSize);
		if (GetLastError() == ERROR_FILE_NOT_FOUND) {
			hrReport = GetProtocolData(lpszUrl);
			if (hrReport != S_OK) {
				m_pProtocolSink->ReportResult(hrReport, 0, NULL);
				return FALSE;
			}
			bAlreadyMimeTypeReport = TRUE;
			GetUrlCacheEntryInfo(lpszUrl, NULL, &dwSize);
		}

		if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
			lpInfo = (LPINTERNET_CACHE_ENTRY_INFO)HeapAlloc(GetProcessHeap(), 0, dwSize);
			GetUrlCacheEntryInfo(lpszUrl, lpInfo, &dwSize);
		}
		else {
			m_pProtocolSink->ReportResult(E_FAIL, 0, NULL);
			return FALSE;
		}

		if (!bAlreadyMimeTypeReport && GetMimeTypeFromHeader(lpInfo->lpHeaderInfo, lpInfo->dwHeaderInfoSize, szMimeType))
			m_pProtocolSink->ReportProgress(BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE, szMimeType);

		m_dwTotalSize = lpInfo->dwSizeLow;
		m_pProtocolSink->ReportProgress(BINDSTATUS_CACHEFILENAMEAVAILABLE, lpInfo->lpszLocalFileName);

		HeapFree(GetProcessHeap(), 0, lpInfo);
		hrReport = S_OK;
	}
	else
		hrReport = GetProtocolData(lpszUrl);

	if (hrReport == S_OK) {
		m_pProtocolSink->ReportData(BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION | BSCF_DATAFULLYAVAILABLE, m_dwTotalSize, m_dwTotalSize);
		m_pProtocolSink->ReportResult(S_OK, 0, NULL);
	}
	else
		m_pProtocolSink->ReportResult(hrReport, 0, NULL);

	return TRUE;
}

dwNoCacheFlagに指定されている値全てがm_dwBindFlagsに含まれていない場合は、 キャッシュを使用するべきであることを意味します。 今回は処理するプロトコルはhttpであり、このプロトコルのURLのキャッシュはGetUrlCacheEntryInfoで取得できます。 最初の呼び出しではバッファを用意できないため第2引数にNULLを指定し、 2回目の呼び出しで実際にデータを取得します。 ただし、ERROR_FILE_NOT_FOUNDが返った場合はURLに関するキャッシュが存在しないため、 この場合はGetProtocolDataという自作メソッドで実際にURLへアクセスします。 これが成功すればキャッシュが存在するはずですから、GetUrlCacheEntryInfoを呼び出します。 GetMimeTypeFromHeaderという自作メソッドは、キャッシュのヘッダー情報からMIMEタイプを取得します。 MIMEタイプを通知するには、ReportProgressにBINDSTATUS_VERIFIEDMIMETYPEAVAILABLEを指定し、 このときにはURL Monikersを使用したアプリケーションのIBindStatusCallback::OnProgressが呼ばれます。 キャッシュファイルの名前を指定する場合は、BINDSTATUS_CACHEFILENAMEAVAILABLEを指定します。 MIMEタイプの通知は必須ではありませんが、ファイル名の通知は必須です。

キャッシュを使用しない場合は、GetProtocolDataでデータを取得します。 今回はhttpプロトコルを処理するため、WinINetを使用しています。

HRESULT CInternetProtocol::GetProtocolData(LPCWSTR lpszUrl)
{
	HINTERNET       hSession, hConnect, hRequest;
	URL_COMPONENTSW urlComponents;
	WCHAR           szHostName[256], szUrlPath[256];
	DWORD           dwReadByte;
	LPBYTE          lpPrev = NULL;
	WCHAR           szContentType[256];
	DWORD           dwLength;
	BYTE            buf[1];
	
	hSession = InternetOpenW(L"Asynchronous Pluggable Protocol", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
	if (hSession == NULL)
		return E_FAIL;

	ZeroMemory(&urlComponents, sizeof(URL_COMPONENTSW));
	urlComponents.dwStructSize     = sizeof(URL_COMPONENTSW);
	urlComponents.lpszHostName     = szHostName;
	urlComponents.lpszUrlPath      = szUrlPath;
	urlComponents.dwHostNameLength = sizeof(szHostName) / sizeof(WCHAR);
	urlComponents.dwUrlPathLength  = sizeof(szUrlPath) / sizeof(WCHAR);

	if (!InternetCrackUrlW(lpszUrl, lstrlenW(lpszUrl), 0, &urlComponents)) {
		InternetCloseHandle(hSession);
		return INET_E_INVALID_URL;
	}
	
	m_pProtocolSink->ReportProgress(BINDSTATUS_FINDINGRESOURCE, lpszUrl);
	m_pProtocolSink->ReportProgress(BINDSTATUS_CONNECTING, lpszUrl);
	
	hConnect = InternetConnectW(hSession, szHostName, urlComponents.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
	if (hConnect == NULL) {
		InternetCloseHandle(hSession);
		return INET_E_CANNOT_CONNECT;
	}

	hRequest = HttpOpenRequestW(hConnect, L"GET", szUrlPath, NULL, NULL, NULL, 0, 0);
	if (hRequest == NULL) {
		InternetCloseHandle(hConnect);
		InternetCloseHandle(hSession);
		return INET_E_DATA_NOT_AVAILABLE;
	}
	
	m_pProtocolSink->ReportProgress(BINDSTATUS_SENDINGREQUEST, lpszUrl);

	if (!HttpSendRequest(hRequest, NULL, 0, NULL, 0)) {
		InternetCloseHandle(hRequest);
		InternetCloseHandle(hConnect);
		InternetCloseHandle(hSession);
		return INET_E_DATA_NOT_AVAILABLE;
	}
	
	dwLength = sizeof(szContentType);
	HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_TYPE, szContentType, &dwLength, NULL);
	m_pProtocolSink->ReportProgress(BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE, szContentType);

	dwLength = sizeof(DWORD);
	HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &m_dwTotalSize, &dwLength, NULL);
	m_lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, m_dwTotalSize);
	InternetReadFile(hRequest, m_lpData, m_dwTotalSize, &dwReadByte);
	InternetReadFile(hRequest, buf, 1, &dwReadByte);

	InternetCloseHandle(hRequest);
	InternetCloseHandle(hConnect);
	InternetCloseHandle(hSession);

	return S_OK;
}

InternetCrackUrlでURLからサーバー名を抜き出したら、InternetConnectでサーバーへ接続できますが、 その前にBINDSTATUS_FINDINGRESOURCEとBINDSTATUS_CONNECTINGを通知しています。 これらを指定すれば、IBindStatusCallback::OnProgressが呼ばれるため、 現在URLを検索中であることや接続中であることが分かるようになります。 これと同じ要領で、HttpSendRequestを呼び出す前でもリクエストの送信を示すBINDSTATUS_SENDINGREQUESTを通知しています。 HttpQueryInfoにHTTP_QUERY_CONTENT_TYPEを指定すればMIMEタイプを取得できるため、 これもBINDSTATUS_VERIFIEDMIMETYPEAVAILABLEとして通知しています。 HttpQueryInfoにHTTP_QUERY_CONTENT_LENGTHを指定すればデータのサイズを取得でき、 このサイズだけバッファを確保してInternetReadFileを呼び出せば、バッファにデータが格納されます。 2回目のInternetReadFileは、データがキャッシュに保存されるために呼び出しています。 この呼び出しでは既にデータを取得しているため、dwReadByteが0になり、 この場合はデータがキャッシュに保存されます。

今回は使用していませんが、IInternetProtocolSinkにはSwitchというメソッドも用意されています。 これはワーカースレッドがURL Monikersを呼び出したスレッドを動作させたい場合に呼び出すことができ、 引数を通じてデータを渡すこともできます。 URL Monikersを呼び出したスレッドは、内部でIInternetProtocol::Startを呼び出しますが、 このメソッドでS_OKを返した場合は、処理を一旦待機します。 このとき、ワーカースレッドがIInternetProtocolSink::Switchを呼び出せば、 待機しているスレッドは起き上がりIInternetProtocol::Continueを実行します。 つまり、StartとContinueを実行するスレッドは同一であり、 仮にStart内でSwitchを呼び出しても、Continueが制御を返すまで制御を返しません。 しかし、ワーカースレッドでSwitchを呼び出せば、Switchは直ちに制御を返します。


戻る