EternalWindows
メタデータ API / 型の列挙

前節では、メタデータから特定の型を検索しましたが、 定義されているすべての型を列挙するようなことも可能です。 これには、IMetaDataImport::EnumTypeDefsを呼び出します。

HRESULT EnumTypeDefs (
  HCORENUM   *phEnum, 
  mdTypeDef  rTypeDefs[],
  ULONG      cMax, 
  ULONG      *pcTypeDefs
);

phEnumは、列挙子を受け取る変数のアドレスを指定します。 rTypeDefsは、TypeDefトークンを受け取る配列を指定します。 cMaxは、rTypeDefsのサイズを指定します。 pcTypeDefsは、rTypeDefsに格納されたTypeDefトークンの数を受け取る変数のアドレスを指定します。

IMetaDataImportは、メタデータに含まれる情報を統一的に扱うことができます。 たとえば、mdTypeDefを列挙する場合はEnumTypeDefsを呼び出すわけですが、 対象をmdMethodDefにしたい場合はEnumMethodsを呼び出すします。 また、mdTypeDefの情報を取得する場合はGetTypeDefPropsを呼び出しましたが、 mdMethodDefの場合はGetMethodPropsを呼び出します。 メソッドは型に含まれるものですから、EnumMethodsやGetMethodPropsを呼び出すには、 事前にmdTypeDefを取得しておきます。

今回のプログラムは、メタデータに含まれる型を左のリストボックスに列挙します。 型が選択されると、右のリストボックスにその型のメソッドとフィールドを列挙します。

#include <windows.h>
#include <mscoree.h>
#include <cor.h>

void GetTypeData(IMetaDataImport *pMetaDataImport, mdTypeDef typeDef, LPWSTR lpszBuf);
void GetMethodData(IMetaDataImport *pMetaDataImport, mdMethodDef methodDef, LPWSTR lpszBuf);
void GetFieldData(IMetaDataImport *pMetaDataImport, mdFieldDef fieldDef, LPWSTR lpszBuf);
void GetElementTypeName(PCCOR_SIGNATURE *ppSig, IMetaDataImport *pMetaDataImport, LPWSTR lpszBuf);
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 IMetaDataDispenser *pMetaDataDispenser = NULL;
	static IMetaDataImport    *pMetaDataImport = NULL;
	static mdTypeDef          typeDefs[2048];

	switch (uMsg) {
	
	case WM_CREATE: {
		ULONG    i, uCount;
		HCORENUM hCorEnum;
		WCHAR    szBuf[2048];
		HRESULT  hr;

		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		CoInitialize(NULL);
		
		hr = CoCreateInstance(CLSID_CorMetaDataDispenser, 0, CLSCTX_INPROC_SERVER, IID_IMetaDataDispenser, (void **)&pMetaDataDispenser);
		if (FAILED(hr))
			return -1;

		hr = pMetaDataDispenser->OpenScope(L"C:\\ClassLibrary1.dll", ofRead, IID_IMetaDataImport, (IUnknown **)&pMetaDataImport);	
		if (FAILED(hr))
			return -1;

		hCorEnum = NULL;
		pMetaDataImport->EnumTypeDefs(&hCorEnum, typeDefs, sizeof(typeDefs), &uCount);

		for (i = 0; i < uCount; i++) {
			ZeroMemory(szBuf, sizeof(szBuf));
			GetTypeData(pMetaDataImport, typeDefs[i], szBuf);
			SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)szBuf);
		}

		return 0;
	}
	
	case WM_COMMAND: {
		DWORD       dwIndex;
		ULONG       i, uCount;
		HCORENUM    hCorEnum;
		mdMethodDef methodDefs[2048];
		mdFieldDef  fieldDefs[2048];
		WCHAR       szBuf[2048];
		
		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;
		
		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);
		
		dwIndex = (DWORD)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0);

		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)L"-----Method-----");
		hCorEnum = NULL;
		pMetaDataImport->EnumMethods(&hCorEnum, typeDefs[dwIndex], methodDefs, sizeof(methodDefs), &uCount);
		for (i = 0; i < uCount; i++) {
			ZeroMemory(szBuf, sizeof(szBuf));
			GetMethodData(pMetaDataImport, methodDefs[i], szBuf);
			SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		}
		pMetaDataImport->CloseEnum(hCorEnum);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)L"");

		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)L"-----Field-----");
		hCorEnum = NULL;
		pMetaDataImport->EnumFields(&hCorEnum, typeDefs[dwIndex], fieldDefs, sizeof(fieldDefs), &uCount);
		for (i = 0; i < uCount; i++) {
			ZeroMemory(szBuf, sizeof(szBuf));
			GetFieldData(pMetaDataImport, fieldDefs[i], szBuf);
			SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		}
		pMetaDataImport->CloseEnum(hCorEnum);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)L"");

		return 0;
	}

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

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

	default:
		break;

	}

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

