[转载]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).
refanytype - Push 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