EternalWindows
URL Monikers / 独自プロトコルの登録

リソースが存在する位置を表す文字列として、URLは日常的に使用されています。 たとえば、http://eternalwindows.jp/ならば、httpプロトコルでeternalwindows.jpからデータを取得するという意味であり、 文字列からどのようなことを行うかを直観的に理解できます。 こうしたURLの利点に注目した場合、開発者が考えた独自プロトコルをURLに含められるようになれば、 データを取得する方法もより多様化するのではないでしょうか。 たとえば、"sql://my-database/select * from my-table"のような文字列を指定できるようになれば、 URLを通じてデータベースの中身を取得できて便利といえるでしょう。 Asynchronous Pluggable Protocolsの機能を使用すれば、 このような独自のプロトコルをサポートするCOMオブジェクトを作成できるようになります。

独自URLを用意したアプリケーションは、URL MonikersにそのURLを指定します。 このとき、URL Monikersは内部でURLからプロトコルの部分を取得し、 それを実装するAsynchronous Pluggable Protocolsのオブジェクトに処理を渡します。 次に、オブジェクトはプロトコル独自の処理を行い、用意したデータをURL Monikersに返します。 そして最後は、URL Monikersがアプリケーションにデータを返し、 アプリケーションはプロトコル独自のデータを受け取れるようになります。 こうした仕組みから分かるように、URL MonikersはネットワークへアクセスするAPIというよりも プロトコルからどのオブジェクトへ処理を渡すかを決める中継としての傾向の方が強いといえるでしょう。 登録されているオブジェクトは、次のレジストリキーから確認できます。

HKEY_CLASSES_ROOT\PROTOCOLS

たとえば、IEでabout:Blankを入力すると空白のページが表示されますが、 これはIEにそのための処理が含まれているのではなく、 aboutプロトコルを実装するオブジェクトに処理が含まれています。 IEはネットワークアクセスにURL Monikersを使用するため、 URL Monikersがabout:のハンドラへの中継になったわけです。 CLSIDに記述されるのはオブジェクト(COMのインプロセスサーバー)のCLSIDであり、 実際にHKEY_CLASSES_ROOT\CLSID以下から確認できるでしょう。 ちなみに、httpやfileなどのプロトコルをサポートするオブジェクトはurlmon.dllです。 urlmon.dllはURL MonikersとしてのAPIを公開する一方で、 ハンドラとしての役割も担っているわけです。 httpやfileなどをサポートするオブジェクトは、内部でWinINetを使用しています。

Asynchronous Pluggable Protocolsとしての役割を果たすオブジェクトは、IInternetProtocolを実装しなければなりません。 アプリケーションがURL Monikersを呼び出すと、その内部でIInternetProtocolのメソッドが呼ばれるため、 そこでオブジェクトは独自のプロトコルのデータを返すことができます。 今回は、IInternetProtocolを継承したCInternetProtocolというクラスを定義しています。

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

	CInternetProtocol();
	~CInternetProtocol();

	void ResetData();
	
private:
	LONG   m_cRef;
	DWORD  m_dwTotalSize;
	DWORD  m_dwReadSize;
	LPBYTE m_lpData;
	BOOL   m_bCacheAvailable;
	BOOL   m_bComplete;
};

IInternetProtocolはIInternetProtocolRootを継承しているため、IInternetProtocolRootのメソッドも実装することになります。 具体的には、StartからResumeまでがIInternetProtocolRootのメソッドであり、 ReadからUnlockRequestまでがIInternetProtocolのメソッドです。 メソッドが呼ばれる順番は一般的に、Start、Read、LockRequest、Terminate、UnlockRequestになります。 CInternetProtocolを作成するIClassFactory::CreateInstanceが複数回呼ばれる関係上、CInternetProtocolも複数個作成されることになるため、 データはグローバルではなくメンバとして定義するようにします。

IInternetProtocolのメソッドで重要となるのは、StartとReadです。 Startでは特定のプロトコルを含んだURLが渡されるため、 そのプロトコルに関連するデータを用意します。 そして、Readでは用意しておいたデータを返します。 そうすると、URL MonikersのIBindStatusCallback::OnDataAvailableが呼ばれ、 アプリケーションはReadで返されたデータを取得できます。 Startの実装は次のようになっています。