void GetTypeData(IMetaDataImport *pMetaDataImport, mdTypeDef typeDef, LPWSTR lpszBuf)
{
	WCHAR szName[256];
	DWORD dwAttr;

	pMetaDataImport->GetTypeDefProps(typeDef, szName, 256, NULL, &dwAttr, NULL);
	if (IsTdInterface(dwAttr))
		lstrcatW(lpszBuf, L"Interface ");
	if (IsTdPublic(dwAttr))
		lstrcatW(lpszBuf, L"Public ");
	if (IsTdAbstract(dwAttr))
		lstrcatW(lpszBuf, L"Abstract ");
	if (IsTdSealed(dwAttr))
		lstrcatW(lpszBuf, L"Sealed ");

	lstrcatW(lpszBuf, szName);
}

void GetMethodData(IMetaDataImport *pMetaDataImport, mdMethodDef methodDef, LPWSTR lpszBuf)
{
	int             i, nArg;
	WCHAR           szName[256], szType[256], szParam[256];
	DWORD           dwAttr;
	PCCOR_SIGNATURE pSig, p;
	HCORENUM        hCorEnum;
	ULONG           uCount;
	mdParamDef      paramDefs[2048];

	pMetaDataImport->GetMethodProps(methodDef, NULL, szName, 256, NULL, &dwAttr, &pSig, NULL, NULL, NULL);
	if (IsMdPrivate(dwAttr))
		lstrcatW(lpszBuf, L"Private ");
	if (IsMdPublic(dwAttr))
		lstrcatW(lpszBuf, L"Public ");
	if (IsMdStatic(dwAttr))
		lstrcatW(lpszBuf, L"Static ");
	if (IsMdVirtual(dwAttr))
		lstrcatW(lpszBuf, L"Virtual ");

	p = &pSig[2];
	GetElementTypeName(&p, pMetaDataImport, szType);
	lstrcatW(lpszBuf, szType);
	
	lstrcatW(lpszBuf, L" ");
	lstrcatW(lpszBuf, szName);
	lstrcatW(lpszBuf, L"(");

	hCorEnum = NULL;
	pMetaDataImport->EnumParams(&hCorEnum, methodDef, paramDefs, sizeof(paramDefs), &uCount);

	nArg = pSig[1];
	for (i = 0; i < nArg; i++) {
		GetElementTypeName(&p, pMetaDataImport, szType);
		lstrcatW(lpszBuf, szType);

		pMetaDataImport->GetParamProps(paramDefs[i], NULL, NULL, szParam, 256, NULL, NULL, NULL, NULL, NULL);

		lstrcatW(lpszBuf, L" ");
		lstrcatW(lpszBuf, szParam);
		if (i + 1 != nArg)
			lstrcatW(lpszBuf, L", ");
	}

	lstrcatW(lpszBuf, L")");
}

void GetFieldData(IMetaDataImport *pMetaDataImport, mdFieldDef fieldDef, LPWSTR lpszBuf)
{
	WCHAR           szName[256], szType[256];
	DWORD           dwAttr;
	PCCOR_SIGNATURE pSig, p;

	pMetaDataImport->GetFieldProps(fieldDef, NULL, szName, 256, NULL, &dwAttr, &pSig, NULL, NULL, NULL, NULL);
	if (IsFdStatic(dwAttr))
		lstrcatW(lpszBuf, L"Static ");
	if (IsFdLiteral(dwAttr))
		lstrcatW(lpszBuf, L"Const ");
	if (IsFdPrivate(dwAttr))
		lstrcatW(lpszBuf, L"Private ");
	if (IsFdPublic(dwAttr))
		lstrcatW(lpszBuf, L"Public ");

	p = &pSig[1];
	GetElementTypeName(&p, pMetaDataImport, szType);
	lstrcatW(lpszBuf, szType);

	lstrcatW(lpszBuf, L" ");
	lstrcatW(lpszBuf, szName);
}

