EternalWindows
プロファイル API / パラメータの出力

前節では、メソッドのプロトタイプを表示する部分まで説明したため、 今回はメソッドの各パラメータを扱う方法を説明します。 パラメータの取得は、次のループ文で行われています。

for (i = 0; i < pArgInfo->numRanges; i++) {
	lplpValue = (LPVOID *)pArgInfo->ranges[i].startAddress;

	type = GetElementType(&p, &typeDef, &bRef, &bArray);
	GetElementTypeName(pMetaDataImport, type, typeDef, bRef, bArray, szType);

	if (bArray) {
		GetArray(lplpValue, type, szValue);
		wsprintfW(szBuf, L"%s = %s", szType, szValue);
		WriteLogFile(szBuf);
	}
	else if (type == ELEMENT_TYPE_CLASS) {
		ClassID  classId;
		ObjectID oid = *(ObjectID *)(lplpValue);

		m_pProfilerInfo2->GetClassFromObject(oid, &classId);
		GetClass(pMetaDataImport, classId, szType, *lplpValue);
	}
	else if (type == ELEMENT_TYPE_VALUETYPE) {
		ClassID  classId;
		ModuleID moduleId;

		m_pProfilerInfo2->GetFunctionInfo(functionId, 0, &moduleId, 0);
		m_pProfilerInfo2->GetClassFromToken(moduleId, typeDef, &classId);
		
		GetClass(pMetaDataImport, classId, szType, lplpValue);
	}
	else {
		GetValue(bRef ? *lplpValue : lplpValue, type, szValue);
		wsprintfW(szBuf, L"%s = %s", szType, szValue);
		WriteLogFile(szBuf);
	}
}

パラメータは、COR_PRF_FUNCTION_ARGUMENT_INFO構造体のranges[i].startAddressから参照できます。 GetElementTypeを呼び出して型の情報を取得しているのは、パラメータの扱いが型によって異なるからです。 たとえば、パラメータがint型の変数である場合は、lplpValueがその変数のアドレスを格納していますが、 パラメータがint型の参照である場合は、lplpValueが変数へのアドレスのアドレスを格納しています。 int型のような単純型はGetValueを通じて値を取得することになりますが、 第1引数ではbRefによって指定するアドレスを変えているのが分かると思います。 GetValueの実装は次のようになっています。

void CProfilerCallback::GetValue(LPVOID lpValue, CorElementType type, LPWSTR lpszBuf)
{
	if (type == ELEMENT_TYPE_VOID)
		lstrcpyW(lpszBuf, L"void");
	else if (type == ELEMENT_TYPE_BOOLEAN) {
		if (*(bool *)lpValue == true)
			lstrcpyW(lpszBuf, L"true");
		else
			lstrcpyW(lpszBuf, L"false");
	}
	else if (type == ELEMENT_TYPE_CHAR)
		wsprintfW(lpszBuf, L"%c", *(char *)lpValue);	
	else if (type == ELEMENT_TYPE_I1)
		wsprintfW(lpszBuf, L"%d", *(char *)lpValue);
	else if (type == ELEMENT_TYPE_U1)
		wsprintfW(lpszBuf, L"%u", *(unsigned char *)lpValue);
	else if (type == ELEMENT_TYPE_I2)
		wsprintfW(lpszBuf, L"%d", *(short *)lpValue);
	else if (type == ELEMENT_TYPE_U2)
		wsprintfW(lpszBuf, L"%u", *(unsigned short *)lpValue);
	else if (type == ELEMENT_TYPE_I4)
		wsprintfW(lpszBuf, L"%d", *(int *)lpValue);
	else if (type == ELEMENT_TYPE_U4)
		wsprintfW(lpszBuf, L"%u", *(unsigned int *)lpValue);
	else if (type == ELEMENT_TYPE_I8)
		wsprintfW(lpszBuf, L"%d", *(LONGLONG *)lpValue);
	else if (type == ELEMENT_TYPE_U8)
		wsprintfW(lpszBuf, L"%u", *(ULONGLONG *)lpValue);
	else if (type == ELEMENT_TYPE_STRING)
		lstrcpyW(lpszBuf, GetString(*((LPBYTE *)lpValue)));
	else if (type == ELEMENT_TYPE_CLASS)
		lstrcpyW(lpszBuf, L"---");
	else if (type == ELEMENT_TYPE_VALUETYPE)
		lstrcpyW(lpszBuf, L"---");
	else if (type == ELEMENT_TYPE_OBJECT)
		GetObject(lpValue, type, lpszBuf);
	else
		lstrcpyW(lpszBuf, L"?");
}

