As vb6 migration experts in our company we deal everyday with a lot of issues around Interop and serialization.
One important thing to note is the concept of “Bittable Types”. I’m not making up terms. Those terms actually exist. Just see this link in MSDN.
In a few words, a bittable type is a type that has the same representation in managed and unmanaged code.
Why in earth is that important at all?
Because if you are calling that great C++ DLL implemented some years ago that just works ok, you won’t be able to pass a NON-Bittable type because that DLL will expect a binary representation different from that in the .NET virtual machine.
This is also an issue in other scenarios like:
- Serializing content to files
- Sending messages through messaging mechanisms like named-pipes or sockets.
Well, we have just introduced the problem so now let’s think on a nice solution for this problem.
Well Bittable Types are:
The following types from the System namespace are blittable types:
So now let’s look at a couple of non-BITTABLE types
DateTime
To test this differences let’s make a small test in VB6 and write a Date value to a file:
Private Sub SaveDateToFile()
Open "C:\test1.bin" For Binary Access Write As #1
Dim d1 As Date
d1 = "1/1/2009"
Put #1, , d1
Close #1
End Sub
Now let’s make a quick program in Vb.NET
Sub Main()
Dim f As System.IO.FileStream = System.IO.File.Open("C:\test2.bin", IO.FileMode.Create, IO.FileAccess.Write)
Dim fw As New System.IO.BinaryWriter(f)
Dim d As Date
d = Convert.ToDateTime("1/1/2009")
Dim val As Long = d.ToBinary()
fw.Write(val)
fw.Close()
Main2()
End Sub
If we compare these files we will have:
So the values are obviously different. This is because VB6 Date are stores with the OLE Automation DateFormat
So let’s change the C# code for something like:
Sub Main2()
Dim f As System.IO.FileStream = System.IO.File.Open("C:\test3.bin", IO.FileMode.Create, IO.FileAccess.Write)
Dim fw As New System.IO.BinaryWriter(f)
Dim d As Date
d = Convert.ToDateTime("1/1/2009")
fw.Write(d.ToOADate())
fw.Close()
End Sub
And now when we compare the files we will have:
So to make your Date values compatible with VB6 format you must user the DateTime method .ToOADate. Now if you are calling a DLL that expects a Date value in the same format used by VB6 then you will have to do this:
Dim d As Date
d = Convert.ToDateTime("1/1/2009")
Dim handle As System.Runtime.InteropServices.GCHandle = System.Runtime.InteropServices.GCHandle.Alloc(d.ToOADate(), Runtime.InteropServices.GCHandleType.Pinned)
Dim memory_address As IntPtr = handle.AddrOfPinnedObject()
Try
APICall(memory_address)
Finally
d = DateTime.FromOADate(System.Runtime.InteropServices.Marshal.ReadInt64(memory_address))
handle.Free()
End Try
String
Most of the time you wont have to deal with String marshalling because adding marshaling tags to your API call solves most of the problems, but if you arent that luckyly then you might do something like:
IntPtr ptrToStringVar = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(strVar);
try
{
APICall(ptrToStringVar);
}
finally
{
strVar = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptrToStringVar);
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptrToStringVar);
}
NOTE: if you have an API that might return an string with /0 characters you must call the API with System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptrToStringVar,size), if you do that the Framework will take in consideration the size bytes at the ptrToStringVar memory address.
Double and Singles
At least between VB6 and VB.NET the double and single types follows the same format. Well, at least, that is the result of my tests.
Try it yourself, the following shows a simple test for double variables:
VB6
Private Sub SaveDoubleToFile()
Open "C:\test1.bin" For Binary Access Write As #1
Dim d1 As Double
d1 = 1.123
Put #1, , d1
Close #1
End Sub
Sub Main()
SaveDoubleToFile
End Sub
.NET
Module Module1
Sub Main()
Dim f As System.IO.FileStream = System.IO.File.Open("C:\test2.bin", IO.FileMode.Create, IO.FileAccess.Write)
Dim fw As New System.IO.BinaryWriter(f)
Dim d As Double
d = 1.123
fw.Write(d)
fw.Close()
End Sub
End Module
So you could make an api call in those cases with something like:
Dim handle As System.Runtime.InteropServices.GCHandle = System.Runtime.InteropServices.GCHandle.Alloc(d, System.Runtime.InteropServices.GCHandleType.Pinned)
Dim ptr As System.IntPtr = handle.AddrOfPinnedObject()
Try
APICall(ptr)
Finally
handle.Free()
End Try