STDMETHODIMP CInternetProtocol::Start(LPCWSTR szUrl, IInternetProtocolSink *pOIProtSink, IInternetBindInfo *pOIBindInfo, DWORD grfPI, HANDLE_PTR dwReserved)
{
	DWORD dwBindFlags;
	DWORD dwNoCacheFlag = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;

	pOIBindInfo->GetBindInfo(&dwBindFlags, 0);
	if ((dwBindFlags & dwNoCacheFlag) != dwNoCacheFlag) {
		HANDLE hFile;
		WCHAR  szFilePath[] = L"filepath";
		
		hFile = CreateFileW(szFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		if (hFile == INVALID_HANDLE_VALUE) {
			pOIProtSink->ReportResult(E_FAIL, 0, NULL);
			return S_OK;
		}
		m_dwTotalSize = GetFileSize(hFile, NULL);
		CloseHandle(hFile);

		pOIProtSink->ReportProgress(BINDSTATUS_CACHEFILENAMEAVAILABLE, szFilePath);
		m_lpData = 0;
		m_dwTotalSize = m_dwTotalSize;
		m_bCacheAvailable = TRUE;
	}
	else {
		char szData[] = "<html><body><b>data</b></body></html>";
		m_dwTotalSize = sizeof(szData) - 1;
		m_lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, m_dwTotalSize);
		CopyMemory(m_lpData, szData, m_dwTotalSize);
		m_bCacheAvailable = FALSE;
	}

	pOIProtSink->ReportProgress(BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE, L"text/html");
	
	pOIProtSink->ReportData(BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION | BSCF_DATAFULLYAVAILABLE, m_dwTotalSize, m_dwTotalSize);
	pOIProtSink->ReportResult(S_OK, 0, NULL);

	return S_OK;
}

オブジェクトは、IInternetProtocolSink::ReportDataとIInternetProtocolSink::ReportResultを必ず呼び出すようにします。 ReportDataを呼び出せばデータが利用可能になったことを通知でき、内部でIInternetProtocol::Readが呼ばれます。 アプリケーションはReportDataを呼び出す前にプロトコルのデータを用意しなくてはなりませんが、 バインドフラグにキャッシュの使用を無効にする値(dwNoCacheFlag)が含まれていない場合は、少し話が変わります。 この場合はキャッシュを使用するということで、IInternetProtocolSink::ReportProgressにBINDSTATUS_CACHEFILENAMEAVAILABLEを指定し、 第2引数にキャッシュファイルの名前を指定するようにします。 キャッシュを使用しない場合は、アプリケーションに返したいデータを格納します。 ここではデータをハードコードしていますが、本当にデータベースに対応するのであれば、 URLを解析してデータベースからデータを取得し、そのデータと<table>タグを含めることになるでしょう。 ReportProgressにBINDSTATUS_VERIFIEDMIMETYPEAVAILABLEを指定しているのは、MIMEタイプを通知するためです。 これは必須の処理ではありませんが、行っていた方がよいとされています。 ReportDataの第1引数のBSCF_FIRSTDATANOTIFICATIONはデータの利用が可能になったことを意味し、 BSCF_LASTDATANOTIFICATIONはデータの利用が終了したことを意味します。 また、BSCF_DATAFULLYAVAILABLEは全てのデータを利用したことを意味します。 このような定数をまとめて指定しているのは、ReportDataが内部でIInternetProtocol::Readを呼び出すからであり、 Readではデータを全て返すまでS_OKを返し、全てのデータを返し終わったらS_FALSEを返しているからです。 つまり、一回のReportDataで全てのデータを処理しているため、各定数をまとめて指定しています。 ReadがS_FALSEを返せばReportDataも制御を返すことになり、そこではReportResultを呼び出しています。 これは、処理の結果を報告するのが目的で、第1引数にHRESULT型のエラーコード、 第2引数にプロトコル固有のエラーコード、第3引数にプロトコル固有の文字列を指定します。

IInternetProtocolSink::ReportDataの内部で呼ばれるIInternetProtocol::Readは、次のようになっています。

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

