EternalWindows
COMサーバー / タイプライブラリの登録

前節では、IDispatchによるメソッド呼び出しについて説明しましたが、 このような呼び出しを行うためには、オブジェクトがどのようなメソッドをサポートしているかを事前に把握しておかなければなりません。 タイプライブラリとは、インターフェースの型情報などを格納したファイルであり、これを作成して公開するようにすれば、 クライアントは呼び出し可能なメソッドを理解できるようになります。

IDLファイルのコンパイルによってタイプライブラリが作成されるようにするには、IDLファイルにlibrary属性を指定します。

[
	uuid(CFE6456E-8930-416d-B169-09B06AC9ECE2),
	version(1.0),
	helpstring("sample TypeLib")
]
library MyServerLib
{
	importlib("stdole2.tlb");

	[
		uuid(79BDE8FF-CEE2-4c6d-A7B2-BED85A67A708)
	]
	coclass MyServer
	{
		[default] interface IFileControl;
	}
}

これまで述べてきたように、IDLファイルにおける1つの情報はヘッダとボディに分けて指定します。 ボディの手前に記述されている属性が情報の種類を表しており、 上記の場合はlibrary属性になっているため、タイプライブラリに関する情報であると分かります。 よって、ヘッダのuuidはタイプライブラリのID(LIBID)になります。 versionは、タイプライブラリのバージョンを指定します。 helpstringは、任意の説明文を指定することができます。 ボディに記述されているimportlibは、タイプライブラリのインポートに使用できます。 通常、stdole2.tlbはインポートしておくべきと思われます。 libraryのボディにはcoclassの情報を含めることが一般的であるため、 上記ではそのようにしています。

タイプライブラリを作成し、さらにオブジェクトがIDispatchを実装している場合は、 タイプライブラリの機能を使用してIDispatchのメソッドを処理できるようになります。 まず、オブジェクトのコンストラクタで次の処理を行います。

ITypeLib *pTypeLib;
HRESULT  hr;

m_pTypeInfo = NULL;

hr = LoadRegTypeLib(LIBID_MyServerLib, 1, 0, 0, &pTypeLib);
if (SUCCEEDED(hr)) {
	pTypeLib->GetTypeInfoOfGuid(IID_IFileControl, &m_pTypeInfo);
	pTypeLib->Release();
}

LoadRegTypeLibを呼び出せば、レジストリからタイプライブラリの情報を取得し、 それをITypeLibで表すことができます。 第1引数に指定しているLIBID_MyServerLibはタイプライブラリのGUIDであり、 これはIDLファイルをコンパイルすることで定義されるようになります。 GetTypeInfoOfGuidを呼び出せば、第1引数に指定したGUIDに関する情報をITypeInfoで表すことができます。 このITypeInfoがあれば、IDispatchのメソッドを次のように処理できます。

STDMETHODIMP CMyServer::GetTypeInfoCount(UINT *pctinfo)
{
	*pctinfo = m_pTypeInfo != NULL ? 1 : 0;
	
	return S_OK;
}

STDMETHODIMP CMyServer::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
	if (m_pTypeInfo == NULL)
		return E_NOTIMPL;

	m_pTypeInfo->AddRef();
	*ppTInfo = m_pTypeInfo;

	return S_OK;
}

STDMETHODIMP CMyServer::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
	if (m_pTypeInfo == NULL)
		return E_NOTIMPL;

	return m_pTypeInfo->GetIDsOfNames(rgszNames, cNames, rgDispId);
}

STDMETHODIMP CMyServer::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
	void *p = static_cast<IFileControl *>(this);
	
	if (m_pTypeInfo == NULL)
		return E_NOTIMPL;

	return m_pTypeInfo->Invoke(p, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
}

ITypeInfoを使用する場合はGetTypeInfoCountで1を返し、 GetTypeInfoでITypeInfoを返すようにします。 GetIDsOfNamesでは、メソッド名に対応するDISPIDを返さなければなりませんが、 そのような処理はITypeInfo::GetIDsOfNamesを呼び出すことで実現できます。 また、InvokeではDISPIDに対応するメソッドを呼び出さなければなりませんが、 これはITypeInfo::Invokeを呼び出すことで実現できます。 この呼び出しではメソッドを呼び出すという関係上、オブジェクトのアドレスが必要になるため、 第1引数にそれを指定します。

