.NET Forum / .NET Framework / CLR / April 2007
Reflection.Emit Call
|
|
Thread rating:  |
Fabio - 12 Apr 2007 13:32 GMT Hi all!
Someone can tell me how to add a
call instance <method>
to my IL with Reflection.Emit()?
I can add
call <method> callvirt <method>
but I need a call instance <method>, because (it seems) that with value types the callvirt give the warning <'instance' added to method's calling convention> compiling with ILASM :(
the code is:
.locals init ( int32 V_0 ) IL_0005: ldc.i4 3 IL_0011: stloc.0 IL_0012: ldloca.s V_0 IL_0014: callvirt string [mscorlib]System.Int32::ToString()
Using
IL_0014: call instance string [mscorlib]System.Int32::ToString()
All go fine, but I don't know how to write in with Reflection.Emit...
Some help?
Thanks!
Barry Kelly - 12 Apr 2007 14:27 GMT > Hi all! > > Someone can tell me how to add a > > call instance <method> The 'instance' is part of the method ref/def. If you have a MethodInfo that refers to an instance method, then you get 'instance', if it's static, you get 'static'. As long as you got the right MethodInfo, you don't need to worry about it.
-- Barry
 Signature http://barrkel.blogspot.com/
Fabio - 12 Apr 2007 17:08 GMT "Barry Kelly" <barry.j.kelly@gmail.com> ha scritto nel messaggio
>> Hi all! >> [quoted text clipped - 6 lines] > static, you get 'static'. As long as you got the right MethodInfo, you > don't need to worry about it. mmm... the method in question is Int32.ToString() that is an instance method and it is (i think) a "callvirt" method, since it is an override of object.ToString() (I think I can correctly get that it is an override because method.IsVirtual and method.IsHideBySig are true).
So, if I call Emit(CallVirt... it works, but the IlAsm tell me that the right way to do it is call instance, and if I replace in the code callvirt with call instance ilasm compile without claims.
So, my question is: can I do a Emit(Call ... instance?
Thanks
Barry Kelly - 12 Apr 2007 19:09 GMT > "Barry Kelly" <barry.j.kelly@gmail.com> ha scritto nel messaggio > > [quoted text clipped - 19 lines] > > So, my question is: can I do a Emit(Call ... instance? What's wrong with this?
---8<--- static void Write42(ILGenerator cg) { LocalBuilder x = cg.DeclareLocal(typeof(Int32)); cg.Emit(OpCodes.Ldc_I4, 42); cg.Emit(OpCodes.Stloc, x); cg.Emit(OpCodes.Ldloca, x); cg.Emit(OpCodes.Call, typeof(System.Int32).GetMethod("ToString", new Type[0])); cg.Emit(OpCodes.Call, typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(string) })); } --->8---
Does it do something different to what you want?
I suggest you read the specification for 'call', in Partition III section 3.19 of ECMA 335. In particular:
"The metadata token carries sufficient information to determine whether the call is to a static method, an instance method, a virtual method, or a global function."
[...]
"It is valid to call a virtual method using call (rather than callvirt); this indicates that the method is to be resolved using the class specified by method rather than as specified dynamically from the object being invoked"
-- Barry
 Signature http://barrkel.blogspot.com/
Fabio - 12 Apr 2007 22:50 GMT > What's wrong with this? > [quoted text clipped - 15 lines] > > Does it do something different to what you want? Not for me, but just try to debug (i.e. with the DbgCLR.exe) the produced code
---- .locals init (
int32 V_0
)
IL_0000: ldc.i4 42
IL_0005: stloc.0
IL_0006: ldloca.s V_0
IL_0008: call string [mscorlib]System.Int32::ToString()
IL_000d: call void [mscorlib]System.Console::WriteLine(string)
----
I got a (traslated from italian) 'cannot find the method 'System.String System.Int32.ToString()'.
This because using the 'call' you must calla *static* method (i.e. Console.WriteLine()). For virtual methods you have to use 'callvirt', and for instance methods (overridden or non virtual) you should use 'call instance', if you use callvirt the compiler correct this for you, but it is not the right way.
> I suggest you read the specification for 'call', in Partition III > section 3.19 of ECMA 335. In particular: It's about a week that I'm studing that (obscene) document
> "The metadata token carries sufficient information to determine whether > the call is to a static method, an instance method, a virtual method, or [quoted text clipped - 6 lines] > specified by method rather than as specified dynamically from the object > being invoked" and also something different as I described before and that I don't remember where (mybe on MSDN).
Thanks
Barry Kelly - 12 Apr 2007 23:54 GMT > Not for me, but just try to debug (i.e. with the DbgCLR.exe) the produced > code > [...] > I got a (traslated from italian) 'cannot find the method 'System.String > System.Int32.ToString()'. Sounds like a bug in DbgCLR. I've never used it.
> This because using the 'call' you must calla *static* method (i.e. > Console.WriteLine()). This is not true. Read the specification. In particular, it's not necessary to use callvirt for overridden ToString() calls on valuetypes, because all valuetypes are sealed (aka final). Further quote from 3.19:
"When using the call opcode to call a non-final virtual method on an instance other than a boxed value type, verification checks that the instance reference to the method being called is the result of ldarg.s 0, ldarg 0 and the caller’s body does not contain starg.s 0, starg 0 or ldarga.s 0, ldarga 0."
In fact it's important in this case, in order to avoid redundant boxing and to permit the JIT to inline calls.
If I recall correctly, C# always generates callvirt even for non-virtual instance calls on reference types because part of the semantics of C# is that calling an instance method on a null instance causes a NullPointerException. This is not the case for some other languages targeting the CLR. For example, Delphi for .NET permits instance method calls on null instances. In order for this to work on the CLR, it needs to use the 'call' opcode instead of 'callvirt'.
> For virtual methods you have to use 'callvirt', and for instance methods > (overridden or non virtual) you should use 'call instance' This 'call instance' is a complete fabrication by *you*. It DOES NOT EXIST in that form. The 'instance' is part of the METHOD specification, to help ILASM perform the lookup. IT IS NOT PART OF THE CALL OPCODE.
When you see:
'call instance void [foo]Bar.Baz()'
you should read this:
(call) (instance void [foo]Bar.Baz())
The 'instance' is saying that Bar.Baz is an instance method, i.e., is not a static method.
At the binary level, the call opcode is a single byte (value 0x28), and it is followed by a 32-bit integer token. This token refers to either a MethodRef or MethodDef (or MethodSpec) in the assembly; it's the MethodDef / MethodRef that contains all information necessary to actually link to the target method. (MethodSpec is for generic methods.)
-- Barry
 Signature http://barrkel.blogspot.com/
Fabio - 13 Apr 2007 01:36 GMT >> I got a (traslated from italian) 'cannot find the method 'System.String >> System.Int32.ToString()'. > > Sounds like a bug in DbgCLR. I've never used it. No. I got the same error launching the compiled exe :(
>> This because using the 'call' you must calla *static* method (i.e. >> Console.WriteLine()). > > This is not true. Read the specification. In particular, it's not > necessary to use callvirt for overridden ToString() calls on valuetypes, > because all valuetypes are sealed (aka final). Further quote from 3.19: Well, here I think you can be right :) I'm having another issue to investigate with Int32.ToString(string) I'll re-read the call chapter :(
> This 'call instance' is a complete fabrication by *you*. It DOES NOT > EXIST in that form. The 'instance' is part of the METHOD specification, > to help ILASM perform the lookup. IT IS NOT PART OF THE CALL OPCODE. I don't understand this.
call instance <method>
is compilable, and in decompiled code (I can see it with Reflector) I can see the 4 distinct forms: call call instance callvirt callvirt instance
and If call instance doesn't exist, why ilasm tell me to specify it, and when I do it all goes right?
Thanks again
Fabio - 13 Apr 2007 01:46 GMT > I'm having another issue to investigate with Int32.ToString(string) Nothing, As I sayd, If I check Method.IsHideBySig == true and use the callvirt all works fine (except the ilasm that claims about it).
But I'll re-read the call chapter...
Ben Voigt - 13 Apr 2007 20:53 GMT > call instance <method> > [quoted text clipped - 4 lines] > callvirt > callvirt instance No, they are not distinct forms. There is only call and callvirt. The argument to call or callvirt may have an instance modifier, but it is not a new opcode.
> and If call instance doesn't exist, why ilasm tell me to specify it, and > when I do it all goes right? const int i; int * p = &i; // error const int * q = &i; // ok
But q is not const! I can now do q++; Only what is pointed *to* is const, not the pointer.
Similarly, what is called is an instance method, the call is not a "call instance". The keyword "instance" modifies the method, not the call.
Barry Kelly - 13 Apr 2007 20:57 GMT > >> I got a (traslated from italian) 'cannot find the method 'System.String > >> System.Int32.ToString()'. [quoted text clipped - 3 lines] > No. > I got the same error launching the compiled exe :( Observe:
Step 1: Save the following as EmitWrite32.cs
---8<--- using System; using System.Reflection; using System.Reflection.Emit;
class Program { delegate void Method(); static void Main() { try { AssemblyBuilder assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( new AssemblyName("Write42"), AssemblyBuilderAccess.Save); ModuleBuilder modBuilder = assBuilder.DefineDynamicModule("Write42.exe"); TypeBuilder typeBuilder = modBuilder.DefineType("App"); MethodBuilder mainMeth = typeBuilder.DefineMethod("Main", MethodAttributes.Public | MethodAttributes.Static); Write42(mainMeth.GetILGenerator()); typeBuilder.CreateType(); assBuilder.SetEntryPoint(mainMeth); assBuilder.Save("Write42.exe"); } catch (Exception ex) { Console.WriteLine(ex.Message); } } static void Write42(ILGenerator cg) { LocalBuilder x = cg.DeclareLocal(typeof(Int32)); cg.Emit(OpCodes.Ldc_I4, 42); cg.Emit(OpCodes.Stloc, x); cg.Emit(OpCodes.Ldloca, x); cg.Emit(OpCodes.Call, typeof(System.Int32).GetMethod("ToString", new Type[0])); cg.Emit(OpCodes.Call, typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(string) })); cg.Emit(OpCodes.Ret); } } --->8---
Step 2: Compile with 'csc EmitWrite42.cs'
Step 3: Execute EmitWrite42.exe and observe that Write42.exe is created without error.
Step 4: Execute Write42.exe and verify that it does indeed write 42, without error.
Step 5: Execute 'ildasm Write42.exe -all -out:Write42.il', look inside Write42.il and observe the following code has been emitted for App.Main:
---8<--- .method /*06000001*/ public static void Main() cil managed // SIG: 00 00 01 { .entrypoint // Method begins at RVA 0x2050 // Code size 19 (0x13) .maxstack 1 .locals /*11000001*/ init (int32 V_0) IL_0000: /* 20 | 2A000000 */ ldc.i4 0x2a IL_0005: /* 0A | */ stloc.0 IL_0006: /* 12 | 00 */ ldloca.s V_0 IL_0008: /* 28 | (0A)000001 */ call instance string [mscorlib/*23000001*/]System.Int32/*01000002*/::ToString() /* 0A000001 */ IL_000d: /* 28 | (0A)000002 */ call void [mscorlib/*23000001*/]System.Console/*01000003*/::WriteLine(string) /* 0A000002 */ IL_0012: /* 2A | */ ret } // end of method App::Main --->8---
In particular, I draw your attention to these two lines:
---8<--- IL_0008: /* 28 | (0A)000001 */ call instance string [mscorlib/*23000001*/]System.Int32/*01000002*/::ToString() /* 0A000001 */ IL_000d: /* 28 | (0A)000002 */ call void [mscorlib/*23000001*/]System.Console/*01000003*/::WriteLine(string) /* 0A000002 */ --->8---
'call instance' in the first line, just 'call' in the second, all with just a single opcode with value 0x28: magic!
Why?
Because the first call is to an instance method, and the second call is to a static method. It really is as simple as that!
-- Barry
 Signature http://barrkel.blogspot.com/