void GetElementTypeName(PCCOR_SIGNATURE *ppSig, IMetaDataImport *pMetaDataImport, LPWSTR lpszBuf)
{
	CorElementType type = (CorElementType)**ppSig;
	BOOL           bRef, bArray;
	WCHAR          szType[256];

	*ppSig += 1;

	if (type == ELEMENT_TYPE_SZARRAY) {
		type = (CorElementType)**ppSig;
		*ppSig += 1;
		bArray = TRUE;
	}
	else
		bArray = FALSE;
	
	if (type == ELEMENT_TYPE_BYREF) {
		type = (CorElementType)**ppSig;
		*ppSig += 1;
		bRef = TRUE;
	}
	else
		bRef = FALSE;

	if (type == ELEMENT_TYPE_VALUETYPE || type == ELEMENT_TYPE_CLASS) {
		mdTypeDef typeDef;
		*ppSig += CorSigUncompressToken(*ppSig, &typeDef);
		pMetaDataImport->GetTypeDefProps(typeDef, lpszBuf, 256, NULL, NULL, NULL);
		return;
	}
	
	if (type == ELEMENT_TYPE_VOID)
		lstrcpyW(szType, L"void");
	else if (type == ELEMENT_TYPE_BOOLEAN)
		lstrcpyW(szType, L"bool");
	else if (type == ELEMENT_TYPE_CHAR)
		lstrcpyW(szType, L"char");
	else if (type == ELEMENT_TYPE_I1)
		lstrcpyW(szType, L"sbyte");
	else if (type == ELEMENT_TYPE_U1)
		lstrcpyW(szType, L"byte");
	else if (type == ELEMENT_TYPE_I2)
		lstrcpyW(szType, L"short");
	else if (type == ELEMENT_TYPE_U2)
		lstrcpyW(szType, L"ushort");
	else if (type == ELEMENT_TYPE_I4)
		lstrcpyW(szType, L"int");
	else if (type == ELEMENT_TYPE_U4)
		lstrcpyW(szType, L"uint");
	else if (type == ELEMENT_TYPE_I8)
		lstrcpyW(szType, L"long");
	else if (type == ELEMENT_TYPE_U8)
		lstrcpyW(szType, L"ulong");
	else if (type == ELEMENT_TYPE_STRING)
		lstrcpyW(szType, L"string");
	else if (type == ELEMENT_TYPE_OBJECT)
		lstrcpyW(szType, L"object");
	else
		wsprintfW(szType, L"unknown-type %x", type);

	if (bArray)
		wsprintfW(lpszBuf, L"%s[]", szType);
	else if (bRef)
		wsprintfW(lpszBuf, L"ref %s", szType);
	else
		lstrcpyW(lpszBuf, szType);
}

WM_CREATEでは、EnumTypeDefsを呼び出してmdTypeDefの配列を取得しています。 これは、WM_COMMANDで参照されることになるため、staticで宣言されています。 for文では、型の数だけ左のリストボックスに型の名前を追加します。 この名前が選択されると、WM_COMMANDが生成されて右のリストボックスが更新されます。

WM_COMMANDではLB_GETCURSELメッセージを通じて、選択されたアイテムのインデックスを取得します。 これをtypeDefsの添え字に指定してEnumMethodsを呼び出せば、 型に含まれるメソッドを格納した配列を取得できます。 GetMethodDataはメソッドのデータを取得する自作関数ですが、 まず次の部分を確認します。

pMetaDataImport->GetMethodProps(methodDef, NULL, szName, 256, NULL, &dwAttr, &pSig, NULL, NULL, NULL);
if (IsMdPrivate(dwAttr))
	lstrcatW(lpszBuf, L"Private ");
if (IsMdPublic(dwAttr))
	lstrcatW(lpszBuf, L"Public ");
if (IsMdStatic(dwAttr))
	lstrcatW(lpszBuf, L"Static ");
