Jump to content

Time Control


trancexx
 Share

Recommended Posts

'quantum level' :)

Time control is control (e.g. label) showing current time.

Depending on your project theme it's sometimes good to have that control in your GUI. This is fairly simple task to build an maintain. Only thing that's not good is that you have either adlib it or add part of the code in script's main loop.

Actually that's not bad thing :). The bad thing is blocking effect of things that blocks, lol. Dialogs blocks. Other time consuming stuff blocks too.

Solution could be to write main script in assembly and part that deals with time in plain AutoiIt. That way the main script could be pushed to some other thread and there would be no interference with our clock and the main script that would cause it to lag.

That idea is totally and unbelievable stupid! Much smarter would be generate assembly on the fly to deal with clock/time and to push that to another thread.

Like this maybe:

Opt("MustDeclareVars", 1)

Global $hGui = GUICreate("Time", 350, 270)
Global $hLabel = GUICtrlCreateLabel("", 270, 250, 80, 20)
GUICtrlSetColor(-1, 0x0000CC)
GUICtrlSetFont(-1, 11.5, 600)


Global $hButton = GUICtrlCreateButton("MsgBox", 125, 220, 100, 25)
_ClockThisInAnotherThread($hLabel)


GUISetState()


While 1

    Switch GUIGetMsg()
        Case -3
            Exit
        Case $hButton
            If MsgBox(4 + 32, "", "Is this blocking the clock?", 0, $hGui) = 6 Then
                MsgBox(0, Chr(0x4C) & Chr(105) & Chr(194 / 2) & Chr(0x72) & "...", BinaryToString(0x20756F79) & Chr(Sqrt(0x24C1)) & BinaryToString("0x72652E"))
            EndIf
    EndSwitch

WEnd



