EternalWindows
モニカ / アイテムモニカ

これまで、クラスオブジェクトやファイルに関連したオブジェクトなどを見てきましたが、 多くのアプリケーションが必要としているのは、より直観的で分かりやすいオブジェクトであると思われます。 たとえば、ExcelのWorksheetオブジェクトを取得するためにSheet1という文字列や、 Rangeオブジェクトを取得するためにA1:B2といった文字列を指定したいはずです。 こうした独自の名前で識別されるオブジェクトを取得するためには、 その名前を持ったアイテムモニカを作成することになります。

HRESULT CreateItemMoniker(
  LPCOLESTR lpszDelim,
  LPCOLESTR lpszItem,
  LPMONIKER *ppmk
);

lpszDelimは、アイテムモニカと他のモニカを結合する際の区切り文字を指定します。 通常は、!を指定します。 lpszItemは、検索したいオブジェクトの名前を指定します。 ppmkは、アイテムモニカを受け取る変数のアドレスを指定します。

アイテムモニカだけを作成しても、その名前のオブジェクトを直ちに取得するようにはなりません。 単純に考えて、名前だけではそのオブジェクトがどこに存在するか分からないからです。 それではどうするのかというと、ファイルモニカとアイテムモニカを連結するようにします。 このようにすると、sample.xlsxのSheet1というような明確な位置情報をモニカに与えることができます。 次に、モニカを連結する例を示します。

CreateFileMoniker(L"C:\\sample.xlsx", &pFileMoniker);
CreateItemMoniker(L"!", L"Sheet1", &pItemMoniker);
pFileMoniker->ComposeWith(pItemMoniker, FALSE, &pMoniker); // CreateGenericComposite(pFileMoniker, pItemMoniker, &pMoniker);でも可能

2つのモニカが連結されて作成されたモニカは汎用複合モニカと呼ばれますが、 このモニカには左と右という概念があります。 そして、右のモニカは左のモニカに依存するという特徴があります。 たとえば、Sheet1という名前はsample.xlsxというファイルの中で有効となる名前ですから、 アイテムモニカはファイルモニカの右側に存在しなければなりません。 IMoniker::ComposeWithの第1引数には自身の右側に位置させるモニカを指定するため、 ここにはアイテムモニカを指定することになります。 IMoniker::ComposeWithの呼び出しは、CreateGenericCompositeに置き換えても問題ありません。

汎用複合モニカを作成するために、ファイルモニカとアイテムモニカを作成するのは煩わしいといえるかもしれません。 次のようにMkParseDisplayNameを呼び出せば、簡単に汎用複合モニカを作成することができます。

MkParseDisplayName(pBindCtx, L"C:\\sample.xlsx!Sheet1", &uEaten, &pMoniker);

汎用複合モニカを作成する場合は、各モニカを!(バンと読む)という文字で区切るようにします。 アイテムモニカはファイルモニカに依存しているので、ファイルモニカより右側に存在していなければなりません。

アイテムモニカにオブジェクトの名前を指定するといっても、 目的のオブジェクトがどのような名前を持つのかが分からないという問題があります。 たとえば、先ほどからExcel内のワークシートをSheet1という言葉で表していますが、 実際にこの名前を基に汎用複合モニカを作成してBindToObjectを呼び出しても、 目的のオブジェクトを取得することはできません。 ただし、埋め込みオブジェクトの場合は名前がはっきりとしているため、 これを参照する例を考えいくことにします。 まず、Excelのファイルを開きます。

「挿入」タブから「オブジェクト」という項目を選択すると、どのオブジェクトを挿入するかを決定することができます。 ここで「Microsoft Office Word 文書」を選択すると、次のようにWordがExcelに埋め込まれます。

左上のボックスに設定されている名前が、埋め込まれたオブジェクトの名前になります。 そして、この名前はアイテムモニカとして指定することができるようです。 埋め込まれたWordを編集するには、Word上でダブルクリックを行います。

