Home All Groups Group Topic Archive Search About

.NET Marshalling Optimization - FILETIME conversion

Author
2 Jun 2009 7:47 PM
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

--

Author
2 Jun 2009 11:52 PM
Armin Zingler
Mike wrote:
Show quoteHide quote
> 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
Author
3 Jun 2009 12:55 AM
Mike
Armin Zingler wrote:

> 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.

Show quoteHide quote
>> 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

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 <g>).

> 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.

--