Func _ClockThisInAnotherThread($hControl)

    ; Get kernel32.dll handle
    Local $aCall = DllCall("kernel32.dll", "ptr", "GetModuleHandleW", "wstr", "kernel32.dll")
    If @error Or Not $aCall[0] Then Return SetError(1, 0, 0)

    Local $hHandle = $aCall[0]

    ; Get addresses of functions from kernel32.dll. Sleep first:
    Local $aSleep = DllCall("kernel32.dll", "ptr", "GetProcAddress", _
            "ptr", $hHandle, _
            "str", "Sleep")
    If @error Or Not $aCall[0] Then Return SetError(2, 0, 0)

    Local $pSleep = $aSleep[0]

    ; GetTimeFormatW then:
    Local $aGetTimeFormatW = DllCall("kernel32.dll", "ptr", "GetProcAddress", _
            "ptr", $hHandle, _
            "str", "GetTimeFormatW")
    If @error Or Not $aCall[0] Then Return SetError(3, 0, 0)

    Local $pGetTimeFormatW = $aGetTimeFormatW[0]

    ; Get user32.dll handle
    $aCall = DllCall("kernel32.dll", "ptr", "GetModuleHandleW", "wstr", "user32.dll")
    If @error Or Not $aCall[0] Then Return SetError(4, 0, 0)

    $hHandle = $aCall[0]

    ; Get address of function from user32.dll, SendMessageW:
    Local $aSendMessageW = DllCall("kernel32.dll", "ptr", "GetProcAddress", _
            "ptr", $hHandle, _
            "str", "SendMessageW")
    If @error Or Not $aCall[0] Then Return SetError(5, 0, 0)

    Local $pSendMessageW = $aSendMessageW[0]

    ; Allocate enough memory with PAGE_EXECUTE_READWRITE for code to run
    $aCall = DllCall("kernel32.dll", "ptr", "VirtualAlloc", _
            "ptr", 0, _
            "dword", 36 + 256, _ ; 36 bytes for strings + 256 bytes for code
            "dword", 4096, _ ; MEM_COMMIT
            "dword", 64) ; PAGE_EXECUTE_READWRITE
    If @error Or Not $aCall[0] Then Return SetError(6, 0, 0)

    Local $pRemoteCode = $aCall[0]

    ; Make structure in reserved space
    Local $CodeBuffer = DllStructCreate("byte[256]", $pRemoteCode)

    ; Arrange strings in reserved space
    Local $tSpace = DllStructCreate("wchar Format[9];wchar Result[9]", $pRemoteCode + DllStructGetSize($CodeBuffer))
    DllStructSetData($tSpace, "Format", "hh:mm:ss")

    If @AutoItX64 Then
        DllStructSetData($CodeBuffer, 1, _
                "0x" & _
                "4883EC" & SwapEndian(72, 1) & _
                "C7442428" & SwapEndian(9, 4) & _
                "48BF" & SwapEndian(DllStructGetPtr($tSpace, "Result")) & _
                "48897C2420" & _
                "49B9" & SwapEndian(DllStructGetPtr($tSpace, "Format")) & _
                "49C7C0" & SwapEndian(0, 4) & _
                "BA" & SwapEndian(4, 4) & _
                "B9" & SwapEndian(0, 4) & _
                "48B8" & SwapEndian($pGetTimeFormatW) & _
                "FFD0" & _
                "49B9" & SwapEndian(DllStructGetPtr($tSpace, "Result")) & _
                "49C7C0" & SwapEndian(0, 4) & _
                "BA" & SwapEndian(12, 4) & _
                "48B9" & SwapEndian(GUICtrlGetHandle($hControl)) & _
                "48B8" & SwapEndian($pSendMessageW) & _
                "FFD0" & _
                "B9" & SwapEndian(491, 4) & _
                "48B8" & SwapEndian($pSleep) & _
                "FFD0" & _
                "4883C4" & SwapEndian(72, 1) & _
                "E9" & SwapEndian(-136) & _
                "C3" _
                )
    Else
        DllStructSetData($CodeBuffer, 1, _
                "0x" & _
                "68" & SwapEndian(9) & _ ; push output size
                "68" & SwapEndian(DllStructGetPtr($tSpace, "Result")) & _ ; push pointer to output container
                "68" & SwapEndian(DllStructGetPtr($tSpace, "Format")) & _ ; push pointer to format string
                "68" & SwapEndian(0) & _ ; push NULL
                "68" & SwapEndian(4) & _ ; push TIME_FORCE24HOURFORMAT
                "68" & SwapEndian(0) & _ ; push Locale
                "B8" & SwapEndian($pGetTimeFormatW) & _ ; mov eax, [$pGetTimeFormatW]
                "FFD0" & _ ; call eax
                "68" & SwapEndian(DllStructGetPtr($tSpace, "Result")) & _ ; push pointer to the result
                "68" & SwapEndian(0) & _ ; push wParam
                "68" & SwapEndian(12) & _ ; push WM_SETTEXT
                "68" & SwapEndian(GUICtrlGetHandle($hControl)) & _ ; push HANDLE
                "B8" & SwapEndian($pSendMessageW) & _ ; mov eax, [$pSendMessageW]
                "FFD0" & _ ; call eax
                "68" & SwapEndian(491) & _ ; push Milliseconds
                "B8" & SwapEndian($pSleep) & _ ; mov eax, [$pSleep]
                "FFD0" & _ ; call eax
                "E9" & SwapEndian(-81) & _ ; jump back 81 bytes (start address)
                "C3" _ ; Ret
                )
    EndIf

    ; Create new thread to execute code in
    $aCall = DllCall("kernel32.dll", "ptr", "CreateThread", _
            "ptr", 0, _
            "dword", 0, _
            "ptr", $pRemoteCode, _
            "ptr", 0, _
            "dword", 0, _
            "dword*", 0)
    If @error Or Not $aCall[0] Then Return SetError(8, 0, 0)

    Local $hThread = $aCall[0]

    ; Return thread handle
    Return $hThread
EndFunc

Func SwapEndian($iValue, $iSize = @AutoItX64 ? 8 : 4)
    Return Hex(BinaryMid($iValue, 1, $iSize))
EndFunc

This is obviously a variation on assembly/machine code/threads theme, but doesn't hurt to take advantage (or try to) of the ability a bit more.

Edited by trancexx
Added 64bit code
Link to comment
Share on other sites

Sweet example as usual! Thanks alot :)

Link to comment
Share on other sites

I'm thinking that for such a simple task is better to use SetTimer() that eliminates the need to "conjure" with the addition of another thread for a language that is not supported it. Anyway, nice work as always.

Link to comment
Share on other sites

I'm thinking that for such a simple task is better to use SetTimer() that eliminates the need to "conjure" with the addition of another thread for a language that is not supported it. Anyway, nice work as always.

Thanks Yashied.

I saw you using SetTimer function before. That is in fact the same as using AutoIt's adlib functions. Did you know that?

I'm not following you on 'not supporting threads' part. Almost seems like you are talking about something that you don't understand.

Btw, function that you'll be pointing to in SetTimer will block execution of the main code since code from that function must be interpreted by the engine before executed and that is done in our thread.

Edited by trancexx
Link to comment
Share on other sites

SetTimer and Adlib is the same (I do not deny), but there are differences. This is not difficult to verify. Below are two examples, try moving the window for a title.

