EternalWindows
モニカ / クラスモニカ

ある特定の状況でどのようなオブジェクトが必要になるかを分かっていながらも、 それを取得するための手順に悩まされることがあります。 たとえば、Excelから特定の範囲を表すRangeオブジェクトを取得しようとした場合、 Workbooksオブジェクト→Workbookオブジェクト→Worksheetオブジェクト→Rangeオブジェクトという流れを実行することになり、 本来必要でないオブジェクトも取得するため煩わしいといえます。 アプリケーションがモニカを使用するようになれば、このようなオブジェクトの階層を文字列として表すことができるため、 目的のオブジェクトを取得するまでの手順を短くすることができます。

モニカの正体は、IMonikerで識別できるCOMオブジェクトです。 このオブジェクトの役割は指定された文字列から関連するオブジェクトを検索することですが、 この文字列の形式は常に固定とは限りません。 たとえば、CLSIDを基に検索をしたいのであれば文字列にはCLSIDを指定しますし、 ファイルパスを基に検索したいのであれば文字列にはファイルパスを指定します。 つまり、文字列によって検索のアルゴリズムが変化することになるため、 モニカにもいくつかの種類があるようになっています。 次に、モニカの種類の一部を示します。

モニカの種類 説明
クラスモニカ クラスオブジェクトを検索するためのモニカ。CreateClassMonikerで作成可能。
ファイルモニカ ファイルに関連するオブジェクトを検索するためのモニカ。 ここで言うオブジェクトは、Wordの場合であればDocumentオブジェクト、Excelの場合であればWorkbookオブジェクトに相当する。 CreateFileMonikerで作成可能。
アイテムモニカ 特定のオブジェクト内に存在するオブジェクト(アイテム)を検索するためのモニカ。 CreateItemMonikerで作成可能。
汎用複合モニカ 複数のモニカが連結されたモニカ。 右側のモニカ(通常アイテムモニカ)で識別される名前を持ったオブジェクトを、左側のモニカから検索する。 CreateGenericCompositeで作成可能。
URLモニカ 指定したURLからデータを取得するオブジェクト(主にIStream)を検索するためのモニカ。 CreateURLMonikerで作成可能。
COM昇格モニカ COMサーバーを昇格して実行させるためのモニカ。 検索時の文字列は、"Elevation:Administrator!new:オブジェクトのCLISD"のようになる。 MkParseDisplayNameで作成可能。
セッションモニカ COMサーバーを特定のセッションで実行させるためのモニカ。 検索時の文字列は、"Session:console!clsid:オブジェクトのCLISD"のようになる。 MkParseDisplayNameで作成可能。

モニカはシステムに予め用意されているため、アプリケーションは既存のモニカを使用することになります。 簡単に言えば、CLSIDを基に検索を行いたい場合はCreateClassMonikerを呼び出し、 ファイルパスを基に検索を行いたい場合はCreateFileMonikerを呼び出すことになります。 MkParseDisplayNameは、指定する文字列次第でどのようなモニカでも作成することができます。

クラスモニカを作成するCreateClassMonikerは、次のように定義されています。

HRESULT CreateClassMoniker(
  REFCLSID rclsid,
  LPMONIKER *ppmk
);

rclsidは、CLSIDを格納したCLSID構造体を指定します。 ppmkは、クラスモニカを受け取る変数のアドレスを指定します。

CreateClassMonikerは構造体としてのCLSIDを要求するわけですが、 この点に注目するとCreateClassMonikerを呼び出す意味はほとんどありません。 理由は、クラスオブジェクトを取得するための関数であるCoGetClassObjectを呼び出すことができるからです。 つまり、モニカを通じてクラスオブジェクトを検索するようなことはせずに、 直接クラスオブジェクトを取得した方が早いということです。 ただし、CLSIDを文字列として外部から取得した場合はCoGetClassObjectを呼び出すことができませんから、 そのような場合はクラスモニカが必要になります。 この場合に呼び出す関数は、モニカを取得する汎用的な関数であるMkParseDisplayNameです。

HRESULT MkParseDisplayName(
  LPBC pbc,
  LPCOLESTR szUserName,
  ULONG *pchEaten,
  LPMONIKER *ppmk
);