タイプライブラリを使用する場合は、その情報をレジストリに登録しておくとよいでしょう。 そうすることで、タイプライブラリがどこに存在するかを把握できやすくなりますし、 LoadRegTypeLibのような関数を呼び出すこともできるようになります。 COMサーバーが登録される際にはDllRegisterServerが呼ばれますが、 その際に次のような処理を行えばよいでしょう。

GetModuleFileNameW(g_hinstDll, szTypeLibPath, sizeof(szTypeLibPath) / sizeof(TCHAR));
hr = LoadTypeLib(szTypeLibPath, &pTypeLib);
if (SUCCEEDED(hr)) {
	hr = RegisterTypeLib(pTypeLib, szTypeLibPath, NULL);
	if (SUCCEEDED(hr)) {
		wsprintf(szKey, TEXT("CLSID\\%s\\TypeLib"), g_szClsid);
		if (!CreateRegistryKey(HKEY_CLASSES_ROOT, szKey, NULL, g_szLibid)) {
			pTypeLib->Release();
			return E_FAIL;
		}
	}	
	pTypeLib->Release();
}

タイプライブラリの登録はRegisterTypeLibで行うことができますが、 このためにはLoadTypeLibを呼び出してITypeLibを取得しておく必要があります。 szModulePathWにはタイプライブラリのフルパスが格納されていなければなりませんが、 DLLがタイプライブラリをリソースとして埋め込んでいる場合は、 DLLのフルパスが格納されていても問題はありません。 RegisterTypeLibが成功した場合は、HKEY_CLASSES_ROOT\CLSID\<オブジェクトのCLSID>\TypeLibにタイプライブラリのGUIDを格納するようにします。 このようにすれば、オブジェクトのCLSIDを基にタイプライブラリのGUIDをたどれるようになります。 ちなみに、タイプライブラリが登録されるレジストリキーは次のようになっています。

HKEY_CLASSES_ROOT\TypeLib

タイプライブラリの登録を解除する場合は、DllUnregisterServerで次の処理を行います。

UnRegisterTypeLib(LIBID_MyServerLib, 1, 0, LOCALE_NEUTRAL, SYS_WIN32);

UnRegisterTypeLibを呼び出せば、第1引数のGUIDで識別されるレジストリキーが削除されます。

タイプライブラリをリソースとしてDLLに埋め込むには、Visual Studioのプロジェクトからリソースファイルを追加します。 これにより、.rcファイルが作成されるため、それをメモ帳などで開き、 次の箇所を修正します。

#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE 3 リソースから生成されました。
//
1 typelib Release\mydll.tlb

/////////////////////////////////////////////////////////////////////////////
#endif    // APSTUDIO_INVOKED でない場合

Release\mydll.tlbの部分を、埋め込みたいタイプライブラリの名前に置き換えるようにします。 これでリビルドを行うと、タイプライブラリがDLLに埋め込まれるようになります。 タイプライブラリを埋め込む場合でも、.tlbファイルは通常通り出力されます。

タイプライブラリの中身を視覚的に確認したい場合は、 %ProgramFiles%\Microsoft SDKs\Windows\v6.0\Binに存在するOleView.exeを実行するのがよいでしょう。 メニューの「File」から「ViewTypeLib」を選択してタイプライブラリを開けば、 次のようなウインドウが表示されます。

左側のペインには、IDLファイルで定義していた情報が列挙されます。 たとえば、coclassを定義していた場合はcoclassという名前が列挙されますし、 interfaceを定義していた場合はinterfaceという名前が列挙されます。 dispinterfaceは定義していませんでしたが、IDLファイルにdual属性を指定した場合はデュアルインターフェースを持つことになりますから、 interface(カスタムインターフェース)の他に、dispinterface(ディスパッチインターフェース)も列挙されることになります。 interfaceもdispinterfaceも記述されている情報は基本的に同一ですが、 カスタムインターフェースを使用してメソッドにアクセスする場合は、 interfaceからメソッドの情報を確認するのが一般的でしょう。 逆に、IDispatchを使用したり、スクリプト言語からメソッドを呼び出したりする場合は、 dispinterfaceからメソッドの情報を確認します。


戻る