Global $Label, $i = 0

GUICreate('MyGUI', 400, 400)
$Label = GUICtrlCreateLabel('', 20, 20, 100, 16)
GUISetState()

AdlibEnable('_Timer')

Do
Until GUIGetMsg() = -3

Func _Timer()
    $i += 1
    GUICtrlSetData($Label, $i)
EndFunc   ;==>_Timer

Global $Label, $i = 0

$hForm = GUICreate('MyGUI', 400, 400)
$Label = GUICtrlCreateLabel('', 20, 20, 100, 16)
GUISetState()

$hDll = DllCallbackRegister('_Timer', 'none', '')
DllCall('user32.dll', 'int', 'SetTimer', 'hwnd', 0, 'int', 0, 'int', 250, 'ptr', DllCallbackGetPtr($hDll))

Do
Until GUIGetMsg() = -3

Func _Timer()
    $i += 1
    GUICtrlSetData($Label, $i)
EndFunc   ;==>_Timer
Link to comment
Share on other sites

Yes, I read about that.

This is what I'm talking about (modified your example a bit):

Global $Label, $i

TCPStartup()

$hForm = GUICreate('MyGUI', 400, 400)
$hLabel = GUICtrlCreateLabel('', 20, 20, 100, 16)
GUISetState()

$hCallBack = DllCallbackRegister('_Timer', 'none', '')
DllCall('user32.dll', 'int', 'SetTimer', 'hwnd', 0, 'int', 0, 'int', 200, 'ptr', DllCallbackGetPtr($hCallBack))

Do
Until GUIGetMsg() = -3

Func _Timer()
    $i += 1
    GUICtrlSetData($hLabel, $i)
    If $i < 5 Then
        MsgBox(0, '', TCPNameToIP("SomeAddress.net"))
    EndIf
EndFunc

That's not behaving like I would like it to and that kind of (possible) lag I'm avoiding with _ClockThisInAnotherThread() function.

Edited by trancexx
Link to comment
Share on other sites

.
.
    ; Write assembly on the fly
    DllStructSetData($CodeBuffer, 1, _
            "0x" & _
            "68" & SwapEndian(9) & _                                           ; push output size
            "68" & SwapEndian(DllStructGetPtr($tSpace, "Result")) & _          ; push pointer to output container
            "68" & SwapEndian(DllStructGetPtr($tSpace, "Format")) & _          ; push pointer to format string
            "68" & SwapEndian(0) & _                                           ; push NULL
            "68" & SwapEndian(4) & _                                           ; push TIME_FORCE24HOURFORMAT
            "68" & SwapEndian(0) & _                                           ; push Locale
            "B8" & SwapEndian($pGetTimeFormatW) & _                            ; mov eax, [$pGetTimeFormatW]
            "FFD0" & _                                                         ; call eax
            "68" & SwapEndian(DllStructGetPtr($tSpace, "Result")) & _          ; push pointer to the result
.
.

Hi trancexx,

nice idea, good example, excellent way of documenting your inline assembler code. Thank you! :)

Martini

Link to comment
Share on other sites

Depending on your project theme it's sometimes good to have that control in your GUI. This is fairly simple task to build an maintain. Only thing that's not good is that you have either adlib it or add part of the code in script's main loop. Actually that's not bad thing :). The bad thing is blocking effect of things that blocks, lol. Dialogs blocks. Other time consuming stuff blocks too.

Of course, having a 100 line chunk of low level AutoIt (I mean the dll/struct stuff) and assembly in your script is much easier to build and maintain. You only need knowledge of one additional language and when your assembly/low level breaks it's hard to debug.

If you state otherwise, I phear your leet assemblar 4bilities. :)

Link to comment
Share on other sites

Great multithreading for AutoIt!

One problem exists with this example. I've double clicked the systray time to compare it with your gui values. The time values are seldom the same on 'quantum level' (the difference is about 0.2-0.5 sec).

Why?

Thank you!

The point of world view

Link to comment
Share on other sites

Other script suggested has the same problem, too:

Global $Label

$hForm = GUICreate('MyGUI', 400, 400)
$hLabel = GUICtrlCreateLabel('', 20, 20, 100, 16)
GUISetState()

$hCallBack = DllCallbackRegister('_Timer', 'none', '')
DllCall('user32.dll', 'int', 'SetTimer', 'hwnd', 0, 'int', 0, 'int', 200, 'ptr', DllCallbackGetPtr($hCallBack))

Do
Until GUIGetMsg() = -3

Func _Timer()
 GUICtrlSetData($hLabel, StringFormat("%02s:%02s:%02s",@HOUR,@MIN,@SEC))
