I was just perusing the decompiled code for System.Collections.Generic.List(Of T).Add(item As T) using ILSpy and I found a call to __PostIncrement. I've never heard of such a thing in VB, so I did some digging and found:
In VB, the code is Me._items(__PostIncrement(Me._size)) = item.
In C#, the code is this._items[this._size++] = item; using the actual post-increment operator in that language
In MSIL, there is not a function call. It appears to work like a C# post-increment would (the comments are mine, but I'm no expert at MSIL,so I could be wrong).
The code is:
IL_001e: ldarg.0
IL_001f: ldfld !0[] class System.Collections.Generic.List`1<!T>::_items
IL_0024: ldarg.0
IL_0025: dup
IL_0026: ldfld int32 class System.Collections.Generic.List`1<!T>::_size
IL_002b: dup
IL_002c: stloc.0 // store the size pre incrementing
IL_002d: ldc.i4.1
IL_002e: add // do the increment
IL_002f: stfld int32 class System.Collections.Generic.List`1<!T>::_size
IL_0034: ldloc.0 // reload the stored size to use as index in stelem
IL_0035: ldarg.1
IL_0036: stelem.any !T
What exactly is this __PostIncrement? Is it a SharpDevelop invention to symbolize post-increment IL code in VB? Or is it actually some kind of definition I could use in my own VB code?
__PostIncrement is the equivalent of the C# operator++. It is a quick way to increment (add one) to a number. Unfortunately, according to the list of operators in Visual Basic, there is no equivalent.
Related
I'm new to IL in .NET, and am messing around trying to autogenerate a method that is pure boilerplate.
My test app generates the below IL, but it is throwing a NullReferenceException at IL_002f, after retrieving the value of an int property on an instance which is known not to be NULL (both via the IL (IL_0001) and from the test case I generated). The instance is passed in the first argument (arg.0) to the emitted method.
I expect the value of BasicClass.IntProperty to be the first item on the stack before calling System.Int32.ToString(), so what is possibly going wrong here? Any help to get my head out of my ahem on this would be appreciated.
IL_0000: ldarg.0
IL_0001: brfalse IL_0045
IL_0006: ldarg.0
IL_0007: call System.String get_StringProperty()/QuickSearchTests.Entities.BasicClass
IL_000c: dup
IL_000d: brfalse IL_0022
IL_0012: ldarg.1
IL_0013: call Boolean ContainsIgnoreNull(System.String, System.String)/MyCompany.MyProduct.Common.Extensions.StringExtensions
IL_0018: brtrue IL_0047
IL_001d: br IL_0023
IL_0022: pop
IL_0023: ldarg.2
IL_0024: brfalse IL_0045
IL_0029: ldarg.0
IL_002a: call Int32 get_IntProperty()/QuickSearchTests.Entities.BasicClass
IL_002f: call System.String ToString()/System.Int32
IL_0034: ldarg.1
IL_0035: call Boolean ContainsIgnoreNull(System.String, System.String)/MyCompany.MyProduct.Common.Extensions.StringExtensions
IL_003a: brtrue IL_0047
IL_003f: br IL_0045
IL_0044: pop
IL_0045: ldc.i4.0
IL_0046: ret
IL_0047: ldc.i4.1
IL_0048: ret
For reference, here is the definition of BasicClass
public class BasicClass
{
public string StringProperty { get; set; }
public int IntProperty { get; set; }
internal string InternalStringProperty { get; set; }
}
You are calling a method on a non object: the integer value needs to be boxed in order to have the method invocation work. I would patch your code this way (Z is your choice):
.locals
[Z] int32 temp
callvirt instance int32 QuickSearchTests.Entities.BasicClass/BasicClass::get_IntProperty()
stloc.Z
ldloca.s temp
call instance string [System.Runtime]System.Int32::ToString()
A word of warning: writing IL directly is something particularly challenging. You must be aware that once your IL code works as you expect, you have not finished yet, as you should verify it with peverify. Valid IL is not automatically verifiable.
For this reason, as suggested in the comment, the best approach is to study the documentation and learn from a very special teacher: the compiler. Start by writing your code in a supported language and learn how the compiler turns into IL.
In order to be able to test legacy code which relies on SharePoint, I need to mock some of the objects of SharePoint. I do this by tampering with SharePoint assemblies, replacing their methods by mine on the fly.
This works for some cases, but not for others. A strange situation I encountered is this one.
I want to replace the getter of SPContext.Current by my own implementation; for the sake of simplicity, my implementation just throws an exception:
.property class Microsoft.SharePoint.SPContext Current()
{
.get class Microsoft.SharePoint.SPContext Proxy.SPContextProxy::get_Current()
}
.method public hidebysig specialname static
class Microsoft.SharePoint.SPContext get_Current () cil managed
{
// Method begins at RVA 0x877e68
// Code size 12 (0xc)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Proxy don't have an effective implementation of this property."
IL_0006: newobj instance void [mscorlib]System.NotImplementedException::.ctor(string)
IL_000b: throw
} // end of method SPContextProxy::get_Current
When tampering the original assembly, if I replace the IL code corresponding to SPContext.Current getter, the property cannot be used any longer. I can't even visualize its contents in ILSpy, because this is what is shown instead:
System.NullReferenceException: Object reference not set to an instance of an object.
at Mono.Cecil.Cil.CodeReader.ReadExceptionHandlers(Int32 count, Func`1 read_entry, Func`1 read_length)
at Mono.Cecil.Cil.CodeReader.ReadSection()
at Mono.Cecil.Cil.CodeReader.ReadFatMethod()
at Mono.Cecil.Cil.CodeReader.ReadMethodBody()
at Mono.Cecil.Cil.CodeReader.ReadMethodBody(MethodDefinition method)
at Mono.Cecil.MethodDefinition.<get_Body>b__2(MethodDefinition method, MetadataReader reader)
at Mono.Cecil.ModuleDefinition.Read[TItem,TRet](TRet& variable, TItem item, Func`3 read)
at Mono.Cecil.MethodDefinition.get_Body()
at ICSharpCode.Decompiler.Disassembler.ReflectionDisassembler.DisassembleMethodInternal(MethodDefinition method)
at ICSharpCode.ILSpy.ILLanguage.DecompileProperty(PropertyDefinition property, ITextOutput output, DecompilationOptions options)
at ICSharpCode.ILSpy.TextView.DecompilerTextView.DecompileNodes(DecompilationContext context, ITextOutput textOutput)
at ICSharpCode.ILSpy.TextView.DecompilerTextView.<>c__DisplayClass16.<DecompileAsync>b__15()
On the other hand, when I insert my instructions before the original instructions, I can call the getter successfully, as well as see its contents in ILSpy:
.property class Microsoft.SharePoint.SPContext Current()
{
.custom instance void [Microsoft.SharePoint.Client.ServerRuntime]Microsoft.SharePoint.Client.ClientCallableAttribute::.ctor() = (
01 00 00 00
)
.get class Microsoft.SharePoint.SPContext Microsoft.SharePoint.SPContext::get_Current()
}
.method public hidebysig specialname static
class Microsoft.SharePoint.SPContext get_Current () cil managed
{
// Method begins at RVA 0x33e2d8
// Code size 61 (0x3d)
.maxstack 1
.locals init (
[0] class Microsoft.SharePoint.SPContext,
[1] class [System.Web]System.Web.HttpContext,
[2] class Microsoft.SharePoint.SPContext
)
... follows by the instructions that I inserted:
IL_0000: nop
IL_0001: ldstr "Proxy doesn't implement this property yet."
IL_0006: newobj instance void [mscorlib]System.NotImplementedException::.ctor(string)
IL_000b: throw
... follows by the original instructions:
IL_000c: ldnull
IL_000d: stloc.0
IL_000e: call class [System.Web]System.Web.HttpContext [System.Web]System.Web.HttpContext::get_Current()
IL_0013: stloc.1
IL_0014: ldloc.1
IL_0015: brfalse.s IL_0039
.try
{
IL_0017: ldloc.1
IL_0018: call class Microsoft.SharePoint.SPWeb Microsoft.SharePoint.WebControls.SPControl::GetContextWeb(class [System.Web]System.Web.HttpContext)
IL_001d: brtrue.s IL_0023
IL_001f: ldnull
IL_0020: stloc.2
IL_0021: leave.s IL_003b
IL_0023: leave.s IL_002a
} // end .try
catch [mscorlib]System.InvalidOperationException
{
IL_0025: pop
IL_0026: ldnull
IL_0027: stloc.2
IL_0028: leave.s IL_003b
} // end handler
.try
{
IL_002a: ldloc.1
IL_002b: call class Microsoft.SharePoint.SPContext Microsoft.SharePoint.SPContext::GetContext(class [System.Web]System.Web.HttpContext)
IL_0030: stloc.0
IL_0031: leave.s IL_0039
} // end .try
catch [mscorlib]System.IO.FileNotFoundException
{
IL_0033: pop
IL_0034: leave.s IL_0039
} // end handler
catch [mscorlib]System.InvalidOperationException
{
IL_0036: pop
IL_0037: leave.s IL_0039
} // end handler
IL_0039: ldloc.0
IL_003a: ret
IL_003b: ldloc.2
IL_003c: ret
} // end of method SPContext::get_Current
What prevents the code from being loaded by ILSpy when original instructions are removed before new ones are inserted?
Notes:
Tampering is done with Mono.Cecil by using MethodDefinition.Body.Instructions collection (and the corresponding Insert and Remove methods.)
A few other methods and properties of Microsoft.SharePoint assembly are tampered successfully: ILSpy displays the resulting IL code.
I thought that .maxstack directive could be a problem (1 in the original property, 8 in the proxied one, 1 in the result). After a few tests on a separate project, it appears that it has no effect.
I also suspected that exceptions could be the cause (the original code throws different exceptions than the new one). After a few tests on a separate project, it appears that it has no effect either.
When IL is shown in textual form, exception handling blocks (.try, catch, etc.) appear as actual blocks of IL instructions, just like they do in C#.
But in the binary form, exception handling blocks are stored separately (see §II.25.4.6 Exception handling clauses of ECMA-335) and reference the IL instructions using offsets. In Cecil, exception handlers are represented using the MethodBody.ExceptionHandlers property.
So, if you replaced the old MethodBody.Instructions with your own instructions, it's very likely that the offsets of the old exception handlers are now invalid, which is causing the issues. (The fact that Cecil throws NullReferenceException sounds like a bug to me, consider reporting it.)
The other example that you linked to which doesn't exhibit this problem is different because there the original method doesn't contain exception handlers, it throws an exception. And throw is just a normal IL instruction, it doesn't have a special representation like e.g. .try/catch does.
I'm at a loss on this one. I have a class that inherits from "BaseClass". BaseClass has a protected member called "UpdateData".
I have a class "DataAccess" that inherits from "UpdateData". DataAccess has many different calls that use the "UpdateData" method. This works just fine except for one single method on "DataAccess" that returns a "Member Not found" exception.
I've cleaned and rebuilt, and it's happening in Visual Studio as well as when deployed to the server. The call to 'Add' works, but the call to 'Add Detail' is failing.
I've tried removing the transaction scopes listed below, tried using a factory pattern to get a new instance for each call, but neither has worked. I'd rather not open the method up as "public" as we have hundreds of classes that use the protected "UpdateData" method just fine?
Any help, or new ideas are greatly appreciated!
''''On the base class
Protected Sub UpdateData(ByVal connString As String, ByVal procName As String, ByVal ParamArray params As Object())
UpdateDataWithTimeout(connString, procName, getTimeoutSetting, params)
End Sub
''''On the inherited class. This one works
Protected Friend Overridable Sub Add(ByVal s1 As String, _
ByRef s2 As String, _
ByVal s3 As String, _
ByVal params As List(Of Object))
Try
params.Insert(0, s1)
params.Insert(1, s2)
params.Insert(2, s3)
UpdateData(DB, SPI_PROC1, params.ToArray())
Catch ex As Exception
Throw ex
End Try
End Sub
''''On the inherited "DataAccess" class. This one fails.
Protected Friend Overridable Sub AddDetail(ByVal ParamArray parms As Object())
If Condition1 Then
UpdateData(_sysConn, SPI_PROC, parms)
End If
End Sub
''''This is from a method in the class calling the "DataAccess" code
Dim da As DataAccess
da = New DataAccess(strVariable)
Using scope As New TransactionScope(TransactionScopeOption.RequiresNew)
''''This call works just fine and can find "UpdateData"
da.Add(string1, string2, string3, objectParameterList)
'Separate database, so avoid MSDTC with new transaction scope
Using scope2 As New TransactionScope(TransactionScopeOption.RequiresNew)
''''This call fails to find UpdateData
da.AddDetail(string1, string5, string6, string7, string8, string3)
scope2.Complete()
scope.Complete()
End Using
End Using
Addendum: There is a difference in the IL here with one being "late binding" (which I need to stop) but am not sure how?
Failing method....
IL_001f: ldstr "UpdateData"
IL_0024: ldc.i4.3
IL_0025: newarr [mscorlib]System.Object
IL_002a: stloc.1
IL_002b: ldloc.1
IL_002c: ldc.i4.0
IL_002d: ldarg.0
IL_002e: ldfld string DataAccess::_sysConn
IL_0033: stelem.ref
IL_0034: ldloc.1
IL_0035: ldc.i4.1
IL_0036: ldloc.0
IL_0037: call object [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetObjectValue(object)
IL_003c: stelem.ref
IL_003d: ldloc.1
IL_003e: ldc.i4.2
IL_003f: ldarg.1
IL_0040: stelem.ref
IL_0041: ldloc.1
IL_0042: stloc.2
IL_0043: ldloc.2
IL_0044: ldnull
IL_0045: ldnull
IL_0046: ldc.i4.3
IL_0047: newarr [mscorlib]System.Boolean
IL_004c: stloc.3
IL_004d: ldloc.3
IL_004e: ldc.i4.0
IL_004f: ldc.i4.1
IL_0050: stelem.i1
IL_0051: ldloc.3
IL_0052: ldc.i4.1
IL_0053: ldc.i4.1
IL_0054: stelem.i1
IL_0055: ldloc.3
IL_0056: ldc.i4.2
IL_0057: ldc.i4.1
IL_0058: stelem.i1
IL_0059: ldloc.3
IL_005a: ldc.i4.1
IL_005b: call object [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.NewLateBinding::LateCall(object,
class [mscorlib]System.Type,
string,
object[],
string[],
class [mscorlib]System.Type[],
bool[],
bool)
IL_0060: pop
IL_0061: ldloc.3
IL_0062: ldc.i4.0
IL_0063: ldelem.i1
IL_0064: brfalse.s IL_0088
IL_0066: ldarg.0
IL_0067: ldloc.2
IL_0068: ldc.i4.0
IL_0069: ldelem.ref
IL_006a: call object [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetObjectValue(object)
From the working method, here is the IL.
IL_003b: ldarg.0
IL_003c: ldstr "conn"
IL_0041: ldstr "proc"
IL_0046: ldarg.0
IL_0047: callvirt instance string DataAccess::get_SYSCODE_SPAPPEND()
IL_004c: call string [mscorlib]System.String::Concat(string,
string)
IL_0051: ldarg.s params
IL_0053: callvirt instance !0[] class [mscorlib]System.Collections.Generic.List`1<object>::ToArray()
IL_0058: callvirt instance void [BaseClass]BaseClass::UpdateData(string,
string,
object[])
Of course, the moment you ask the question the answer comes to you.
I altered my string variables for the SPI_PROC and SPI_PROC1 to be Private constants on the class rather than variables... set the _sysConn and DB in the class constructor and it switched everything to early binding.
So problem solved. Hopefully this will save someone else a few hours of time.
I have the following setter-method, but the object I put in value isn't put through to the called method:
.method public hidebysig specialname instance void set_SeatingCapacity(int32 'value') cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
.maxstack 3
L_0000: ldc.i4 0x6c
L_0005: ldarg.0
L_0006: ldfld int32 Young3.FMSearch.Core.Entities.InGame.BaseObject::MemoryAddress
L_000b: ldarg.1
L_000c: call void Young3.FMSearch.Core.Managers.PropertyInvoker::Set(int32, int32, object)
L_0011: ret
}
I want to call the function in L_000c like Set(0x6c, ldfld MemoryAddress, value). The first two fields are correctly posted to the function. Any clue? It looks quite well when doing something similar and looking at the definition in Reflector.
I had to do a box int32, or by making Set into Set<T>.
Is there an advantage to dynamically attaching/detaching event handlers?
Would manually detaching handlers help ensure that there isn't a reference remaining to a disposed object?
I'm pretty sure that the Handles clause is just syntactic sugar and inserts an AddHandler statement into your constructor. I tested using this code and disabled the application framework so the constructor wouldn't have extra stuff:
Public Class Form1
Public Sub New()
' This call is required by the Windows Form Designer. '
InitializeComponent()
' Add any initialization after the InitializeComponent() call. '
AddHandler Me.Load, AddressOf Form1_Load
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim breakpoint As Integer = 4
End Sub
End Class
The IL ended up like this:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: call instance void [System.Windows.Forms]System.Windows.Forms.Form::.ctor()
IL_0007: nop
IL_0008: ldarg.0
IL_0009: ldarg.0
IL_000a: dup
IL_000b: ldvirtftn instance void WindowsApplication1.Form1::Form1_Load(object,
class [mscorlib]System.EventArgs)
IL_0011: newobj instance void [mscorlib]System.EventHandler::.ctor(object,
native int)
IL_0016: call instance void [System.Windows.Forms]System.Windows.Forms.Form::add_Load(class [mscorlib]System.EventHandler)
'... lots of lines here '
IL_0047: ldarg.0
IL_0048: callvirt instance void WindowsApplication1.Form1::InitializeComponent()
IL_004d: nop
IL_004e: ldarg.0
IL_004f: ldarg.0
IL_0050: dup
IL_0051: ldvirtftn instance void WindowsApplication1.Form1::Form1_Load(object,
class [mscorlib]System.EventArgs)
IL_0057: newobj instance void [mscorlib]System.EventHandler::.ctor(object,
native int)
IL_005c: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Form::add_Load(class [mscorlib]System.EventHandler)
IL_0061: nop
IL_0062: nop
IL_0063: ret
} // end of method Form1::.ctor
Notice two identical blocks of code around IL_000b and IL_0051. I think it's just syntactic sugar.
It's not a question of using AddHandler versus Handles.
If you are concerned about the reference to your event handler interfering with garbage collection, you should use RemoveHandler, regardless of how the handler was attached. In the form or control's Dispose method, remove any handlers.
I have had situations in Windows Forms apps (.NET 1.1 days) where an event handler would be called on controls that had no other references to them (and which for all intents and purposes were dead and I would have thought been GC'ed) -- extremely hard to debug.
I would use RemoveHandler to get rid of handlers on controls that you are not going to reuse.
Declaring a field as WithEvents will cause the compiler to automatically generate a property with that name. The getter returns the value of a backing field. The setter is a little more complicated. It first checks whether the backing field already has the correct value. If so, it exits. Otherwise, if the backing field is non-null, it issues "RemoveHandler" requests for all its events to the object identified by the backing field. Next, regardless of whether the backing field was non-null, it sets it equal to the requested value. Finally, if the new value is non-null, whether the old one was or not, the property issues "AddHandler" requests for all its events to the object identified by the new value.
Provided that one sets all of an object's WithEvents members to Nothing before abandoning it, and avoids manipulating WithEvents members in multiple threads, the auto-generated event code will not leak.
I find that dynamically attaching/detaching event handlers is only of use where you have a long-lived object exposes events that are consumed by many short-lived objects. For most other cases the two objects are disposed around the same time and the CLR does a sufficient job of cleaning up on its own
I manually attach handlers when I manually create controls (for example, dynamically creating a TextBox for each database record). I manually detach handlers when they are handling things I'm not quite ready to handle yet (possibly because I'm using the wrong events? :) )
Manually detaching an event can be important to prevent memory leaks: the object that connects to an event fired by another object, will not be garbage collected until the object that fires the event is garbage collected. In other words, an "event-raiser" has a strong reference to all the "event-listeners" connected to it.
Most of the time the framework takes care of that for you.