pbcは、バインドコンテキストを表すIBindCtxを指定します。 通常、バインドコンテキストはCreateBindCtxで作成することになるでしょう。 szUserNameは、検索するオブジェクトを示した文字列を指定します。 関数が成功すると、この文字列がモニカの表示名になります。 pchEatenは、szUserNameの中で解析された文字数を受け取る変数のアドレスを指定します。 ppmkは、モニカを受け取る変数のアドレスを指定します。

アプリケーションがモニカを必要とする理由は、特定のオブジェクトを検索するためでした。 よって、モニカを取得したらそこで終わりということはなく、 目的のオブジェクトを検索(正確にはバインド)する必要があります。 これには、BindToObjectを呼び出します。

HRESULT IMoniker::BindToObject(
  IBindCtx *pbc,
  IMoniker *pmkToLeft,
  REFIID riidResult,
  void **ppvResult
);

pbcは、バインドコンテキストを表すIBindCtxを指定します。 pmkToLeftは、汎用複合化モニカで使用されるものであるため、通常はNULLを指定します。 riidResultは、モニカで検索するオブジェクトに要求したいIIDを指定します。 ppvResultは、オブジェクトを受け取る変数のアドレスを指定します。

モニカにはクラスモニカやファイルモニカといった種類があるわけですが、 いずれのモニカもIMonikerで識別可能になっています。 こうしたことからIMonikerは、IsSystemMonikerで自身の種類を返せるようになっています。

HRESULT IMoniker::IsSystemMoniker(
  DWORD *pdwMksys
);

pdwMksysは、モニカの種類を受け取る変数のアドレスを指定します。 モニカの種類はMKSYS_XXXで表され、クラスモニカの場合はMKSYS_CLASSMONIKERが返ります。

今回のプログラムは、モニカを使用してWordを起動します。 IMoniker::BindToObjectの実行には、多少の時間が掛かります。

#include <windows.h>

HRESULT Invoke(IDispatch *pDispatch, LPOLESTR lpszName, WORD wFlags, VARIANT *pVarArray, int nArgs, VARIANT *pVarResult);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HRESULT       hr;
	ULONG         uEaten;
	VARIANT       var;
	IBindCtx      *pBindCtx;
	IMoniker      *pMoniker;
	IClassFactory *pClassFactory;
	IDispatch     *pDispatch;

	CoInitialize(NULL);
	
	CreateBindCtx(0, &pBindCtx);

	hr = MkParseDisplayName(pBindCtx, L"clsid:000209FF-0000-0000-C000-000000000046", &uEaten, &pMoniker);
	if (FAILED(hr)) {
		MessageBox(NULL, TEXT("クラスモニカの取得に失敗しました。"), NULL, MB_ICONWARNING);
		pBindCtx->Release();
		CoUninitialize();
		return 0;
	}

	hr = pMoniker->BindToObject(pBindCtx, NULL, IID_PPV_ARGS(&pClassFactory));
	if (FAILED(hr)) {
		MessageBox(NULL, TEXT("オブジェクトの取得に失敗しました。"), NULL, MB_ICONWARNING);
		pMoniker->Release();
		pBindCtx->Release();
		CoUninitialize();
		return 0;
	}
	
	pClassFactory->CreateInstance(NULL, IID_PPV_ARGS(&pDispatch));
	
	var.vt = VT_I4;
	var.lVal = 1;
	Invoke(pDispatch, L"Visible", DISPATCH_PROPERTYPUT, &var, 1, NULL);

	pDispatch->Release();
	pClassFactory->Release();
	pBindCtx->Release();
	pMoniker->Release();
	CoUninitialize();
	
	return 0;
}

HRESULT Invoke(IDispatch *pDispatch, LPOLESTR lpszName, WORD wFlags, VARIANT *pVarArray, int nArgs, VARIANT *pVarResult)
{
	DISPPARAMS dispParams;
	DISPID     dispid;
	DISPID     dispidName = DISPID_PROPERTYPUT;
	HRESULT    hr;

	hr = pDispatch->GetIDsOfNames(IID_NULL, &lpszName, 1, LOCALE_USER_DEFAULT, &dispid);
	if (FAILED(hr))
		return hr;
	
	dispParams.cArgs = nArgs;
	dispParams.rgvarg = pVarArray;
	if (wFlags & DISPATCH_PROPERTYPUT) {
		dispParams.cNamedArgs = 1;
		dispParams.rgdispidNamedArgs = &dispidName;
	}
	else {
		dispParams.cNamedArgs = 0;
		dispParams.rgdispidNamedArgs = NULL;
	}

	hr = pDispatch->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, wFlags, &dispParams, pVarResult, NULL, NULL);

	return hr;
}

