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

  • 相关阅读:
    NanUI for Winform发布,让Winform界面设计拥有无限可能
    使用Nginx反向代理 让IIS和Tomcat等多个站点一起飞
    jQuery功能强大的图片查看器插件
    Entity Framework 5.0 Code First全面学习
    封装EF code first用存储过程的分页方法
    NET中使用Redis (二)
    Redis学习笔记~Redis主从服务器,读写分离
    ID3算法
    定性归纳(1)
    js加密
  • 原文地址:https://www.cnblogs.com/rick/p/861348.html
Copyright © 2011-2022 走看看