通常、Readではpvにデータを返すことになりますが、キャッシュを利用している場合は例外です。 この場合は*pcbReadにデータのサイズを指定してS_FALSEを返せば、 サイズ分だけキャッシュファイルからデータが取得されることになります。 キャッシュを使用しない場合はpvにm_lpDataの中身をコピーしますが、 そのコピーするサイズがpvのサイズを超えてはなりません。 pvのサイズはcbに格納されているため、その値だけコピーするようにし、 コピーしたサイズはpcbReadに格納します。 最終的には、cbに指定されたサイズをコピーできなくなりますから、 この場合はm_lpDataの残りのサイズをcbに格納し、 S_FALSEを返すことでReadがこれ以上呼ばれないようにします。

今回作成したオブジェクトをレジストリに登録すれば、 次のように独自のURLを入力できるようになります。

IEのアドレスバーでURLを入力したら、IEはURL MonikersでそのURLへアクセスしようとするはずです。 プロトコル名がsql:であることから、今回作成したオブジェクトがIEへロードされることになり、 独自のデータがIEに返ることになります。 IEに限らず、前節までで示したプログラムについても、 sql:含んだURLを入力すると独自のデータが返ります。 ページのソースを開いてみれば、StartのszDataに格納していた文字列が含まれていることが分かるでしょう。

次に今回作成したプログラムの内容を示します。 まず、DLLのdefファイルを示します。

LIBRARY	"mydll"

EXPORTS
	DllCanUnloadNow PRIVATE
	DllGetClassObject PRIVATE
	DllRegisterServer PRIVATE
	DllUnregisterServer PRIVATE

続いて、ヘッダーファイルを示します。

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

	CInternetProtocol();
	~CInternetProtocol();

	void ResetData();
	
private:
	LONG   m_cRef;
	DWORD  m_dwTotalSize;
	DWORD  m_dwReadSize;
	LPBYTE m_lpData;
	BOOL   m_bCacheAvailable;
	BOOL   m_bComplete;
};

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

最後に、ソースファイルを示します。

#include <windows.h>
#include <shlwapi.h>
#include <shlobj.h>
#include "sample.h"

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

const CLSID CLSID_InternetProtocol = {0x6c1b733f, 0xa0f1, 0x4dae, {0x95, 0xbe, 0x97, 0x1d, 0xd1, 0x5, 0xac, 0x61}};
const TCHAR g_szClsid[] = TEXT("{6C1B733F-A0F1-4dae-95BE-971DD105AC61}");
const TCHAR g_szProtocol[] = TEXT("sql");

LONG      g_lLocks = 0;
HINSTANCE g_hinstDll = NULL;

void LockModule(BOOL bLock);
BOOL CreateRegistryKey(HKEY hKeyRoot, LPTSTR lpszKey, LPTSTR lpszValue, LPTSTR lpszData);


// CInternetProtocol


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

	LockModule(TRUE);
}

CInternetProtocol::~CInternetProtocol()
{
	LockModule(FALSE);
}

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)
{
	DWORD dwBindFlags;
	DWORD dwNoCacheFlag = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;

	pOIBindInfo->GetBindInfo(&dwBindFlags, 0);
	if ((dwBindFlags & dwNoCacheFlag) != dwNoCacheFlag) {
		HANDLE hFile;
		WCHAR  szFilePath[] = L"filepath";
		
		hFile = CreateFileW(szFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		if (hFile == INVALID_HANDLE_VALUE) {
			pOIProtSink->ReportResult(E_FAIL, 0, NULL);
			return S_OK;
		}
		m_dwTotalSize = GetFileSize(hFile, NULL);
		CloseHandle(hFile);

		pOIProtSink->ReportProgress(BINDSTATUS_CACHEFILENAMEAVAILABLE, szFilePath);
		m_lpData = 0;
		m_dwTotalSize = m_dwTotalSize;
		m_bCacheAvailable = TRUE;
	}
	else {
		char szData[] = "<html><body><b>data</b></body></html>";
		m_dwTotalSize = sizeof(szData) - 1;
		m_lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, m_dwTotalSize);
		CopyMemory(m_lpData, szData, m_dwTotalSize);
		m_bCacheAvailable = FALSE;
	}

	pOIProtSink->ReportProgress(BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE, L"text/html");
	
	pOIProtSink->ReportData(BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION | BSCF_DATAFULLYAVAILABLE, m_dwTotalSize, m_dwTotalSize);
	pOIProtSink->ReportResult(S_OK, 0, NULL);

	return S_OK;
}

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

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

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

