How would one access object properties in CIL (MSIL)? - properties

I'm an absolute beginner, you see. Say I have a string object on the stack and want to get the number of characters in it - its .Length property. How would I get the int32 number hidden inside?
Many thanks in advance!

There's really no such thing as properties in IL. There are only fields and methods. The C# property construct is translated to get_PropertyName and set_PropertyName methods by the compiler, so you have to call these methods to access the property.
Sample (debug) IL for code
var s = "hello world";
var i = s.Length;
IL
.locals init ([0] string s,
[1] int32 i)
IL_0000: nop
IL_0001: ldstr "hello world"
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: callvirt instance int32 [mscorlib]System.String::get_Length()
IL_000d: stloc.1
As you can see the Length property is accessed via the call to get_Length.

I cheated ... I took the following C# code and took a look at it in ildasm/Reflector
static void Main(string[] args)
{
string h = "hello world";
int i = h.Length;
}
is equivalent to
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 1
.locals init (
[0] string h,
[1] int32 i)
L_0000: nop
L_0001: ldstr "hello world"
L_0006: stloc.0
L_0007: ldloc.0
L_0008: callvirt instance int32 [mscorlib]System.String::get_Length()
L_000d: stloc.1
L_000e: ret
}

Related

Compiler Differences Between Different Builds of Visual Studio

