EternalWindows
COMサーバー / レジストリへの登録

オブジェクトの実装を終えたサーバーは、そのオブジェクトの情報をレジストリに書き込む必要があります。 CoCreateInstanceが内部で呼び出すCoGetClassObjectは、 指定されたCLSIDからオブジェクトを実装するDLLを特定するわけですが、 この際にはレジストリのHKEY_CLASSES_ROOT\CLSID以下が参照されるからです。 このキーを開くと、次のように大量のCLSIDを確認することができます。

CLSID以下に書き込まれているのはオブジェクトのCLSIDですから、 ここに列挙されているCLSIDは原則としてCoCreateInstanceに指定することができます。 今回取り上げるコードを実行すると、次のようなキーが作成されます。

選択されているキーは独自に作成したキーであり、このキーの名前はオブジェクトのCLSIDでなければなりません。 キーに格納されるデータはオブジェクトの説明文であり、これは自由に決定して構いません。 キーの下にはInprocServer32というサブキーが作成されていますが、 このサブキーにサーバーのDLLのパスが格納されることになります。

上図のように、既定のエントリにDLLのパスが書き込まれるようにしておきます。 この他、ThreadingModelというエントリにApartmentという文字列を書き込む必要がありますが、 この意味については別の章で説明することにします。

上図では、InprocServer32というサブキーの他にProgIDというサブキーも作成していますが、これは必須ではありません。 しかし、このサブキーを作成していると、ProgIDFromCLSIDを呼び出しによってCLSIDからProgIDへの変換が可能になるので、 できれば作成しておいたほうがよいでしょう。 ProgIDはいわばオブジェクトの名前であり、一見してそれがどのようなオブジェクトを識別しているのかを見分けることができます。

上図のように、既定のエントリとしてProgIDの値を書き込みます。 ProgIDのフォーマットは、<ライブラリ名>.<クラス名前>.<バージョン>という形になりますが、 実際にはどのような形で文字列を指定しても構いません。

オブジェクトがProgIDを持つ場合は、ProgIDからCLSIDへの変換もサポートしておくべきです。 このためには、HKEY_CLASSES_ROOT以下にProgIDの名前をしたキーを作成します。

上図のように、ProgIDの名前をしたキーをHKEY_CLASSES_ROOT以下に作成します。 既定のエントリの値は、オブジェクトの説明文でよいでしょう。 後はCLSIDという名前のサブキーを作成し、そこにCLSIDを書き込めば完了です。

上図のように、既定のエントリとしてオブジェクトのCLSIDを書き込みます。 これにより、CLSIDFromProgIDを呼び出してProgIDからCLSIDへの変換が可能になります。

COMサーバーをクライアントが使えるように登録するには、 上記した情報をレジストリに書き込む必要があります。 通常、この処理はサーバーがエクスポートするDllRegisterServerで行われます。

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("COM Server Sample")))
		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("CLSID\\%s\\ProgID"), g_szClsid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, (LPTSTR)g_szProgid))
		return E_FAIL;

	wsprintf(szKey, TEXT("%s"), g_szProgid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, TEXT("COM Server Sample")))
		return E_FAIL;
  
	wsprintf(szKey, TEXT("%s\\CLSID"), g_szProgid);
	if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, (LPTSTR)g_szClsid))
		return E_FAIL;

	return S_OK;
}

g_szClsidはオブジェクトのCLSIDを格納し、g_szProgidはオブジェクトのProgIDを格納します。 CreateRegistryKeyは、レジストリキーの作成とデータの書き込みをラッピングした自作関数であり、 第1引数はルートキー、第2引数はキーの位置、第3引数はエントリの名前、第4引数は書き込みたいデータです。 第3引数がNULLの場合は既定のエントリに書き込みが行われます。 1回目の呼び出しでは、CLSIDキー以下に独自のCLSIDの名前を持ったキーが作成され、既定のエントリに説明文が書き込まれます。 2回目の呼び出しでは、先に作成したキーの下にInprocServer32キーが作成され、 既定のエントリとしてDLLのパスが書き込まれます。 DLLのパスは、GetModuleFileNameで取得することができます。 3回目の呼び出しでは、InprocServer32キーにThreadingModelというエントリが作成され、 Apartmentという文字列が書き込まれます。 4回目の呼び出しでは、独自のCLSIDキーの下にProgIDという名前のキーが作成され、 既定のエントリとしてProgIDの中身が書き込まれます。 5回目の呼び出しでは、HKEY_CLASSES_ROOTの直下にProgIDの名前を持ったキーが作成され、 既定のエントリとして説明文が書き込まれます。 6回目の呼び出しでは、先に作成したキーの下にCLSIDという名前のキーが作成され、 既定のエントリとしてCLSIDの中身が書き込まれます。 CreateRegistryKeyの内部は、次のようになります。

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

