Traitement des erreurs non-COM.

Aide et conseils concernant AutoIt et ses outils.
Règles du forum
.
Répondre
Avatar du membre
Barthandelus
Niveau 3
Niveau 3
Messages : 41
Enregistré le : mer. 02 janv. 2019 14:34
Status : Hors ligne

Traitement des erreurs non-COM.

#1

Message par Barthandelus » mar. 12 mars 2019 17:22

Bonjour,

Si TL;DR ne lire que la partie en gras.


Je tente de mettre en place une Supervision (réseau & sécurité) assez conséquente sur un réseau de plus de 250 périphériques (actuellement, le code est fonctionnel et dépasse les 1000 lignes sans les librairies que j'utilise ou que j'ai du faire à coté). Ce programme, en finalité, devra être compiler et utiliser sans Scite. Cependant, je souhaiterais tout de même être au courant de ses crashs provenant d'erreur non-COM.

Je m'explique : Ce même outil est actuellement en mesure de communiquer avec une IHM sur un serveur Apache / SQL à distance, mais aussi en direct avec des périphériques en local. A titre d'information, l'outil effectue actuellement dans les 70 communications / secondes. Cependant, les communications sont parfois tronqués, ou possèdent des termes invalides. Petit à petit, je fais en sorte que mon programme puisse s'adapter à ces cas, mais j'ai peur d'en rater.

A savoir que le programme a une fonctionnalité FTP dans lequel il va chercher des fichiers et l'imbriquer pour s'ajouter des fonctionnalités en live, sans avoir à recompiler quoi que ce sois, mais aussi pour recevoir des tables de données, ou des flux EDI.

L'objet Autoit.Error permet d'handle les erreurs COM, par contre, s'il y a une erreur de syntaxe ou de déclaration de variable provoqué par un argument reçu d'un autre périphérique (imbriqué par exemple dans une requête SQL, ou dans une fonctionnalité récupérer par FTP), l'objet n'est pas en mesure de les handles.

