I am currently porting a VB6 application to VB.NET and having run the project through the VS2008 conversion wizard, I am now trying to fix the as yet unknowable number of errors that it could not resolve. One of these relates to truncation of integers to yield hi- or lo- words.
The hi-word was ok - VB.NET gives you shift operators so the following is possible.
Dim i As Int32
'...
Dim s As Short
s = i >> 16
Ordinarily ( i.e. in C# ) the lo-word would not pose a problem either as an unchecked ( the default ) conversion from int to short would yield the correct result, simply discarding the upper two bytes
static short LoWord( int val ) {
return ( short ) val;
}
This can be confirmed by disassembling the compiled IL code ( using ILDasm.exe )
.method private hidebysig static int16 LoWord(int32 val) cil managed
{
// Code size 8 (0x8)
.maxstack 1
.locals init (int16 V_0)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: conv.i2
IL_0003: stloc.0
IL_0004: br.s IL_0006
IL_0006: ldloc.0
IL_0007: ret
} // end of method prog::LoWord
The
conv.i2
instruction labelled IL_0002
does not perform overflow checking. However, a similarly written function in VB.NET does not behave the same way:
Function LoWordVb( ByVal i As Integer ) As Short
Return CShort( i )
End Function
This disassembles to:
.method public static int16 LoWordVb(int32 i) cil managed
{
// Code size 7 (0x7)
.maxstack 1
.locals init (int16 V_0)
IL_0000: ldarg.0
IL_0001: conv.ovf.i2
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ldloc.0
IL_0006: ret
} // end of method prog::LoWordVb
Which uses the same overflow checking conv.ovf.i2 instruction as the following C# function:
static short LoWordChecked( int val ) {
checked {
return ( short ) val;
}
}
Which disassembles to:
.method private hidebysig static int16 LoWordChecked(int32 val) cil managed
{
// Code size 9 (0x9)
.maxstack 1
.locals init (int16 V_0)
IL_0000: nop
IL_0001: nop
IL_0002: ldarg.0
IL_0003: conv.ovf.i2
IL_0004: stloc.0
IL_0005: br.s IL_0007
IL_0007: ldloc.0
IL_0008: ret
} // end of method prog::LoWordChecked
( There are a few details that you need to be aware of when using overflow checking in C# which I won't go into - there's a very comprehensive and well written post on the subject here: http://www.codeproject.com/KB/cs/overflow_checking.aspx )
So, while C# offers extremely granular control of overflow checking by means of the
checked
construct, VB.NET does not ( that I could find out about ), so short of controlling this behaviour globally for the entire application ( something which I am a little wary of doing ), there's no immediately obvious way to perform this apparently simple task.My solution was to copy, memory wise, the lower bytes of the Int32 to the Int16; this may not be the most elegant or efficient solution, but it does work.
VB.NET does not offer any of the syntax-level pointer manipulation that C# does ( once you use the terrifyingly named 'unsafe' construct and compiler switch ), however the .NET BCL makes everything you need available via the
System.Runtime.InteropServices
namespace in Mscorlib.dll - once you get to know these tools you can use them from any CLR targeting language.First you need an instance of the
GCHandle
structure for the managed object you are copying from ( in this case it will have to be a boxed copy of the Int32
), you obtain this as follows:
Dim i As Int32
' ....
Dim h As GCHandle = GCHandle.Alloc( i, GCHandleType.Pinned )
And the
GCHandle
must be cleaned up responsibly to prevent resource leaks ( yes, in .NET ) - I suggest a try/finally block immediately after allocation that calls h.Free()
in the finally clause - ensuring that it's freed. One thing that may occur to you is that passing a value type instance such as
Int32
as a parameter of type Object
will result in a box operation, generating a reference that is never explicitly held onto by the calling function and that this could cause problems - this is not the case as both the Pinned
and Normal
members of the GCHandleType
enumeration result in the GCHandle
instance preventing the reference passed in from being collected until the handle is itself freed. Using Pinned
rather than Normal
gives our code the opportunity to use the address of the instance without worrying about it being moved by the garbage collector.The next step is using the
Marshal
class to copy from memory to a managed instance
Dim s As Short
Dim o As Object
o = Marshal.PtrToStructure( h.AddrOfPinnedObject(), GetType( Short ) )
' simple unbox
s = CShort( o )
That's it! These elements can be put together in a function as follows ( a complete program is available at the end of the post ):
Imports System.Runtime.InteropServices
'....
Function LoWordVbUnchecked( ByVal i As Integer ) As Short
Dim s As Short
Dim h As GCHandle = GCHandle.Alloc( i, GCHandleType.Pinned )
Try
Dim o As Object
o = Marshal.PtrToStructure( h.AddrOfPinnedObject(), GetType( Short ) )
s = CShort( o )
Finally
h.Free()
End Try
Return s
End Function
A lot of words, you might think, but I think that this example serves to cover many interesting aspects of developing code for the .NET CLR.
Complete program:
'author: Tosh Afanasiev ( http://tosh.me/ )
Imports System
Imports System.Runtime.InteropServices
Module prog
Public Sub Main()
Dim i As Int32 = &Haaaabbbb
Dim s As Int16 = i >> 16
Console.WriteLine( "i: {0:x}", i )
Console.WriteLine( "hi: {0:x}", s )
Console.WriteLine( "using LoWordVb causes an OverflowException for this value" )
Try
s = LoWordVb( i )
Catch ex As OverflowException
Console.WriteLine( "told you: {0}", ex )
End Try
Console.WriteLine( "LoWordVbUnchecked is ok though" )
s = LoWordVbUnchecked( i )
Console.WriteLine( "lo: {0:x}", s )
End Sub
Function LoWordVb( ByVal i As Integer ) As Short
Return CShort( i )
End Function
Function LoWordVbUnchecked( ByVal i As Integer ) As Short
Dim s As Short
Dim h As GCHandle = GCHandle.Alloc( i, GCHandleType.Pinned )
Try
Dim o As Object
o = Marshal.PtrToStructure( h.AddrOfPinnedObject(), GetType( Short ) )
s = CShort( o )
Finally
h.Free()
End Try
Return s
End Function
End Module
No comments:
Post a Comment