Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# IL - call constructor

Tags:

c#

.net

cil

I'm studying C# IL simple example and can't understand something. I've got very simple program:

void Main()
{
   C c = new C(1);
}
class C
{
   public C(){}
   public C(int i){}
}

there is CIL:

IL_0001:  ldc.i4.1    
IL_0002:  newobj      UserQuery+C..ctor
IL_0007:  stloc.0     // c

C..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  nop         
IL_0007:  nop         
IL_0008:  nop         
IL_0009:  ret         

C..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  nop         
IL_0007:  nop         
IL_0008:  nop         
IL_0009:  ret  

I don't understand, how virtual machine will distinguish which one constructor should be call. There is two same labels and the only difference seems to be in pushing argument in main. Is there something more deeper when calling constructor? Maybe compiler are providing some meta-data to distinguish which one should be called?

So let's assume this:

void Main()
{
  C c = new C(1);
}
class C
{
  public C(){}
  public C(int i){ i += 1;}
}

IL_0001:  ldc.i4.1    
IL_0002:  newobj      UserQuery+C..ctor
IL_0007:  stloc.0     // c

C..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  nop         
IL_0007:  nop         
IL_0008:  nop         
IL_0009:  ret         

C..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  nop         
IL_0007:  nop         
IL_0008:  ldarg.1     
IL_0009:  ldc.i4.1    
IL_000A:  add         
IL_000B:  starg.s     01 
IL_000D:  nop         
IL_000E:  ret   

Now, how to distinguish which one to call, at the label-level we cannot distinguish it.

like image 717
Puchacz Avatar asked Dec 08 '22 05:12

Puchacz


2 Answers

I went to make the experiment...

I used the following code:

class Program
{
    static void Main()
    {
        CallConstructorA();
        CallConstructorB();
    }

    static void CallConstructorA()
    {
        GC.KeepAlive(new C());
    }

    static void CallConstructorB()
    {
        GC.KeepAlive(new C(1));
    }
}

class C
{
    public C() { }
    public C(int i)
    {
        GC.KeepAlive(i);
    }
}

The following is MSIL got with Telerik JustDecompile for the class Program:

.class private auto ansi beforefieldinit Test.Program
    extends [mscorlib]System.Object
{
    .method public hidebysig specialname rtspecialname instance void .ctor () cil managed 
    {
        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: ret
    }

    .method private hidebysig static void CallConstructorA () cil managed 
    {
        IL_0000: nop
        IL_0001: newobj instance void Test.C::.ctor()
        IL_0006: call void [mscorlib]System.GC::KeepAlive(object)
        IL_000b: nop
        IL_000c: ret
    }

    .method private hidebysig static void CallConstructorB () cil managed 
    {
        IL_0000: nop
        IL_0001: ldc.i4.1
        IL_0002: newobj instance void Test.C::.ctor(int32)
        IL_0007: call void [mscorlib]System.GC::KeepAlive(object)
        IL_000c: nop
        IL_000d: ret
    }

    .method private hidebysig static void Main () cil managed 
    {
        .entrypoint
        IL_0000: nop
        IL_0001: call void Test.Program::CallConstructorA()
        IL_0006: nop
        IL_0007: call void Test.Program::CallConstructorB()
        IL_000c: nop
        IL_000d: ret
    }
}

So you can see that the calls are different...

The first one says:

        IL_0001: newobj instance void Test.C::.ctor()

The second says:

        IL_0002: newobj instance void Test.C::.ctor(int32)

So, I'm gessing it is your decompiler that is not showing all the details of the intermediate code. In fact I did try similar code to the one above in LINQPad and there both call looks alike.


For the detail of how the annotation is done in binary... honestly I don't know.

like image 109
Theraot Avatar answered Jan 10 '23 05:01

Theraot


The actual code identifies the constructor to be called by a MethodToken These are unique for each overload.

Your disassembler has an inadequate token-to-string conversion which is giving you only the name, which isn't unique, and cannot be assembled. In constrast, ildasm converts the token into a full signature which is able to roundtrip back to a working assembly (using ilasm).

like image 34
Ben Voigt Avatar answered Jan 10 '23 07:01

Ben Voigt