L'objectif ici serait de pouvoir handle tout type d'erreur et réussir à les envoyer (ftp, sql) avant de bien faire crash le logiciel (je ne souhaite pas qu'il continu un traitement erroné). Avez-vous une idée ?


A titre d'information, l'erreur serait ensuite récupérer par un autre programme afin de créer une alerte s'acheminant jusque moi.

jchd
AutoIt MVPs (MVP)
AutoIt MVPs (MVP)
Messages : 2046
Enregistré le : lun. 30 mars 2009 21:57
Localisation : Sud-Ouest de la France (43.622788,-1.260864)
Status : Hors ligne

Re: Traitement des erreurs non-COM.

#2

Message par jchd » mar. 12 mars 2019 21:19

EDIT : Il existe certes une tentative de contourner la limitation dont je parle mais ce n'est pas vraiment un moyen natif utilisable à l'aveuglette.
Voir ce fil (US) et bien étudier les implications : https://www.autoitscript.com/forum/topi ... al-errors/

Il n'y a aucun moyen d'intercepter les erreurs fatales, comme l'accès à un indice hors bornes d'un tableau, lire une variable pas encore déclarée, l'appel par Call() d'une fonction non existante, soumettre un nombre d'arguments invalide à l'appel d'une fonction et autres joyeusetés du même genre. Tout celà est considéré comme des bogues et la seule contre-mesure est de produire un code propre et robuste.

Les erreurs interceptables (hors objets) sont à traiter via @error. Le traitement de données incomplètes ou invalides tombe dans cette catégorie.
La cryptographie d'aujourd'hui c'est le taquin plus l'électricité.

Avatar du membre
TommyDDR
Modérateur
Modérateur
Messages : 1787
Enregistré le : mar. 22 juil. 2008 20:55
Localisation : Nantes
Status : Hors ligne

Re: Traitement des erreurs non-COM.

#3

Message par TommyDDR » mer. 13 mars 2019 02:23

Vous pouvez aussi vous servir de OnAutoitExitRegister qui permet de lancer une fonction lors de l'arrêt de votre script.
Avec @exitCode, vous saurez s'il y a eu une erreur, mais pas la nature de celle-ci.

Et encore mieux, si votre script est à base de AdlibRegister / déclenchements de fonction par événements, il pourra continuer de fonctionner.

Un exemple en dessous (appuyez sur " ² " pour déclencher une erreur)
Cependant, à la 2ème erreur, le programme se fermera.
#include <GUIConstantsEx.au3>

HotKeySet("²", "error")

Opt("GUIOnEventMode", 1)
Opt("MustDeclareVars", 1)

Global $gui
Global $label
Global $posLabel = [5, 5]
Global $taille[2] = [300, 300]

OnAutoItExitRegister("EXITME")

$Gui = GUICreate("test", $taille[0], $taille[1])
GUISetOnEvent($GUI_EVENT_CLOSE, "quit")
$label = GUICtrlCreateLabel("-", $posLabel[0], $posLabel[1])
GUISetState()

AdlibRegister("MoveMe", 100)

While(True)
        Sleep(10)
WEnd

Func error()
        MsgBox(0, "", $gui[5][4])
        ConsoleWrite($taille[2] & @LF)
EndFunc

Func MoveMe()
        $posLabel[0] += 1
        $posLabel[1] += 1
        GUICtrlSetPos($label, $posLabel[0], $posLabel[1])
EndFunc

Func quit()
        Exit
EndFunc

Func EXITME()
        If(@exitCode <> 0) Then
                ConsoleWrite("ERROR !")
                While(True)
                        Sleep(100)
                WEnd
        EndIf
EndFunc
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679

Avatar du membre
Barthandelus
Niveau 3
Niveau 3
Messages : 41
Enregistré le : mer. 02 janv. 2019 14:34
Status : Hors ligne

Re: Traitement des erreurs non-COM.

#4

Message par Barthandelus » mer. 13 mars 2019 09:13

Bonjour,

@jchd : J'en prends note et je testerais la solution OnAutoItErrorRegister pour voir si elle me convient.

@TommyDDR : J'utilise effectivement déjà cette solution (notamment pour fermer mes connexions SQL quoi qu'il arrive), mais je ne connaissais pas @exitCode. C'est tout de même dommage de ne pas pouvoir récupérer ces erreurs.

C'est peut être un peu tiré par les cheveux, mais lorsque l'erreur survient sous DEBUG Scite, l'erreur est bien détectée et afficher en mode console. Est-ce que c'est le programme AU3 qui renvoi cette valeur ou Scite qui injecte quelque chose dans le programme pour pouvoir obtenir cette info ? Dans tout les cas, ne serait-il pas possible d'embarquer cette fonctionnalité ou de rediriger ce flux d'erreur autre part (ex : Un fichier texte / une BDD ?)

Les infos sont, après tout, juste là et déjà en place, il suffirait juste de l'envoyer autre part.

jchd
AutoIt MVPs (MVP)
AutoIt MVPs (MVP)
Messages : 2046
Enregistré le : lun. 30 mars 2009 21:57
Localisation : Sud-Ouest de la France (43.622788,-1.260864)
Status : Hors ligne

Re: Traitement des erreurs non-COM.

#5

Message par jchd » mer. 13 mars 2019 13:43

Je m'avance peut-être un peu (trop), mais dans le cas où l'abort provoque l'apparition d'une fenêtre comme celle que produit le code ci-dessous, il est éventuellement concevable d'intercepter l'alimentation du contrôle qui contient les informations sur l'erreur. Avec _WinAPI_SetWindowsHookEx() il devrait être possible de filtrer les messages Windows qui sont à destination de cette fenêtre et de choper l'info au vol. Bon, c'est un sacré conditionnel, mais ça se tente.

Utiliser Au3Info pour en savoir plus sur la fenêtre et le contrôle en question.
; Window
; AutoIt Error
; Class #32770

; Control
; Class Static
; Instance 2
; ClassnameNN Static2
; Advance Mode [CLASS:Static; INSTANCE:2]

Code : Tout sélectionner

