Seltsames Verhalten mit Return

  • Hallo in die Runde.

    Ich bin auf ein für mich seltsames Phänomen gestoßen. Viele Stunden habe ich zwischenzeitlich damit verbracht, um herauszufinden, woher das Problem rührt. Ich weiß mir keinen Rat mehr und hoffe jemand von euch hat einen hilfreichen Gedanken hierzu.

    Da mein Script sehr umfangreich geworden ist und die relevanten Teile für das Problem nicht einfach herauskopiert werden können, will ich versuchen, den Sachverhalt so exakt wie möglich zu beschreiben.

    Innerhalb des Scripts ruft eine Funktion A eine weitere Funktion B auf. Der erwartete Rückgabewert von B an A soll in Abhängigkeit der in B auszuführenden Schritte entweder ein „“ (leerer String) oder eine Pfadangabe sein. Das funktioniert nach mehreren Testläufen genauso wie gedacht. Nur in einem speziellen Fall (s. u.), wird anstatt des Variableninhalts eine 0 (Null) zurückgegeben. Geprüft habe ich innerhalb von Funktion B:

    Code
    ConsoleWrite( "$newPath = >" & $newPath & "< Variableninhalt unmittelbar vor Return!!!" & @CRLF ) ; ergibt beispielsweise “D:\Temp”
    Return $newPath

    Die aufrufende Funktion A erhält jedoch eine 0, obwohl ich D:\Temp erwartet hätte. Ich habe keine Ahnung, woher diese 0 kommt und wie sie sich dazwischen zecken kann, um den korrekten Variablenwert (in dem Beispiel wäre das D:\Temp) zu ersetzen.

    Sonderfall, wo dieses Problem auftritt:

    In Funktion B wird der Rückgabewert der von hier aufgerufenen Funktion FileSelectFolder() dahingehend überprüft, ob es sich um ein a) Verzeichnis handelt (in diesem konkreten Beispiel sicher redundant) und b) ob der Anwender in diesem Verzeichnis Schreibrechte hat. Beide Prüfungen erfolgen in einer weiteren Funktion C (_validatePathAccessRights() siehe nachstehend), die von Funktion B aufgerufen wird.

    Wahrscheinlich geht das auch eleganter, doch ich bin kein Profi und programmiere hier für private Zwecke.

    Zurück zum Problem: Wenn die Validierung ergibt, dass die Schreibrechte für ein Verzeichnis fehlen, dann wird False zurückgegeben, andernfalls True. Das scheint nach meinen bisherigen Tests prima zu funktionieren. Nur nachdem ein False zurückgegeben wurde und anschließend nochmals FileSelectFolder() aufgerufen wird - jetzt jedoch mit einem Verzeichnis als Rückgabewert, für das True bezüglich der Schreibrechte zurückgegeben wird (via ConsoleWrite überprüft) - genau dann passiert das Problem und in der Funktion A kommt die ominöse 0 an. Im übrigen tritt dieses Phänomen auch dann auf, wenn ich die SetError-Zeilen in _validatePathAccessRights() auskommentiere. 🤔

    Wird danach das Prozedere erneut durchlaufen und direkt ein Verezichnis ausgewählt, für das der Anwender Schreibrechte hat, dann stimmt der Rückgabewert an die Funktion A. 🙃

    Wer hat eine Idee, wie ich das Problem beseitigen kann bzw. in welche Richtung ich schauen muss?

    Vielen lieben Dank an allen Mitdenkenden 🙏. Ich wünsche einen Guten Start in das neue Jahr. 🍀

  • Hallo!

    Bei mir kommt immer True oder False - den Fehler kann ich leider nicht so ganz nachstellen!

    Aber irgendwie passen die Errorwerte nicht mit den erwartenden Ergebisse zusammen: Gebe ich als Parameter für die Funktion eine Datei an bekomme ich Error 1 (..."Es besteht ein grundlegendes Zugriffsproblem auf das Verzeichnis.")

    Verwende ich ein Verzeichnis auf das ich nicht schreiben darf ("C:\Windows\System32\LogFiles\") kommt für @error 0 und extended 0 ?(

    Wie wäre es für die Prüfung ob es ein Verzeichnis ist oder nicht mit FileChangeDir und dann erst die Schreibzugriff probieren.

    lg

    Racer

  • Moin!

    OffTopic:

    Ich war vor ein paar Jahren unter anderem Namen schon mal hier, habe ein paar Tools mit AutoIt geschrieben und bin deshalb kein absoluter Anfänger. Dennoch muss ich mich erst langsam wieder an AutoIt gewöhnen.

    OnTopic:

    Der Bitte um den Code der Funktionen "A" und "B" solltest Du nachkommen. Bei Funktion "C" springt mir aber Folgendes ins Auge:

    Code
             If $h_tempFile <> -1 Then ; die erforderlichen Zugriffsrechte sind vorhanden
                FileClose( $h_tempFile )
                FileDelete( $h_tempFile )
                Return True
             Else
                SetError(3, 0, "Es fehlen die erforderlichen Schreib-Zugriffsrechte.")
                FileClose( $h_tempFile )
                FileDelete( $h_tempFile )
             EndIf

    Diese Sequenz ist für mich fehlerhaft.

    FileClose() erwartet als Parameter ein gültiges Dateihandle. Das trifft für den If-Zweig zu, jedoch nicht für den Else-Zweig. Im Else-Zweig gibt es auch keinen Grund, eine Datei zu schließen.

    FileDelete() erwartet als Parameter den Dateinamen als String. Mit einem Dateihandle kann es nichts anfangen. Im Else-Zweig gibt es auch keinen Grund, eine Datei zu löschen.

    Viel Glück im neuen Jahr!

  • Vielen lieben Dank an euch - für eure Zeit und den Input :thumbup: Ich seh schon, da muss ich erst noch meine Hausaufgaben erledigen. Zwischenzeitlich werde ich das Thema hier erst einmal still legen.

  • Nochmals vielen Dank für eure Rückmeldungen. Ich habe den dasProblem betreffenden Teil aus meinem Script extrahiert. Das zuvor geschilderte Problem lässt sich damit exakt nachstellen. Alles weitere im Code.

    Ich bin echt gespannt, was ihr erkennt/herausfindet. Vielen Dank schon jetzt für jede Untertsützung.

    Spoiler anzeigen

    #include <WinAPIFiles.au3>
    ;
    ; HINWEISE
    ; Der nachstehende Aufbau/Ablauf entspricht prinzipiell dem meines Scriptes.
    ; Alle das Problem betreffenden Stellen sind nachstehend aufgeführt.
    ; Das (Problem-)Verhalten entspricht 1:1 dem Original.
    ; Das Programm wird unter Windows 10 x64 mit Standard-Benutzerrechten (NICHT Adminrechte) ausgeführt.
    ;
    ; TESTSZENARIEN (siehe auch Problembeschreibung im ElseIf-Zweig)
    ; Fall 1)
    ; Sript starten - ein Verzeichnis auswählen, in dem der Benutzer Schreibrechte hat > Der Rückgabewert
    ; vor und nach Return ist identisch.
    ;
    ; Fall 2)
    ; Script starten - ein Verzeichnis auswählen, in dem der Benutzer KEINE Schreibrecht hat - danach ein Verzeichnis
    ; auswählen, in dem der Benutzer Schreibrechte hat > Der Rückgabewert vor und nach Return sind unterschiedlich
    ;
    Local $targetPath ; wird im Original-Script ebenfalls an dieser Stelle deklariert

    _aufrufendeFunktion()

    Func _aufrufendeFunktion() ; _setTargetPath() wird so wie hier innerhalb des Scripts von einer weiteren Funktion aufgerufen
        $targetPath = _setTargetPath( 0 ) ; Als Rückgabewert wird entweder eine Pfadangabe oder "" erwartet
        ConsoleWrite( @CRLF & " Rueckgabewert nach RETURN: >>>" & $targetPath & "<<<" & @CRLF & @CRLF )
    EndFunc

    Func _setTargetPath( $mode )
        If StringIsInt( $mode ) And StringLen( $mode ) = 1 And StringRegExp( $mode, '[0-1]' ) Then ; Prüfung des Parameters

            If $mode = 0 Then ; Aufruf beim Hinzufügen eines neuen Eintrages
                Local $newPath0 = FileSelectFolder( "Zielverzeichnis auswählen", $targetPath )
                If $newPath0 = "" Then ; Es wurde KEIN ZIELPFAD ausgewählt
                    Return $newPath0
                ElseIf $newPath0 <> "" And __validatePathAccess( $newPath0 ) Then ; Es wurde ein KORREKTER ZIELPFAD ausgewählt
                    ConsoleWrite( @CRLF & " Rueckgabewert vor RETURN = >>>" & $newPath0 & "<<< (ElseIf-Zweig)" & @CRLF & @CRLF )
                    Return $newPath0
                             ; PROBLEM: Wenn _setTargetPath() erneut aufgerufen wird, nachdem die Funktion unmittelbar davor in
                             ; den Else-Zweig (Ungenügende Zugriffsrechte für das ausgewählte Verzeichnis) gesprungen ist,
                             ; dann wird der eigentliche Inhalt von $newPath0 als Rückgabewert ignoriert. Obwohl ein
                             ; korrektes Verzeichnis (Pfadangabe oder "") ausgewählt wurde und die betreffende Variable
                             ; $newPath0 unmittelbar vor dem RETURN den korrekten Wert ausgibt. Stattdessen wird eine 0 (Null)
                             ; zurückgegeben und in _aufrufendeFunktion() an $targetPath übergeben.
                             ; Dieses Verhalten gilt unabhängig davon, ob _setTargetPath() mit dem Parameter 0 oder 1 aufgerufen wird.
                Else ; Ungenügende Zugriffsrechte für das ausgewählte Verzeichnis
                    _setTargetPath( 0 ) ; Sollte das ausgewählte Verzeichnis nicht über die ausreichenden Zugriffsrechte
                                        ; verfügen (Schreibrechte), dann wird dem Benutzer die Verzeichnisauswahl erneut
                                        ; angeboten (durch erneuten Aufruf der Funktion), bis ein zulässiges Ergebnis (valide Pfadangabe
                                        ; oder ein leeren String) zurückgegeben werden kann.
                EndIf
            ElseIf $mode = 1 Then ; Aufruf beim Ändern eines oder meherer vorhandener Einträge
                ; Abweichende Programmlogik zu $mode = 0. Jedoch besteht das Problem mit dem falschen Rückgabewert auch hier,
                ; da diese Programmteile identisch zu $mode = 0 sind.
            EndIf
        Else
            ; Falscher Übergabeparameter $mode
        EndIf
    EndFunc ; ==> _setTargetPath( $mode )

    Func __validatePathAccess( $path )
        If _WinAPI_PathIsDirectory( $path ) Then
            If StringRight( $path, 1 ) = "\" Then $path = StringTrimRight( $path, 1 )
            Local $h_tempFile = FileOpen( $path & "\DieseDateiSollteGeloeschtWerden.txt", $FO_OVERWRITE )
            If $h_tempFile <> -1 Then ; die erforderlichen Zugriffsrechte sind vorhanden
                FileClose( $h_tempFile )
                FileDelete( $path & "\DieseDateiSollteGeloeschtWerden.txt" )
                Return True
            EndIf
        EndIf
        Return False ; es fehlen die erforderlichen Zugriffsrechte
    EndFunc ; == > __validatePathAccess( $path )

  • ; PROBLEM: Wenn _setTargetPath() erneut aufgerufen wird, nachdem die Funktion unmittelbar davor in
    ; den Else-Zweig (Ungenügende Zugriffsrechte für das ausgewählte Verzeichnis) gesprungen ist,
    ; dann wird der eigentliche Inhalt von $newPath0 als Rückgabewert ignoriert.

    Das ist auch völlig korrekt. Du rufst die Funktion aus sich selbst heraus auf. Dabei übernimmt dieser Aufruf aber keine bisherigen Werte. Es ist ein absolut frischer Aufruf.

    Möchtest du bis dahin erstellte Werte beim rekursiven Aufruf nutzen, übergib diese einfach in einem zusätzlichen optionalen Parameter (z.B. $newPath0_rec), den du mit Null vorbelegst.

    In der Funktion prüfst du dann bei Verwendung der Variablen $newPath0, ob $newPath0_rec <> Null ist und weist dann diesen Wert zu.

    Der treffendere Titel für dein Problem ist somit: Bei rekursivem Funktionsaufruf gehen bisher in der Funktion erstellte Werte verloren. ;)

  • Moin,

    der treffende Titel für das Problem sollte sein: Beim rekursiven Funktionsaufruf geht der Rückgabewert der Funktion verloren.

    Code
                Else ; Ungenügende Zugriffsrechte für das ausgewählte Verzeichnis
                    _setTargetPath( 0 ) ; Sollte das ausgewählte Verzeichnis nicht über die ausreichenden Zugriffsrechte
                                        ; verfügen (Schreibrechte), dann wird dem Benutzer die Verzeichnisauswahl erneut
                                        ; angeboten (durch erneuten Aufruf der Funktion), bis ein zulässiges Ergebnis (valide Pfadangabe
                                        ; oder ein leeren String) zurückgegeben werden kann.

    Hier gibt die Funktion _setTargetPath() höchstwahrscheinlich einen Verzeichnisnamen zurück. Er wird aber ignoriert und landet deshalb im Nirwana.

  • sollte sein: Beim rekursiven Funktionsaufruf geht der Rückgabewert der Funktion verloren.

    Das sehe ich anders.

    Eine rekursive Funktion muss ihren Return zwingend am Ende der Funktion ausführen (oder als letzen Schritt in jedem If-Statement oder Switch/Select-Zweig). Ansonsten kann die Ausführung zu kruden Ergebnissen führen. Eine unsauber geschriebene, rekursiv arbeitende Funktion kann dir echt den Tag versauen. Es kann bei 99 Aufrufen alles paletti sein und beim 100.ten gehts schief ohne auf Anhieb sichtbaren Grund. ;)

  • Eine rekursive Funktion muss ihren Return zwingend am Ende der Funktion ausführen (oder als letzen Schritt in jedem If-Statement oder Switch/Select-Zweig).

    Wie ich es sehe, würde genau das hier geschehen, wenn der Rückgabewert des rekursiven Aufrufs 'returniert' werden würde.

    Code
    ElseIf $newPath0 <> "" And __validatePathAccess( $newPath0 ) Then ; Es wurde ein KORREKTER ZIELPFAD ausgewählt
        ConsoleWrite( @CRLF & " Rueckgabewert vor RETURN = >>>" & $newPath0 & "<<< (ElseIf-Zweig)" & @CRLF & @CRLF )
        Return $newPath0
    Else ; Ungenügende Zugriffsrechte für das ausgewählte Verzeichnis
        Return _setTargetPath( 0 )
    EndIf

    Ich finde allerdings 'eine einzige feste Zeile' am Ende der Funktion für die Rückgabe auch übersichtlicher. Aber auch für diesen Fall müsste der Rückgabewert aus dem rekursiven Aufruf gespeichert werden (es sei denn, man benutzt eine globale Variable (!igittigitt!). ;)

  • Wie ich es sehe, würde genau das hier geschehen, wenn der Rückgabewert des rekursiven Aufrufs 'returniert' werden würde.

    Genau :) - Wenn das Wörtchen Wenn nicht wär... 8o

    Ich finde allerdings 'eine einzige feste Zeile' am Ende der Funktion für die Rückgabe auch übersichtlicher. Aber auch für diesen Fall müsste der Rückgabewert aus dem rekursiven Aufruf gespeichert werden (es sei denn, man benutzt eine globale Variable (!igittigitt!). ;)

    Bin ich absolut dabei. ^^

  • Hier wird doch ein Funktionsaufruf als GoTo missbraucht, was zu einem rekursiven Funktionsaufruf führt (mit den entsprechenden Problemen).

    Bei Einsatz einer Schleife ist das aber gar nicht nötig:

  • Das ist auch völlig korrekt. Du rufst die Funktion aus sich selbst heraus auf. Dabei übernimmt dieser Aufruf aber keine bisherigen Werte. Es ist ein absolut frischer Aufruf.

    Möchtest du bis dahin erstellte Werte beim rekursiven Aufruf nutzen, übergib diese einfach in einem zusätzlichen optionalen Parameter (z.B. $newPath0_rec), den du mit Null vorbelegst.

    In der Funktion prüfst du dann bei Verwendung der Variablen $newPath0, ob $newPath0_rec <> Null ist und weist dann diesen Wert zu.

    Der treffendere Titel für dein Problem ist somit: Bei rekursivem Funktionsaufruf gehen bisher in der Funktion erstellte Werte verloren. ;)

    Da habe ich mich vermutlich unklar ausgedrückt. Selbstverständlich gehen hier die vorherigen Werte verloren, wenn die Funktion erneut aufgerufen wird. Das ich so auch gewollt.

    Das Problem ist, dass wenn die Funktion in dem beschriebenen Szenario (S. Fall 2) erneut aufgerufen wird IMMER DIE 0 zurückgegeben wird, obwohl die Variable $newPath0, die retuniert wird, eine Pfadangabe enthält. Dass ist mein Problem, zu dem ich keinen Lösungsansatz habe.

  • Hier wird doch ein Funktionsaufruf als GoTo missbraucht, was zu einem rekursiven Funktionsaufruf führt (mit den entsprechenden Problemen).

    Bei Einsatz einer Schleife ist das aber gar nicht nötig:

    Hallo Oscar,

    ich habe zwar noch nicht das Problem verstanden, aber dein Ansatz scheint in auch in meinem Original-Script zu funktionieren. Dafür erst einmal meinen herzlichen Dank an dich.

    Könntest du mir bitte hierzu noch etwas mehr Info geben "... Funktionsaufruf als GoTo missbraucht, was zu einem rekursiven Funktionsaufruf führt (mit den entsprechenden Problemen)." - damit ich das Problem verstehen kann? Und wieso sich bei meinem Ansatz in dem Szenario Fall 2 immer die 0 "reingemogelt" hatte.

    Beste Grüße :)

  • Könntest du mir bitte hierzu noch etwas mehr Info geben "... Funktionsaufruf als GoTo missbraucht, was zu einem rekursiven Funktionsaufruf führt (mit den entsprechenden Problemen)." - damit ich das Problem verstehen kann? Und wieso sich bei meinem Ansatz in dem Szenario Fall 2 immer die 0 "reingemogelt" hatte.

    Wenn Du innerhalb einer Funktion die gleiche Funktion nochmal aufrufst, dann handelt es sich um einen rekursiven Aufruf. Die Rückgabe der Funktion wird nicht an das "Hauptprogramm" zurückgegeben, sondern an die Funktion.

    So etwas brauchst Du hier gar nicht. Du willst einfach nur eine Schleife.

  • Das Problem ist, dass wenn die Funktion in dem beschriebenen Szenario (S. Fall 2) erneut aufgerufen wird IMMER DIE 0 zurückgegeben wird, obwohl die Variable $newPath0, die retuniert wird, eine Pfadangabe enthält. Dass ist mein Problem, zu dem ich keinen Lösungsansatz habe.

    Das ist durch die Rekursion!!! Nach Beendigung des 2.ten Funktionsaufrufes (der einen gültigen Pfad ergibt), wird der erste Funktionsaufruf fortgesetzt!!! - Das ist das Wesen rekursiver Funktionen, deshalb mein Hinweis, dass man da verflixt aufpassen muss.

    Übrigens hatte Oscar dir ja die beste Lösung bereits serviert.

    Hier mal noch ein paar Ausgabezeilen mehr in deiner Funktion. Teste das, dann wird es dir vielleicht klar.

  • Um Dir mal zu demonstrieren, dass man mit rekursiven Funktionsaufrufen vorsichtig sein sollte, hier ein Testprogramm:

    AutoIt
    _RekursiveFunktion(0)
    
    Func _RekursiveFunktion($test)
        $test += 1
        ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $test = ' & $test & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
        _RekursiveFunktion($test)
    EndFunc

    Nach 3799 Funktionsaufrufen ist die max. Rekursionstiefe erreicht und AutoIt beendet das Script mit:

    Recursion level has been exceeded - AutoIt will quit to prevent stack overflow.

  • Hallo an euch alle!

    Danke für eure Geduld und dass ihr mir versucht habt, das Problem zu erklären. Ich nehme mit "Finger weg vor Rekursion" - und scheinbar bin ich da einem ganz grundlegenden Denkfehler unterlegen und werde das bei mir "aufräumen". Ganz besonderen Dank nochmals an Oscar, dessen Tipp mir letztlich weitergeholfen hat.

    Viele Grüße :)

  • Mit 2 kleinen Änderungen könnte man aber die RekursiveFunktion trozdem ohne Probleme benutzen,

    indem man daraus eine bedingte Rekursion macht oder liege ich damit falsch?

    AutoIt
    _RekursiveFunktion(0)
    
    Func _RekursiveFunktion($test)
        $test += 1
        Sleep(1000)
            Tooltip('@@ Debug(' & @ScriptLineNumber & ') : $test = ' & $test & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
        If $test = 10 Then Return
        Return _RekursiveFunktion($test)
    EndFunc

    Läuft bei mir problemlos bis 10 und dann folgt Scriptende.

  • Rekursionen sind kein 'Teufelskram' sondern eine gängige Programmiermethode. Man muss allerdings darauf achten, dass die Zahl der Rekursionen bestimmte Grenzen nicht übersteigt. Und die sind in den verschiedenen Skript- bzw. Programmiersprachen unterschiedlich. Wenn man sich nicht absolut sicher ist, dass die jeweilige Grenze nicht errreicht werden kann, sollte man deshalb Abbruchbedingungen einbauen, wie im Beispiel von Tuxedo.

    Für diese Aufgabe schließe ich mich aber derMeinung an, dass Rekursionen überhaupt nicht gebraucht werden, weil man das einfach mit einer Schleife lösen kann. Ich würde die Schleife allerdings im passenden Case 0 Zweig unterbringen.