Barry Kelly - 13 Apr 2007 22:32 GMT > and If call instance doesn't exist, why ilasm tell me to specify it, and > when I do it all goes right? A piece of the puzzle you may be missing is that instance methods have a hidden 'this' parameter. For example:
class A { void M() {} static void M() {} }
Now 'void A::M()' is ambiguous - does it refer to the instance method or the static method? The 'instance' is what ILASM needs to solve this problem.
When you're using Reflection.Emit, you don't have this problem, because the method has already been resolved, by dint of having acquired a MethodInfo for it.
-- Barry
 Signature http://barrkel.blogspot.com/
Fabio - 14 Apr 2007 08:51 GMT > When you're using Reflection.Emit, you don't have this problem, because > the method has already been resolved, by dint of having acquired a > MethodInfo for it. I'm really sorry, it was all my fault :(
All the discussion was because I have a class that translate OpCodes.Callvirt to the string "callvirt" regardless of the tharget method. (for debug reasons I compile the code produced by this class with the ilasm, I don't use the AssemblyBuilder.Save(), but I think I need a revision of this strategy).
Just one finally question (I know you already explained this in theory, but I need a "code" confirmation) Having the MethodInfo, how can I say "ok, on this I can use callvirt/call/call instance" to be compiled with ILasm?
Thanks again to all!
Ben Voigt - 18 Apr 2007 02:29 GMT >> When you're using Reflection.Emit, you don't have this problem, because >> the method has already been resolved, by dint of having acquired a [quoted text clipped - 13 lines] > Having the MethodInfo, how can I say "ok, on this I can use > callvirt/call/call instance" to be compiled with ILasm? see MethodInfo.IsStatic, MethodInfo.IsVirtual properties
> Thanks again to all!
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|