void CInternetProtocol::ResetData()
{
	m_dwTotalSize = 0;
	m_dwReadSize = 0;
	m_bCacheAvailable = FALSE;
	m_bComplete = FALSE;

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


// 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()
{
	LockModule(TRUE);

	return 2;
}

STDMETHODIMP_(ULONG) CClassFactory::Release()
{
	LockModule(FALSE);

	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)
{
	LockModule(fLock);

	return S_OK;
}


// DLL Export


STDAPI DllCanUnloadNow(void)
{
	return g_lLocks == 0 ? S_OK : S_FALSE;
}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)
{
	static CClassFactory serverFactory;
	HRESULT hr;
	
	*ppv = NULL;

	if (IsEqualCLSID(rclsid, CLSID_InternetProtocol))
		hr = serverFactory.QueryInterface(riid, ppv);
	else
		hr = CLASS_E_CLASSNOTAVAILABLE;

	return hr;
}

STDAPI DllRegisterServer(void)
{
	TCHAR szModulePath[MAX_PATH];
	TCHAR szKey[256];

	wsprintf(szKey, TEXT("CLSID\\%s"), g_szClsid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, TEXT("My Asynchronous Pluggable Protocol Handler")))
		return E_FAIL;

	GetModuleFileName(g_hinstDll, szModulePath, sizeof(szModulePath) / sizeof(TCHAR));
	wsprintf(szKey, TEXT("CLSID\\%s\\InprocServer32"), g_szClsid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, szModulePath))
		return E_FAIL;
	
	wsprintf(szKey, TEXT("CLSID\\%s\\InprocServer32"), g_szClsid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, TEXT("ThreadingModel"), TEXT("Apartment")))
		return E_FAIL;

	wsprintf(szKey, TEXT("PROTOCOLS\\Handler\\%s"), g_szProtocol);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, TEXT("CLSID"), (LPTSTR)g_szClsid))
		return E_FAIL;

	return S_OK;
}

STDAPI DllUnregisterServer(void)
{
	TCHAR szKey[256];

	wsprintf(szKey, TEXT("CLSID\\%s"), g_szClsid);
	SHDeleteKey(HKEY_CLASSES_ROOT, szKey);
	
	wsprintf(szKey, TEXT("PROTOCOLS\\Handler\\%s"), g_szProtocol);
	SHDeleteKey(HKEY_CLASSES_ROOT, szKey);
	
	return S_OK;
}

BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD dwReason, LPVOID lpReserved)
{
	switch (dwReason) {

	case DLL_PROCESS_ATTACH:
		g_hinstDll = hinstDll;
		DisableThreadLibraryCalls(hinstDll);
		return TRUE;

	}

	return TRUE;
}


// Function


void LockModule(BOOL bLock)
{
	if (bLock)
		InterlockedIncrement(&g_lLocks);
	else
		InterlockedDecrement(&g_lLocks);
}

BOOL CreateRegistryKey(HKEY hKeyRoot, LPTSTR lpszKey, LPTSTR lpszValue, LPTSTR lpszData)
{
	HKEY  hKey;
	LONG  lResult;
	DWORD dwSize;

	lResult = RegCreateKeyEx(hKeyRoot, lpszKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
	if (lResult != ERROR_SUCCESS)
		return FALSE;

	if (lpszData != NULL)
		dwSize = (lstrlen(lpszData) + 1) * sizeof(TCHAR);
	else
		dwSize = 0;

	RegSetValueEx(hKey, lpszValue, 0, REG_SZ, (LPBYTE)lpszData, dwSize);
	RegCloseKey(hKey);
	
	return TRUE;
}

作成したDLLを登録するには、コマンドプロント上で次の文字列を入力します。 は次の方法でレジストリに登録できます。

regsvr32 C:\sample.dll (登録の解除時は regsvr32 /u C:\sample.dll のようにする)

これによってDLLのDllRegisterServerが呼ばれ、HKEY_CLASSES_ROOT\PROTOCOLS以下に書き込みが行われます。


戻る