zoukankan      html  css  js  c++  java
  • [转载]Calling printf from C# The tale of the hidden __arglist keyword

    Browsing the SSCLI can be enlighting from time to time (if not all the time). Take a look at the following function implemented in console.cs:

    [HostProtection(UI=true)]
    [
    CLSCompliant(false
    )]
    public static void WriteLine(String format, Object arg0, Object arg1, Object arg2,Object arg3, __arglist
    )
    {
       Object
    [] objArgs;
       int
    argCount;

       ArgIterator
    args = new ArgIterator(__arglist);

       //+4 to account for the 4 hard-coded arguments at the beginning of the list.
       argCount = args.GetRemainingCount() + 4;

       objArgs =
    new Object
    [argCount];

       //Handle the hard-coded arguments
       objArgs[0] = arg0;
       objArgs[1] = arg1;
       objArgs[2] = arg2;
       objArgs[3] = arg3;

       //Walk all of the args in the variable part of the argument list.
       for (int
    i=4; i<argCount; i++) {
          objArgs[i] =
    TypedReference
    .ToObject(args.GetNextArg());
       }

       Out.WriteLine(format, objArgs);
    }

    Hmm, looks weird isn't it? Using that dark keyword __arglist (it really is a keyword, look at the keyword coloring of VS2005). Not to talk about TypedReference and ArgIterator yet. So, what is it and what does it do?

    Compile the following piece of code:

    class Program
    {

       static
    void Main(string[] args)
       {
          Foo(__arglist(1, 2, 3));
       }

       static
    void Foo(__arglist)
       {
          ArgIterator iter = new ArgIterator(__arglist
    );
          for (int
    n = iter.GetRemainingCount(); n > 0; n--)
             Console.WriteLine(TypedReference
    .ToObject(iter.GetNextArg()));
       }
    }

    Output will just print
    1
    2
    3

    on the screen.

    Now take a look at the IL code:

    .method private hidebysig static vararg void
    Foo() cil managed
    {
       .locals init ([0] valuetype [mscorlib]System.ArgIterator iter,
          [1] int32 n,
          [2] bool CS$4$0000)
       IL_0000: nop
       IL_0001: ldloca.s iter
       IL_0003: arglist
       IL_0005: call instance void [mscorlib]System.ArgIterator::.ctor(valuetype [mscorlib]System.RuntimeArgumentHandle)
       ...
    } // end of method Program::Foo

    Welcome to mysteria lane again: RuntimeArgumentHandle pops up. Information on MSDN reveals the C/C++ programming language support it's intended for. Taking a look at the CIL instruction set in Partition III, section 3.4 of the ECMA 355 CLI standard learns us that arglist is used to return argument list handle for the current method. So, by calling arglist (metadata shows that the method supports this, cf. vararg) a pointer to the argument list is pushed on the stack.

    All of this mysterious stuff makes one wonder about possible other hidden secrets, and guess what: tokens.h in the csharp folder of the SSCLI reveals four such keywords:

    TOK(L"__arglist" , TID_ARGS , TFF_MSKEYWORD | TFF_TERM , 0 , NOPARSEFN , OP_NONE , OP_NONE , OP_ARGS , KEYWORD )
    TOK(L
    "__makeref"
    , TID_MAKEREFANY , TFF_MSKEYWORD | TFF_TERM , 0 , NOPARSEFN , OP_NONE , OP_NONE , OP_MAKEREFANY , KEYWORD )
    TOK(L
    "__reftype"
    , TID_REFTYPE , TFF_MSKEYWORD | TFF_TERM , 0 , NOPARSEFN , OP_NONE , OP_NONE , OP_REFTYPE , KEYWORD )
    TOK(L
    "__refvalue" , TID_REFVALUE , TFF_MSKEYWORD | TFF_TERM , 0 , NOPARSEFN , OP_NONE , OP_NONE , OP_REFVALUE , KEYWORD )

    Don't worry about the macro stuff in here. The basic usage of each of these (undocumented == don't use) keywords is shown below (you can find out about the syntax by inspecting and understanding the tokens.h file):

    int i = 1;

    TypedReference
    tr = __makeref(i); // tr = &i
    __refvalue(tr, int) = 2;
    // *tr = 2

    Console
    .WriteLine(TypedReference.ToObject(tr));
    Console.WriteLine(__refvalue(tr,int));
    // kind of a "cast back" (à la 'tr as int')

    Console.WriteLine(TypedReference
    .GetTargetType(tr));
    Console.WriteLine(__reftype(tr)); // i.GetType()

    This prints out:
    2
    2
    System.Int32
    System.Int32

    IL analysis reveals four dark IL instructions once more:

    .locals init ([0] int32 i,
       [1] typedref tr)
    IL_0001: ldc.i4.1
    IL_0002: stloc.0
    IL_0003: ldloca.s i
    IL_0005: mkrefany [mscorlib]System.Int32
    IL_000a: stloc.1
    IL_000b: ldloc.1
    IL_000c: refanyval [mscorlib]System.Int32
    IL_0011: ldc.i4.2
    IL_0012: stind.i4
    IL_0013: ldloc.1
    IL_0014: call object [mscorlib]System.TypedReference::ToObject(typedref)
    IL_0019: call void [mscorlib]System.Console::WriteLine(object)
    IL_001f: ldloc.1
    IL_0020: refanyval [mscorlib]System.Int32
    IL_0025: ldind.i4
    IL_0026: call void [mscorlib]System.Console::WriteLine(int32)
    IL_002c: ldloc.1
    IL_002d: call class [mscorlib]System.Type [mscorlib]System.TypedReference::GetTargetType(typedref)
    IL_0032: call void [mscorlib]System.Console::WriteLine(object)
    IL_0038: ldloc.1
    IL_0039: refanytype
    IL_003b: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    IL_0040: call void [mscorlib]System.Console::WriteLine(object)

    mkrefany - Push a typed reference to stack.Pop() of type arg[0] onto the stack (4.18) - On line IL_0005 this pops the address of i (obtained in IL_0003) from the stack and pushes a typed reference ([1]) to i on the stack.

    refanyval - Push the address stored in a typed reference (4.22) - On line IL_000c this pops the typed reference ([1]) from the stack and pushes the stored address (&i) on the stack (cf. stind in line IL_0012 uses the address to do the assignment of 2, kind of *tr = 2).

    refanytypePush the type token stored in a typed reference (4.21) - On line IL_0039 this pops the typed reference ([1]) from the stack and pushes the type token (i.e. a handle to the type) on the stack. Line IL_003b uses this token to construct a Type object out of it.

    Now, what can we do with all of this? Nothing much to worry about; you haven't missed yet another powerful feature in C#. As we do have the params keyword at our service in C#, we don't need this construct. Anyway, when browsing the SSCLI source this knowledge can be handy to have.

    However, there is one nice (but useless) thing you can do:

    [DllImport("msvcrt40.dll")]
    public static extern int printf(string format, __arglist
    );

    static void Main(string
    [] args)
    {
       printf(
    "Hello %s!\n", __arglist("Bart"
    ));
    }

    Woohoo, "Hello Bart!" on my screen. Guess I'll stick with Console.WriteLine anyhow :-). And you should too; it's very easy to shoot yourself in the foot using undocumented stuff, needless to say so.

    Time to return to reality and wipe out this journey through the dark side of .NET from our memories. Happy __arglist-less coding!

    http://bartdesmet.net/blogs/bart/archive/2006/09/28/4473.aspx
    http://www.codeproject.com/dotnet/pointers.asp

  • 相关阅读:
    模块:标准库Shelve
    模块:标准库shutil
    模块:标准库sys
    关于html中的文本节点问题
    MVVM
    iOS 检测有没有安装其它应用 和ios9下要注意的地方
    iOS9对SDK的影响(iOS9适配必看)
    CircleLayout
    MapSearch 阅读随笔
    苹果官网 demo The Elements 阅读随笔
  • 原文地址:https://www.cnblogs.com/rick/p/861348.html
Copyright © 2011-2022 走看看