if (IsMdVirtual(dwAttr))
	lstrcatW(lpszBuf, L"Virtual ");

GetMethodPropsを呼び出すと、第3引数にメソッドの名前が返り、第6引数にメソッドの属性が返ります。 この属性はCorMethodAttr列挙型で表され、たとえばIsMdPrivateの結果が真である場合は、メソッドがprivateに定義されていることが分かります。 こうした要領はmdTypeDefのときと非常に似ていますが、メソッドの場合はこうした情報だけでは不足しているはずです。 たとえば、メソッドの戻り値や引数の型を知りたいと思うはずです。 こうした情報はシグネチャと呼ばれ、GetMethodPropsの第7引数を通じて取得できます。 シグネチャはPCCOR_SIGNATUREで識別でき、配列のように扱うことになりますが、 何番目の要素にどのような情報が格納されているのでしょうか。 MSDNマガジンの記事によれば、次のような情報が格納されているとされています。

pSig[0] : 呼び出し方式
pSig[1] : 引数の数
pSig[2] : 戻り値の型
pSig[3] : 引数1の型
pSig[4] : 引数2の型
...

呼び出し方式は、CorCallingConvention列挙型の値が格納されます。 一方、戻り値と引数の型はCorElementType列挙型の値が格納されます。 たとえば、値がELEMENT_TYPE_I4である場合は、int型ということになります。 ただし、場合によっては型の情報は2つの要素にまたがることがある点に注意してください。 もし型がint[]である場合は、pSig[2]がになり、 pSig[3]がELEMENT_TYPE_I4になるため、メソッドの第1引数はpSig[3]ではなく、pSig[4]に格納されることになるのです。 こうしたことから、型の参照はpSig[2]のように行うことはできず、次のように行っています。

p = &pSig[2];
GetElementTypeName(&p, pMetaDataImport, szType);
lstrcatW(lpszBuf, szType);

pSig[2]を指すpという変数を用意し、このアドレスをGetElementTypeNameという自作関数に渡すようにします。 アドレスを渡すということは、pが指し示す内容がpSig[3]になる場合もあれば、pSig[4]になる場合もあるということです。 関数の内部は次のようになっています。

void GetElementTypeName(PCCOR_SIGNATURE *ppSig, IMetaDataImport *pMetaDataImport, LPWSTR lpszBuf)
{
	CorElementType type = (CorElementType)**ppSig;
	BOOL           bRef, bArray;
	WCHAR          szType[256];

	*ppSig += 1;

	if (type == ELEMENT_TYPE_SZARRAY) {
		type = (CorElementType)**ppSig;
		*ppSig += 1;
		bArray = TRUE;
	}
	else
		bArray = FALSE;
	
	if (type == ELEMENT_TYPE_BYREF) {
		type = (CorElementType)**ppSig;
		*ppSig += 1;
		bRef = TRUE;
	}
	else
		bRef = FALSE;

	if (type == ELEMENT_TYPE_VALUETYPE || type == ELEMENT_TYPE_CLASS) {
		mdTypeDef typeDef;
		*ppSig += CorSigUncompressToken(*ppSig, &typeDef);
		pMetaDataImport->GetTypeDefProps(typeDef, lpszBuf, 256, NULL, NULL, NULL);
		return;
	}
	
	if (type == ELEMENT_TYPE_VOID)
		lstrcpyW(szType, L"void");
	else if (type == ELEMENT_TYPE_BOOLEAN)
		lstrcpyW(szType, L"bool");
	else if (type == ELEMENT_TYPE_CHAR)
		lstrcpyW(szType, L"char");
	else if (type == ELEMENT_TYPE_I1)
		lstrcpyW(szType, L"sbyte");
	else if (type == ELEMENT_TYPE_U1)
		lstrcpyW(szType, L"byte");
	else if (type == ELEMENT_TYPE_I2)
		lstrcpyW(szType, L"short");
	else if (type == ELEMENT_TYPE_U2)
		lstrcpyW(szType, L"ushort");
	else if (type == ELEMENT_TYPE_I4)
		lstrcpyW(szType, L"int");
	else if (type == ELEMENT_TYPE_U4)
		lstrcpyW(szType, L"uint");
	else if (type == ELEMENT_TYPE_I8)
		lstrcpyW(szType, L"long");
	else if (type == ELEMENT_TYPE_U8)
		lstrcpyW(szType, L"ulong");
	else if (type == ELEMENT_TYPE_STRING)
		lstrcpyW(szType, L"string");
	else if (type == ELEMENT_TYPE_OBJECT)
		lstrcpyW(szType, L"object");
	else
		wsprintfW(szType, L"unknown-type %x", type);

	if (bArray)
		wsprintfW(lpszBuf, L"%s[]", szType);
	else if (bRef)
		wsprintfW(lpszBuf, L"ref %s", szType);
	else
		lstrcpyW(lpszBuf, szType);
}