今回のプログラムは、Excelに埋め込まれたWordのテキストを取得して表示します。

#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   varResult;
	IBindCtx  *pBindCtx;
	IMoniker  *pMoniker;
	IDispatch *pDocument;
	IDispatch *pContent;

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

	hr = MkParseDisplayName(pBindCtx, L"C:\\sample.xlsx!オブジェクト 1", &uEaten, &pMoniker);
	if (FAILED(hr)) {
		MessageBox(NULL, TEXT("モニカの取得に失敗しました。"), NULL, MB_ICONWARNING);
		pBindCtx->Release();
		CoUninitialize();
		return 0;
	}

	hr = pMoniker->BindToObject(pBindCtx, NULL, IID_PPV_ARGS(&pDocument));
	if (FAILED(hr)) {
		MessageBox(NULL, TEXT("オブジェクトの取得に失敗しました。"), NULL, MB_ICONWARNING);
		pMoniker->Release();
		pBindCtx->Release();
		CoUninitialize();
		return 0;
	}

	VariantInit(&varResult);
	Invoke(pDocument, L"Content", DISPATCH_PROPERTYGET, NULL, 0, &varResult);
	pContent = varResult.pdispVal;

	VariantInit(&varResult);
	Invoke(pContent, L"Text", DISPATCH_PROPERTYGET, NULL, 0, &varResult);
	MessageBoxW(NULL, varResult.bstrVal, L"OK", MB_OK);
	
	pContent->Release();
	pDocument->Release();
	pMoniker->Release();
	pBindCtx->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引数には、ファイルパスとオブジェクトの名前を!で区切って指定します。 これにより、sample.xlsxのオブジェクト 1を検索するための汎用複合モニカが作成されたことになります。 BindToObjectを呼び出せば実際にオブジェクト 1を取得することができ、 これはWordにおけるDocumentオブジェクトに相当します。 よって、ContentプロパティからContentオブジェクトを取得し、 TextプロパティからWordのテキストを取得できます。

IOleItemContainerについて

汎用複合モニカのBindToObjectは、どのような仕組みで目的のオブジェクトを取得するようになっているのでしょうか。 MkParseDisplayNameに"C:\\sample.xlsx!オブジェクト 1"が指定されていたと仮定すると、 まず文字列が"C:\\sample.xlsx"と"オブジェクト 1"に分割されます。 そして、右側の文字列を名前としてアイテムモニカを作成し、このモニカのBindToObjectを呼び出します。 このとき、第2引数には左のファイルモニカが指定されることになり、 ファイルモニカで検索できるオブジェクト(Workbook)からIOleItemContainerが取得されます。 このインターフェースのGetObjectWにアイテムモニカの名前を指定すれば、 Workbookから"オブジェクト 1"を取得することになり、 目的のオブジェクトにたどり着いたことになります。

ファイルモニカで検索できるオブジェクトがIOleItemContainerを実装し、 アイテムモニカがそれを使用していることが分かりましたが、 このIOleItemContainerはアプリケーションからも使用することができます。 次に例を示します。

IOleItemContainer *pWorkbook;

MkParseDisplayName(pBindCtx, L"C:\\sample.xlsx", &uEaten, &pMoniker);
pMoniker->BindToObject(pBindCtx, NULL, IID_PPV_ARGS(&pWorkbook));
pWorkbook->GetObjectW(L"オブジェクト 1", BINDSPEED_INDEFINITE, pBindCtx, IID_PPV_ARGS(&pDocument));

まず、MkParseDisplayName(またはCreateFileMoniker)でファイルモニカを作成します。 次に、BindToObjectでファイルモニカに関連するオブジェクトを取得し、 これをIOleItemContainerで識別するようにします。 そして、IOleItemContainer::GetObjectWを呼び出せば、 Workbookから第1引数の名前を持ったオブジェクトを取得できます。



戻る