AHK COM floating point (double) conversion

Report problems with documented functionality
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

AHK COM floating point (double) conversion

11 May 2017, 06:43

In v1.1 AHK clearly converts all (TDateTime) floating point (double) values to strings, which goes wrong when second half of the floating point is midnight (42867.000000). The expected systemtime conversion would have been "2017-05-12 00:00:00". Instead it looks like this "2017-05-12", the time value is missing as if one can assume some right auto-trim is enabled leaving the date value and torpedoing the Time value completely. Without having the slightest clue upon why this is the result, my assumption can only be...this is a bug, yes? I cannot imagine its intended behaviour. https://autohotkey.com/boards/viewtopic ... lit=delphi

Trying to workaround it (the hard way) seems impossible. Have been playing around with intercepting the binary respresentation of the starttime floating point value, before AHK does its "no good" conversion, and trying to convert the variant time back to system time using lexikos SystemTime class and VariantTimeToSystemTime dll calls. But utilizing it on these kind of COM IDispatch calls seems merely impossible. The variant data types cannot be determined...and the dll call does consequently not work.
Doing it when using COM safearrays proved to be possible for some reason. See sample below the one below....

Code: Select all

#NoEnv
;DllCall("oleaut32\SystemTimeToVariantTime", "Ptr", (_ := SystemTime.Now()).p, "Double*", oleSt)
DllCall("oleaut32\SystemTimeToVariantTime", "Ptr", (_ := SystemTime.FromString(20170512000000)).p, "Double*", oleSt)
;SystemTime to convert = 20170512000000
;Results in floating point (double) OleSt = 42867.000000
iDVBViewer := ComObjActive("DVBViewerServer.DVBViewer")
channelnumber := 2 ; number of your wanted channel
epgcid := iDVBViewer.channelmanager(channelnumber).EPGChannelID
SuD := iDVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.SID
TuD := iDVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.TransportStreamID
o_epgGet := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0)
epgnow := o_epgGet.item(0).Title
epgnext := o_epgGet.item(1).Title
starttime := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,oleSt).item(0).Time ; AHK converts oleSt floating point value to systemtime and fails to bring back the HH:mm:ss part. 
endtime := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0).item(0).Endtime

msgbox % starttime . "  " . epgnow . "`n" . endtime "  " epgnext " "

; Written by Lexikos for EnableUIAccess
/*
    SystemTime - Wrapper for Win32 SYSTEMTIME Structure
        http://msdn.microsoft.com/en-us/library/ms724950
    Usage Examples:
    ; Create structure from string.
    st := SystemTime.FromString(A_Now)
    ; Shortcut:
    st := SystemTime.Now()
    ; Update values.
    st.FromString(A_Now)
    ; Retrieve components.
    year    := st.Year
    month   := st.Month
    weekday := st.DayOfWeek
    day     := st.Day
    hour    := st.Hour
    minute  := st.Minute
    second  := st.Second
    ms      := st.Milliseconds
    ; Set or perform math on component.
    st.Year += 10
    ; Create structure to receive output from DllCall.
    st := new SystemTime
    DllCall("GetSystemTime", "ptr", st.p)
    MsgBox % st.ToString()
    ; Fill external structure.
    st := SystemTime.FromPointer(externalPointer)
    st.FromString(A_Now)
    ; Convert external structure to string.
    MsgBox % SystemTime.ToString(externalPointer)
*/
class SystemTime
{
    FromString(str)
    {
        if this.p
            st := this
        else
            st := new this
        if !(p := st.p)
            return 0
        FormatTime wday, %str%, WDay
        wday -= 1
        FormatTime str, %str%, yyyy M '%wday%' d H m s '0'
        Loop Parse, str, %A_Space%
            NumPut(A_LoopField, p+(A_Index-1)*2, "ushort")
        return st
    }
    FromPointer(pointer)
    {
        return { p: pointer, base: this }   ; Does not call __New.
    }
    ToString(st = 0)
    {
        if !(p := (st ? (IsObject(st) ? st.p : st) : this.p))
            return ""
        VarSetCapacity(s, 28), s := SubStr("000" NumGet(p+0, "ushort"), -3)
        Loop 6
            if A_Index != 2
                s .= SubStr("0" NumGet(p+A_Index*2, "ushort"), -1)
        return s
    }
    Now()
    {
        return this.FromString(A_Now)
    }
    __New()
    {
        if !(this.SetCapacity("struct", 16))
        || !(this.p := this.GetAddress("struct"))
            return 0
        NumPut(0, NumPut(0, this.p, "int64"), "int64")
    }
    __GetSet(name, value="")
    {
        static fields := {Year:0, Month:2, DayOfWeek:4, Day:6, Hour:8
                            , Minute:10, Second:12, Milliseconds:14}
        if fields.HasKey(name)
            return value=""
                ? NumGet(       this.p + fields[name], "ushort")
                : NumPut(value, this.p + fields[name], "ushort")
    }
    static __Get := SystemTime.__GetSet
    static __Set := SystemTime.__GetSet
}
Safearray sample:

