Message-Spezialist gesucht für "echtes" modales Fenster/Control [Beispielscript]

  • Hi,
    seit einiger Zeit hänge ich an folgendem Problem:
    AutoIt-Script mit GUI ruft per MemoryFuncCall() ein eingebettetes Assemblerprogramm auf. Aus diesem Assemblerprogramm springe ich per Callback in eine AutoIt-Funktion _CB() und nach Abarbeitung einiger Berechnungen wieder zurück ins Assemblerprogramm. Das funktioniert einwandfrei.

    Nun folgendes Problem:
    Versuche ich, innerhalb der callback-Funktion _CB() das Programm "anzuhalten" (while 1 / wend), bekommt das Hauptfenster nach einigen Sekunden die Meldung "Keine Rückmeldung" . Das kann ich mit ersetzen der while/wend durch eine MsgBox() vermeiden. Das kommt wohl daher, dass die MsgBox() ein echtes modales Fenster ist, mit dementsprechendem Message-Handling.
    In der GUI enthaltene Buttons kann ich natürlich per GuiRegisterMsg() auch während der Anzeige der Msgbox abfragen.

    Frage:
    Was muss ich machen um innerhalb der callback-Funktion ein Warteschleife (ohne Messagebox) zu realisieren ohne dass die GUI die "Keine Rückmeldung"-Message erhält?

    Am liebsten wäre mir in der GUI ein "Weiter"-Button, der das angehaltene Script weiterlaufen lässt.
    /EDIT/ habe mich schon mit subclasses beschäftigt, ohne da wirklich weiterzukommen. Ich vermute, dass durch die AutoIt/Assembler/callback-AutoIt-Kombination die Messages durcheinanderkommen, obwohl sie sich eigentlich im gleichen Thread(Adressraum) befinden.


    Spoiler anzeigen
    [autoit]

    #include <WinAPI.au3>
    #include <GUIConstantsEx.au3>
    ;#include <assembleit.au3>
    #include <WindowsConstants.au3>
    ;32 Bit only

    [/autoit] [autoit][/autoit] [autoit]

    $hgui = GUICreate("test", 400, 200, 20, 20) ;gui erstellen mit button
    $hbutton = GUICtrlCreateButton("Button", 20, 20, 100, 40)
    GUISetState()

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    GUIRegisterMsg($WM_COMMAND, "MyWM_COMMAND") ;buttonklicks abfangen

    [/autoit] [autoit][/autoit] [autoit]

    Global $CB = DllCallbackRegister("_CB", "dword", "dword") ;autoitfunktion, die aus assembler angesprungen wird

    [/autoit] [autoit][/autoit] [autoit]

    ;~ $_assembleit_flag=0
    ;~ $a = _assembleit("int", "_asm", "int", 5,"ptr",DllCallbackGetPtr($CB)) ;schleife 5x durchlaufen
    ;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $a = ' & $a & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console

    [/autoit] [autoit][/autoit] [autoit]

    Global $tCodeBuffer = DllStructCreate("byte[16]") ;reserve Memory for opcodes
    DllStructSetData($tCodeBuffer, 1,"0x8B4C240460518B74242CFFD661E2F5C3") ;write opcodes into memory

    [/autoit] [autoit][/autoit] [autoit]

    ;assemblerprogramm aufrufen, 5 schleifendurchgänge und adresse callback übergeben
    $ret = DllCall("user32.dll", "int", "CallWindowProcW", "ptr", DllStructGetPtr($tCodeBuffer), "int", 5, "ptr", DllCallbackGetPtr($CB), "int", 0, "int", 0)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, "Test", "Fertig")
    DllCallbackFree($CB)
    Exit

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _CB($ecx) ;parameter wird aus dem assemblerteil übergeben
    ConsoleWrite($ecx & @CRLF) ;parameter, der aus dem assemblerscript übergeben wurde

    [/autoit] [autoit][/autoit] [autoit]

    ;folgende 2 zeilen aktivieren, um die "keine Rückmeldung"-Nachricht zu empfangen
    ;~ while 1
    ;~ wend

    [/autoit] [autoit][/autoit] [autoit]

    ;diese MessageBox soll ersetzt werden durch eine Endlosschleife oder durch ein Fenster bzw Button innerhalb der GUI
    MsgBox(0, "_CB() das " & 6 - $ecx & "te mal aufgerufen", "Bitte OK klicken für weiter")
    Return 1
    EndFunc ;==>_CB

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func MyWM_COMMAND($hwnd, $message, $wParam, $lParam)
    Local $sMessage
    If _WinAPI_LoWord($wParam) = $hbutton Then ;button gedrückt
    MsgBox(0, "MyWM_command", "Button wurde gedrückt", 1)
    EndIf
    Return $GUI_RUNDEFMSG
    EndFunc ;==>MyWM_COMMAND

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ;~ Func _asm()
    ;~ _("use32")
    ;~ _("mov ecx,dword[esp+4]") ;parameter holen = 5
    ;~ _("@@:") ;label
    ;~ _("pushad") ;alle register sichern
    ;~ _("push ecx") ;parameter an autoit übergeben
    ;~ _("mov esi,dword[esp+44]") ;autoit-funktion callen
    ;~ _("call esi") ;call
    ;~ _("popad") ;alle register wieder herstellen
    ;~ _("loop @b") ;ecx mal (5x) durch die schleife
    ;~ _("ret")
    ;~ EndFunc ;==>_asm

    [/autoit] [autoit][/autoit] [autoit][/autoit]

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    8 Mal editiert, zuletzt von Andy (26. Februar 2011 um 14:08) aus folgendem Grund: Beispielscript hinzugefügt

  • Habe mal ein Beispielscript angehängt, einfach starten, so läuft es einwandfrei(32Bit).
    Während der Anzeige der Messagebox kann auch der Button in der GUI angeklickt werden.

    Wenn aber in der Callback-Funktion _CB() die while/wend einkommentiert wird, kommt nach einigen Sekunden Sanduhr die "keine Rückmeldung"-Msg in der GUI.

  • Hallo Andy,

    das Problem ist zwar ganz einfach zu beheben, aber für dich eher kontraproduktiv: du müsstest einfach einen sleep in die einzige Endlosschleife bauen. So bekommt die Gui keine Chance irgendwelche Nachrichten zu empfangen.

    Edit oder besser so:

    Spoiler anzeigen
    [autoit]

    #include <WinAPI.au3>
    #include <GUIConstantsEx.au3>
    ;#include <assembleit.au3>
    #include <WindowsConstants.au3>
    ;32 Bit only

    [/autoit] [autoit][/autoit] [autoit]

    $hgui = GUICreate("test", 400, 200, 20, 20) ;gui erstellen mit button
    $hbutton = GUICtrlCreateButton("Button", 20, 20, 100, 40)
    GUISetState()

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    GUIRegisterMsg($WM_COMMAND, "MyWM_COMMAND") ;buttonklicks abfangen

    [/autoit] [autoit][/autoit] [autoit]

    Global $CB = DllCallbackRegister("_CB", "dword", "dword") ;autoitfunktion, die aus assembler angesprungen wird

    [/autoit] [autoit][/autoit] [autoit]

    ;~ $_assembleit_flag=0
    ;~ $a = _assembleit("int", "_asm", "int", 5,"ptr",DllCallbackGetPtr($CB)) ;schleife 5x durchlaufen
    ;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $a = ' & $a & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console

    [/autoit] [autoit][/autoit] [autoit]

    Global $tCodeBuffer = DllStructCreate("byte[16]") ;reserve Memory for opcodes
    DllStructSetData($tCodeBuffer, 1,"0x8B4C240460518B74242CFFD661E2F5C3") ;write opcodes into memory

    [/autoit] [autoit][/autoit] [autoit]

    ;assemblerprogramm aufrufen, 5 schleifendurchgänge und adresse callback übergeben
    $ret = DllCall("user32.dll", "int", "CallWindowProcW", "ptr", DllStructGetPtr($tCodeBuffer), "int", 5, "ptr", DllCallbackGetPtr($CB), "int", 0, "int", 0)

    [/autoit] [autoit][/autoit] [autoit]

    while GuiGetMsg() <> -3
    ; Sleep(10)
    wend

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, "Test", "Fertig")
    DllCallbackFree($CB)
    Exit

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _CB($ecx) ;parameter wird aus dem assemblerteil übergeben
    ConsoleWrite($ecx & @CRLF) ;parameter, der aus dem assemblerscript übergeben wurde

    [/autoit] [autoit][/autoit] [autoit]

    ;folgende zeilen auskommentieren

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, "_CB() das " & 6 - $ecx & "te mal aufgerufen", "Bitte OK klicken für weiter",5) ;TimeOut gesetzt
    Return 1
    EndFunc ;==>_CB

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func MyWM_COMMAND($hwnd, $message, $wParam, $lParam)
    Local $sMessage
    If _WinAPI_LoWord($wParam) = $hbutton Then ;button gedrückt
    MsgBox(0, "MyWM_command", "Button wurde gedrückt", 1)
    EndIf
    Return $GUI_RUNDEFMSG
    EndFunc ;==>MyWM_COMMAND

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ;~ Func _asm()
    ;~ _("use32")
    ;~ _("mov ecx,dword[esp+4]") ;parameter holen = 5
    ;~ _("@@:") ;label
    ;~ _("pushad") ;alle register sichern
    ;~ _("push ecx") ;parameter an autoit übergeben
    ;~ _("mov esi,dword[esp+44]") ;autoit-funktion callen
    ;~ _("call esi") ;call
    ;~ _("popad") ;alle register wieder herstellen
    ;~ _("loop @b") ;ecx mal (5x) durch die schleife
    ;~ _("ret")
    ;~ EndFunc

    [/autoit]

    sollte nicht so stark ausbremsen,

    mfg autoBert

  • Hi,
    vielen Dank, aber ich glaube, du hattest es etwas falsch verstanden^^
    Ich benötige die Endlosschleife innerhalb der Funktion _CB(), gewissermassen an Stelle der Messagebox!
    Die Messagebox hält das Script innerhalb der _CB() an, ohne dass es zu einer "Keine Rückmeldung"-Nachricht an die GUI kommt.
    Die Messagebox soll nun durch eine Endlosschleife ersetzt werden, so dass es nicht zur "Keine Rückmeldung"-Nachricht an die GUI kommt.

    Ich möchte ein "warte auf Mausklick auf den WEITER (in der MsgBox ist das der OK) -Button" realisieren.
    Letztendlich soll in der _CB() eine Endlos-Schleife laufen, welche durch einen Klick auf den Button beendet wird. Per globalem Flag zum Beispiel.
    Ein kleines Fensterchen, welche die Funktion der Messagebox übernimmt an der Position des Buttons würde mir auch schon reichen!


    Ich habe schon eine dll erstellt, die aber nichts weiter als ein "echtes" modales Fenster (nur mit OK-Button) an der Position des "Weiter"-Buttons erstellt. Aber ich möchte diese dll nicht in den Code einbinden und daraus starten bzw. als externe Datei mitgeben, da sie im Vergleich zum restlichen AutoIt-Code immens gross ist....90kB um ein kleines Fensterchen mit einem OK-Button irgendwo auf eine GUI zu pinnen ist ziemlich happig!

  • Das dürfte an der Implementierung der Callbackfunktionen liegen. So weit ich weiß, wird das intern über WindowMessages gehandhabt und daher harmoniert das nicht gut mit GUIs.

    Was ich machen würde:
    - [AU3] event = CreateEvent
    - [ASM] als extra Thread, Übergabe von event
    - [ASM] Callback aufrufen, um das Hauptprogramm zu informieren (oder auch das über events lösen)
    - [ASM] WaitForSingleObject(event)
    - [AU3] SetEvent(event)
    - [ASM] weitermachen

  • Zitat

    Ist aber wirklich seltsam, dass eine msgbox das Problem umgehen kann.

    Nein, das ist garnicht seltsam, das hängt damit zusammen, dass MsgBox die API-Funktion DialogBox() aufruft, welche eine eigene Message loop installiert. Und die wiederum "fängt" sich die Messages.

    Prog@ndy, vielen Dank, da habe ich ja was zu tun^^
    Da der asm-Code Threadsicher ist, könnte ich ihn in eigene Threads verfrachten, mal schauen, was passiert.
    Im Zweifelsfall mache ich doch eine "eingebettete" dll, das Problem von DialogBox() ist eigentlich nur, dass es die Fensterdaten aus den Ressourcen einer Datei ausliest. Eine asm-dll habe ich mittlerweile auf einige Bytes zusammengestampft, da die Ressourcen reingepackt und fertig wäre die Laube. Ich hatte schon das komplette Fenster als modale GUI getestet, das funktioniert auch, baut das Fenster aber in jedem Durchlauf "neu" auf, auch suboptimal....

  • Du kannst per CreateDialogIndirect auch ohne Ressourcen einen Doialog erstellen, aber dafür musst du die ganzen Strukturen von Hand zusammenbasteln ;)
    DAs kannst du auch in einem Stück ASM-Code ohne DLL tun.
    Oder du hängst di Dialog-Ressourcen an die AutoIt-Exe an, aber dann musst du immer erst kompilieren.

  • Zitat

    aber dafür musst du die ganzen Strukturen von Hand zusammenbasteln ;)

    hehe, da bin ich auch schon durch, da ist die dll mit den Ressourcen schneller und einfacher zu basteln.

    Zitat

    Oder du hängst di Dialog-Ressourcen an die AutoIt-Exe an, aber dann musst du immer erst kompilieren.

    genau das wollte ich vermeiden, es geht um einen "Debugger" für den inline-asm-code (AssembleIt), der zur LAUFZEIT des Assemblercodes Daten an eine AutoIt-GUI ausgibt. Da ist permanentes kompilieren Kontraproduktiv imho.

    Zzt. bin ich so weit, den kompletten "Debugger" (eigentlich ist es nur ein Anzeigefenster für alle Registerinhalte, Prozessorflags, FPU- und SSE-Registerinhalte, Inhalte des Stacks usw.) in Autoit in das AssembleIt-tool eingebunden zu haben. Nur das "weiterschalten" der GUI geschieht zzt. noch über das modale MsgBox-Fenster.

    Aktueller Stand ist so, dass zwischen die einzelnen Assemblerbefehle ein _ASM_DEBUG() eingefügt werden kann, welches dann während der Laufzeit die GUI für die Anzeige der Daten aufruft. Weiterhin kann damit auch ein Abbruchbefehl z.B. in einer ASM-Schleife eingefügt werden. _ASM_DEBUG("eax<50") lässt dann den Assemblercode so lange laufen, bis das Register eax einen Wert kleiner 50 enthält. Also schon sehr komfortabel.

    Ob man für "Weiter" nun auf den "OK"-button der MsgBox klickt oder auf einen Button innerhalb der GUI ist eher Kosmetik, aber du kennst das ja, für die 5% verbesserte Usability werden 95% Gehirnschmalz und Arbeit aufgewendet^^

  • Ich hab mal einen einfachen Dialog erstellt. Nur für den Weiter-Button geht das ja schnell.

    Spoiler anzeigen
    [autoit]

    #include<WindowsConstants.au3>
    Global Const $tagDLGTEMPLATE = "align 2;DWORD style; DWORD dwExtendedStyle; WORD cdit; short x; short y; short cx; short cy;"
    ;+2+2+2
    Global Const $tagDLGITEMTEMPLATE = "align 2;DWORD style; DWORD dwExtendedStyle; short x; short y; short cx; short cy; WORD id;"
    ;+(2+2) +14 +2
    ; ^--> 14 bytes: Platz für Unicode "Weiter" + Nullchar (2*6+2),

    [/autoit] [autoit][/autoit] [autoit]

    $tDLG = DllStructCreate("byte[200]")
    $ptr = DllStructGetPtr($tDLG)
    $tTEMPLATE = DllStructCreate($tagDLGTEMPLATE, $ptr)
    DllStructSetData($tTEMPLATE, 1, BitOR(0x10000000, 0x80000000))
    DllStructSetData($tTEMPLATE, 3, 1)
    DllStructSetData($tTEMPLATE, 4, 100)
    DllStructSetData($tTEMPLATE, 5, 100)
    DllStructSetData($tTEMPLATE, 6, 80)
    DllStructSetData($tTEMPLATE, 7, 20)
    $ptr += DllStructGetSize($tTEMPLATE)
    $ptr += 2
    $ptr += 2
    $ptr += 2
    $ptr += Mod($ptr, 4) ; DWORD align für ITEMTEMPLATE. Ist in diesem Beispiel nicht nötig
    $tITEM = DllStructCreate($tagDLGITEMTEMPLATE, $ptr)
    DllStructSetData($tITEM, 1, BitOR(0x10000000, 0x40000000))
    DllStructSetData($tITEM, 3, 0)
    DllStructSetData($tITEM, 4, 0)
    DllStructSetData($tITEM, 5, 80)
    DllStructSetData($tITEM, 6, 20)
    DllStructSetData($tITEM, 7, 1)
    $ptr += DllStructGetSize($tITEM)
    $tX = DllStructCreate("word[2]", $ptr )
    DllStructSetData($tX, 1, 0xFFFF, 1)
    DllStructSetData($tX, 1, 0x0080, 2)
    $ptr += 4
    $tX = DllStructCreate("wchar[14]", $ptr)
    DllStructSetData($tX, 1, "Weiter")

    [/autoit] [autoit][/autoit] [autoit]

    $dlgproc = DllCallbackRegister("_DlgProc", "bool", "hwnd;uint;wparam;lparam")
    DllCall("user32.dll", "int", "DialogBoxIndirectParamW", "ptr", 0, "ptr", DllStructGetPtr($tDLG), "hwnd", 0, "ptr", DllCallbackGetPtr($dlgproc), "lparam", 0)
    Func _DlgProc($hWnd, $uMsg, $wParam, $lParam)
    If $uMsg = $WM_CLOSE Then
    DllCall("user32.dll", "bool", "EndDialog", "hwnd", $hWnd, "int_ptr", 0)
    Return True
    ElseIf $uMsg = $WM_COMMAND Then
    If BitAND($wParam, 0xFFFF) == 1 Then
    DllCall("user32.dll", "bool", "EndDialog", "hwnd", $hWnd, "int_ptr", 1)
    Return True
    EndIf
    EndIf
    Return False
    EndFunc

    [/autoit]
  • Was soll ich sagen, ULTRACREMIG! Vielen Dank, funktioniert einwandfrei. Den Button hänge ich jetzt noch irgendwie an die GUI, damit er sich ggf. mitverschiebt, aber das sollte jetzt nicht mehr so wild werden!
    Meine dll hab ich auf 96 Byte eingestampft, incl der Ressourcen natürlich^^, allerdings wird das wiederum aufgefressen von der Orgie, das Ding in den Speicher zu entpacken, dort zu starten, registrieren uswusf, in AutoIt nicht wirklich "schön"...

    Dein Button macht das Rennen! Änderungen an Grösse, Style usw sind so auch einfacher nachvollziehbar wie Änderungen in einer geladenen dll.... :thumbup:

  • Hab mal etwas rumgespielt:

    Spoiler anzeigen
    [autoit]

    #include<WindowsConstants.au3>
    $dlgproc = DllCallbackRegister("_DlgProc", "bool", "hwnd;uint;wparam;lparam")
    $tDLG = DllStructCreate("byte[65]")

    [/autoit] [autoit][/autoit] [autoit]

    $x = 50
    $y = 100
    $w = 100
    $h = 30
    $bXY = _ShortBin($x) & _ShortBin($y)
    $bWH = _ShortBin($w) & _ShortBin($h)
    DllStructSetData($tDLG, 1, Binary("0x00000090400000040100") & $bXY & $bWH & Binary("0x000000000000000000500000000000000000")&$bWH&Binary("0x0100FFFF800057006500690074006500720000000000000000"))
    ; WS_POPUP, WS_VISIBLE und WS_EX_MDICHILD, WS_EX_NOPARENTNOTIFY

    [/autoit] [autoit][/autoit] [autoit]

    $GUI = GUICreate("")
    GUICtrlCreateLabel("jhhjhjhj", 10, 20, 99, 20)
    GUICtrlCreateButton("test", 50, 70, 200, 20)
    GUISetState()

    [/autoit] [autoit][/autoit] [autoit]

    DllCall("user32.dll", "int", "DialogBoxIndirectParamW", "ptr", 0, "ptr", DllStructGetPtr($tDLG), "hwnd", $GUI, "ptr", DllCallbackGetPtr($dlgproc), "lparam", 0)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _ShortBin($x)
    Return BinaryMid(Binary($x), 1, 2)
    EndFunc
    Func _DlgProc($hWnd, $uMsg, $wParam, $lParam)
    If $uMsg = $WM_CLOSE Then
    DllCall("user32.dll", "bool", "EndDialog", "hwnd", $hWnd, "int_ptr", 0)
    Return True
    ElseIf $uMsg = $WM_COMMAND Then
    If BitAND($wParam, 0xFFFF) == 1 Then
    DllCall("user32.dll", "bool", "EndDialog", "hwnd", $hWnd, "int_ptr", 1)
    Return True
    EndIf
    EndIf
    Return False
    EndFunc

    [/autoit]
  • habe mir angewöhnt, wenn möglich sowieso alle structs "am Stück" hintereinander in einen zusammenhängenden Speicherblock zu schreiben. Der wird dann einmal an einer 16-byte-Grenze aligned und dann kann man nach belieben damit arbeiten. Im Debugger sind das mittlerweile fast 2k an struct, Da kommts dann auf das bisschen 65 Bytes auch nicht mehr an :thumbup:

  • Ich glaube, da hängen ein paar Nullbytes zu viel hinten dran, aber ich hatte keine Lust, genau abzuzählen :D
    Edit: Hab noch mal am Binärcode rumgebastelt und etwas kommentiert

    Spoiler anzeigen
    [autoit]

    $x = 50
    $y = 100
    $w = 100
    $h = 30
    $bTitle = StringToBinary("Weiter !!f45", 2)
    $bXY = _ShortBin($x) & _ShortBin($y)
    $bWH = _ShortBin($w) & _ShortBin($h)
    $bDIALOG = Binary("0x00000090400000040100") & $bXY & $bWH & Binary("0x000000000000")&Binary("0x000000500000000000000000")&$bWH&Binary("0x0100FFFF8000")& $bTitle & Binary("0x0000") & Binary("0x00000000")
    ; |Style ||ExStyl||cdit| |Empty "Arrays"| |Style ||ExStyl||x ||y | |id||BUTTON| |Chr0| |irgend welche anderen Arrays
    $tDLG = DllStructCreate("byte["&BinaryLen($bDIALOG)&"]")
    DllStructSetData($tDLG, 1, $bDIALOG)

    [/autoit]

    2 Mal editiert, zuletzt von progandy (22. Februar 2011 um 19:30)