文章目的:主要解决typeof的时候到底发生了什么的问题。
工程:想像这样一个Solution,一个基础工程BaseProject中拥有一个接口IInterface,以及一个静态类Manager用来管理所有IInterface的具体实现类。Manager提供一个Regiest方法来注册IInterface具体实现类的相关信息,这些信息包括一个委托Func<IInterface>构造器,以及一个Type类型的实例。
然后有一个扩展工程ExtensionProject,工程中定义了一个IInterface的具体实现类implementclass,然后ExtensionProject引用了BaseProject,并使用静态类Manager的Regiest方法注册了implementclass的类型以及一个构造器.
最后是一个客户端,ConsoleApplication1,这个客户端引用了BaseProject以及ExtensionProject两个工程,然后在ExtensionProject注册完成后,通过Manager取得构造器并在生成IInterface的实例后调用其方法。
工程基本结构如下图所示:
注意看ConsoleApplication的Main方法先调用了ExtensionProject中Class1的Regiest方法注册了implementclass类型和一个Func<Interface>类型的构造器,
static public class Class1
{
static public void Regiest()
{
Manager.Regiest(typeof(implementclass), () => new implementclass());
}
static void Test(Type type)
{
Console.WriteLine(type.ToString());
}
}
Manager的定义是这样的:
public static class Manager
{
static Type _type;
static Func<IInterface> _creator;
static public void Regiest(Type type, Func<IInterface> creator)
{
_type = type;
_creator = creator;
}
static public void ShowType()
{
Console.WriteLine(_type.ToString());
}
static public IInterface Get()
{
return _creator();
}
}
然后调用了BaseClass1的Test方法执行测试。在这个测试方法里面,我执行了这样的代码:
毫无疑问,这个程序可以正常运转。
这个过程的奇妙之处在于,首先.Net里面有一个基本单位Module,我们这个工程在运行时将会在内存中加载3个Module,分别是BaseProject,ExtensionProject,以及ConsoleApplication1。
然后在运行时我们“居然”在BaseProject里面生成了ExtensionProject的imlementclass类的实例,并且BaseProject没有引用ExtensionProject。这在普通的应用程序里也许没什么大惊小怪的,他们的代码实际上都是毫无区别的加载在内存中可以随意使用。但是我们前面说了.Net的程序加载的是相对独立的Module。
每一个Module的元数据都是相互独立,拥有自己的type列表(type_def_table),type引用列表(type_ref_table)等,以及metadatatoken的。
metadatatoken是什么呢,metadatatoken是一个四字节的数字,其中第1个字节表示自己的类型,部分类型列表如下:
typedef enum CorTokenType {
mdtModule = 0x00000000,
mdtTypeRef = 0x01000000,
mdtTypeDef = 0x02000000,
mdtFieldDef = 0x04000000,
mdtMethodDef = 0x06000000,
(可以到这里了解更多内容:http://blog.csdn.net/CsToD/article/details/4212568)
后3个字节则表明了自己在相应的类型列表中的位置。如果是编译时的引用类型的话,metadatatoken的类型
将会是mdtTypeRef,这个时候可以通过该token从type_ref_table中读取相应信息,然后运用该信息从定义了这个type的module中加载这个类型。
而如果这个类型就是定义在本module内部的话,这个metadatatoken的类型是mdtTypeDef ,可以直接从type_def_table中读取类型的信息。显然,token尤其是mdtTypeDef 的作用域只是module内部而已,无法跨module使用。
我们注册implementclass的地方是在ExtensionProject里面,通过调用的BaseProject中Manager的方法实现的,这个时候如果传入的是metadatatoken,那么显然这个token的类型应该是mdtTypeDef,且在BaseProject的Module中是无法使用的,那么它是怎么做的呢?我们看注册部分的代码(稍微修改了一下):
源代码(Reflector):
1 public static void Regiest()
2 {
3 Test(typeof(implementclass));
4 Console.WriteLine("Prepare Regiest......");
5 Console.ReadLine();
6 if (CS$<>9__CachedAnonymousMethodDelegate1 == null)
7 {
8 CS$<>9__CachedAnonymousMethodDelegate1 = new Func<IInterface>(null, (IntPtr) <Regiest>b__0);
9 }
10 Manager.Regiest(typeof(implementclass), CS$<>9__CachedAnonymousMethodDelegate1);
11 }
12
13
其中有许多关于构造器的闭包的部分,这一部分我们先不管它,主要看第10行的typeof(implementclass)的部分,正是这个部分把ExtensionProjectModule中implementclass的信息传递过去了的。为了简化这个过程我在第3行写了一个没用的Test方法,通过它我们可以只关注typeof的部分而不必理会闭包的操作。
然后我们看IL代码:
1 .method public hidebysig static void Regiest() cil managed
2 {
3 .maxstack 4
4 L_0000: nop
5 L_0001: ldtoken ExtensionProject.implementclass
6 L_0006: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
7 L_000b: call void ExtensionProject.Class1::Test(class [mscorlib]System.Type)
8 L_0010: nop
9 L_0011: ldstr "Prepare Regiest......"
10 L_0016: call void [mscorlib]System.Console::WriteLine(string)
11 L_001b: nop
12 L_001c: call string [mscorlib]System.Console::ReadLine()
13 L_0021: pop
14 L_0022: ldtoken ExtensionProject.implementclass
15 L_0027: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
16 L_002c: ldsfld class [mscorlib]System.Func`1<class [BaseProject]BaseProject.IInterface> ExtensionProject.Class1::CS$<>9__CachedAnonymousMethodDelegate1
17 L_0031: brtrue.s L_0046
18 L_0033: ldnull
19 L_0034: ldftn class [BaseProject]BaseProject.IInterface ExtensionProject.Class1::<Regiest>b__0()
20 L_003a: newobj instance void [mscorlib]System.Func`1<class [BaseProject]BaseProject.IInterface>::.ctor(object, native int)
21 L_003f: stsfld class [mscorlib]System.Func`1<class [BaseProject]BaseProject.IInterface> ExtensionProject.Class1::CS$<>9__CachedAnonymousMethodDelegate1
22 L_0044: br.s L_0046
23 L_0046: ldsfld class [mscorlib]System.Func`1<class [BaseProject]BaseProject.IInterface> ExtensionProject.Class1::CS$<>9__CachedAnonymousMethodDelegate1
24 L_004b: call void [BaseProject]BaseProject.Manager::Regiest(class [mscorlib]System.Type, class [mscorlib]System.Func`1<class [BaseProject]BaseProject.IInterface>)
25 L_0050: nop
26 L_0051: ret
27 }
只要看第5,6行是将typeof(implementclass)入栈,然后再第7行调用Test方法的部分。后面的部分我们先不管它。
其中第5行我们看到首先用ldtoken指令,
ldtoken ExtensionProject.implementclass
这个指令可不是将token入栈,按照MSN的说明这个指令是
传递的标记被转换为 RuntimeHandle 并被推送到堆栈上。
这里具体而言是入栈了System.RuntimeTypeHandle,
第6行利用这个RuntimeTypeHandle构造了System.Type(这其实是一个RuntimeType的内部类型,以后的文章中我们将用Windbg看到这一点),
call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
第7行调用Test方法传入了RuntimeType,
call void ExtensionProject.Class1::Test(class [mscorlib]System.Type)
综上,调用typeof的时候我们做了2件事情,
一个是生成了一个RuntimeTypeHandle实例,
然后利用这个实例生成了一个RuntimeType,RuntimeType是Type的子类,且显然可以跨Module使用,而且从名称上看应该就是为了在运行时跨Module使用而设计的。
而且我们可以得出结论,静态的引用其他Module的类型我们可以通过mdtTypeRef类型的token实现,运行时动态创建其他Module中定义的类型我们可以通过RuntimeTypeHandle实现,那么更进一步的,这个RuntimeTypeHandle是什么呢?
我们运行这样的代码看一看:
1 RuntimeTypeHandle rth = Type.GetTypeHandle(new implementclass());
2 Console.WriteLine("RuntimeTypeHandle:"+rth.Value);
3 ModuleHandle mh = rth.GetModuleHandle();
4 Console.WriteLine("metadatatoken" + typeof(implementclass).MetadataToken);
5 var handle = mh.GetRuntimeTypeHandleFromMetadataToken(typeof(implementclass).MetadataToken);
6 Console.WriteLine("TypeHandleFromModule" + handle.Value);
运行结果 :
显然通过ModuleHandle和metadatatoken取得的TypeHandle是相同的,我们于是知道的定义一个元数据需要一个module和一个metadata,或者一个runtimehandle。
记住2638948和33554435两个值,它们的16进制分别是284464和02000003
我们用Windbg来Debug这个程序
0:004> !dumpmt 284464
EEClass: 00332910
Module: 002838c4
Name: ExtensionProject.implementclass
mdToken: 02000003
File: F:\Lab\ClientBin\TestCompressedFile\ConsoleApplication1\bin\Debug\ExtensionProject.dll
BaseSize: 0xc
ComponentSize: 0x0
Slots in VTable: 6
Number of IFaces in IFaceMap: 1
显然,十进制的2638948正是implementclass的MT(methodtable)的地址,这个RuntimeTypeHandle既然保存了MethodTable的内存地址当然可以提供跨Moduel的信息。而mdToken的02000003则正是十进制的33554435,其中第一个字节的02代表它是一个mdtTypeDef,后面的000003则指出了它在type_def_table中的位置。