EternalWindows
モニカ / ファイルモニカ

前節では、CLSIDをベースにオブジェクトを取得するためにクラスモニカを取り上げました。 今回は、ファイルパスをベースにオブジェクトを取得するファイルモニカを取り上げます。 ファイルモニカを作成するにはCreateFileMonikerを呼び出します。

HRESULT CreateFileMoniker(
  LPCOLESTR lpszPathName,
  LPMONIKER *ppmk
);

lpszPathNameは、ファイルパスを指定します。 ppmkは、ファイルモニカを受け取る変数のアドレスを指定します。

ファイルモニカのBindToObjectを呼び出した場合はファイルに関連するオブジェクトを取得できるわけですが、 これは具体的にはどのような仕組みで行われているのでしょうか。 まず、ファイルモニカはファイルパスを基にGetClassFileを呼び出して、 ファイルからオブジェクトのCLSIDを取得します。 CLSIDを格納していないファイルについてはここで失敗することになります。 CLSIDを取得したらCoCreateInstanceでオブジェクトを作成し、 そのオブジェクトをIPersistFileで表すようにします。 そして、IPersistFile::Loadにファイルパスを指定すれば、オブジェクトはそのファイルをロードすることになります。 取得したオブジェクトが具体的に何であるかはファイルによって異なりますが、 WordならばDocumentオブジェクト、ExcelならばWorkbookオブジェクトになるようです。 これらのオブジェクトは、IPersistFileの他にIOleItemContainerやIOleObject、IOleDocumentなどを実装しています。

指定したファイルが既に開かれている場合、ファイルモニカのBindToObjectは処理の時間が短くなる可能性があります。 WordやExcelは起動時にROTと呼ばれるテーブルにモニカとオブジェクトをセットで登録することになっているのですが、 BindToObjectはファイルに対してGetClassFileを呼び出す前に、ROTからオブジェクトを取得しようとするからです。 つまり、既にオブジェクトが実行されているのであれば新しくオブジェクトを作成せずに、 既存のオブジェクトを返そうとするわけです。 オブジェクトがROTに登録されていない場合は、BindToObject内部でオブジェクトが作成されることになります。

今回のプログラムは、Excelのファイルからデータを取得して表示します。

#include <windows.h>

HRESULT GetRangeValue(IDispatch *pRange);
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;
	VARIANT   var;
	VARIANT   varResult;
	IBindCtx  *pBindCtx;
	IMoniker  *pMoniker;
	IDispatch *pWorkbook;
	IDispatch *pWorksheet;
	IDispatch *pRange;

	CoInitialize(NULL);

	hr = CreateFileMoniker(L"C:\\sample.xls", &pMoniker);
	if (FAILED(hr)) {
		MessageBox(NULL, TEXT("ファイルモニカの取得に失敗しました。"), NULL, MB_ICONWARNING);
		CoUninitialize();
		return 0;
	}

	CreateBindCtx(0, &pBindCtx);

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

	VariantInit(&varResult);
	Invoke(pWorkbook, L"ActiveSheet", DISPATCH_PROPERTYGET, NULL, 0, &varResult);
	pWorksheet = varResult.pdispVal;
 
	var.vt = VT_BSTR;
	var.bstrVal = SysAllocString(L"A1:B2");
	VariantInit(&varResult);
	Invoke(pWorksheet, L"Range", DISPATCH_PROPERTYGET, &var, 1, &varResult);
	pRange = varResult.pdispVal;
	SysFreeString(var.bstrVal);

	GetRangeValue(pRange);
	
	pRange->Release();
	pWorksheet->Release();
	pWorkbook->Release();
	pBindCtx->Release();
	pMoniker->Release();
	CoUninitialize();
	
	return 0;
}

