看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);

声明的printf永远都不能正常工作(谁能?), 还好C#提供了一个__arglist关键字来支持古老的vararg, 如何使用__arglist声明和调用printf见下面的代码:

 

[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

2. Pointers UNDOCUMENTED

Posted on 2007-07-25 02:26 Adrian H. 阅读(...) 评论(...) 编辑 收藏