在 CLR 2.0 中 Reflection 部分做了不少改进,例如增加了仅用于 Reflection 的 Assembly 加载模式,MethodBody 等一组与 IL 函数体相关的对象,以及 Token 句柄等支持。详细的介绍可以参考 What's new in System.Reflection (and friends) 一文。
而其中某些功能还是非常具有吸引力的,例如通过 MethodBody 类可以对 IL 函数体信息进行获取,包括 IL 代码流、异常处理、局部变量以及堆栈等等信息,在编写底层代码时非常方便。下面将根据在 CLR 1.1 中实现相同功能的 MethodBody 类的过程,简要介绍 Metadata 中的函数定义与基本结构。因为本文主要目的是介绍 MethodBody 的获取流程,所以涉及到过于基础的知识就不一一展开了,有兴趣的朋友可以参考我 blog 上以前的相关文章。
首先我们仿照 CLR 2.0 定义与 MethodBody 相关的几个结构:
{
public enum ExceptionHandlingClauseOptions
{
Clause = 0,
Fault = 4,
Filter = 1,
Finally = 2
}
public class ExceptionHandlingClause // 异常处理
{
public Type CatchType { get; } // 捕获的目标异常类型
public int FilterOffset { get; } // 过滤代码的偏移
public ExceptionHandlingClauseOptions Flags { get; } // 过滤的类型
public int HandlerLength { get; } // 处理代码的长度
public int HandlerOffset { get; } // 处理代码的偏移
public int TryLength { get; } // 包含代码的长度
public int TryOffset { get; } // 包含代码的偏移
}
public class LocalVariableInfo // 局部变量
{
public virtual bool IsPinned { get; }
public virtual bool IsByRef { get; }
public virtual bool IsPointer { get; }
public virtual int LocalIndex { get; } // 局部变量索引
public virtual Type LocalType { get; } // 局部变量类型
}
public class MethodBody // 方法体
{
public byte[] GetILAsByteArray() { get; } // IL 代码流
public ExceptionHandlingClause[] ExceptionHandlingClauses { get; } // 异常处理语句集
public bool InitLocals { get; } // 是否初始化局部变量
public int LocalSignatureMetadataToken { get; } // 局部变量类型定义
public LocalVariableInfo[] LocalVariables { get; } // 局部变量集
public int MaxStackSize { get; } // 最大堆栈长度
}
}
熟悉 CLR PE 结构的朋友可以发现,这几个类型基本上是与 CLE PE 中对函数体的定义对应的,只不过将内部定义时的 Token 和 Signature 变成了运行时的 Reflection 类型。
因此要获取这些信息,我们可以分两步完成:
1.获取静态的函数体定义
2.将静态定义转换为动态 Reflection 类型
对第一步来说,实现的方法很多:
首先可以自己对 Metadata 结构进行解析,例如我以前 blog 上对 Metadata 结构进行分析的系列文章,就给出了在 Delphi- Jedi JCL 项目中提供的 Delphi 版本实现。这种方式实现的灵活性和扩展性非常强,但工作量以及对 CLR PE/Metadata 的理解也要去较高。有兴趣的朋友可以参考我以前的文章,这里就不罗嗦了。
其次可以通过 CLR 底层提供的 Unmanaged API 来完成,.Net Framework 自带的 Metadata Unmanaged API.doc 文档里面有较为详细的介绍。这种方案无需对 CLR PE 结构有太深入的理解,但功能上受到一定限制,例如对 MethodBody 就没有提供函数一级的支持。
另外一种相对较好的方法,是委托让 CLR 自己进行解析和处理,我们直接通过 Reflection 的方式去获取,并进行一定程度的解析。本文后面的实现就将使用此思路,Managed C# 处理上层逻辑,Managed C++ 处理底层 解析。
而在代码组织上,以名字空间 NSFocus.ILEngine.Model 定义基本的模型类;以名字空间 NSFocus.ILEngine.Helper 提供底层的辅助解析实现类;以名字空间 NSFocus.ILEngine.Core 提供高层组装支持,使用 Helper 对 Model 进行组织,并对外提供统一的接口。如果有必要,还可以通过多 Module 的方式将不同语言实现的代码组织到一个 Assembly 中进行签名和发布。
在考察了 MethodBase/MethodInfo 类型后,可以发现虽然 CLR 1.1 没有提供底层信息的封装,但仍留有一个突破口,这就是 MethodBase.MethodHandle 属性。这个属性是一个 RuntimeMethodHandle 类型的值对象,保存了一个指向运行时 MethodDesc 结构的指针。CLR 中对每个类型维护一个 MethodTable,每个 MethodTable 包含多个 MethodDesc 来表述其方法。与 MethodTable/MethodDesc 相关的信息,请参考 《.NET本质论 第1卷:公共语言运行库》 以及我发的 《用WinDbg探索CLR世界 [3] 跟踪方法的 JIT 过程》一文。
在 MethodDesc 中与我们关系较大的,是其包含的 CodeOrIL 字段。 MethodDesc 的结构大致如下:
private __gc class MethodBodyImpl : public MethodBody
{
private:
__value struct MethodDesc
{
WORD SlotNumber;
WORD Flags;
DWORD CodeOrIL;
DWORD GetRVA()
{
if((CodeOrIL & METHOD_IS_IL_FLAG) == METHOD_IS_IL_FLAG)
{
return CodeOrIL & ~METHOD_IS_IL_FLAG;
}
return 0;
}
};
}
如果 CodeOrIL 的 METHOD_IS_IL_FLAG 标志位被设置,则此字段的低 24 保存着 IL 代码在模块中的 RVA (相对地址),通过这些信息我们可以很容易获取到方法体被加载到内存中的位置。
{
public:
MethodBodyImpl(IMetaDataImport *pMdImport, MethodBase *method)
{
MethodDesc *pMD = (MethodDesc *)(method->get_MethodHandle().Value.ToPointer());
IntPtr pFilename = Marshal::StringToHGlobalUni(method->DeclaringType->Assembly->Location);
HMODULE hDll = ::LoadLibraryW((LPCWSTR)pFilename.ToPointer());
Marshal::FreeHGlobal(pFilename);
DWORD dwAddr = (DWORD)hDll + pMD->GetRVA();
// 对方法体进行解析
//
}
}
注意这里获取模块加载地址,使用了 LoadLibraryW 对方法所在 Assembly 的 DLL 重新加载的方法。因为在 Win32 中,重复对一个 DLL 加载不会执行任何操作,仅仅返回其原本加载的基址。
而在对方法体进行解析的时候,笔者直接使用 CLR 提供的一组辅助类 COR_ILMETHOD_DECODER 等,这些类的定义可以在 corhlpr.h/corhlpr.cpp 中找到。
而更为基本的方法体结构在 CorHdr.h 中被定义如下:
/* Used when the method is tiny (< 64 bytes), and there are no local vars */
typedef struct IMAGE_COR_ILMETHOD_TINY
{
BYTE Flags_CodeSize;
} IMAGE_COR_ILMETHOD_TINY;
/************************************/
// This strucuture is the 'fat' layout, where no compression is attempted.
// Note that this structure can be added on at the end, thus making it extensible
typedef struct IMAGE_COR_ILMETHOD_FAT
{
unsigned Flags : 12; // Flags
unsigned Size : 4; // size in DWords of this structure (currently 3)
unsigned MaxStack : 16; // maximum number of items (I4, I, I8, obj ), on the operand stack
DWORD CodeSize; // size of the code
mdSignature LocalVarSigTok; // token that indicates the signature of the local vars (0 means none)
} IMAGE_COR_ILMETHOD_FAT;
typedef union IMAGE_COR_ILMETHOD
{
IMAGE_COR_ILMETHOD_TINY Tiny;
IMAGE_COR_ILMETHOD_FAT Fat;
} IMAGE_COR_ILMETHOD;
根据方法的复杂程度,其方法头有 Tiny/Fat 两种模式。对前者来说,方法不包含局部变量、不使用堆栈因此只需要一个定义长度的字段即可。
CorHlpr.h 对这些基本信息的访问进行了封装,使我们不必直接对字段进行繁琐的操作:
/* Used when the method is tiny (< 64 bytes), and there are no local vars */
typedef struct tagCOR_ILMETHOD_TINY : IMAGE_COR_ILMETHOD_TINY
{
bool IsTiny() const { return((Flags_CodeSize & (CorILMethod_FormatMask >> 1)) == CorILMethod_TinyFormat); }
unsigned GetCodeSize() const { return(((unsigned) Flags_CodeSize) >> (CorILMethod_FormatShift-1)); }
unsigned GetMaxStack() const { return(8); }
BYTE* GetCode() const { return(((BYTE*) this) + sizeof(struct tagCOR_ILMETHOD_TINY)); }
DWORD GetLocalVarSigTok() const { return(0); }
COR_ILMETHOD_SECT* GetSect() const { return(0); }
} COR_ILMETHOD_TINY;
/************************************/
// This strucuture is the 'fat' layout, where no compression is attempted.
// Note that this structure can be added on at the end, thus making it extensible
typedef struct tagCOR_ILMETHOD_FAT : IMAGE_COR_ILMETHOD_FAT
{
bool IsFat() const { return((Flags & CorILMethod_FormatMask) == CorILMethod_FatFormat); }
unsigned GetMaxStack() const { return(MaxStack); }
unsigned GetCodeSize() const { return(CodeSize); }
mdToken GetLocalVarSigTok() const { return(LocalVarSigTok); }
BYTE* GetCode() const { return(((BYTE*) this) + 4*Size); }
const COR_ILMETHOD_SECT* GetSect() const {
if (!(Flags & CorILMethod_MoreSects)) return(0);
return(((COR_ILMETHOD_SECT*) (GetCode() + GetCodeSize()))->Align());
}
} COR_ILMETHOD_FAT;
struct COR_ILMETHOD
{
// a COR_ILMETHOD header should not be decoded by hand. Instead us
// COR_ILMETHOD_DECODER to decode it.
friend class COR_ILMETHOD_DECODER;
//private:
union
{
COR_ILMETHOD_TINY Tiny;
COR_ILMETHOD_FAT Fat;
};
// Code follows the Header, then immedately after the code comes
// any sections (COR_ILMETHOD_SECT).
};
而真正进行方法头解码的是在 COR_ILMETHOD_DECODER 类中调用的 DecoderInit 函数:
{
public:
// Decode the COR header into a more convinient internal form
// This is the ONLY way you should access COR_ILMETHOD so format changes are easier
COR_ILMETHOD_DECODER(const COR_ILMETHOD* header) { DecoderInit(this,(COR_ILMETHOD*)header); };
unsigned EHCount() const {
if (EH == 0) return(0);
else return(EH->EHCount());
}
// Flags these are available because we inherit COR_ILMETHOD_FAT
// MaxStack
// CodeSize
const BYTE* Code;
PCCOR_SIGNATURE LocalVarSig; // pointer to signature blob, or 0 if none
const COR_ILMETHOD_SECT_EH* EH; // eh table if any 0 if none
const COR_ILMETHOD_SECT* Sect; // additional sections 0 if none
};
void __stdcall DecoderInit(void * pThis, COR_ILMETHOD* header)
{
memset(pThis, 0, sizeof(COR_ILMETHOD_DECODER));
if (header->Tiny.IsTiny()) {
((COR_ILMETHOD_DECODER*)pThis)->MaxStack = header->Tiny.GetMaxStack();
((COR_ILMETHOD_DECODER*)pThis)->Code = header->Tiny.GetCode();
((COR_ILMETHOD_DECODER*)pThis)->CodeSize = header->Tiny.GetCodeSize();
((COR_ILMETHOD_DECODER*)pThis)->Flags |= CorILMethod_TinyFormat;
return;
}
if (header->Fat.IsFat()) {
_ASSERTE((((size_t) header) & 3) == 0); // header is aligned
*((COR_ILMETHOD_FAT*) pThis) = header->Fat;
((COR_ILMETHOD_DECODER*)pThis)->Code = header->Fat.GetCode();
_ASSERTE(header->Fat.Size >= 3); // Size if valid
((COR_ILMETHOD_DECODER*)pThis)->Sect = header->Fat.GetSect();
if (((COR_ILMETHOD_DECODER*)pThis)->Sect != 0 && ((COR_ILMETHOD_DECODER*)pThis)->Sect->Kind() == CorILMethod_Sect_EHTable) {
((COR_ILMETHOD_DECODER*)pThis)->EH = (COR_ILMETHOD_SECT_EH*) ((COR_ILMETHOD_DECODER*)pThis)->Sect;
((COR_ILMETHOD_DECODER*)pThis)->Sect = ((COR_ILMETHOD_DECODER*)pThis)->Sect->Next();
}
return;
}
// so we don't asert on trash _ASSERTE(!"Unknown format"[img]/images/wink.gif[/img];
}
值得注意的是,这里提供的 DecoderInit 函数,实际上进行的是一个并不完整的解码过程,方法体中非常重要的局部变量信息 LocalSig 并没有提供。在其注释和 sscli 的实现中,提供了另外一个依赖于 IMDInternalImport 内部接口的实现,提供完成的处理支持。因此我们只能通过现有的这些有限支持,自行对局部变量进行处理。在下面可以看到大量的代码,用于对局部变量的 Signature 进行解码 :S
在获取方法体入口并进行解码后,大部分工作都是将解码后的信息填充到 MethodBody 的内容中:
{
public:
MethodBodyImpl(IMetaDataImport *pMdImport, MethodBase *method)
{
// 获取方法体地址
// 对方法头进行解码
// 填充方法体信息
// 构造结构化异常语句集
// 构造局部变量集
}
}
可以看到剩余代码主要分为三个部分:
1.填充 MethodBody 的信息
2.构造结构化异常语句集
3.构造局部变量集
对方法体信息的填充,以及结构化异常信息的填充,基本上是平铺直叙的代码,这里就不多说了。
麻烦的是如何对局部变量进行解码。因为在方法头中,局部变量是以一个 token 的方式提供的,它指向 StandAloneSig 元数据表中的一个 Sign,用以定义局部变量集。其 Sign 的格式 LocalVarSign 大致如下:
以下为引用:
+------------------------------------+
| +------------+ \
v v \ \
LOCAL_SIG -> Count ---> Constraint --> ByRef -> Type ->
\ ^ \ ^
\ / \ /
+----------+ +---+
呵呵,懒得贴图了,画个 ASIIC 的示意图意思意思吧,有兴趣的朋友可以看看 .NET Framework SDK 自带的 Partition II Metadata.doc 文档的 LocalVarSign 相关图表。
可以看到解码的工作大概可以分为两个阶段。
首先是判断是否为 LocalVarSign,并获取局部变量的数量:
{
public:
MethodBodyImpl(IMetaDataImport *pMdImport, MethodBase *method)
{
if(m_localSignatureMetadataToken == NULL)
{
m_localVariables = __gc new LocalVariableInfo *[];
}
else
{
PCCOR_SIGNATURE pSig;
DWORD cbSig;
Marshal::ThrowExceptionForHR(pMdImport->GetSigFromToken(m_localSignatureMetadataToken, &pSig, &cbSig));
if(CorSigUncompressCallingConv(pSig) == IMAGE_CEE_CS_CALLCONV_LOCAL_SIG)
{
DWORD dwCount;
pSig += CorSigUncompressData(pSig, &dwCount);
m_localVariables = __gc new LocalVariableInfo *[dwCount];
for(int i=0; i<m_localVariables->Length; i++)
{
m_localVariables[i] = __gc new LocalVariableInfoImpl(
i, pMdImport, method->DeclaringType->Module, pSig);
}
}
}
}
}
对每个局部变量,构造一个 LocalVariableInfoImpl 对象进行实际的解析。
{
public:
LocalVariableInfoImpl(int idx, IMetaDataImport *pMdImport, Module *pModule, PCCOR_SIGNATURE &pSig)
: LocalVariableInfo(idx)
{
m_isPinned = ELEMENT_TYPE_PINNED == *pSig;
if(m_isPinned) pSig++;
m_isByRef = ELEMENT_TYPE_BYREF == *pSig;
if(m_isByRef) pSig++;
Type *type = UncompressElement(pMdImport, pModule, pSig);
if(type)
m_typeHandle = type->TypeHandle;
}
};
对 LocalVarSign 来说,Constraint 只有一种可选的 ELEMENT_TYPE_PINNED,定义此对象是否被固定在堆中,以提供 Interop 的支持。而 ByRef 则通过可选的 ELEMENT_TYPE_BYREF 定义此局部变量是否为引用。最后也是最关键的 Type 元素,解析过程被封装到 UncompressElement 函数中。它将提供对 Type 的解析并返回一个 Reflection 的对应 Type 对象。
最终在 LocalVariableInfo.m_typeHandle 中保存此类型的 RuntimeTypeHandle 以便在后期使用。而这个 RuntimeTypeHandle 与前面 RuntimeMethodHandle 很类似,内部保存了执行 MethodTable 的指针,并可使用 Type.GetTypeFromHandle 函数将之转换回类型。在 CLR 2.0 中,这几种 Handle 的功能被大大增强,回头有空再单独写文章详细讨论。
{
public class LocalVariableInfo
{
protected RuntimeTypeHandle m_typeHandle;
public virtual Type LocalType
{
get
{
return m_typeHandle.Value != IntPtr.Zero ? Type.GetTypeFromHandle(m_typeHandle) : null;
}
}
}
}
最后剩下的 UncompressElement 较为复杂,因为 Type 这个 Signature 为了灵活性,提供了非常强大的扩展机制。
以下为引用:
Type ::=
BOOLEAN | CHAR | I1 | U1 | I2 | U2 | I4 | U4 | I8 | U8 | R4 | R8 | I | U |
| VALUETYPE TypeDefOrRefEncoded
| CLASS TypeDefOrRefEncoded
| STRING
| OBJECT
| PTR CustomMod* VOID
| PTR CustomMod* Type
| FNPTR MethodDefSig
| FNPTR MethodRefSig
| ARRAY Type ArrayShape (general array, see clause 22.2.13)
| SZARRAY CustomMod* Type (single dimensional, zero-based array i.e. vector)
因此 UncompressElement 将分为几个部分介绍:
1.原始类型
2.结构和类
3.数组
4.指针与函数指针
对原始类型的处理较为简单,基本上都是对 CorElementType 枚举类型中元素,与 CLR 中对应元素的映射。
{
Type *UncompressElement(IMetaDataImport *pMdImport, Module *pModule, PCCOR_SIGNATURE &pSig)
{
switch(CorSigUncompressElementType(pSig))
{
// 处理原始类型
case ELEMENT_TYPE_VOID: // 0x1
{
return __typeof(Void);
}
case ELEMENT_TYPE_BOOLEAN: // 0x2
{
return __typeof(Boolean);
}
case ELEMENT_TYPE_CHAR: // 0x3
{
return __typeof(Char);
}
case ELEMENT_TYPE_I1: // 0x4
{
return __typeof(SByte);
}
case ELEMENT_TYPE_U1: // 0x5
{
return __typeof(Byte);
}
case ELEMENT_TYPE_I2: // 0x6
{
return __typeof(Int16);
}
case ELEMENT_TYPE_U2: // 0x7
{
return __typeof(UInt16);
}
case ELEMENT_TYPE_I4: // 0x8
case ELEMENT_TYPE_I: // 0x18, native integer size
{
return __typeof(Int32);
}
case ELEMENT_TYPE_U4: // 0x9
case ELEMENT_TYPE_U: // 0x19, native unsigned integer size
{
return __typeof(UInt32);
}
case ELEMENT_TYPE_I8: // 0xa
{
return __typeof(Int64);
}
case ELEMENT_TYPE_U8: // 0xb
{
return __typeof(UInt64);
}
case ELEMENT_TYPE_R4: // 0xc
{
return __typeof(Single);
}
case ELEMENT_TYPE_R8: // 0xd
{
return __typeof(Double);
}
case ELEMENT_TYPE_STRING: // 0xe
{
return __typeof(String);
}
case ELEMENT_TYPE_OBJECT: // 0x1C, Shortcut for System.Object
{
return __typeof(Object);
}
case ELEMENT_TYPE_TYPEDBYREF: // 0x16, This is a simple type.
{
return __typeof(TypedReference);
}
// 处理结构和类
// 处理数组
// 处理指针与函数指针
}
}
对结构和类型来说,则需要根据其提供的 token 来定位实际的类型。
{
Type *UncompressElement(IMetaDataImport *pMdImport, Module *pModule, PCCOR_SIGNATURE &pSig)
{
switch(CorSigUncompressElementType(pSig))
{
// 处理原始类型
case ELEMENT_TYPE_VALUETYPE: // 0x11, VALUETYPE <class Token>
case ELEMENT_TYPE_CLASS: // 0x12, CLASS <class Token>
{
mdToken token;
pSig += CorSigUncompressToken(pSig, &token);
wchar_t typeName[MAX_CLASSNAME_LENGTH];
switch(TypeFromToken(token))
{
case mdtTypeDef: // 类型定义
case mdtTypeRef: // 类型引用
case mdtTypeSpec: // 类型描述
}
break;
}
// 处理数组
// 处理指针与函数指针
}
}
根据 Type Signature 的定义,这里 token 是 TypeDefOrRefEncoded 类型,其 token 可以指向 TypeDef, TypeRef 或 TypeSpec 三种不同的元数据表。因此在使用 CorSigUncompressToken 函数对 TypeDefOrRefEncoded 类型进行解码后,需要分别处理三种情况。
对类型定义 (TypeDef) 来说,局部变量使用的类型与方法在同一个 Module 中,因此理论上只要知道名字就可以直接通过 Module.GetType 来获取 Type,例如
DWORD dwFlags;
mdToken tkExtends;
Marshal::ThrowExceptionForHR(pMdImport->GetTypeDefProps(
token, typeName, MAX_CLASSNAME_LENGTH, NULL, &dwFlags, &tkExtends));
return pModule->GetType(Marshal::PtrToStringUni(typeName), true, false);
但实际上我们还需要对嵌套类型进行处理,例如:
{
internal abstract class WaitQueue
{
public class WaitNode
{
//
}
}
}
对这种情况而言,如果局部变量类型的 token 直接指向 WaitNode,在 GetTypeDefProps 时获得的也只是嵌套类型自身的名字,而仅通过这个名字是无法从 Module 进行类型查询的。因此我们必须通过判断 GetTypeDefProps 方法返回的 dwFlags 是否设置为 tdNestedXXX,来决定是否需要使用 GetNestedClassProps 方法来获取当前类型的 enclosing type (外部类),处理代码如下:
{
DWORD dwFlags;
mdToken tkExtends;
Stack *enclosing = __gc new Stack();
Stack *binding = __gc new Stack();
while(token)
{
Marshal::ThrowExceptionForHR(pMdImport->GetTypeDefProps(
token, typeName, MAX_CLASSNAME_LENGTH, NULL, &dwFlags, &tkExtends));
enclosing->Push(Marshal::PtrToStringUni(typeName));
if((dwFlags & tdVisibilityMask) <= tdPublic)
break;
binding->Push(__box(((dwFlags & tdVisibilityMask) == tdNestedPublic ?
BindingFlags::Public : BindingFlags::NonPublic) | BindingFlags::Static));
Marshal::ThrowExceptionForHR(pMdImport->GetNestedClassProps(token, &token));
}
Type *type = pModule->GetType(static_cast<String *>(enclosing->Pop()), true, false);
while(type && enclosing->Count > 0)
{
String *enclosingTypeName = static_cast<String *>(enclosing->Pop());
BindingFlags bindingFlags = (BindingFlags)*dynamic_cast<__box int*>(binding->Pop());
type = type->GetNestedType(enclosingTypeName, bindingFlags);
}
return type;
}
对类型引用 (TypeRef) 来说则更加麻烦,因为 GetTypeRefProps 返回的仅仅是针对目标 Module/Assembly 的类型名字。这里设计到 Metadata 加载中的一个 Scope 的概念,表示一个类型所加载的有效范围,有点类似于 Java 中的 ClassLoader 的类型域的概念。范围一般是以 Module/Assembly 来界定的,因此对 TypeRef 能够获取的是类型的名称及其所在的范围。
{
mdToken tkResScope;
Marshal::ThrowExceptionForHR(pMdImport->GetTypeRefProps(
token, &tkResScope, typeName, MAX_CLASSNAME_LENGTH, NULL));
// 对类型进行定位
switch(TypeFromToken(tkResScope))
{
case mdtModuleRef: // 外部 Module
case mdtAssemblyRef: // 外部 Assembly
}
}
ModuleRef 一般用于处理在一个 Assembly 中包含多个 Module 的情况。例如这段代码自身,就可以封装成由三个 Module 组成的 Assembly,分别用 C# 和 Managed C++ 来实现。此时在进行局部变量引用的时候,就会出现跨 Module 的情况。
{
case mdtModuleRef:
{
wchar_t moduleName[MAX_CLASSNAME_LENGTH];
Marshal::ThrowExceptionForHR(pMdImport->GetModuleRefProps(
tkResScope, moduleName, MAX_CLASSNAME_LENGTH, NULL));
return pModule->Assembly->
GetModule(Marshal::PtrToStringUni(moduleName))->
GetType(Marshal::PtrToStringUni(typeName), true, false);
}
而更多的情况则是跨 Assembly 的类型引用,为此我们需要获取一个新的 IMetaDataAssemblyImport 接口,用于 Assembly 一级的处理。这里 IMetaDataImport/IMetaDataAssemblyImport 等接口我就不详细介绍了,有兴趣的朋友可以直接参考 Unmanaged Metadata API.doc 文档。
通过 IMetaDataAssemblyImport.GetAssemblyRefProps 方法,我们可以获取 AssemblyRef 的一些详细信息,包括 Assembly 的名字、版本、hash key 等等。因为被分析的目标 Assembly 是被 CLR 加载的,因此它所引用的 Assembly 也会被相应加载和定位。我们可以直接通过 GetAssemblyRefProps 获取到的信息,在目标 Assembly 的引用 Assembly 中定位到需要的 Assembly。
{
IMetaDataAssemblyImport *pAsmImport;
wchar_t asmName[MAX_CLASSNAME_LENGTH];
ASSEMBLYMETADATA asmMD;
const void *pbPublicKeyOrToken = NULL, *pbHashValue = NULL;
DWORD cbPublicKeyOrToken, cbHashValue, dwAssemblyRefFlags;
Marshal::ThrowExceptionForHR(pMdImport->QueryInterface(
IID_IMetaDataAssemblyImport, (void **)&pAsmImport));
HRESULT hr = pAsmImport->GetAssemblyRefProps(
tkResScope, &pbPublicKeyOrToken, &cbPublicKeyOrToken,
asmName, MAX_CLASSNAME_LENGTH, NULL, &asmMD,
&pbHashValue, &cbHashValue, &dwAssemblyRefFlags);
pAsmImport->Release();
Marshal::ThrowExceptionForHR(hr);
AssemblyName *refAsm[] = pModule->Assembly->GetReferencedAssemblies();
for(int i=0; i<refAsm->Length; i++)
{
if(refAsm[i]->Name->Equals(Marshal::PtrToStringUni(asmName)) &&
refAsm[i]->Version->Major == asmMD.usMajorVersion &&
refAsm[i]->Version->Minor == asmMD.usMinorVersion &&
refAsm[i]->Version->Revision == asmMD.usRevisionNumber &&
refAsm[i]->Version->Build == asmMD.usBuildNumber)
{
if(pbPublicKeyOrToken && cbPublicKeyOrToken > 0 &&
refAsm[i]->GetPublicKeyToken() != NULL &&
refAsm[i]->GetPublicKeyToken()->Length == cbPublicKeyOrToken)
{
unsigned char *key = new unsigned char[cbPublicKeyOrToken];
Marshal::Copy(refAsm[i]->GetPublicKeyToken(), 0, key, cbPublicKeyOrToken);
if(0 == memcmp(pbPublicKeyOrToken, key, cbPublicKeyOrToken))
{
return Assembly::Load(refAsm[i])->
GetType(Marshal::PtrToStringUni(typeName), true, false);
}
}
}
}
}
}
而对类型描述 (mdtTypeSpec) 的处理就比较简单了,通过其 Token 找到描述 Signature 并解析之即可。
{
PCCOR_SIGNATURE pTypeSig;
DWORD cbSig;
Marshal::ThrowExceptionForHR(pMdImport->GetSigFromToken(token, &pTypeSig, &cbSig));
return UncompressElement(pMdImport, pModule, pTypeSig);
}
与类型的处理相比,对数组的处理相对容易一些,笔者利用 CLR 提供的 Array.CreateInstance 特性,根据数组的定义信息来动态创建数组实例,并最终获取其类型。这样可以大大降低对数组类型自行定位的繁琐。
{
SkipCustomMod(pSig);
return Array::CreateInstance(UncompressElement(pMdImport, pModule, pSig), 0)->GetType();
}
case ELEMENT_TYPE_ARRAY: // 0x14, MDARRAY <type> <rank> <bcount> <bound1> <lbcount> <lb1>
{
Type *elementType = UncompressElement(pMdImport, pModule, pSig);
DWORD dwCount;
pSig += CorSigUncompressData(pSig, &dwCount); // Rank
int lengths __gc[] = new int __gc[dwCount];
pSig += CorSigUncompressData(pSig, &dwCount); // NumSizes
DWORD dwValue;
for(size_t i=0; i<dwCount; i++)
{
pSig += CorSigUncompressData(pSig, &dwValue); // Size
lengths[i] = dwValue;
}
pSig += CorSigUncompressData(pSig, &dwCount); // NumLowBounds
int loBounds __gc[] = new int __gc[dwCount];
for(size_t i=0; i<dwCount; i++)
{
pSig += CorSigUncompressData(pSig, &dwValue); // Size
loBounds[i] = dwValue;
}
return Array::CreateInstance(elementType, lengths, loBounds)->GetType();
}
这里 ELEMENT_TYPE_SZARRAY 实际上是对最常见的一维数组的优化,而 ELEMENT_TYPE_ARRAY 则提供较为完整的数组类型定义。可以细到有几维,每个维度的数组长度和起始索引号。
最后的对指针和函数指针的处理较为直接,基本上是前面用过的函数的汇总:
{
SkipCustomMod(pSig);
m_isPointer = true;
return UncompressElement(pMdImport, pModule, pSig);
}
case ELEMENT_TYPE_FNPTR: // 0x1B, FNPTR <complete sig for the function including calling convention>
{
bool hasThis = IMAGE_CEE_CS_CALLCONV_HASTHIS == *pSig;
if(hasThis) pSig++;
bool explicitThis = IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS == *pSig;
if(explicitThis) pSig++;
DWORD dwCallConv, dwParamCount;
pSig += CorSigUncompressData(pSig, &dwCallConv);
pSig += CorSigUncompressData(pSig, &dwParamCount);
SkipCustomMod(pSig);
bool isByRef = ELEMENT_TYPE_BYREF == *pSig;
if(isByRef) pSig++;
Type *retType = UncompressElement(pMdImport, pModule, pSig);
for(size_t i=0; i<dwParamCount; i++)
{
SkipCustomMod(pSig);
if(isByRef) pSig++;
Type *paramType = UncompressElement(pMdImport, pModule, pSig);
}
}
至此对方法体结构的解析工作就基本完成了,针对特定的情况可能需要做一些兼容性的修改。
而对其的使用也非常简单
{
MethodBody body = helper.GetMethodBody(_method);
foreach(LocalVariableInfo var in body.LocalVariables)
{
stats.Add(new CodeVariableDeclarationStatement(var.LocalType, "var" + var.LocalIndex));
}
}
因为时间和精力有限,很多问题我就不展开讲了,只是通过贴代码的方式把思路提出来。如果有什么疑惑或者改进的思路,欢迎讨论。
出于完整性考虑,后面把 Unmanaged Metadata API 的环境构建代码大致贴一下:
// MetadataHelper.h
//
#pragma once
#include <cor.h>
#include <CorHdr.h>
#using <mscorlib.dll>
#include "MethodHelper.h"
using namespace System;
using namespace System::Reflection;
using namespace System::Runtime::InteropServices;
using namespace NSFocus::ILEngine::Model;
namespace NSFocus
{
namespace ILEngine
{
namespace Helper
{
__gc public class MetadataHelper : public IDisposable
{
private:
IntPtr m_pDispenser, m_pMDImport;
static IntPtr CreateDispenser();
public:
MetadataHelper(String *filename, bool readonly);
MetadataHelper(IntPtr pData, size_t cbData, bool readonly);
virtual void Dispose();
__property IMetaDataDispenserEx *get_MetaDataDispenser()
{
return static_cast<IMetaDataDispenserEx *>(m_pDispenser.ToPointer());
}
__property IMetaDataImport *get_MetaDataImport()
{
return static_cast<IMetaDataImport *>(m_pMDImport.ToPointer());
}
MethodBody *GetMethodBody(MethodBase *method)
{
return (__gc new MethodHelper(MetaDataImport, method))->GetMethodBody();
}
};
}
}
}
以下内容为程序代码:
///////////////////////
// MetadataHelper.cpp
//
#include "StdAfx.h"
#include "MetadataHelper.h"
namespace NSFocus
{
namespace ILEngine
{
namespace Helper
{
MetadataHelper::MetadataHelper(String *filename, bool readonly)
: m_pDispenser(CreateDispenser())
{
IntPtr pFilename = Marshal::StringToHGlobalUni(filename);
IMetaDataImport *pImport;
HRESULT hr = MetaDataDispenser->OpenScope((LPCWSTR)pFilename.ToPointer(),
readonly ? 0 : 1, IID_IMetaDataImport, (IUnknown **)&pImport);
m_pMDImport = pImport;
Marshal::FreeHGlobal(pFilename);
Marshal::ThrowExceptionForHR(hr);
}
MetadataHelper::MetadataHelper(IntPtr pData, size_t cbData, bool readonly)
: m_pDispenser(CreateDispenser())
{
IMetaDataImport *pImport;
HRESULT hr = MetaDataDispenser->OpenScopeOnMemory(pData.ToPointer(), cbData,
readonly ? 0 : 1, IID_IMetaDataImport, (IUnknown **)&pImport);
m_pMDImport = pImport;
Marshal::ThrowExceptionForHR(hr);
}
IntPtr MetadataHelper::CreateDispenser()
{
IMetaDataDispenserEx *pDispenser;
Marshal::ThrowExceptionForHR(::CoCreateInstance(CLSID_CorMetaDataDispenserRuntime, NULL, CLSCTX_INPROC_SERVER,
IID_IMetaDataDispenserEx, (LPVOID *)&pDispenser));
return pDispenser;
}
void MetadataHelper:[img]/images/biggrin.gif[/img]ispose()
{
if(MetaDataDispenser)
{
MetaDataDispenser->Release();
m_pDispenser = IntPtr::Zero;
}
if(MetaDataImport)
{
MetaDataImport->Release();
m_pMDImport = IntPtr::Zero;
}
}
}
}
}