MkParseDisplayNameの第2引数に指定しているのはWordのCLSIDです。 これにより、Wordのクラスオブジェクトを取得するためのクラスモニカを取得できます。 CLSIDを指定する場合は、Syntaxとしてclsid:を指定しなければならないことに注意してください。 BindToObjectを呼び出せば、クラスオブジェクトを表すIClassFactoryを取得することができます。 後はこのインターフェースのCreateInstanceを呼び出せば、実際にオブジェクトを作成できたことになります。 今回のオブジェクトはWordのApplicationオブジェクトに相当しますから、 VisibleプロパティによってWordを表示させることができます。

クラスモニカには不可解な点がいくつか存在します。 まず、BindToObjectにクラスオブジェクトが実装していないインターフェースのIIDを指定するべきではありません。 たとえば、IID_IClassFactory以外のIIDを指定した場合は、 「COMコンポーネントのインストール中」というダイアログが表示されると思われます。 また、クラスモニカのBindToObjectを呼び出すと、オブジェクトのサーバーが2つ起動されることがあるようです。 全てのインターフェースを開放しても、1つのサーバーは依然として起動されたままになることを確認済みです。

バインドコンテキストについて

バインド時に必要になるバインドコンテキストには、バインド済みオブジェクト、バインドオプション、 オブジェクトパラメータという主に3つの情報が含まれています。 バインド済みオブジェクトというのは、汎用複合モニカのように複数のモニカを操作する場合に使用され、 目的のオブジェクトを取得するまでの間にバインドされたオブジェクトがバインド済みになります。 複数のバインド処理が行われる場合でも、同じバインドコンテキストが参照され続けます。

バインドオプションはバインド時における情報を含んでおり、これはBIND_OPTS構造体で表すことができます。 バインドコンテキストを作成するCreateBindCtxは、バインドオプションの設定を次のように行います。

BIND_OPTS bindOpts;

bindOpts.cbStruct            = sizeof(BIND_OPTS);
bindOpts.grfFlags            = 0;
bindOpts.grfMode             = STGM_READWRITE;
bindOpts.dwTickCountDeadline = 0;

pBindCtx->SetBindOptions(&bindOpts);

cbStructは、BIND_OPTS構造体のサイズを指定します。 grfFlagsは0で構いませんが、BIND_MAYBOTHERUSERという定数を指定することもできます。 この定数を指定すると、バインド時にユーザー入力が必要になる場合にUIが表示されることがあります。 BIND_JUSTTESTEXISTENCEという定数を指定すると、実際にバインドを行わず、バインドが可能であるかを調べることができます。 grfModeは、バインド済みオブジェクトが何らかのストレージをオープンする際に使用するモードを指定します。 dwTickCountDeadlineは、バインド時におけるタイムアウト値をミリ秒単位で指定します。 0を指定した場合はタイムアウトが発生しません。

オブジェクトパラメータは、バインドの補足情報として与えたいオブジェクトを表します。 モニカを通じてオブジェクトを取得する際にオブジェクトパラメータを使用する例は見たことがありませんが、 シェル名前空間で使用されるIShellFolderはオブジェクトパラメータを使用することがあります。 たとえばIShellFolder::ParseDisplayNameは、ファイルパスから関連するアイテムIDリストを取得することができますが、 このメソッドは引数としてバインドコンテキストを要求します。 これはいわば、バインドコンテキストに設定されたオブジェクトパラメータによっては、本来の取得のメカニズムが変化することを意味しています。 オブジェクトパラメータの設定はIBindCtx::RegisterObjectParamで行いますが、 たとえば第1引数にSTR_FILE_SYS_BIND_DATAを指定した場合は、 ParseDisplayNameに存在しないファイルパスを指定していても、そのパスを表すアイテムIDリストが作成されることになります。 オブジェクトが実装するべきインターフェースは第1引数の文字列によって変化し、 STR_FILE_SYS_BIND_DATAを指定する場合はIFileSystemBindData2になります。



戻る