• Undocumented C# Keywords: __makeref, __refvalue, __arglist ...


    看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

  • 相关阅读:
    BZOJ 1027: [JSOI2007]合金 (计算几何+Floyd求最小环)
    BZOJ 4522: [Cqoi2016]密钥破解 (Pollard-Rho板题)
    BZOJ 4802: 欧拉函数 (Pollard-Rho)
    BZOJ 3944: Sum (杜教筛)
    BZOJ 3309: DZY Loves Math (莫比乌斯反演)
    BZOJ 2599: [IOI2011]Race(点分治板题)
    BZOJ 3680: 吊打XXX // Luogu [JSOI2004]平衡点 / 吊打XXX (模拟退火)
    Luogu P3690【模板】Link Cut Tree (LCT板题)
    [HNOI2007]最小矩形覆盖
    [SCOI2007]最大土地面积
  • 原文地址:https://www.cnblogs.com/Dah/p/830164.html
Copyright © 2020-2023  润新知