HRESULT GetRangeValue(IDispatch *pRange)
{
	LONG      i, j;
	LONG      lIndexMin1, lIndexMax1;
	LONG      lIndexMin2, lIndexMax2;
	LONG      indices[2];
	SAFEARRAY *pArray;
	VARIANT   var;
	VARIANT   varResult;
	HRESULT   hr;
	TCHAR     szBuf[256];

	VariantInit(&varResult);
	hr = Invoke(pRange, L"Value", DISPATCH_PROPERTYGET, NULL, 0, &varResult);
	if (FAILED(hr))
		return hr;
	pArray = varResult.parray;
	
	SafeArrayGetLBound(pArray, 1, &lIndexMin1);
	SafeArrayGetUBound(pArray, 1, &lIndexMax1);
	SafeArrayGetLBound(pArray, 2, &lIndexMin2);
	SafeArrayGetUBound(pArray, 2, &lIndexMax2);

	for (i = lIndexMin1; i <= lIndexMax1; i++) {
		for (j = lIndexMin2; j <= lIndexMax2; j++) {
			indices[0] = i;
			indices[1] = j;
			VariantInit(&var);
			SafeArrayGetElement(pArray, indices, &var);
			if (var.vt == VT_BSTR) {
				MessageBoxW(NULL, (LPWSTR)var.bstrVal, L"OK", MB_OK);
				VariantClear(&var);
			}
			else if (var.vt == VT_R8) {
				VARIANT varNew;
				VariantInit(&varNew);
				VariantChangeType(&varNew, &var, 0, VT_BSTR);
				MessageBoxW(NULL, (LPWSTR)varNew.bstrVal, L"OK", MB_OK);
				VariantClear(&varNew);
			}
			else if (var.vt == VT_DATE) {
				SYSTEMTIME systemTime;
				VariantTimeToSystemTime(var.date, &systemTime);
				wsprintf(szBuf, TEXT("%d/%d/%d"), systemTime.wYear, systemTime.wMonth, systemTime.wDay);
				MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
			}
			else if (var.vt == VT_EMPTY)
				MessageBox(NULL, TEXT(""), TEXT("OK"), MB_OK);
			else {
				wsprintf(szBuf, TEXT("予期しないデータ型 %d"), var.vt);
				MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
			}
		}
	}

	VariantClear(&varResult);

	return hr;
}

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

ファイルモニカの作成はCreateFileMonikerで行うことができますが、 ファイルパスをMkParseDisplayNameに指定してもファイルモニカの作成は可能です。 どちらを使用しても変化はないと思われますが、今回はCreateFileMonikerを呼び出しています。 ファイルモニカを取得したらBindToObjectを呼び出して、関連するオブジェクトを取得することができます。 このオブジェクトはExcelの場合であればWorkbookオブジェクトに相当するため、 ActiveSheetプロパティからWorksheetオブジェクトを取得でき、 さらにそこからRangeオブジェクトを取得できます。 この時点まで来ればセルのデータを取得することができるため、 GetRangeValueでそれを行っています。 IDispatchを使用したOfficeアプリケーションの操作は、オートメーションの章で取り上げています。

目的のオブジェクトを取得するために必要な作業が、モニカの使用によって簡略化されているかどうかは確認しておかなければなりません。 ファイルモニカを使用せずにオブジェクトを取得しようとした場合は、 まずCoCreateInstanceでExcelのインスタンスを作成し、次にWorkbooksオブジェクトの取得、 そしてOpenメソッドを通じてWorkbookオブジェクトを取得することになりますが、 この流れは今回のコードよりも複雑であると考えられます。 よって、今回の場合はファイルモニカを使用した方が効率的であると言えます。

バインドの簡略化

これまで述べてきたように、モニカを通じて目的のオブジェクトを取得するためには主に3つの作業が必要です。 1つは検索するためのモニカの作成であり、もう1つはバインドコンテキストの作成、 そして最後にオブジェクトへのバインドという具合になります。 ただし、アプリケーションがBindMonikerを呼び出すようになると、 バインドコンテキストの作成とバインドを同時に行えるようになります。

CreateFileMoniker(L"C:\\sample.xlsx", &pMoniker);
BindMoniker(pMoniker, 0, IID_PPV_ARGS(&pWorkbook));

モニカを作成していれば、BindMonikerを呼び出すことができます。 BindMonikerは内部でCreateBindCtxとIMoniker::BindToObjectを呼び出します。

次に示すCoGetObjectを呼び出せば、モニカの作成自体もアプリケーションから隠蔽することができます。

CoGetObject(L"C:\\sample.xlsx", NULL, IID_PPV_ARGS(&pWorkbook));

第1引数には、CreateFileMonikerやMkParseDisplayNameに指定する文字列を指定します。 第2引数はBIND_OPTS構造体を指定することができますが、NULLで問題ありません。



戻る