基本的には型に応じてキャストし、値を参照するだけです。 本当はfloatを表すELEMENT_TYPE_R4やdoubleを表すELEMENT_TYPE_R8も処理したいところですが、 wsprintfでは小数点を表示できないため見送っています。 ELEMENT_TYPE_STRINGにおけるGetStringは、次のように実装されています。

LPWSTR CProfilerCallback::GetString(LPBYTE lp)
{
	HRESULT hr;
	LPWSTR  lpString;
	ULONG   uBufferOffset;
	
	hr = m_pProfilerInfo2->GetStringLayout(NULL, NULL, &uBufferOffset);
	if (FAILED(hr))
		return NULL;
	
	lpString = (LPWSTR)(lp + uBufferOffset);
	
	return lpString;
}

GetStringLayoutを呼び出せば、実際に文字列が格納されている位置へのオフセットを取得できます。 これをlpに加算すれば、実際に文字列を参照できます。

パラメータが配列である場合は、GetArrayが呼ばれます。

void CProfilerCallback::GetArray(LPVOID lpValue, CorElementType type, LPWSTR lpszBuf)
{
	ObjectID oid = *(ObjectID *)(lpValue);
	ULONG32  i, uDimSize;
	int      nDimLowerBound;
	PBYTE    pData;
	WCHAR    szValue[256], szBuf[1024];

	m_pProfilerInfo2->GetArrayObjectInfo(oid, 1, &uDimSize, &nDimLowerBound, &pData);

	lstrcpyW(lpszBuf, L"{");

	for (i = nDimLowerBound; i < uDimSize; i++) {
		GetValue(pData, type, szValue);
		wsprintfW(szBuf, L"%s", szValue);
		if (i + 1 != uDimSize)
			lstrcatW(szBuf, L", ");

		lstrcatW(lpszBuf, szBuf);

		if (type == ELEMENT_TYPE_I4)
			pData += sizeof(int);
		else if (type == ELEMENT_TYPE_CHAR)
			pData += sizeof(char);
		else if (type == ELEMENT_TYPE_I1 || type == ELEMENT_TYPE_U1)
			pData += sizeof(BYTE);
		else if (type == ELEMENT_TYPE_I2 || type == ELEMENT_TYPE_U2)
			pData += sizeof(short);
		else if (type == ELEMENT_TYPE_I4 || type == ELEMENT_TYPE_U4)
			pData += sizeof(int);
		else if (type == ELEMENT_TYPE_I8 || type == ELEMENT_TYPE_U8)
			pData += sizeof(LONGLONG);
		else if (type == ELEMENT_TYPE_BOOLEAN)
			pData += sizeof(bool);
		else if (type == ELEMENT_TYPE_STRING)
			pData += sizeof(ObjectID);
		else if (type == ELEMENT_TYPE_OBJECT)
			pData += sizeof(ObjectID);
		else if (type == ELEMENT_TYPE_CLASS)
			pData += sizeof(ObjectID);
		else
			;
	}

	lstrcatW(lpszBuf, L"}");
}

配列や文字列、クラスといった参照型パラメータの先頭には、ObjectIDが格納されることになっています。 ただしIDといっても、正確にはオブジェクトへのアドレスを識別しているだけです。 配列を識別するObjectIDをGetArrayObjectInfoに指定すれば、配列の要素数やインデックスとして指定する最小値を取得できます。 pDataは配列の先頭要素を指しており、次の要素を参照する場合は、型のサイズ分だけ加算します。