I recently installed Visual Studio 2019 after using 2017. I have a vb.net web api application that I develop for and every since I started building the project in 2019, I am receiving errors that never occurred before in 2017. I have previously working code that was performing a null check and count greater than 0 check on a dictionary (dictionary1?.Count > 0). This code is failing to check for nothing and my next check is throwing Object Reference must be set to an instance of an object because my dictionary is nothing. It only seems to happen when I build it with 2019 on my computer. None of my team members appear to have this same issue. Does anyone know what I could have missed in the install? Do I need to reinstall 2019 to fix the issue?
Edit
Null Reference Exception Example
I also had some issues with VB.NET solution files in 2019 that I did NOT have with 2017. I never could figure out what was wrong exactly, so I created a "new" project in 2019, and then just copied by code in into the new project. It was a pain, but it worked. So far I've been on 2019 without any further issues.
I've used the following code on VS 2017 and 2019, in an netcore (2019) and netstd472 (both 2017 and 2019) app:
Sub Main(args As String())
Dim d As Dictionary(Of String, String) = Nothing
Dim k As KeyValuePair(Of String, String) = Nothing
If d?.Count > 0 AndAlso d.ContainsKey(k.Key) Then
End If
End Sub
None of them throw an exception.
Here's the generated IL for the netstd, built with 2017's Microsoft (R) Build Engine version 15.9.21+g9802d43bc3 for .NET Framework:
.method public static void Main(string[] args) cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 51 (0x33)
.maxstack 2
.locals init ([0] class [mscorlib]System.Collections.Generic.Dictionary`2<string,string> d,
[1] valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string,string> k,
[2] bool V_2)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloca.s k
IL_0005: initobj valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string,string>
IL_000b: ldloc.0
IL_000c: brtrue.s IL_0011
IL_000e: ldc.i4.0
IL_000f: br.s IL_001a
IL_0011: ldloc.0
IL_0012: call instance int32 class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::get_Count()
IL_0017: ldc.i4.0
IL_0018: cgt
IL_001a: brfalse.s IL_002b
IL_001c: ldloc.0
IL_001d: ldloca.s k
IL_001f: call instance !0 valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string,string>::get_Key()
IL_0024: callvirt instance bool class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::ContainsKey(!0)
IL_0029: br.s IL_002c
IL_002b: ldc.i4.0
IL_002c: stloc.2
IL_002d: ldloc.2
IL_002e: brfalse.s IL_0031
IL_0030: nop
IL_0031: nop
IL_0032: ret
} // end of method Module1::Main
and for the netcore31 built with 2019's Microsoft (R) Build Engine version 16.4.0+e901037fe for .NET Framework:
.method public static void Main(string[] args) cil managed
{
.entrypoint
.custom instance void [System.Runtime]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 51 (0x33)
.maxstack 2
.locals init (class [System.Collections]System.Collections.Generic.Dictionary`2<string,string> V_0,
valuetype [System.Runtime]System.Collections.Generic.KeyValuePair`2<string,string> V_1,
bool V_2)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloca.s V_1
IL_0005: initobj valuetype [System.Runtime]System.Collections.Generic.KeyValuePair`2<string,string>
IL_000b: ldloc.0
IL_000c: brtrue.s IL_0011
IL_000e: ldc.i4.0
IL_000f: br.s IL_001a
IL_0011: ldloc.0
IL_0012: call instance int32 class [System.Collections]System.Collections.Generic.Dictionary`2<string,string>::get_Count()
IL_0017: ldc.i4.0
IL_0018: cgt
IL_001a: brfalse.s IL_002b
IL_001c: ldloc.0
IL_001d: ldloca.s V_1
IL_001f: call instance !0 valuetype [System.Runtime]System.Collections.Generic.KeyValuePair`2<string,string>::get_Key()
IL_0024: callvirt instance bool class [System.Collections]System.Collections.Generic.Dictionary`2<string,string>::ContainsKey(!0)
IL_0029: br.s IL_002c
IL_002b: ldc.i4.0
IL_002c: stloc.2
IL_002d: ldloc.2
IL_002e: brfalse.s IL_0031
IL_0030: nop
IL_0031: nop
IL_0032: ret
} // end of method Program::Main
Hope it's of some use
Update
Though the above code doesn't crash, this does:
Sub Main(args As String())
Dim d As Dictionary(Of String, String) = Nothing
Dim k As KeyValuePair(Of String, String) = Nothing
Dim x = d?.Count > 0
If x AndAlso d.ContainsKey(k.Key) Then
End If
End Sub
Here's the IL:
.method public static void Main(string[] args) cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 112 (0x70)
.maxstack 2
.locals init ([0] class [mscorlib]System.Collections.Generic.Dictionary`2<string,string> d,
[1] valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string,string> k,
[2] valuetype [mscorlib]System.Nullable`1<bool> x,
[3] valuetype [mscorlib]System.Nullable`1<bool> V_3,
[4] bool V_4,
[5] valuetype [mscorlib]System.Nullable`1<bool> V_5)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloca.s k
IL_0005: initobj valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string,string>
IL_000b: ldloc.0
IL_000c: brtrue.s IL_0019
IL_000e: ldloca.s V_3
IL_0010: initobj valuetype [mscorlib]System.Nullable`1<bool>
IL_0016: ldloc.3
IL_0017: br.s IL_0027
IL_0019: ldloc.0
IL_001a: call instance int32 class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::get_Count()
IL_001f: ldc.i4.0
IL_0020: cgt
IL_0022: newobj instance void valuetype [mscorlib]System.Nullable`1<bool>::.ctor(!0)
IL_0027: stloc.2
IL_0028: ldloc.2
IL_0029: dup
IL_002a: stloc.3
IL_002b: stloc.s V_5
IL_002d: ldloca.s V_5
IL_002f: call instance bool valuetype [mscorlib]System.Nullable`1<bool>::get_HasValue()
IL_0034: brfalse.s IL_003f
IL_0036: ldloca.s V_3
IL_0038: call instance !0 valuetype [mscorlib]System.Nullable`1<bool>::GetValueOrDefault()
IL_003d: brfalse.s IL_0059
IL_003f: ldloc.0
IL_0040: ldloca.s k
IL_0042: call instance !0 valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string,string>::get_Key()
IL_0047: callvirt instance bool class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::ContainsKey(!0)
IL_004c: brtrue.s IL_0056
IL_004e: ldc.i4.0
IL_004f: newobj instance void valuetype [mscorlib]System.Nullable`1<bool>::.ctor(!0)
IL_0054: br.s IL_0057
IL_0056: ldloc.3
IL_0057: br.s IL_005f
IL_0059: ldc.i4.0
IL_005a: newobj instance void valuetype [mscorlib]System.Nullable`1<bool>::.ctor(!0)
IL_005f: stloc.3
IL_0060: ldloca.s V_3
IL_0062: call instance !0 valuetype [mscorlib]System.Nullable`1<bool>::GetValueOrDefault()
IL_0067: stloc.s V_4
IL_0069: ldloc.s V_4
IL_006b: brfalse.s IL_006e
IL_006d: nop
IL_006e: nop
IL_006f: ret
} // end of method Module1::Main
I don't really have an explanation though

NullReferenceException in VS2015 C++/CLI Release Build