Code: Select all

#NoEnv
iDVBViewer := ComObjActive("DVBViewerServer.DVBViewer")

; https://lexikos.github.io/v2/docs/commands/ComObject.htm#ByRef
VarSetCapacity(var, 24, 0)
vref := ComObject(0x400C, &var)
expected_type := 0x2000 | 0xC

DllCall("oleaut32\SystemTimeToVariantTime", "Ptr", (_ := SystemTime.Now()).p, "Double*", oleSt)
if ((count := iDVBViewer.EPGManager.GetAsArray(0, oleSt, oleSt, vref)) && NumGet(ComObjValue(vref),, "UShort") & expected_type) {
	VarSetCapacity(rgIndices, 4*2), st := IsObject(_) ? _ : new SystemTime ; allocate space to store array indices for SafeArrayPtrOfIndex and reuse SystemTime class object if it exists
	List := ComObject(expected_type, NumGet(ComObjValue(vref), 8, "Ptr"), 1)

	Loop %count% {
		EPGChannelID := List[A_Index - 1, 0] ;  // Longword (unsigned int)
		EventID := List[A_Index - 1, 1]      ;  // Longword (unsigned int)
		Time := List[A_Index - 1, 2]         ;  // Datetime (Double)
		Duration := List[A_Index - 1, 3]     ;  // Datetime (Double)
		Event := List[A_Index - 1, 4]        ;  // String
		Title := List[A_Index - 1, 5]        ;  // String
		Description := List[A_Index - 1, 6]  ; 	// String
		CharSet := List[A_Index - 1, 7]      ;  // Byte
		Content := List[A_Index - 1, 8]      ;  // Byte   
        
		if (Time) {
			NumPut(2, NumPut(A_Index - 1, rgIndices, "Int"), "Int") 
			if (DllCall("oleaut32\SafeArrayPtrOfIndex", "Ptr", ComObjValue(List), "Ptr", &rgIndices, "Ptr*", varTime) == 0 && varTime) 
				if (NumGet(varTime+0,, "UShort") & 19)  
					if (DllCall("oleaut32\VariantTimeToSystemTime", "Double", NumGet(varTime+0, 8, "Double"), "Ptr", st.p))
						FormatTime, rawTime, % st.ToString(), yyyy-MM-dd HH:mm:ss
		}
		MsgBox % "EPGChannelID= " EPGChannelID . "`r`nEventID: " . EventID . "`r`nTime: " . (rawTime ? rawTime : "") 
		. "`r`nDuration: " . Duration . "`r`nEvent: " . Event . "`r`nTitle: " . Title . "`r`nDescription: " . Description 
		. "`r`nCharSetList: " . CharSetList . "`r`nContent: " . Content
		rawTime := "" ; clear the rawTime variable so that the next call doesn't get the old date if conversion fails
	
	}

}
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: AHK COM floating point (double) conversion

12 May 2017, 02:48

Nope, no bug here. Just bog-standard COM behaviour.

AutoHotkey doesn't understand or interpret date-time values. For any types it doesn't understand, it just requests a standard COM conversion to string.
would have been "2017-05-12 00:00:00". Instead it looks like this "2017-05-12"
The latter is exactly what I would expect, having worked with Microsoft Access and VBScript. A date value is exactly the same as a date-time value with time 00:00:00.
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: AHK COM floating point (double) conversion

12 May 2017, 14:52

lexikos wrote:Nope, no bug here. Just bog-standard COM behaviour.
Ok.
lexikos wrote:AutoHotkey doesn't understand or interpret date-time values. For any types it doesn't understand, it just requests a standard COM conversion to string.
Aha, so that is how it works! Thanks lexikos! Im guessing AutoHotkey does request it from the IDispatch VariantChangeType(Ex) function. Got it from this COM IDispatch Interface paper: http://thrysoee.dk/InsideCOM+/ch05b.htm
lexikos wrote:
would have been "2017-05-12 00:00:00". Instead it looks like this "2017-05-12"
The latter is exactly what I would expect, having worked with Microsoft Access and VBScript. A date value is exactly the same as a date-time value with time 00:00:00.
Yes, that was my experience too when testing with VBScript a while ago. Never used MS Access though. I imagine the DVBViewer COM IDispatch Interface would be better off using the VariantTimeToSystemTime function, without knowing what difficulties that may bring:
https://msdn.microsoft.com/en-us/librar ... s.85).aspx

Return to “Bug Reports”

Who is online

Users browsing this forum: No registered users and 27 guests