文字列を取得するためにGetStringLayout、配列を取得するためにGetArrayObjectInfoという専用の関数を呼び出したように、 クラスや構造体の情報を取得するにはGetClassLayoutを呼び出します。 このメソッドを呼び出すにはClassIDというものが必要になりますが、これを取得する方法がクラスと構造体で異なります。

else if (type == ELEMENT_TYPE_CLASS) {
	ClassID  classId;
	ObjectID oid = *(ObjectID *)(lplpValue);

	m_pProfilerInfo2->GetClassFromObject(oid, &classId);
	GetClass(pMetaDataImport, classId, szType, *lplpValue);
}
else if (type == ELEMENT_TYPE_VALUETYPE) {
	ClassID  classId;
	ModuleID moduleId;

	m_pProfilerInfo2->GetFunctionInfo(functionId, 0, &moduleId, 0);
	m_pProfilerInfo2->GetClassFromToken(moduleId, typeDef, &classId);
	
	GetClass(pMetaDataImport, classId, szType, lplpValue);
}

クラスのような参照型パラメータの先頭にはObjectIDが格納されているため、 これを取得してGetClassFromObjectに指定します。 そうすると、オブジェクトの型であるクラスのClassIDを取得できます。 一方、値型である構造体の場合は、GetFunctionInfoでModuleIDを取得し、 これとTypeDefトークンGetClassFromTokenに指定することでClassIDを取得します。 TypeDefトークンは、GetElementTypeの呼び出しによって既に取得済みです。 ちなみに、構造体を識別するELEMENT_TYPE_VALUETYPEのVALUETYPEという単語は、 構造体がSystem.ValueTypeを継承していることに由来していると思われます。 GetClassの実装は次のようになっています。

void CProfilerCallback::GetClass(IMetaDataImport *pMetaDataImport, ClassID classId, LPWSTR lpszClassName, LPVOID lpValue)
{
	WCHAR            szBuf[1024];
	LPBYTE           lpAddress  = (LPBYTE)lpValue;
	ULONG            i, uFieldOffsetCount = 0;
	COR_FIELD_OFFSET *pFieldOffset;
	
	m_pProfilerInfo2->GetClassLayout(classId, NULL, 0, &uFieldOffsetCount, NULL);
	if (uFieldOffsetCount == 0)
		return;
	
	pFieldOffset = (COR_FIELD_OFFSET *)HeapAlloc(GetProcessHeap(), 0, sizeof(COR_FIELD_OFFSET) * uFieldOffsetCount);
	m_pProfilerInfo2->GetClassLayout(classId, pFieldOffset, uFieldOffsetCount, &uFieldOffsetCount, NULL);

	for (i = 0; i < uFieldOffsetCount; i++) {
		PCCOR_SIGNATURE pSig, p;
		WCHAR           szName[256], szType[256], szValue[256];
		CorElementType  type;
		BOOL            bArray, bRef;
		mdTypeDef       typeDef;

		pMetaDataImport->GetFieldProps(pFieldOffset[i].ridOfField, NULL, szName, 256, NULL, NULL, &pSig, NULL, NULL, NULL, NULL);

		p = &pSig[1];
		type = GetElementType(&p, &typeDef, &bRef, &bArray);
		GetElementTypeName(pMetaDataImport, type, typeDef, bRef, bArray, szType);

		if (bArray)
			GetArray((LPVOID)(lpAddress + pFieldOffset[i].ulOffset), type, szValue);
		else
			GetValue((LPVOID)(lpAddress + pFieldOffset[i].ulOffset), type, szValue);

		wsprintfW(szBuf, L"%s.%s %s = %s", lpszClassName, szType, szName, szValue);
		WriteLogFile(szBuf);
	}

	HeapFree(GetProcessHeap(), 0, pFieldOffset);
}

