Thursday, January 31, 2008

VB6 -> VB.NET Upgrade Wizard, Fixed Width Structures

I used the upgrade wizard included in Visual Studio.NET and found it overall pretty good. My biggest issue with it was the way it handled structures with fixed width fields. In VB you'd define a structure like this:

Type example
field1 As String * 10
field2 As String * 10
End Type

In VB.NET it would get converted as:

Structure example
<VBFixedString(10), _
System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=10)> _
Public field1 As String
<VBFixedString(10), _
System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=10)> _
Public field2 As String
End Structure

So it looks like the wizard did the trick by adding all those attributes. I thought that this was adequate until we realized during testing that we weren't getting properly padded fields out of the structure like we had been in VB6. So if we set field1 to "hello" in VB6, then retrieved the value, we'd get "hello     " (thats 5 trailing spaces). In VB.NET, the same value in field1 would be retrieved as "hello" with no spaces. I'd bet that that fancy pants attributes actually does its job when used as a library from another COM app. But in just a normal capacity it did not behave expected.

No problem, we'll just have to try and fix it! So my first thought is that maybe there is some sort of .NET library to redirect all member access to a function. Sort of like __getattr__ in python. We could conceivably just change the Structure to a Class, and inherit it from a class that implements __getattr__ and we wouldn't have to change any code.

No dice, as far as I can tell there is nothing in .NET to bounce member access to a function. It would have been nice to have some sort of class that would provide the bounce for us, then we just override the member resolution function or something.

So what I did instead was turn those Structures to Classes anyways, and had them all inherit from a class implementing this property and method:

Default Public ReadOnly Property GetField(ByVal retrievethisfield As String) As String
Return _GetField(retrievethisfield)
End Get
End Property

Public Function _GetField(ByVal fieldname As String) As String
Dim field As Reflection.FieldInfo = Me.GetType().GetField(fieldname)
Dim val As Object = field.GetValue(Me)
Dim fixedlength As Integer
For Each attr As Attribute In field.GetCustomAttributes(True)
If TypeOf attr Is VBFixedStringAttribute Then
fixedlength = CType(attr, VBFixedStringAttribute).Length
Return Left(CStr(val).PadRight(fixedlength, " "), fixedlength)
End If
Return val
End Function

So the dirty part is that to set you use normal member lookup via obj.field1 etc, but to do a get you do obj("field1") and it will do the padding for you (Note the use of the default property...). You can get rid of some of those wizard attributes, as long as you use VBFixed. Ugly huh! What would be nice is to be able to then tag the member variables in the structure/class as writeonly so I could find (as errors in the task list) all the gets and replace them with my lookup. This is possible for properties, but not for member variables because they must be second class citizens.

Sometimes I feel like VB.NET has some python like things I like (specifically invoking unknown methods on an 'object' object). Other times I realize its a totally different animal and I can find comparable things between any language if I wanted to.

No comments: