.NET Marshalling Optimization - FILETIME conversion

  • Thread starter Thread starter Mike
  • Start date Start date
M

Mike

Folks,

I think this ok and probably should not worry about it and look at it
as some future point, but I am wondering if the following should be
done another way.

Background:

In our SDK, it makes extensive use of the 8 byte FILETIME Win32
structure which is defined (in C/C++)

typedef struct _FILETIME {
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME, *PFILETIME;

A DWORD (Double Word) is an unsigned int32.

In VB.NET, it would be:

Public Structure FILETIME
Public dwLowDateTime As UInteger
Public dwHighDateTime As UInteger
End Structure

Side Note: I understand there is already a FILETIME in

System.Runtime.InteropServices.ComTypes.FILETIME

But I don't wish to bind to any more assemblies if I don't
have to or require developers have to import it for each source
code plus, when you use COMTYPE.FILETIME, you get tons of silly
IDE underscores and compiler warnings about compatibility. So
I am sticking with my own FILETIME. If someone feels there
a good reason to use COMTYPE.FILETIME, please comment.

Now, this is only one step in creating the SDK. The usefulness is how
its employed and used easily by developers close to the way they are
using it now. So in the spirits and elegance of VB.NET OOPs and type
conversion automation, I added a bunch of operators:

Public Structure FILETIME
Public dwLowDateTime As UInteger
Public dwHighDateTime As UInteger

Public Sub new(hi as Uinteger, lo as Uinteger)
dwHighDateTime = hi
dwLowDateTime = lo
End Sub

' convert Operator for converting self to long
Public Shared Widening Operator CType(ft As FILETIME) As Long
Return MakeLong64(ft)
End Operator

' convert Operator for assigning a long
Public Shared Widening Operator CType(l As long) As FILETIME
Dim bytes() As Byte = BitConverter.GetBytes(l)
dim lo as UInteger = BitConverter.ToUInt32(bytes, 0)
dim hi as UInteger = BitConverter.ToUint32(bytes, 4)
Return new FILETIME(hi,lo)
End Operator

' convert operator for assigning a Datetime
Public Shared Widening Operator CType(dt As DateTime) As FILETIME
Dim bytes() As Byte = BitConverter.GetBytes(dt.ToBinary())
dim lo as UInteger = BitConverter.ToUInt32(bytes, 0)
dim hi as UInteger = BitConverter.ToUint32(bytes, 4)
Return new FILETIME(hi,lo)
End Operator

' convert operator for assiging a FILETIME and returning Datetime
Public Shared Widening Operator CType(ft As FILETIME) As DateTime
Return DateTime.FromBinary(MakeLong64(ft))
End Operator

' operator: return diff in seconds
Public Shared Operator -(lt As FILETIME, rt as FILETIME) As long
Return (MakeLong64(lt)-MakeLong64(rt))/10000000
End Operator

' operator: compare less than
Public Shared Operator < (lt As FILETIME, rt as FILETIME) _
As boolean
Return MakeLong64(lt) < MakeLong64(rt)
End Operator

' operator: compare greater than
Public Shared Operator > (lt As FILETIME, rt as FILETIME) _
As boolean
Return MakeLong64(lt) > MakeLong64(rt)
End Operator

' operator: compare greater/equal than
Public Shared Operator >= (lt As FILETIME, rt as FILETIME) _
As boolean
Return MakeLong64(lt) >= MakeLong64(rt)
End Operator

' operator: compare less/equal than
Public Shared Operator <= (lt As FILETIME, rt as FILETIME) _
As boolean
Return MakeLong64(lt) <= MakeLong64(rt)
End Operator

' operator: compare equal
Public Shared Operator = (lt As FILETIME, rt as FILETIME) _
As boolean
Return MakeLong64(lt) = MakeLong64(rt)
End Operator

' operator: compare not equal
Public Shared Operator <> (lt As FILETIME, rt as FILETIME) _
As boolean
Return MakeLong64(lt) <> MakeLong64(rt)
End Operator

Public Overrides Function ToString() as string
return DateTime.FromBinary( _
MakeLong64(dwHighDateTime, dwLowDateTime))
end function
End Structure

These two functions are part of my DATELIB.VB class

Public Function MakeLong64(hi As object, lo As object) As Long
Return CLng(CLng(hi) << 32) + CLng(lo)
End Function

Public Function MakeLong64(ft As FILETIME) As Long
Return MakeLong64(ft.dwHighDateTime, ft.dwLowDateTime)
End Function

All those provides various ways to convert between FILETIME
and DATETIME:

sub DoDateTest1()
dim [now] as datetime = DateTime.Now()
dim ft1 as FILETIME = [now].ToBinary()
dim ft2 as FILETIME = [now]
dim ft3 as New FILETIME(ft2.dwHighDateTime, ft2.dwLowDateTime)

dim dt4 as DateTime = ft3
dim dt5 as DateTime = dt4.AddSeconds(30)
dim ft6 as FILETIME = dt5
dim diff7 as long = ft6 - ft2

dim ft8 as FileTime = dt4.AddYears(1)

Console.WriteLine("now: {0,25} ", [now])
Console.WriteLine("ft1: {0,25} ", ft1)
Console.WriteLine("ft2: {0,25} ", ft2)
Console.WriteLine("ft3: {0,25} ", ft3)
Console.WriteLine("dt4: {0,25} ", dt4)
Console.WriteLine("dt4: {0,25} ", dt5)
Console.WriteLine("ft6: {0,25} ", ft6)
Console.WriteLine("diff7: {0,25} ", diff7)
Console.WriteLine("ft6 > ft2: {0}", ft6 > ft2) 'expect true
Console.WriteLine("ft1 = ft2: {0}", ft1 = ft2) 'expect true
Console.WriteLine("ft1 <>ft2: {0}", ft1 <> ft2) 'expect false
Console.WriteLine("ft8: {0,25} ", ft8)
end sub

My questions to the VB.NET experts:

1) Is using this structure FILETIME with all these operator going to
be a performance hit?

2) Related to #1, should I break this up? A Structure and a Class?

There were two things that made think about it:

- Desire to separate SDK library with wrapper classes;
a base SDK library with structures and API function imports,
and a wrapper library for usability.

- The SDK has many structures that use the FILETIME. Thinking
there might be RTTI and serialization overhead during the
marshalling. Even if negligible, is there a preferred
alternative to reduce redundancy?

Any tips, suggestions, comments, "Got chas?"

thanks

--
 
Mike said:
Folks,

I think this ok and probably should not worry about it and look at it
as some future point, but I am wondering if the following should be
done another way.

Background:

In our SDK, it makes extensive use of the 8 byte FILETIME Win32
structure which is defined (in C/C++)

typedef struct _FILETIME {
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME, *PFILETIME;

A DWORD (Double Word) is an unsigned int32.

In VB.NET, it would be:

Public Structure FILETIME
Public dwLowDateTime As UInteger
Public dwHighDateTime As UInteger
End Structure

Side Note: I understand there is already a FILETIME in

System.Runtime.InteropServices.ComTypes.FILETIME

But I don't wish to bind to any more assemblies if I don't
have to or require developers have to import it for each source
code plus, when you use COMTYPE.FILETIME, you get tons of silly
IDE underscores and compiler warnings about compatibility. So
I am sticking with my own FILETIME. If someone feels there
a good reason to use COMTYPE.FILETIME, please comment.

Now, this is only one step in creating the SDK. The usefulness is how
its employed and used easily by developers close to the way they are
using it now. So in the spirits and elegance of VB.NET OOPs and type
conversion automation, I added a bunch of operators:

Public Structure FILETIME
Public dwLowDateTime As UInteger
Public dwHighDateTime As UInteger

Public Sub new(hi as Uinteger, lo as Uinteger)
dwHighDateTime = hi
dwLowDateTime = lo
End Sub

' convert Operator for converting self to long
Public Shared Widening Operator CType(ft As FILETIME) As Long
Return MakeLong64(ft)
End Operator

' convert Operator for assigning a long
Public Shared Widening Operator CType(l As long) As FILETIME
Dim bytes() As Byte = BitConverter.GetBytes(l)
dim lo as UInteger = BitConverter.ToUInt32(bytes, 0)
dim hi as UInteger = BitConverter.ToUint32(bytes, 4)
Return new FILETIME(hi,lo)
End Operator

' convert operator for assigning a Datetime
Public Shared Widening Operator CType(dt As DateTime) As FILETIME
Dim bytes() As Byte = BitConverter.GetBytes(dt.ToBinary())
dim lo as UInteger = BitConverter.ToUInt32(bytes, 0)
dim hi as UInteger = BitConverter.ToUint32(bytes, 4)
Return new FILETIME(hi,lo)
End Operator

' convert operator for assiging a FILETIME and returning Datetime
Public Shared Widening Operator CType(ft As FILETIME) As DateTime
Return DateTime.FromBinary(MakeLong64(ft))
End Operator

' operator: return diff in seconds
Public Shared Operator -(lt As FILETIME, rt as FILETIME) As long
Return (MakeLong64(lt)-MakeLong64(rt))/10000000
End Operator

' operator: compare less than
Public Shared Operator < (lt As FILETIME, rt as FILETIME) _
As boolean
Return MakeLong64(lt) < MakeLong64(rt)
End Operator

' operator: compare greater than
Public Shared Operator > (lt As FILETIME, rt as FILETIME) _
As boolean
Return MakeLong64(lt) > MakeLong64(rt)
End Operator

' operator: compare greater/equal than
Public Shared Operator >= (lt As FILETIME, rt as FILETIME) _
As boolean
Return MakeLong64(lt) >= MakeLong64(rt)
End Operator

' operator: compare less/equal than
Public Shared Operator <= (lt As FILETIME, rt as FILETIME) _
As boolean
Return MakeLong64(lt) <= MakeLong64(rt)
End Operator

' operator: compare equal
Public Shared Operator = (lt As FILETIME, rt as FILETIME) _
As boolean
Return MakeLong64(lt) = MakeLong64(rt)
End Operator

' operator: compare not equal
Public Shared Operator <> (lt As FILETIME, rt as FILETIME) _
As boolean
Return MakeLong64(lt) <> MakeLong64(rt)
End Operator

Public Overrides Function ToString() as string
return DateTime.FromBinary( _
MakeLong64(dwHighDateTime, dwLowDateTime))
end function
End Structure

These two functions are part of my DATELIB.VB class

Public Function MakeLong64(hi As object, lo As object) As Long
Return CLng(CLng(hi) << 32) + CLng(lo)
End Function

Public Function MakeLong64(ft As FILETIME) As Long
Return MakeLong64(ft.dwHighDateTime, ft.dwLowDateTime)
End Function

All those provides various ways to convert between FILETIME
and DATETIME:

sub DoDateTest1()
dim [now] as datetime = DateTime.Now()
dim ft1 as FILETIME = [now].ToBinary()
dim ft2 as FILETIME = [now]
dim ft3 as New FILETIME(ft2.dwHighDateTime, ft2.dwLowDateTime)

dim dt4 as DateTime = ft3
dim dt5 as DateTime = dt4.AddSeconds(30)
dim ft6 as FILETIME = dt5
dim diff7 as long = ft6 - ft2

dim ft8 as FileTime = dt4.AddYears(1)

Console.WriteLine("now: {0,25} ", [now])
Console.WriteLine("ft1: {0,25} ", ft1)
Console.WriteLine("ft2: {0,25} ", ft2)
Console.WriteLine("ft3: {0,25} ", ft3)
Console.WriteLine("dt4: {0,25} ", dt4)
Console.WriteLine("dt4: {0,25} ", dt5)
Console.WriteLine("ft6: {0,25} ", ft6)
Console.WriteLine("diff7: {0,25} ", diff7)
Console.WriteLine("ft6 > ft2: {0}", ft6 > ft2) 'expect true
Console.WriteLine("ft1 = ft2: {0}", ft1 = ft2) 'expect true
Console.WriteLine("ft1 <>ft2: {0}", ft1 <> ft2) 'expect false
Console.WriteLine("ft8: {0,25} ", ft8)
end sub


Some things first:
- Why not ULong?
- Datetime.tobinary() can not be "bitblitted" to FILETIME.
- There's DateTime.ToFiletime, DateTime.FromFiletime. (looking at them, it
also answers why you might have used Long instead of ULong)
- I'd like the "-" operator to return a TimeSpan (because you will never
remember the unit of the returned Long value otherwise. Seconds? Ticks?)

My questions to the VB.NET experts:

1) Is using this structure FILETIME with all these operator going to
be a performance hit?


For performance reasons, I'd skip the MakeLong64 call, for example:

Public Shared Operator = (lt As FILETIME, rt as FILETIME) _
As boolean

Return lt.dwlowdatetime = rt.dwlowdatetime andalso _
lt.dwhidatetime=rt.dwhidatetime

End Operator


Or even faster:

<structlayout(explicit)>
Public Structure FILETIME
<fieldoffset(0)> private DateTime As ULong
<fieldoffset(0)> Public dwLowDateTime As UInteger
<fieldoffset(4)> Public dwHighDateTime As UInteger
End Structure

Public Shared Operator = (lt As FILETIME, rt as FILETIME) _
As boolean
Return lt.DateTime = rt.DateTime
End Operator

This would simplify it a lot because conversion can be omitted in most
places.


In general, I would prefer _not_ to work with a FILETIME object. I'd use
it only if necessary with API calls. Then immediatelly convert to DateTime
or DateTimeOffset and work with them. Then you can also drop some operators
(or even everything but the two fields).


I'm not sure what you mean with #2.


Armin
 
Armin said:
Some things first:
- Why not ULong?
- Datetime.tobinary() can not be "bitblitted" to FILETIME.

Am I using it wrong? See a problem? I saw a MSDN note saying TO/From
Binary would be the way to do proper type casting and conversion.
- There's DateTime.ToFiletime, DateTime.FromFiletime. (looking at them, it
also answers why you might have used Long instead of ULong)

Right. Being trying to stay as strict as required.
- I'd like the "-" operator to return a TimeSpan (because you will never
remember the unit of the returned Long value otherwise. Seconds? Ticks?)

Yes, definitely crossed my mind to use timespace or DateTimeDiff, etc.
But the reason why because we make extensive use of FILETIME
calculations and differences and off the top of my head I didn't want
to introduce yet another class for this part.

One quick usage, Get Files updated in the last 30 seconds:

dim nowUTC as FILETIME = SystemFileTime()
Dim FileList As New ServerFiles(Of TFileRecord)
for each File as TFileRecord in FileList
if (nowUTC - file.PostTime) < 30 then
....
end if
next

I did a timer profile and there was real difference in performance.
Impressed in fact. I might get it in when all said in done. For now,
need to duplicate functionally, then replace.
For performance reasons, I'd skip the MakeLong64 call, for example:

Public Shared Operator = (lt As FILETIME, rt as FILETIME) _
As boolean

Return lt.dwlowdatetime = rt.dwlowdatetime andalso _
lt.dwhidatetime=rt.dwhidatetime

End Operator

Good catch!
Or even faster:

<structlayout(explicit)>
Public Structure FILETIME
<fieldoffset(0)> private DateTime As ULong
<fieldoffset(0)> Public dwLowDateTime As UInteger
<fieldoffset(4)> Public dwHighDateTime As UInteger
End Structure

OMG! Get out of town! I've been looking to see how to do union idea in
VB.NET. THANKS!!! Thats going to help in other areas!
This would simplify it a lot because conversion can be omitted in most
places.

No doubt. Thanks for sharing this!
In general, I would prefer _not_ to work with a FILETIME object. I'd use
it only if necessary with API calls. Then immediatelly convert to DateTime
or DateTimeOffset and work with them. Then you can also drop some
operators (or even everything but the two fields).

Right, considered - making it easier for developer to use what they
know (and its documented via MSDN said:
I'm not sure what you mean with #2.

I think Partial Classes answered this. I just pulled all the FILETIME
and SYSTEMTIME structure and consolidatedw with other WIN32 stuff and
put into its own WIN32.vb file but I used Partial CLass WCSERVERAPI so
its part of the main SDK class library.

But at first the class was caled WIN32 and that required a reference
to WIN32. Either via import line or directly in the code.

I still need to one day get a better organization of my new classes,
modules and libraries. Right now I am porting many of my C/C++
libraries and putting them all into one DLL - separate files though.

Thanks a million for your great input.

--
 
Back
Top