#forcedef $JeNexistePas
ConsoleWrite($JeNexistePas)
A compiler et exécuter bêtement.

Par contre je pense qu'un scénario similaire n'a aucune chance de fonctionner sur une exception gérée par l'OS, car au moment où la fenêtre est créée le programme AutoIt est terminé et c'est l'OS qui nous rend compte. Mais comme Windows nous propose le débogage (utilisable si le code contient les infos ad hoc et si un débogueur compatible est prêt à l'emploi, ce qui n'est pas le cas en général) on peut penser que tout est resté en mémoire. Ceci dit, ça ne risque pas de nous apporter grand chose, mais il y a quand même des infos disponibles concernant l'exception (cliquer sur "Voir les détails sur ce problème").

Code : Tout sélectionner

; ici on provoque une exception en lisant une adresse mémoire qui ne nous appartient pas
Local $DummyPtr = Ptr(1024*1024*1024)
Local $t = DllStructCreate("char[1024]", $DummyPtr)
Local $c = DllStructGetData($t, 1)
Ce sont juste des pistes éventuelles, mais je n'ai pas actuellement le temps de creuser tout ça.
La cryptographie d'aujourd'hui c'est le taquin plus l'électricité.

Avatar du membre
TommyDDR
Modérateur
Modérateur
Messages : 1787
Enregistré le : mar. 22 juil. 2008 20:55
Localisation : Nantes
Status : Hors ligne

Re: Traitement des erreurs non-COM.  

#6

Message par TommyDDR » mer. 13 mars 2019 15:38

Après pas mal de tests, il semblerait que le paramètre "/ErrorStdOut" puisse servir, le seul soucis, c'est que vous devez avoir 2 processus, un appelant et un enfant (les deux processus résident dans le même programme, donc pas de soucis niveau déploiement, cela sera comme avant).

Le code consiste en 2 parties, l'une qui sera lancé lors du double clic (ou lancé via ligne de commande) et la 2ème qui sera lancée par la 1ère en indiquant un paramètre précis pour pouvoir être différenciée.

