EternalWindows
オートメーション / タイプライブラリ

IDispatchを使用してオブジェクトを操作するクライアントは、そのオブジェクトについての情報を予め知っておく必要があります。 たとえば、メソッドの名前が分からないことには一切のメソッドを呼び出すことができませんから、 サーバーはこうした点を踏まえて、クライアントにオブジェクトについての情報を公開する必要があります。 この公開方法として最も有名なのはタイプライブラリと呼ばれるファイルであり、 クライアントはこのファイルの中身を確認することで、 サーバーが定義しているインターフェースやメソッド、列挙型などを知ることができます。 タイプライブラリは.tlbや.olbという拡張子を持って存在することもありますし、 DLL内にリソースとして埋め込まれている場合もあります。 タイプライブラリを作成するサーバーは、IDLファイルにLibrary属性を記述してコンパイルすることになります。

タイプライブラリの中身を視覚的に確認したい場合は、 Windows SDKに付属しているOleView.exeを実行するのがよいでしょう。 デフォルトのパスにインストールしたのであれば、%ProgramFiles%\Microsoft SDKs\Windows\v6.0\Bin以下に存在すると思われます。 初回起動は管理者として実行するようにしてください。

上図のようなウインドウが表示されたら、「Type Libraries」というルートアイテムを開きます。 この中には各タイプライブラリの表示名が列挙されるため、 これを手がかりに目的のタイプライブラリを探すことになります。 今回は、Wordのタイプライブラリを確認するということで、 Wordという語句に注目していたところ次のようなアイテムを発見することができました。

win32という箇所には、タイプライブラリを格納するDLLのパスやタイプライブラリ自身のパスが表示されます。 上記では、タイプライブラリ自身のパスが表示されているため、 WordのタイプライブラリがMSWORD.OLBであることが分かります。 後はこれを「File」メニューの「ViewTypeLib」で開けば、 タイプライブラリの中身を確認することができます。

タイプライブラリを開くと、列挙型やインターフェース、coclassなどの一覧が表示されます。 これら1つの1つの情報をタイプ情報と呼びます。 問題は、このタイプ情報からどのようにしてWordを操作する手段を見出すかですが、 取り敢えず上図のように_Applicationインターフェースを選択してみてください。 下にスクロールすると分かるように、このインターフェースはVisibleというプロパティを持っていますが、 このプロパティこそが前節のIDispatch::Invokeで呼び出したプロパティです。 この事を証明するために次のコードを検討します。

#include <windows.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	IDispatch *pApplication;
	ITypeInfo *pTypeInfo;
	TYPEATTR  *pTypeAttr;
	CLSID     clsid;
	HRESULT   hr;
	VARIANT   var;
	TCHAR     szGuid[256];

	CoInitialize(NULL);
	
	hr = CLSIDFromProgID(L"Word.Application", &clsid);
	if (FAILED(hr))
		return 0;
	
	hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&pApplication));
	if (FAILED(hr))
		return 0;
	
	pApplication->GetTypeInfo(0, 0, &pTypeInfo);
	if (FAILED(hr))
		return 0;
	
	pTypeInfo->GetTypeAttr(&pTypeAttr);
	StringFromGUID2(pTypeAttr->guid, szGuid, sizeof(szGuid) / sizeof(TCHAR));
	MessageBox(NULL, szGuid, TEXT("OK"), MB_OK);

	pTypeInfo->ReleaseTypeAttr(pTypeAttr);
	pTypeInfo->Release();
	pApplication->Release();
	CoUninitialize();
	
	return 0;
}