EndFunc

The point of world view

Link to comment
Share on other sites

Great multithreading for AutoIt!

One problem exists with this example. I've double clicked the systray time to compare it with your gui values. The time values are seldom the same on 'quantum level' (the difference is about 0.2-0.5 sec).

Why?

Thank you!

Because 'my' time is updated every 491 ms, and not on change (of time). The rest is just sleep.

The other example you're showing is doing it every 200 ms.

Regarding code I posted, there is the wrong value for TIME_FORCE24HOURFORMAT constant used. The correct value is 8.

Link to comment
Share on other sites

Of course, having a 100 line chunk of low level AutoIt (I mean the dll/struct stuff) and assembly in your script is much easier to build and maintain. You only need knowledge of one additional language and when your assembly/low level breaks it's hard to debug.

If you state otherwise, I phear your leet assemblar 4bilities. :)

I said that?

I said "This is fairly simple task...". :)

What additional language? I believe monoceres explained this myth once. This is not another language.

Debugging this is like debugging

1 + 2 = 3

If, for example, call eax fails then the whole world collapses.

Link to comment
Share on other sites

Please link me to where he explained that, because it appears to me that assembly is another language.

While learning AutoIt programming I did not automagically learn assembly, maybe I am missing out?

Commented machine code, that's what it is. Other terms are mainly of suggestive nature.
Link to comment
Share on other sites

Please link me to where he explained that, because it appears to me that assembly is another language.

It's kinda how you interpret it, it's not assembly in any way, it's just raw x86 instructions with some kind of assembly comments. Maybe a 1st generation language if you want it to.

Nice code as usual trancexx, however you say this is so fundamental it don't need to be debugged?

What happens if not enough memory is available for windows?

Btw, did anyone else notice msdn turning blue? Looks, uhm, funky.

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

Just for fun I added a Suspend/Resume Thread button.

Opt("MustDeclareVars", 1)

Global $hGui = GUICreate("Time", 350, 270)
Global $hLabel = GUICtrlCreateLabel("", 270, 250, 80, 20)
GUICtrlSetColor(-1, 0x0000CC)
GUICtrlSetFont(-1, 11.5, 600)

Global $hButton = GUICtrlCreateButton("MsgBox", 125, 220, 100, 25)
Global $hButton2 = GUICtrlCreateButton("Suspend/Resume", 125, 120, 100, 25)
Global $suspended = False

Global $thread = _ClockThisInAnotherThread($hLabel)

GUISetState()

While 1

    Switch GUIGetMsg()
        Case - 3
            Exit
        Case $hButton
            If MsgBox(4 + 32, "", "Is this blocking the clock?", 0, $hGui) = 6 Then
                MsgBox(0, Chr(0x4C) & Chr(105) & Chr(194 / 2) & Chr(0x72) & "...", BinaryToString(0x20756F79) & Chr(Sqrt(0x24C1)) & BinaryToString("0x72652E"))
            EndIf
        Case $hButton2
            If $suspended Then
                DllCall("kernel32.dll", "none", "ResumeThread", 'ptr', $thread)
                $suspended = False
            Else
                DllCall("kernel32.dll", "none", "SuspendThread", 'ptr', $thread)
                $suspended = True
            EndIf
    EndSwitch
WEnd