I'm getting "System.NullReferenceException: Object reference not set to an instance of an object." on my release build. I have created a sample application that imitates what's there in my production code.
void Abc::LogService::Log(String^ message)
{
try
{
int ret = DoProcessing(message);
Exception^ ex;
if (ret == 0)
{
ex = gcnew ArgumentException("Processing done.");
}
else
{
ex = gcnew ArgumentNullException("message", "Null args");
}
throw ex;
}
finally
{
//do someother thing.
}
}
With the above code, it reports the exception line to be:
at Abc.LogService.Log(String message) in logservice.cpp:line 19 which corresponds to the throw ex; statement in the code.
The MSIL in the release build for this function looks as:
.method public hidebysig instance void Log(string message) cil managed
{
// Code size 46 (0x2e)
.maxstack 4
.locals ([0] class [mscorlib]System.Exception V_0,
[1] class [mscorlib]System.Exception ex)
.try
{
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: call instance int32 Abc.LogService::DoProcessing(string)
IL_0007: ldnull
IL_0008: stloc.1
IL_0009: brtrue.s IL_0018
IL_000b: ldstr "Processing done."
IL_0010: newobj instance void [mscorlib]System.ArgumentException::.ctor(string)
IL_0015: stloc.0
IL_0016: br.s IL_0028
IL_0018: ldstr "message"
IL_001d: ldstr "Null args"
IL_0022: newobj instance void [mscorlib]System.ArgumentNullException::.ctor(string,
string)
IL_0027: stloc.0
IL_0028: ldloc.1
IL_0029: throw
IL_002a: leave.s IL_002d
} // end .try
finally
{
IL_002c: endfinally
} // end handler
IL_002d: ret
} // end of method LogService::Log
From the MSIL code, it shows that at statement IL_0028, it loads up a null value and calls the throw in the subsequent statement.
The strange part is this happens only if I have the try-finally block.
Debug build of the above code works fine.
Does this sound as a bug in VS2015 v140 toolkit?
Yes, this is an optimizer bug. Pretty unusual, first one I've seen for C++/CLI, a language where the jitter is supposed to do the heavy lifting. It appears to be tripped by declaring the ex variable inside the try-block, getting it to choke on the initialization guarantee. Looks like a flow analysis bug.
Short from compiling with /Od, one workaround is to move the variable out of the try block
void Log(String^ message) {
Exception^ ex;
try {
// etc...
}
Also producing much better MSIL, completely eliminating the variable:
.method public hidebysig instance void Log(string message) cil managed
{
// Code size 41 (0x29)
.maxstack 4
.try
{
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: call instance int32 Test::DoProcessing(string)
IL_0007: brtrue.s IL_0015
IL_0009: ldstr "Processing done."
IL_000e: newobj instance void [mscorlib]System.ArgumentException::.ctor(string)
IL_0013: br.s IL_0024
IL_0015: ldstr "message"
IL_001a: ldstr "Null args"
IL_001f: newobj instance void [mscorlib]System.ArgumentNullException::.ctor(string,
string)
IL_0024: throw
IL_0025: leave.s IL_0028
} // end .try
finally
{
IL_0027: endfinally
} // end handler
IL_0028: ret
} // end of method Test::Log
Optimizer bugs suck, you can report it at connect.microsoft.com

Invalid IL code in XXX(): IL_0023: brfalse IL_00ba

I am trying to understand why this is invalid IL code and/or what would cause this fault.
The exception thrown is:
System.InvalidProgramException: Invalid IL code in
away3d.containers.View3D:updateBackBuffer (): IL_0023: brfalse
IL_00ba
I disassembled using monodis and the method that is being called (updateBackBuffer) and throwing the error follows below, but I can not spot anything wrong with the branch if false statement or the IL around it:
// method line 841
.method family virtual hidebysig newslot
instance default void updateBackBuffer () cil managed
{
// Method begins at RVA 0x1e4d4
// Code size 226 (0xe2)
.maxstack 5
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld class away3d.core.managers.Stage3DProxy away3d.containers.View3D::_stage3DProxy
IL_0007: callvirt instance class [pscorlib_monomac]flash.display3D.Context3D class away3d.core.managers.Stage3DProxy::get_context3D()
IL_000c: brfalse IL_00e1
IL_0011: ldarg.0
IL_0012: ldfld bool away3d.containers.View3D::_shareContext
IL_0017: brtrue IL_00e1
IL_001c: nop
IL_001d: ldarg.0
IL_001e: ldfld float64 away3d.containers.View3D::_width
IL_0023: brfalse IL_00ba
IL_0028: ldarg.0
IL_0029: ldfld float64 away3d.containers.View3D::_height
IL_002e: brfalse IL_00ba
IL_0033: nop
IL_0034: ldarg.0
IL_0035: ldfld class away3d.core.managers.Stage3DProxy away3d.containers.View3D::_stage3DProxy
IL_003a: callvirt instance bool class away3d.core.managers.Stage3DProxy::get_usesSoftwareRendering()
IL_003f: brfalse IL_008c
IL_0044: nop
IL_0045: ldarg.0
IL_0046: ldfld float64 away3d.containers.View3D::_width
IL_004b: ldc.r8 2048.
IL_0054: ble.un IL_0068
IL_0059: ldarg.0
IL_005a: ldc.r8 2048.
IL_0063: stfld float64 away3d.containers.View3D::_width
IL_0068: ldarg.0
IL_0069: ldfld float64 away3d.containers.View3D::_height
IL_006e: ldc.r8 2048.
IL_0077: ble.un IL_008b
IL_007c: ldarg.0
IL_007d: ldc.r8 2048.
IL_0086: stfld float64 away3d.containers.View3D::_height
IL_008b: nop
IL_008c: ldarg.0
IL_008d: ldfld class away3d.core.managers.Stage3DProxy away3d.containers.View3D::_stage3DProxy
IL_0092: ldarg.0
IL_0093: ldfld float64 away3d.containers.View3D::_width
IL_0098: conv.i4
IL_0099: ldarg.0
IL_009a: ldfld float64 away3d.containers.View3D::_height
IL_009f: conv.i4
IL_00a0: ldarg.0
IL_00a1: ldfld unsigned int32 away3d.containers.View3D::_antiAlias
IL_00a6: conv.i4
IL_00a7: ldc.i4.1
IL_00a8: callvirt instance void class away3d.core.managers.Stage3DProxy::configureBackBuffer(int32, int32, int32, bool)
IL_00ad: ldarg.0
IL_00ae: ldc.i4.0
IL_00af: stfld bool away3d.containers.View3D::_backBufferInvalid
IL_00b4: nop
IL_00b5: br IL_00e0
IL_00ba: nop
IL_00bb: ldarg.0
IL_00bc: ldarg.0
IL_00bd: callvirt instance class [pscorlib_monomac]flash.display.Stage class [pscorlib_monomac]flash.display.DisplayObject::get_stage()
IL_00c2: callvirt instance int32 class [pscorlib_monomac]flash.display.Stage::get_stageWidth()
IL_00c7: conv.r8
IL_00c8: callvirt instance void class [pscorlib_monomac]flash.display.DisplayObject::set_width(float64)
IL_00cd: ldarg.0
IL_00ce: ldarg.0
IL_00cf: callvirt instance class [pscorlib_monomac]flash.display.Stage class [pscorlib_monomac]flash.display.DisplayObject::get_stage()
IL_00d4: callvirt instance int32 class [pscorlib_monomac]flash.display.Stage::get_stageHeight()
IL_00d9: conv.r8
IL_00da: callvirt instance void class [pscorlib_monomac]flash.display.DisplayObject::set_height(float64)
IL_00df: nop
IL_00e0: nop
IL_00e1: ret
} // end of method View3D::updateBackBuffer
Maybe it's because loaded value at that place is not boolean?

force vb14 / visual studio 2015 to retain old behavior for implicit conversions

This code behaves differently in VB12 vs VB14.
How can we force the vb compiler to keep the old behavior? The new behavior throws an unhandled excetion because of the new call to Microsoft.VisualBasic.CompilerServices.Conversions::ToDate(string) in the VB14 IL version.
Exception
Unhandled Exception: System.InvalidCastException: Conversion from string "" to type 'Date' is not valid.
at Microsoft.VisualBasic.CompilerServices.Conversions.ToDate(String Value)
at VBConsoleApplication1.Module1.Main() in c:\Module1.vb:line 7
Source Code
Module Module1
Sub Main()
Dim dt As New DateTime
dt = vbNullString
Console.WriteLine(dt)
Console.WriteLine("Any Key")
Console.ReadKey(False)
End Sub
End Module
VB12 IL
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 49 (0x31)
.maxstack 1
.locals init ([0] valuetype [mscorlib]System.DateTime dt,
[1] valuetype [mscorlib]System.DateTime VB$t_date$N0)
IL_0000: nop
IL_0001: ldloca.s dt
IL_0003: initobj [mscorlib]System.DateTime
IL_0009: ldloca.s dt
IL_000b: initobj [mscorlib]System.DateTime
IL_0011: ldloc.0
IL_0012: box [mscorlib]System.DateTime
IL_0017: call void [mscorlib]System.Console::WriteLine(object)
IL_001c: nop
IL_001d: ldstr "Any Key"
IL_0022: call void [mscorlib]System.Console::WriteLine(string)
IL_0027: nop
IL_0028: ldc.i4.0
IL_0029: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey(bool)
IL_002e: pop
IL_002f: nop
IL_0030: ret
} // end of method Module1::Main
VB14 IL
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 47 (0x2f)
.maxstack 1
.locals init ([0] valuetype [mscorlib]System.DateTime dt)
IL_0000: nop
IL_0001: ldloca.s dt
IL_0003: initobj [mscorlib]System.DateTime
IL_0009: ldnull
IL_000a: call valuetype [mscorlib]System.DateTime [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Conversions::ToDate(string)
IL_000f: stloc.0
IL_0010: ldloc.0
IL_0011: box [mscorlib]System.DateTime
IL_0016: call void [mscorlib]System.Console::WriteLine(object)
IL_001b: nop
IL_001c: ldstr "Any Key"
IL_0021: call void [mscorlib]System.Console::WriteLine(string)
IL_0026: nop
IL_0027: ldc.i4.0
IL_0028: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey(bool)
IL_002d: pop
IL_002e: ret
} // end of method Module1::Main