このCoCreateInstanceの呼び出しによって確かに言えることは、 Word内で何らかのオブジェクトが作成され、それをアプリケーションがIDispatchで識別しているという点です。 しかし、どのようなオブジェクトであるかを特定しなければ、呼び出すことのできるメソッドやプロパティも分かりませんから、 IDispatch::GetTypeInfoを呼び出してタイプ情報を取得します。 ここで取得できるタイプ情報はインターフェースであり、 タイプライブラリ内で定義されているいずれかのインターフェースに該当します。 その後、ITypeInfo::GetTypeAttrでタイプ情報の属性を取得し、 インターフェースのIIDを格納するguidメンバを文字列として表示していますが、この表示結果に注目してください。 興味深いことに、先に示した_Applicationのuuidと同一の値になっています。 これはどういうことかというと、CoCreateInstanceで作成されたオブジェクトを_Applicationで識別できることを意味します。 つまり、CoCreateInstanceに_ApplicationのIID(uuidの値)を指定しても関数は問題なく成功します。 これを理解しながらもIID_IDispatchを指定するのは、_Applicationの定義というものがWindows SDKに含まれていないからであり、 さらに_ApplicationがIDispatchを継承するという関係上、 _Applicationで識別できるオブジェクトはIDispatchでも識別できるからです。

_Applicationで識別できるオブジェクトをApplicationオブジェクトと呼ぶことにして、先の話を整理してみましょう。 まず、Wordを起動したらApplicationオブジェクトが作成され、これはIDispatchで識別することができます。 つまり、IDispatch::Invokeの呼び出しは、Applicationオブジェクトが実装する適切なメソッドやプロパティの呼び出しにつながります。 このことから分かるように、IDispatch::Invokeに指定できるメソッドやプロパティは、 Applicationオブジェクトが実装するメソッドやプロパティに依存します。 このリストは_Applicationの定義から分かりますから、 上図の「dispinterface _Application」以下に表示されているCreatorやDocumentsなど全てのメソッドやプロパティは、 IDispatch::Invokeによって呼び出し可能ということになります。 IDispatchを使用する場合は、それが現在どのようなオブジェクトを識別しているのかを特定すると共に、 どのようなメソッドやプロパティを実装しているのかも特定しておくことが重要といえます。

OleView.exeに限らず、タイプライブラリの中身を走査することは通常のアプリケーションからでも可能です。 これにはまず、LoadTypeLibを呼び出してITypeLibのポインタを取得します。

HRESULT LoadTypeLib(
  LPCOLESTR szFile,
  ITypeLib **pptlib
);

szFileは、タイプライブラリのフルパスを指定します。 タイプライブラリを格納するDLLのパスでも構いません。 pptlibは、ITypeLibへのポインタを受け取る変数のアドレスを指定します。