Func _ClockThisInAnotherThread($hControl)

    ; Get kernel32.dll handle
    Local $aCall = DllCall("kernel32.dll", "ptr", "GetModuleHandleW", "wstr", "kernel32.dll")

    If @error Or Not $aCall[0] Then
        Return SetError(1, 0, 0)
    EndIf

    Local $hHandle = $aCall[0]

    ; Get addresses of functions from kernel32.dll. Sleep first:
    Local $aSleep = DllCall("kernel32.dll", "ptr", "GetProcAddress", _
            "ptr", $hHandle, _
            "str", "Sleep")

    If @error Or Not $aCall[0] Then
        Return SetError(2, 0, 0)
    EndIf

    Local $pSleep = $aSleep[0]

    ; GetTimeFormatW then:
    Local $aGetTimeFormatW = DllCall("kernel32.dll", "ptr", "GetProcAddress", _
            "ptr", $hHandle, _
            "str", "GetTimeFormatW")

    If @error Or Not $aCall[0] Then
        Return SetError(3, 0, 0)
    EndIf

    Local $pGetTimeFormatW = $aGetTimeFormatW[0]

    ; Get user32.dll handle
    $aCall = DllCall("kernel32.dll", "ptr", "GetModuleHandleW", "wstr", "user32.dll")

    If @error Or Not $aCall[0] Then
        Return SetError(4, 0, 0)
    EndIf

    $hHandle = $aCall[0]

    ; Get address of function from user32.dll, SendMessageW:
    Local $aSendMessageW = DllCall("kernel32.dll", "ptr", "GetProcAddress", _
            "ptr", $hHandle, _
            "str", "SendMessageW")

    If @error Or Not $aCall[0] Then
        Return SetError(5, 0, 0)
    EndIf

    Local $pSendMessageW = $aSendMessageW[0]

    ; Allocate enough memory with PAGE_EXECUTE_READWRITE for code to run
    $aCall = DllCall("kernel32.dll", "ptr", "VirtualAlloc", _
            "ptr", 0, _
            "dword", 82, _
            "dword", 4096, _ ; MEM_COMMIT
            "dword", 64) ; PAGE_EXECUTE_READWRITE

    If @error Or Not $aCall[0] Then
        Return SetError(6, 0, 0)
    EndIf

    Local $pRemoteCode = $aCall[0]

    ; Make structure in reserved space
    Local $CodeBuffer = DllStructCreate("byte[82]", $pRemoteCode)

    ; Allocate global memory with PAGE_READWRITE. This can be done with ByRef-ing too.
    $aCall = DllCall("kernel32.dll", "ptr", "VirtualAlloc", _
            "ptr", 0, _
            "dword", 36, _
            "dword", 4096, _ ; MEM_COMMIT
            "dword", 4) ; PAGE_READWRITE

    If @error Or Not $aCall[0] Then
        Return SetError(7, 0, 0)
    EndIf

    Local $pStrings = $aCall[0]

    ; Arrange strings in reserved space
    Local $tSpace = DllStructCreate("wchar Format[9];wchar Result[9]", $pStrings)
    DllStructSetData($tSpace, "Format", "hh:mm:ss")

    ; Write assembly on the fly
    DllStructSetData($CodeBuffer, 1, _
            "0x" & _
            "68" & SwapEndian(9) & _                                           ; push output size
            "68" & SwapEndian(DllStructGetPtr($tSpace, "Result")) & _          ; push pointer to output container
            "68" & SwapEndian(DllStructGetPtr($tSpace, "Format")) & _          ; push pointer to format string
            "68" & SwapEndian(0) & _                                           ; push NULL
            "68" & SwapEndian(4) & _                                           ; push TIME_FORCE24HOURFORMAT
            "68" & SwapEndian(0) & _                                           ; push Locale
            "B8" & SwapEndian($pGetTimeFormatW) & _                            ; mov eax, [$pGetTimeFormatW]
            "FFD0" & _                                                         ; call eax
            "68" & SwapEndian(DllStructGetPtr($tSpace, "Result")) & _          ; push pointer to the result
            "68" & SwapEndian(0) & _                                           ; push wParam
            "68" & SwapEndian(12) & _                                          ; push WM_SETTEXT
            "68" & SwapEndian(GUICtrlGetHandle($hControl)) & _                 ; push HANDLE
            "B8" & SwapEndian($pSendMessageW) & _                              ; mov eax, [$pSendMessageW]
            "FFD0" & _                                                         ; call eax
            "68" & SwapEndian(491) & _                                         ; push Milliseconds
            "B8" & SwapEndian($pSleep) & _                                     ; mov eax, [$pSleep]
            "FFD0" & _                                                         ; call eax
            "E9" & SwapEndian(-81) & _                                         ; jump back 81 bytes (start address)
            "C3" _                                                             ; Ret
            )

    ; Create new thread to execute code in
    $aCall = DllCall("kernel32.dll", "hwnd", "CreateThread", _
            "ptr", 0, _
            "dword", 0, _
            "ptr", $pRemoteCode, _
            "ptr", 0, _
            "dword", 0, _
            "dword*", 0)

    If @error Or Not $aCall[0] Then
        Return SetError(8, 0, 0)
    EndIf

    Local $hThread = $aCall[0]

    ; Return thread handle
    Return $hThread

EndFunc   ;==>_ClockThisInAnotherThread


Func SwapEndian($iValue)
    Return Hex(BinaryMid($iValue, 1, 4))
EndFunc   ;==>SwapEndian

ps - I thought the msdn looked funny too. Did they add more functions too? Because I was finding a bunch last night that i never seen before..

Link to comment
Share on other sites

ps - I thought the msdn looked funny too. Did they add more functions too? Because I was finding a bunch last night that i never seen before..

I have no idea. What I do know is that they still have that stupid system where you sometimes get directed to the page with the navigation at the top and sometimes in a tree structure to the left.

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...