RegCreateKeyExの第1引数にはhKeyRootを指定し、 このキーの下にlpszKeyで識別されるキーが作成されるようにします。 キーの作成が成功すればRegSetValueExを呼び出してエントリにデータを書き込みます。 第2引数がエントリ名になり、第5引数に書き込みたいデータ、第6引数がデータのサイズです。 今回書き込むデータは全て文字列であるため、第4引数はREG_SZになります。 データのサイズには、文字列の終端に存在する\0文字も含めなければならないため、 lstrlenの結果に1を加算します。

登録したCOMサーバーが不要になった場合は、作成したレジストリキーを削除する必要があります。 この処理はDllUnregisterServerで行います。

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

	wsprintf(szKey, TEXT("CLSID\\%s"), g_szClsid);
	SHDeleteKey(HKEY_CLASSES_ROOT, szKey);
	
	wsprintf(szKey, TEXT("%s"), g_szProgid);
	SHDeleteKey(HKEY_CLASSES_ROOT, szKey)

	return S_OK;
}

SHDeleteKeyは、第1引数のキーから第2引数のキーを探して削除します。 キーの削除にはRegDeleteKeyという関数もありますが、 この関数はキーの下にサブキーが存在する場合は失敗するので、 サブキーを含めてキーを削除するSHDeleteKeyを呼び出しています。 SHDeleteKeyと同様の操作はRegDeleteTreeでも可能ですが、 この関数はWindows Vista以降でなければ使用できません。 削除するキーは、HKEY_CLASSES_ROOT\CLSID以下に作成した独自のCLSIDの名前を持つキーと、 HKEY_CLASSES_ROOT直下に作成した独自のProgIDの名前を持つキーです。

DllUnregisterServerでE_FAILを返せば、登録の解除を失敗したことにすることができますが、 これは十分に注意して行わなければなりません。 たとえば、SHDeleteKeyが失敗した場合にE_FAILを返す実装にした場合、 一度削除したキーに対してもう一度SHDeleteKeyを実行すれば関数は必ず失敗しますから、 2回目以降のDllUnregisterServerでは常にE_FAILが返ることになってしまいます。 このため、エラーチェックを行う場合はE_FAILを返すよりも、 エラーの内容を表示してS_OKを返すとよいでしょう。

DllRegisterServerが呼び出しているGetModuleFileNameでは、 第1引数にg_hinstDllという変数を指定していました。 これはDLLがロードされた先頭アドレスを表しており、DllMainで取得しています。

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

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

	}

	return TRUE;
}

DllMainはDLLのエントリポイントであり、クライアントのプロセスにロードされた時に呼ばれます。 hinstDllにロードされたアドレスが格納されているため、これをグローバルに参照できるようにg_hinstDllに格納します。 DisableThreadLibraryCallsの呼び出しは必須ではありませんが、 これを呼び出すとスレッドの作成や終了通知をDllMainで受け取らずに済みます。

GUIDの作成

CLSIDはオブジェクトを一意に識別できなければならないことから、その値はGUIDとして定義されていなければなりません。 GUIDを作成するには、CoCreateGuidを呼び出すアプリケーションを自作する方法もありますが、 通常はGuidGen.exeを起動して作成します。 Windows SDKをデフォルトのパスにインストールしたのであれば、 これは%ProgramFiles%\Microsoft SDKs\Windows\v6.0\Bin以下に存在すると思われます。

GuidGen.exeを起動すると上記のようなダイアログを表示されますが、 実はこの時点で既にGUIDが作成されています。 作成されたGUIDは「Result」に表示されており、 「Copy」ボタンを押すことによって、指定されたフォーマットでクリップボードにコピーされます。 たとえば、3番目のフォーマットは次のようになります。

// {112143A6-62C1-4478-9E8F-872699255E2E}
static const GUID <> = 
{ 0x112143a6, 0x62c1, 0x4478, { 0x9e, 0x8f, 0x87, 0x26, 0x99, 0x25, 0x5e, 0x2e } };

このフォーマットは、GUIDを構造体として定義しています。 CLSIDを定義する場合は、この構造体の中身をコピーすればよいでしょう。 次に、4番目のフォーマットを示します。

{112143A6-62C1-4478-9E8F-872699255E2E}

このフォーマットは、GUIDを構造体ではなく文字列として扱っています。 レジストリキーの名前は、このような文字列で識別されることになります。



戻る