今回のプログラムは、タイプライブラリに含まれているインターフェース名を左のリストボックスに列挙します。 これが選択された場合は、右のリストボックスにメソッドとプロパティを列挙します。

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND     hwndListBoxLeft = NULL;
	static HWND     hwndListBoxRight = NULL;
	static int      nIndexs[4096] = {0};
	static ITypeLib *pTypeLib = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		int       i, j, nCount;
		HRESULT   hr;
		BSTR      *pName;
		TYPEATTR  *pTypeAttr;
		ITypeInfo *pTypeInfo;
		
		CoInitialize(NULL);

		hr = LoadTypeLib(TEXT("C:\\Program Files\\Microsoft Office\\Office12\\MSWORD.OLB"), &pTypeLib);
		if (FAILED(hr))
			return 0;
		
		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
	
		nCount = pTypeLib->GetTypeInfoCount();
		for (i = 0, j = 0; i < nCount; i++) {
			pTypeLib->GetTypeInfo(i, &pTypeInfo);
			pTypeInfo->GetTypeAttr(&pTypeAttr);
			if (pTypeAttr->typekind == TKIND_DISPATCH) {				
				pTypeLib->GetDocumentation(i, (BSTR *)&pName, NULL, NULL, NULL);
				SendMessageW(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)pName);
				nIndexs[j++] = i;
			}
			pTypeInfo->ReleaseTypeAttr(pTypeAttr);
			pTypeInfo->Release();	
		}

		return 0;
	}

	case WM_COMMAND: {
		int       i;
		int       nIndex;
		UINT      uSize;
		BSTR      *pName;
		VARDESC   *pVarDesc;
		FUNCDESC  *pFuncDesc;
		TYPEATTR  *pTypeAttr;
		ITypeInfo *pTypeInfo;

		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;

		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);

		nIndex = (int)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0);

		pTypeLib->GetTypeInfo(nIndexs[nIndex], &pTypeInfo);
		pTypeInfo->GetTypeAttr(&pTypeAttr);
		
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)TEXT("Method:"));

		for (i = 0; i < pTypeAttr->cFuncs; i++) {
			pTypeInfo->GetFuncDesc(i, &pFuncDesc);
			pTypeInfo->GetNames(pFuncDesc->memid, (BSTR *)&pName, 1, &uSize);
			SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)pName);
			pTypeInfo->ReleaseFuncDesc(pFuncDesc);
		}
		
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)TEXT("Property:"));

		for (i = 0; i < pTypeAttr->cVars; i++) {
			pTypeInfo->GetVarDesc(i, &pVarDesc);
			pTypeInfo->GetNames(pVarDesc->memid, (BSTR *)&pName, 1, &uSize);
			SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)pName);
			pTypeInfo->ReleaseVarDesc(pVarDesc);
		}
		
		pTypeInfo->ReleaseTypeAttr(pTypeAttr);
		pTypeInfo->Release();

		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBoxLeft, 0, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 2, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		if (pTypeLib != NULL)
			pTypeLib->Release();
		CoUninitialize();
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

LoadTypeLibでITypeLibへのポインタを取得したら、GetTypeInfoCountを呼び出してタイプ情報の総数を取得します。 個々のタイプ情報はGetTypeInfoで取得することができ、GetTypeAttrを呼び出すとタイプ情報の属性を取得できます。 pTypeAttr->typekindがTKIND_INTERFACEであるということは、 このタイプ情報がメソッドやプロパティを含むインターフェースということなので、 GetDocumentationでインターフェースの名前を取得し、これを左のリストボックスに追加します。 nIndexsという配列にインデックスを格納しているのは、 リストボックスが選択された際に関連するタイプ情報のインデックスを参照するためです。 TKIND_DISPATCHでインターフェースが列挙されない場合は、TKIND_INTERFACEで試してみてください。

左のリストボックスでインターフェースが選択されたら、 そのインターフェースが定義するメソッドやプロパティを右のリストボックスに列挙することになります。 まず、nIndexs[nIndex]とすることでタイプ情報のインデックスを取得し、 これをGetTypeInfoに指定することでITypeInfoへのポインタを取得します。 次にGetTypeAttrでTYPEATTR構造体を取得しているのは、 この構造体にメソッド数を格納するメンバ(cFuncs)とプロパティ数を格納するメンバ(cVars)が含まれているからです。 メソッド名の取得は、GetFuncDescで取得できるFUNCDESC構造体のmemidをGetNamesに指定します。 memidはDISPIDと同じ意味を持ちますが、タイプライブラリ内ではmemidと呼ばれるようです。 プロパティ名の取得は、GetVarDescで取得できるVARDESC構造体のmemidをGetNamesに指定します。 GetNamesの第2引数には名前を受け取る変数のアドレスを指定し、第3引数には第2引数の要素数を指定します。

タイプライブラリとレジストリ

今回はLoadTypeLibを呼び出してITypeLibへのポインタを取得しましたが、 これはLoadRegTypeLibで行うこともできます。 この場合はタイプライブラリのGUIDを指定しなければなりませんが、 これは次のレジストリキーから確認することができます。

HKEY_CLASSES_ROOT\TypeLib

上記キーの下にはGUIDを名前としてサブキーが存在しますから、 この名前をLoadRegTypeLibを指定すればよいことになります。 サブキーを展開すれば、登録されているタイプライブラリの情報を確認することができます。



戻る