看IL指令到mkrefany, 文档中说它的作用是: "push a typed reference on the stack", 不知道在C#的何种语法会用上这条指令, 于是Google之, 发现了从来没有看过的C#关键字:
Object obj = new Object();
TypedReference typedref = __makeref(obj);
Type type = __reftype(typedref);
Object sameObj = __refvalue( typedref,Object);
对应的IL是:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 33 (0x21)
.maxstack 1
.locals init ([0] object obj,
[1] typedref 'typedref',
[2] class [mscorlib]System.Type 'type',
[3] object sameObj)
IL_0000: nop
IL_0001: newobj instance void [mscorlib]System.Object::.ctor()
IL_0006: stloc.0
IL_0007: ldloca.s obj
IL_0009: mkrefany [mscorlib]System.Object
IL_000e: stloc.1
IL_000f: ldloc.1
IL_0010: refanytype
IL_0012: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0017: stloc.2
IL_0018: ldloc.1
IL_0019: refanyval [mscorlib]System.Object
IL_001e: ldind.ref
IL_001f: stloc.3
IL_0020: ret
} // end of method Program::Main
可以发现:
TypedReference对象在IL中是typedref类型;
IL_0009使用mkrefany生成了一个类型为Object的typedref;
IL_0010使用refanytype从typedref中得到了一个RuntimeTypeHandle, 随即调用一个方法得到Type对象;
IL_0019使用refanyval从typedref中获取了一个类型为Object的引用, 从后面一句ldind.ref可以知道refanyval压栈的是一个managed pointer(&类型), 而不是普通的reference, ldind.ref把栈顶的managed pointer转换成了普通的reference.
这三个操作对应的也可以直接用TypedReference的静态方法实现:
MyObj obj = new MyObj(99);
TypedReference tr = __makeref(obj); // TypedReference.MakeTypedReference
Type type = Type.GetTypeFromHandle(TypedReference.TargetTypeToken(tr));
MyObj sameObj = (MyObj)TypedReference.ToObject(tr);
在C#的unsafe语境中可以使用&运算符获取一个值类型量/对象的地址, 但不可以获取一个引用类型对象的地址, 因为引用类型字段值的分配完全受运行时控制, 但TypedReference可以看成为任何托管对象的指针, typeref在CLI里存在的理由是给所谓的动态语言提供一种动态的方式来访问对象.
大家都知道printf是一个不定参数数量的函数, 它的第二个参数是用...声明的, 如果要在C#中使用P/Invoke应用这个函数该如何声明呢? params是.NET中特有的不定参数数量的实现, 但我用
extern static int printf(string format, params object[] args);
[DllImport("msvcrt.dll")]
extern static int printf(string format, __arglist);
static unsafe void Main(string[] args)
{
printf("%d %.2f %s", __arglist(3, 0.4567, "asdf"));
}
我们甚至可以自己写一个带vararg参数的方法, TypedReference也派上用场了:
static void MyPrint(__arglist)
{
ArgIterator itr = new ArgIterator(__arglist);
while (itr.GetRemainingCount() > 0)
{
Console.WriteLine(TypedReference.ToObject(itr.GetNextArg()));
}
}
static unsafe void Main(string[] args)
{
MyPrint(__arglist("asdfasdf", 2, 1.2f, 2.4d, 123L, new object()));
}
其实有很多人已经写过这些东西了, 参考:
1. Calling printf from C# - The tale of the hidden __arglist keyword