WinAPI SendMessage from .NET

I have an example of winapi code:
struct CommunicationInfo {
long internalMsg;
const TCHAR * srcModuleName;
void * info;
};
...
const TCHAR* szText = _T("Hello from my plugin!\n(test message)");
CommunicationInfo ci = { 0x0401, cszMyPlugin, (void *) szText };
::SendMessage( hNppWnd, 0x111, (WPARAM) _T("NppExec.dll"), (LPARAM) &ci );
I want make the same call from .net and i wrote such wrapper:
[StructLayout(LayoutKind.Sequential)]
public struct CommunicationInfo
{
public Int64 internalMsg;
[MarshalAs(UnmanagedType.LPWStr)]
public StringBuilder srcModuleName;
[MarshalAs(UnmanagedType.LPWStr)]
public StringBuilder data;
};
...
[DllImport("user32")]
public static extern IntPtr SendMessage(IntPtr hWnd,
NppMsg Msg, IntPtr wParam,
[MarshalAs(UnmanagedType.Struct)] CommunicationInfo communicationInfo);
...
SendMessage(hNppWnd, 0x111,
Marshal.StringToHGlobalUni("NppExec.dll"),
new CommunicationInfo
{
data = new StringBuilder("test test"),
internalMsg = 0x0401,
srcModuleName = new StringBuilder("ModuleName")
});
But this code doesn't work. Where did I make a mistake ?
"long" field in CommunicationInfo struct is 32-bit in WinAPI, I believe. So try defining "internalMsg" as System.Int32 in C#
To be sure, try calling printf("%d\n", sizeof(CommunicationInfo)) in C/C++ to know the actual size. If it is (4 + 4 + 4) on a 32-bit system, then the C# struct must also be of 12 byte size.
The "char*" pointer must also be the pointer to unmanaged memory, so the StringBuilder just won't do.
See this PInvoke error when marshalling struct with a string in it for the marshalling sample
As Viktor points out, C/C++ long is 32 bits in size so needs to be matched with C# int. On top of that, the passing of the struct is not handled correctly. In addition the call to StringToHGlobalUni leaks since you never call FreeHGlobal.
I'd probably handle the marshalling something like this:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
public struct CommunicationInfo
{
public int internalMsg;
public string srcModuleName;
public string data;
};
....
[DllImport("user32")]
public static extern IntPtr SendMessage(
IntPtr hWnd,
uint Msg,
[MarshalAs(UnmanagedType.LPWStr)] string wParam,
ref CommunicationInfo communicationInfo
);
....
CommunicationInfo communicationInfo = new CommunicationInfo
{
internalMsg = 0x0401,
srcModuleName = "ModuleName",
data = "test test"
};
SendMessage(hNppWnd, 0x111, "NppExec.dll", ref communicationInfo);