Question Calling the API function FileTimeToSystemTime

srelu

Member
Joined
Nov 5, 2007
Messages
14
Programming Experience
Beginner
I'm a VC programmer, ocasionally I try to get familiar with VB.NET, but frankly each time I quit being disapointed by its limitations. But that's not the point. Somebody asked me for help to write the VB.NET code to list the browsing history.

First I wrote it in VC, it works nice. Now I managed to do the same in VB, except the fact that I'm unable to deal with the conversion from FILETIME to SYSTEMTIME. I'm supposed to call the API function FileTimeToSystemTime but it doesn't work. The problem is that I don't have any experience using COM in VB.NET. There's (at least) one error in my code, but I can't find it.

Here's a sample, simplified as much as possible. I created a Windows Forms application, leaving everything with the default values. The whole code associated with Form1 follows:

VB.NET:
Imports System.Runtime.InteropServices
Public Class Form1
	Private Structure SYSTEMTIME
		Public wYear As Short
		Public wMonth As Short
		Public wDayOfWeek As Short
		Public wDay As Short
		Public wHour As Short
		Public wMinute As Short
		Public wSecond As Short
		Public wMilliseconds As Short
	End Structure
	<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
	 Private Shared Function FileTimeToSystemTime( _
	 ByRef lpFileTime As IntPtr, _
	 ByRef lpSystemTime As IntPtr _
	 ) As Integer
	End Function

	Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
		Dim SysTimeBfr As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(SYSTEMTIME)))
		Dim FileTimeBfr As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(ComTypes.FILETIME)))
		Dim SysTime As SYSTEMTIME
		Dim FileTime As ComTypes.FILETIME
		FileTime.dwHighDateTime = 29992273
		FileTime.dwLowDateTime = -23761104 ' I swear these values are valid
		Marshal.StructureToPtr(FileTime, FileTimeBfr, True)
		Dim ret As Integer = FileTimeToSystemTime(FileTimeBfr, SysTimeBfr)
		Dim err As UInteger = Marshal.GetLastWin32Error()
		SysTime = Marshal.PtrToStructure(SysTimeBfr, GetType(SYSTEMTIME))
	End Sub
End Class

First of all I would like to use the latest technology so I don't want to use "Declare".

Note that the two values passed to FileTime variable are a valid FILETIME value (I checked it using Visual C++ code). It refers to March 15, 2009.

To me it appears that a wrong function is called (not the FileTimeToSystemTime function). I have two reasons to believe that:

1. The GetLastWin32Error function returns the error code 1400. That stands for INVALID_WINDOW_HANDLE, but the function is not supposed to deal with any window.

2. The function call
VB.NET:
FileTimeToSystemTime(FileTimeBfr, SysTimeBfr)
modifies the value of the SysTimeBfr, even though it's not supposed to do that. The function should just fill in the correponding memory area with the requested info.

What am I doing wrong ? Any help would be greatly appreciated.
 

jmcilhinney

VB.NET Forum Moderator
Staff member
Joined
Aug 17, 2004
Messages
14,349
Location
Sydney, Australia
Programming Experience
10+
Shouldn't your parameters be passed ByVal, not ByRef? ByRef would equate to pointers, but the IntPtrs represent pointers, so you're essentially passing pointers to pointers.
 

srelu

Member
Joined
Nov 5, 2007
Messages
14
Programming Experience
Beginner
Shouldn't your parameters be passed ByVal, not ByRef? ByRef would equate to pointers, but the IntPtrs represent pointers, so you're essentially passing pointers to pointers.

Thank you for your suggesstion, it was really helpful, you found one of the bugs. You just teached me something valuable. There are more differences between VB and VC than I imagined. ByRef and ByVal aren't just type casts. They really change the parameter passed to the method.

I had two problems, the first was the one you found, the other was the line:
VB.NET:
SysTime = Marshal.PtrToStructure(SysTimeBfr, GetType(SYSTEMTIME))

I replaced it with:
VB.NET:
SysTime = DirectCast(Marshal.PtrToStructure(SysTimeBfr, GetType(SYSTEMTIME)), SYSTEMTIME)

Now it works perfectly. Thanks for your help.
For the case somebody else faces the same problem, here's the fully working code:
VB.NET:
Imports System.Runtime.InteropServices
Public Class Form1
	<StructLayout(LayoutKind.Sequential)> _
	Private Structure SYSTEMTIME
		Public wYear As Short
		Public wMonth As Short
		Public wDayOfWeek As Short
		Public wDay As Short
		Public wHour As Short
		Public wMinute As Short
		Public wSecond As Short
		Public wMilliseconds As Short
	End Structure
	<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
	 Private Shared Function FileTimeToSystemTime( _
	 ByVal lpFileTime As IntPtr, _
	 ByVal lpSystemTime As IntPtr _
	 ) As Integer
	End Function

	Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
		Dim SysTimeBfr As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(SYSTEMTIME)))
		Dim FileTimeBfr As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(ComTypes.FILETIME)))
		Dim SysTime As SYSTEMTIME
		Dim FileTime As ComTypes.FILETIME
		FileTime.dwHighDateTime = 29992273
		FileTime.dwLowDateTime = -23761104
		Marshal.StructureToPtr(FileTime, FileTimeBfr, True)
		FileTimeToSystemTime(FileTimeBfr, SysTimeBfr)
		SysTime = DirectCast(Marshal.PtrToStructure(SysTimeBfr, GetType(SYSTEMTIME)), SYSTEMTIME)
	End Sub
End Class
 
Last edited:

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,439
Location
Norway
Programming Experience
10+
The parameter types in Win32 function is structures, so you can define these and use ByRef like this:
VB.NET:
Namespace Win32
    Public Module Win32

        <StructLayout(LayoutKind.Sequential)> _
        Structure SYSTEMTIME
            Public wYear As Short
            Public wMonth As Short
            Public wDayOfWeek As Short
            Public wDay As Short
            Public wHour As Short
            Public wMinute As Short
            Public wSecond As Short
            Public wMilliseconds As Short
        End Structure

        <StructLayout(LayoutKind.Sequential)> _
        Structure FILETIME
            Public dwLowDateTime As Int32
            Public dwHighDateTime As Int32
        End Structure

        Public Declare Function FileTimeToSystemTime Lib "kernel32.dll" ( _
        ByRef lpFileTime As Win32.FILETIME, _
        ByRef lpSystemTime As Win32.SYSTEMTIME) As Int32

        Public Function MakeLong(ByVal lo As Integer, ByVal hi As Integer) As Long
            Return (hi * (UInteger.MaxValue + 1)) Or (lo And UInteger.MaxValue)
        End Function
    End Module
End Namespace
VB.NET:
Dim SysTime As Win32.SYSTEMTIME
Dim FileTime As Win32.FILETIME
FileTime.dwHighDateTime = 29992273
FileTime.dwLowDateTime = -23761104
Win32.FileTimeToSystemTime(FileTime, SysTime)
If you have the two Integers or the Long you can also use the Date.FromFileTime/FromFileTimeUtc functions:
VB.NET:
Dim filetime As Long = Win32.MakeLong(-23761104, 29992273)
Dim sysdate As Date = Date.FromFileTime(filetime)
 

srelu

Member
Joined
Nov 5, 2007
Messages
14
Programming Experience
Beginner
I'm used to relay on Win32 functions. As long as I'm in VC calling such a function is piece of cake. I was unaware about the Date.FromFileTime(filetime) function. That method solves the whole problem efficiently.

Interresting your technique to combine two UIntegers. Just to mention, you solve the problem well, there is just a terminology problem, DWORD is for 4 byte unsigned integers, while FILETIME requires 8 bytes. Your code suplies the necessary 8 byte value, just you cannot name it DWORD. Plus, the LOWORD and the HIWORD macros do not provide DWORDs. They both provide WORD structures - 2 byte long unsigned integers.
But as I said, the data is well handled, only the terminology is unapropriate.

In fact I don't need to combine the two values because I already have the FILETIME in one piece. That's the way it is supplied by the FindFirstUrlCacheEntry function in the INTERNET_CACHE_ENTRY_INFO structure.

I actually split the FILETIME value in two to make a simple and clear initialization. A sample posted on a forum is supposed to use the simplest way to reproduce the error.

Thanks for your suggestions. You greatly simplified a part of my code.
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,439
Location
Norway
Programming Experience
10+
Interresting your technique to combine two UIntegers. Just to mention, you solve the problem well, there is just a terminology problem, DWORD is for 4 byte unsigned integers, while FILETIME requires 8 bytes. Your code suplies the necessary 8 byte value, just you cannot name it DWORD. Plus, the LOWORD and the HIWORD macros do not provide DWORDs. They both provide WORD structures - 2 byte long unsigned integers.
But as I said, the data is well handled, only the terminology is unapropriate.
Yes, the wording is wrong, I just quickly "upgraded" some 16/32 bit code to 32/64 bit for the sample... As .Net concerns "MakeLong" would be correct here. I changed the code sample for this.

The correct parameter types and FILETIME fields would also be UInteger (dwHighDateTime), and the input value "-23761104" would be "4271206192" as UInteger, but .Net also has a corresponding ComTypes.FILETIME structure where the fields is defined as Integer (still called dwHighDateTime), as you know it is the same unmanaged byte data that is used, only marshaled and perceived differently. I decided to keep the Integer parameters.
 
Top Bottom