**ppSigによって型の値を取得できるため、これが終わればppSigを1つ進めます。 typeがELEMENT_TYPE_SZARRAYである場合は、今指しているppSigに配列の型(int[]場合はint)が格納されているため、 それをtypeに保存します。 typeがELEMENT_TYPE_BYREFである場合は型が参照であることを意味し、 この場合は配列と同じ要領で処理します。 typeがELEMENT_TYPE_VALUETYPEまたはELEMENT_TYPE_CLASSである場合は、型が構造体かクラスであることを意味します。 この場合は、CorSigUncompressTokenで型を表すTypeDefトークンを取得すると共に、ppSigを進めます。 それ以外の場合は、単純に型に関連する文字列をバッファにコピーすればよいでしょう。

hCorEnum = NULL;
pMetaDataImport->EnumParams(&hCorEnum, methodDef, paramDefs, sizeof(paramDefs), &uCount);

メソッドの引数に関するループに入る前に、 EnumParamsを呼び出してmdParamDefの配列を取得します。 このとき、第2引数には対象となるメソッドのmethodDefを指定します。

nArg = pSig[1];
for (i = 0; i < nArg; i++) {
	GetElementTypeName(&p, pMetaDataImport, szType);
	lstrcatW(lpszBuf, szType);

	pMetaDataImport->GetParamProps(paramDefs[i], NULL, NULL, szParam, 256, NULL, NULL, NULL, NULL, NULL);

	lstrcatW(lpszBuf, L" ");
	lstrcatW(lpszBuf, szParam);
	if (i + 1 != nArg)
		lstrcatW(lpszBuf, L", ");
}

lstrcatW(lpszBuf, L")");

引数の数はpSig[1]に格納されているため、これをループ回数として使用します。 pは引数を識別しており、GetElementTypeNameでその型を取得すると共に、 次の引数が識別されるようにします。 GetParamPropsによって、パラメータの名前が第4引数に返ります。

WM_COMMANDでは、EnumMethodsの他にEnumFieldsを呼び出していました。 これは、型に含まれるフィールドを返すことができます。 自作関数のGetFieldDataは、フィールドの情報を取得しています。

void GetFieldData(IMetaDataImport *pMetaDataImport, mdFieldDef fieldDef, LPWSTR lpszBuf)
{
	WCHAR           szName[256], szType[256];
	DWORD           dwAttr;
	PCCOR_SIGNATURE pSig, p;

	pMetaDataImport->GetFieldProps(fieldDef, NULL, szName, 256, NULL, &dwAttr, &pSig, NULL, NULL, NULL, NULL);
	if (IsFdStatic(dwAttr))
		lstrcatW(lpszBuf, L"Static ");
	if (IsFdLiteral(dwAttr))
		lstrcatW(lpszBuf, L"Const ");
	if (IsFdPrivate(dwAttr))
		lstrcatW(lpszBuf, L"Private ");
	if (IsFdPublic(dwAttr))
		lstrcatW(lpszBuf, L"Public ");

	p = &pSig[1];
	GetElementTypeName(&p, pMetaDataImport, szType);
	lstrcatW(lpszBuf, szType);

	lstrcatW(lpszBuf, L" ");
	lstrcatW(lpszBuf, szName);
}

GetFieldPropsを呼び出せば、フィールドの名前や属性、シグネチャなどを取得できます。 属性はCorFieldAttr列挙型で表され、IsFdStaticなどの定義を使用して判別できます。 シグネチャについては、最初の要素がCorCallingConvention列挙型で、 次の要素がCorElementType列挙型で思われます。


戻る