GetClassは、クラス(及び構造体)の中に存在するフィールドの値をファイルに出力します。 ただし、staticフィールドは対象になりません。 クラスないのフィールドはCOR_FIELD_OFFSET構造体の配列で表すことができ、これはGetClassLayoutで取得できます。 ただし、1回目の呼び出しの時点では必要なメモリを確保できていないので、 フィールドの数を取得してメモリを確保してから、2回目の呼び出しで配列を取得します。 COR_FIELD_OFFSET構造体には2つのメンバが存在し、ridOfFieldはメタデータのFieldDefトークンへのオフセットを格納しています。 これをGetFieldPropsに指定すればフィールドのシグネチャを取得でき、 pSig[1]からフィールドの型が分かります。 COR_FIELD_OFFSET構造体のもう1つのメンバはulOffsetであり、これはパラメータを格納しているアドレスへのオフセットです。 lpAddressに加算することで実際にパラメータへアクセスできます。

パラメータの値を取得するGetValueでは、型がELEMENT_TYPE_OBJECTのときにGetObjectを呼び出していました。 ELEMENT_TYPE_OBJECTはobject型を表しており、たとえば次のように使用されます。

object[] objects = new object[2] {"def", 8};

通常、配列を定義する場合はint[]のように記述しますが、これでは当然ながら数値の要素しか指定できません。 しかし、object[]ならば、どのような要素でも指定できます。 理由は、.NETのオブジェクトがSystem.Objectから派生するようになっているからです。 ここで少し面白いのは、参照型のobject型に対して、値型のint型を指定できるという点ですが、 このときには内部的に参照型のオブジェクトが作成されています。 この処理はボクシングと呼ばれていますが、オブジェクトがボクシングによって作成されたかどうかは、 GetBoxClassLayoutで分かります。

void CProfilerCallback::GetObject(LPVOID lpValue, CorElementType type, LPWSTR lpszBuf)
{
	HRESULT         hr;
	ClassID         classId;
	ObjectID        oid = *(ObjectID *)(lpValue);
	ModuleID        moduleId;
	mdTypeDef       typeDef;
	ULONG32         uBufferOffset;
	LPVOID          lpAddress;
	WCHAR           szType[256];
	IMetaDataImport *pMetaDataImport;
	
	m_pProfilerInfo2->GetClassFromObject(oid, &classId);
	hr = m_pProfilerInfo2->GetBoxClassLayout(classId, &uBufferOffset);
	if (SUCCEEDED(hr))
		lpAddress = (LPVOID)(oid + uBufferOffset);
	else
		lpAddress = lpValue;
	
	m_pProfilerInfo2->GetClassIDInfo(classId, &moduleId, &typeDef);
	m_pProfilerInfo2->GetModuleMetaData(moduleId, ofRead, IID_IMetaDataImport, (IUnknown **)&pMetaDataImport);
	pMetaDataImport->GetTypeDefProps(typeDef, szType, 256, NULL, NULL, NULL);
	pMetaDataImport->Release();
	
	type = GetElementTypeFromClassName(szType);
	
	GetValue(lpAddress, type, lpszBuf);
}

ELEMENT_TYPE_OBJECT型パラメータの先頭には、ObjectIDが格納されています。 GetClassFromObjectによってオブジェクトのClassIDを取得したら、GetBoxClassLayoutを呼び出します。 これが成功したら、ObjectIDはボクシングによって作成されたオブジェクトを識別しており、 第2引数に返されるオフセットを足すことで実際にパラメータを参照できます。 一方、GetBoxClassLayoutが失敗した場合は、object型に格納されたのが値型ではなく参照型であることを意味します。 たとえば、文字列型を格納したのであれば、ObjectIDは既に文字列を識別しているため、オフセットなどは必要ありません。 パラメータの型を調べるには、GetClassIDInfoを呼び出してトークンを取得し、 GetTypeDefPropsを呼び出して型の名前を取得します。 そして、自作メソッドのGetElementTypeFromClassNameによって、名前からCorElementTypeに変換します。


戻る