Ci-dessous, le code en question :
Utilisation :
- Vous ne devez pas modifier la fonction checkCmdLine() et elle doit être appelée en 1er !
- Vous pouvez changer la clé envoyée en paramètre à checkCmdLine (il faut juste trouver une clé qui ne servira jamais en tant que paramètre réel du programme, donc une suite de caractère au hasard fait l'affaire)
- Vous pouvez changer le 2nd paramètre de checkCmdLine() pour quitter le programme parent quand le programme enfant n'est plus exécuté
- Mettez votre gestion d'erreur dans la fonction somethingRead() (attention, tous les ConsoleWrite et ConsoleWriteError du processus enfant iront ici, ce ne seront pas forcément que des erreurs)
#include <AutoItConstants.au3>

checkCmdLine("CleRandomPasTropBanale", True)

OnAutoItExitRegister("autoit_exit")

Local $params = ""
For $i = 1 To $cmdLine[0]
        $params &= $cmdLine[$i] & ", "
Next
$params = StringTrimRight($params, 2)
MsgBox(0, "", $params)
Local $error[1]
ConsoleWrite($error[10] & @CRLF) ; Déclanchement de l'erreur

Func autoit_exit()
        If(@exitCode <> 0) Then
                ConsoleWrite("Fermeture du programme suite à une erreur (" & @exitCode & ")." & @CRLF)
        EndIf
EndFunc







Func somethingRead($read)
        ;~      Mettez votre gestion des erreures ici
        MsgBox(0, "Erreur ?", $read)
EndFunc

Func checkCmdLine($cle, $exitOnClose)
        If($cmdLine[0] > 0 And $cmdLine[1] = $cle) Then         ; Si la clé est le 1er paramètre
                For $i = 1 To $cmdLine[0] - 1                                   ; On décale les paramètres en supprimant la clé
                        $cmdLine[$i] = $cmdLine[$i+1]                           ; pour que le programme principale puisse avoir
                Next                                                                                    ; accès aux paramètres normalement
                $cmdLine[0] -= 1
                ReDim $cmdLine[$cmdLine[0]+1]
        Else                                                                                            ; Si la clé n'est pas le 1er paramètre
                Local $params = "/ErrorStdOut " & $cle & " "    ; On ajoute le paramètre pour rédiriger les erreures vers la console ainsi que la clé
                For $i = 1 To $cmdLine[0]
                        $params &= $cmdLine[$i] & " "                           ; On récupères les paramètres qui doivent être envoyés au programme (s'il y en a)
                Next
                Local $cmd = '"' & @ScriptFullPath & '" '               ; On récupère le nom complet de l'exécutable
                If(StringRight(@ScriptName, 4) == ".au3") Then  ; Si le script n'est pas compilé
                        $cmd = '"' & @AutoItExe & '" '                          ; On récupère le nom de l'exécutable AutoIt
                        $params = '/ErrorStdOut /AutoIt3ExecuteScript "' & @ScriptFullPath & '" ' & $cle & ' ' ; Et on y ajoute en +, le nom script à exécuter
                EndIf
                $cmd &= $params                                                                 ; On ajoute les paramètres
                Local $pid = Run($cmd, @WorkingDir, @SW_HIDE, BitOR($STDOUT_CHILD, $STDERR_CHILD, $STDERR_MERGED)) ; On lance le processus enfant
                Local $read
                While 1
                        $read = StdoutRead($pid)                                        ; On lit les retours du processus enfant
                        If(StringLen($read) > 0) Then                           ; Si on a un retour
                                somethingRead($read)                                    ; On lance la fonction somethingRead avec le retour en paramètre
                        EndIf
                        Sleep(10)
                        If(Not(ProcessExists($pid)) And $exitOnClose) Then      ; Si le processus enfant est clos et qu'on a spécifier de quitter pour ce cas
                                Exit                                                                                    ; On quitte le programme
                        EndIf
                WEnd
        EndIf
EndFunc
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679

jchd
AutoIt MVPs (MVP)
AutoIt MVPs (MVP)
Messages : 2046
Enregistré le : lun. 30 mars 2009 21:57
Localisation : Sud-Ouest de la France (43.622788,-1.260864)
Status : Hors ligne

Re: Traitement des erreurs non-COM.

#7

Message par jchd » mer. 13 mars 2019 16:43

Ah oui, j'oublie aussi systématiquement ce switch.
Dans ce cas pourquoi ne pas lancer tout simplement l'exécutable bang.exe toujours ainsi :
C:\folder\bang /ErrorStdOut >> err.log

Si bang.au3 contient mon premier programme au-dessus, on obtient dans err.log :
"C:\folder\Bang.exe" (2) : ==> Variable used without being declared.:
La cryptographie d'aujourd'hui c'est le taquin plus l'électricité.

Avatar du membre
Barthandelus
Niveau 3
Niveau 3
Messages : 41
Enregistré le : mer. 02 janv. 2019 14:34
Status : Hors ligne

Re: Traitement des erreurs non-COM.

#8

Message par Barthandelus » jeu. 14 mars 2019 10:21

J'aime beaucoup ce que je lis !

Effectivement j'avais vu cet argument lors du lancement du programme sous Scite, pour exemple :

>"C:\Program Files (x86)\AutoIt3\SciTE\..\AutoIt3.exe" "C:\Program Files (x86)\AutoIt3\SciTE\AutoIt3Wrapper\AutoIt3Wrapper.au3" /run /prod /ErrorStdOut /in "C:\Users\Admin\Documents\AI3Compagnon\Supervision.au3" /UserParams

Cependant l'objectif final est de compiler le script AU3 pour en faire un .EXE qui sera exécuter sur un serveur doté d'un accès FTP, du coup, je me demande s'il est possible d'embarquer un argument /ErrorStdOut >> error.log nativement ? Je ne sais pas si c'est possible directement dans la conception du programme.

Auquel cas ce ne serais pas possible, est-il possible de démarrer l'application finale .exe sans aucun argument (via un double clique utilisateur), qui regardera si une variable $test a été reçue en argument du programme. Si ce n'est pas le cas, le programme se relance lui même avec en argument /ErrorStdOut >> error.log + la déclaration de la variable $test à TRUE (par exemple, par contre je ne sais pas comment procéder).

A noter que cette solution permettrait d'avoir des logs mais pas forcément de l'handle pour le faire remonter sur un serveur distant sans programmer une tierce application pour venir le récupérer via une tâche planifiée (a moins qu'il soit possible d'injecter /ErrorStdOut directement comme argument d'un autre programme ?)

La solution de @TommyDDR, elle, semble pouvoir permettre une injection automatique de l'erreur en BDD ou toute manipulation possible avec puisque, si j'ai bien compris (attention, j'ai le cerveau un peu en compote donc possible que je n'ai pas tout compris), c'est le processus enfant qui exécuterait le programme principal et le processus parent qui agirait comme un récepteur de l'argument /ErrorStdOut enfant (et de tout les ConsoleWrite() & cie, donc l'ensemble de ce que le processus enfant communique). C'est bien ça ? Par hasard, est-ce que les library du processus parent sont automatiquement injectés dans le processus enfant (comme l'aurait fait un script PHP) ou faut-il redéfinir toute la partie SQL ? D'ailleurs, on est bien d'accord que si aucun ConsoleWrite() ou équivalent n'est définit, l'application enfant ne devrait communiquer exclusivement que sur les erreurs ?

D'ailleurs avec cette méthode je me pose une question, le flux /ErrorStdOut sera déclencher en cas de crash de l'application, mais est-ce que OnAutoItExitRegister() sera tout de même trigger par le processus enfant ? Si oui, avant ou après l'envoi du flux /ErrorStdOut ? Pourquoi cette question : Si je souhaite rattacher l'erreur à l'exécution d'une partie spécifique du programme enfant, il faut que je puisse fournir un identifiant unique sous forme numérique (quitte à ce qu'il soit renseigner en dur dans le code, type $part_code = 1 pour la fonction 1, etc etc).

Dans le programme parent je devrais donc lire le flux reçu du processus enfant, si OnAutoItExitRegister() est exécuté en premier, alors mon code devra attendre une valeur totalement numérique au début avant de se mettre en mode écoute pour le message d'erreur à associer, et à partir de là seulement injecter les valeurs dans une base SQL. Je dis ça parce que je doute qu'il soit possible de modifier le flux /ErrorStdOut ou de transformer ce dernier en Array associatif. Donc, je pense être obliger de devoir faire deux retours pour permettre ses liaisons.

Après je dis tout ça, mais si ça se trouve, la compilation en .exe rendrait non fonctionnel /ErrorStdOut. Auquel cas je devrais procéder autrement. :lol:

EDIT : Pour info, effectuer sous cmd un "start test_console.exe /ErrorStdOut >> log.txt" créer un fichier vide, donc effectivement, je pense que ce n'est pas envisageable. A moins que quelque chose m'échappe ? Etant donné que mon application se connecte à un serveur SQL et qu'il y a donc les identifiants dedans, j'ai besoin de faire en sorte de compiler la source du programme puisqu'elle sera déposer sur un serveur en local (mais pas à ma portée, donc n'importe qui pourrait être en mesure d'en voir le code source si non compiler.)

Avatar du membre
TommyDDR
Modérateur
Modérateur
Messages : 1787
Enregistré le : mar. 22 juil. 2008 20:55
Localisation : Nantes
Status : Hors ligne

Re: Traitement des erreurs non-COM.

#9

Message par TommyDDR » jeu. 14 mars 2019 12:30

Il ne faut pas les voir comme 2 processus, mais comme un seul qui fera des actions différentes, si les script passent par OnAutoitRegister, alors les deux le feront.
Dans le programme, il y a une partie exécutée par les deux (comme les includes), une partie exécutée par le parent uniquement (la partie dans checkCmdine qui va lancer l'enfant et observer en boucle) et une partie exécutée par l'enfant uniquement (la partie dans checkCmdLine qui va remettre les paramètres dans le bon ordre et votre programme principal.
Mais quoi qu'il en soit, si vous avec des include de bibliothèques ou quel code que ce soit qui est dans la partie exécutée par les deux, alors ils le seront dans les 2 processus.

Si vous voulez différencier les parties de votre programme, (mais cela sera par sans effort), vous pouvez définir une variable d'erreur à chaque début de fonction (variable sous forme de nombre ou string) et quand une erreur se déclenche, vous faites un consolWrite de cette variable. Après c'est à vous de la récupérer dans le processus enfant et de parser le teste pour gérer ça.

Oui, si aucun ConsoleWrite n'est utilisé, seulement les erreurs seront reçues.

Il faut savoir que l'erreur sera d’abord envoyer dans le flux de sortie et la fonction associé à OnAutoitExitRegister sera exécutée seulement après

Si la solution de tout rediriger dans un fichier vous convient, alors c'est possible de vérifier cela au début du script et de se ré-appeler soit-même avec la bonne commande oui, j'avais écrit le script en pensant que vous vouliez faire une action précise en cas d'erreur.

La compilation en exe ne rend pas /ErrorStdOut non fonctionnel ! Vous n'avez pas testé de compiler mon script ! :o

PS : N'importe qui sera en mesure de voire votre mot de passe s'il a accès à l’exécutable. Plusieurs posts sur le sujet on été abordé, je ne m'étendrai donc pas, mais un script compilé n'est pas sans faille pour récupérer le code source.
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679

Avatar du membre
Barthandelus
Niveau 3
Niveau 3
Messages : 41
Enregistré le : mer. 02 janv. 2019 14:34
Status : Hors ligne

Re: Traitement des erreurs non-COM.

#10

Message par Barthandelus » jeu. 14 mars 2019 15:00

D'accord, je comprends pour le processus. Le programme se relance lui même mais n'effectue pas les mêmes choses grâce à la variable $cle en argument. (Par contre, il y aura bien 2 process au niveau du Processeur, non ?)

En effet, si je comprends bien (et après avoir regarder plus en détail le code), le programme démarre et lance checkCmdLine() dans tout les cas. Dans le cas du parent, il tombe directement dans la boucle Else puisque la clé n'est pas encore défini comme argument du programme et va donc se figer dans le While 1 (et donc, n'exécutera le programme principal que si $exitOnClose est à false), puis, lorsque checkCmdLine() est effectué par le processus enfant, il tombe dans la première condition qui reconstruit $cmdLine et termine d'exécuter la fonction avant de retomber en mode procédural dans le programme principal. (On oubliera pas de préciser que le code est en mesure de détecter s'il s'agit d'un .AU3 ou d'un compilé .EXE et de s'adapter en fonction ! :wink: )

C'est un très joli code !

Pour information à ceux qui pourraient lire le thread à l'avenir, le code du programme principal serait à placer après la ligne ci-dessous (en prenant soin, bien entendu, de la supprimer et de mettre ses Includes tout en haut)

ConsoleWrite($error[10] & @CRLF) ; Déclanchement de l'erreur

Je pense que ce code mérite d'être adapter en UDF ! Il permettrait énormément plus de contrôle sur les erreurs mais aussi sur les retours Console ! En faisant un include de cet UDF avec dedans un include-once de AutoItConstants.au3 et en exécutant checkCmdLine(), ça me semble faisable !

Je prends note concernant l'ordre des retours de flux : L'erreur avant le déclenchement de OnAutoitExitRegister(). Je n'ai effectivement pas compiler votre script ! J'avais essayer avec celui de @jchd, mais j'ai du mal m'y prendre.

Je confirme que je voulais effectivement faire une action avec l'erreur, donc le code est parfait pour mon besoin.

Pour moi tout est bon, j'ai maintenant les armes pour effectuer ce dont j'ai besoin, merci ! :wink:
EDIT : En retirant la MsgBox() qui affiche $params, parfois ça m'affiche l'erreur, parfois non, j'ai l'impression que mon CPU va trop vite, dans tout les cas, sur un soft avec une IHM, ça fonctionne.

Répondre