IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

API, COM et SDKs Delphi Discussion :

Thread, Pipes, maj VCL et Objets COM : Figeage total !


Sujet :

API, COM et SDKs Delphi

  1. #1
    Membre régulier
    Inscrit en
    Mai 2002
    Messages
    190
    Détails du profil
    Informations forums :
    Inscription : Mai 2002
    Messages : 190
    Points : 83
    Points
    83
    Par défaut Thread, Pipes, maj VCL et Objets COM : Figeage total !
    Bonjour à tous.

    J'aurai besoin de votre aide sur un sujet qui me dépasse complètement.

    J'ai une application composée de 2 process séparés : un moteur et une interface graphique (HMI). Tout le travail est fait par le process moteur, mais toutes les interactions utilisateur sont prises en charge par le process HMI. La HMI affiche ce que fait le moteur et lui envoie des commandes lorsque l'utilisateur clique quelque part.

    Le moteur et sa HMI sont 2 objets COM. Chacun connait et exploite l'interface COM de l'autre pour communiquer.

    Actuellement, l'exécution dans le moteur est synchrone avec l'affichage dans la HMI.

    Je souhaite désynchroniser l'affichage dans la HMI d'avec l'exécution dans le moteur.

    J'ai donc mis en place un pipe de communication entre les 2 process. Le pipe est créé par la HMI, laquelle attends ensuite la connexion du moteur puis lorsque la connexion est établie, la HMI crée un thread qui se chargera de lire les données reçues du moteur et de mettre à jour l'interface graphique.

    La HMI accuse un léger retard par rapport au moteur, mais ca permet au moteur de tourner plus vite. Bref, ça fonctionne bien.

    Jusqu'à ce que l'utilisateur fasse un clic quelque part : la HMI envoie la demande au moteur à travers son interface COM. Et là, il arrive de façon aléatoire que la HMI se fige complètement. TOTALEMENT.

    Je ne comprends pas ce qu'il se passe, je n'ai aucune piste. Je croyais avoir bien fait les choses, autant dans la gestion du pipe que dans celle des objets COM.

    Voici le code du moteur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
     
    {$REGION 'Moteur'}
     
    {$REGION 'Connexion au pipe'}
    	repeat
    		kernel.CursorPipeHandle := CreateFile(pwidechar(format('\\.\pipe\%0:s',[_IN_sCurseurPipeName])), GENERIC_WRITE, 0, nil,
    				OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, 0);
    	until kernel.CursorPipeHandle <> INVALID_HANDLE_VALUE ;
    {$ENDREGION}
     
    {$REGION 'Ecriture dans le pipe'}
    	procedure EcireDansPipe();
    	const
    		g_PipeBufferSizeCursors = 100 ;
    	var
    		l_iWrittenInDefectPipe : Cardinal ;
    		l_acBufferCursorPipe: array [0 .. g_PipeBufferSizeCursors - 1] of char;
    		l_sStrCursorPipe: pchar;
    	begin
    		l_sStrCursorPipe := PWideChar(format('blabla',[]));
    		fillchar(l_acBufferCursorPipe, g_PipeBufferSizeCursors, #0);
    		move(l_sStrCursorPipe[0], l_acBufferCursorPipe[0], Length(l_sStrCursorPipe) * Sizeof(char));
    		WriteFile(kernel.CursorPipeHandle, l_acBufferCursorPipe[0], Length(l_sStrCursorPipe) * Sizeof(char), l_iWrittenInDefectPipe, nil);
    		if(l_iWrittenInDefectPipe <> (Length(l_sStrCursorPipe) * Sizeof(char)))then
    		begin
    			exit ;
    		end;
    	end;
    {$ENDREGION}
     
    {$ENDREGION}

    Et voici le code de la HMI :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
     
    {$REGION 'HMI'}
     
    const
    	g_PipeBufferSize = 32768 ;
     
    {$REGION 'Création du pipe et attente de connexion'}
    	procedure TCursorPipeReaderManager.Execute;
    	var
    		l_iHandleNewPipe : THandle;
    	begin
    		l_iHandleNewPipe := CreateNamedPipe(PWidechar(format('\\.\pipe\%0:s',[self.mPr_sPipeName])), PIPE_ACCESS_DUPLEX,
    				PIPE_TYPE_MESSAGE or PIPE_READMODE_MESSAGE or PIPE_WAIT,
    				PIPE_UNLIMITED_INSTANCES, g_PipeBufferSize, g_PipeBufferSize, 0, nil);
     
    		ConnectNamedPipe(l_iHandleNewPipe, nil);
     
    		mPr_PipeReader := TPipeReaderCursors.Create(l_iHandleNewPipe, true);
    		mPr_PipeReader.FreeOnTerminate := true ;
    		mPr_PipeReader.sName := self.mPr_sPipeName ;
    		mPr_PipeReader.InfoNotification := self.mPr_InfoNotification ;
    		mPr_PipeReader.Start ;
    	end;
    {$ENDREGION}
     
    {$REGION 'Thread de lecture du pipe'}
    	procedure TPipeReaderCursors.Execute; // thread
    	var
    		l_iRead: cardinal;
    		l_dOccupationEnPourcentage : double ;
    		l_sMsg : string ;
    		l_acBuffer: array[0..g_PipeBufferSize - 1] of char;
    		l_bSuccess : boolean ;
    		procedure Traiter(const _IN_sReçu : string);
    		begin
    			l_dOccupationEnPourcentage := (mPr_iRemplissage/g_PipeBufferSize) * 100 ;
    			if(assigned(mPr_InfoNotification))then
    			begin
    				Synchronize(procedure
    					begin
    						// Mise à jour de la HMI.
    						mPr_InfoNotification(self.sName, l_dOccupationEnPourcentage, _IN_sReçu);
    					end);
    			end;
    		end;
    		procedure LocalFinalize();
    		begin
    			DisconnectNamedPipe(mPr_iHandlePipe);
    			CoUninitialize();
    		end;
    	begin
    		CoInitialize(nil);
    		try
    			while not self.terminated do
    			begin
    				l_sMsg := '';
    				FillChar(l_acBuffer, g_PipeBufferSize, #0);
     
    				l_bSuccess := ReadFile(mPr_iHandlePipe, l_acBuffer[0], g_PipeBufferSize, l_iRead, nil);
    				if Terminated then
    				begin
    					LocalFinalize();
    					exit ;
    				end;
    				if((l_bSuccess = false) or (l_iRead = 0))then
    				begin
    					LocalFinalize();
    					exit ;
    				end
    				else
    				begin
    					l_sMsg := l_sMsg + Copy(l_acBuffer, 0, l_iRead);
    					mPr_iRemplissage := GetFileSize(mPr_iHandlePipe,nil) ;
    					Traiter(l_sMsg);
    				end;
    			end;
    			Except on E : exception do
    			begin
    				LocalFinalize();
    				exit;
    			end;
    		end;
    		LocalFinalize();
    	end;
    {$ENDREGION}
     
    {$REGION 'Méthode évènementielle sur HMI'}
    	procedure onBtnClick(Sender : TObject);
    	begin
    		// Appel d'une méthode chez le Moteur via son interface COM.
    		// L'exécution de cette ligne provoque de façon aléatoire le figeage total de la HMI.
    		Hmi.m_fKernel.HtkEvent_BtnOnClick(...); 
    	end;
    {$ENDREGION}
     
    {$ENDREGION}
    Quelqu'un a une idée ?
    Pour mes développements, j'utilise :
    WinX-64bits, Delphi Tokyo 10.2.2
    Merci, merci, merci... moi aussi je vous aime, c'est trop d'émotions...
    Key user des blagues nulles

  2. #2
    Membre régulier
    Inscrit en
    Mai 2002
    Messages
    190
    Détails du profil
    Informations forums :
    Inscription : Mai 2002
    Messages : 190
    Points : 83
    Points
    83
    Par défaut
    La mise à jour de ma GUI était directement dans mon thread de lecture du pipe coté HMI (à travers un Synchronize tout de même)

    J'ai changé ça. Maintenant, le pipe de lecture dépile le pipe puis empile dans une TStringList locale à la HMI dont l'accès est encadré par une section critique. Un gestionnaire d'évènement "OnIdle" de l'application dépile cette même TStringList et fait les affichages dans la GUI.

    Cette solution est beaucoup plus stable et ne fige presque plus.

    Presque, hélas.

    Après de nombreux essais semblant réussis, l'application a fini par figer.

    Je me demande si ça ne serai pas lié à une éventuelle saturation du pipe qui ne serai pas dépilé assez vite ? En effet, la nouvelle méthode perd moins de temps à afficher dans la GUI. Elle se contente de stocker l'information pour gestion ultérieur lorsque l'application aura du temps à ne rien faire. Ce stockage d'information sans traitement est très rapide et permet de dépiler le pipe plus rapidement. Mais au bout d'un moment, le pipe pourrait finir par saturer quand même ! Je croyais que c'était sans grave conséquence autre qu'un ralentissement mais peut-être que je me trompe ?
    Pour mes développements, j'utilise :
    WinX-64bits, Delphi Tokyo 10.2.2
    Merci, merci, merci... moi aussi je vous aime, c'est trop d'émotions...
    Key user des blagues nulles

  3. #3
    Membre émérite
    Avatar de ALWEBER
    Homme Profil pro
    Expert Delphi
    Inscrit en
    Mars 2006
    Messages
    1 496
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 69
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Expert Delphi

    Informations forums :
    Inscription : Mars 2006
    Messages : 1 496
    Points : 2 762
    Points
    2 762
    Billets dans le blog
    10
    Par défaut
    Si tu peux nous faire une maquette on peut peut-être t'aider. est ce que tu utilise les TThreadList ?

  4. #4
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 445
    Points
    28 445
    Par défaut
    j'ai déjà établi des dialogues entre un service Windows et une application GUI via des Pipes, et ça ne fige pas mais je ne trouve pas ça super stable.

    d'un autre côté j'ai mis en place un dialogue entre deux applications via WM_COPYDATA et là ça dépote je fais passer du JSON d'un côté à l'autre et j'aime beaucoup ce mode de fonctionnement.
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  5. #5
    Membre régulier
    Inscrit en
    Mai 2002
    Messages
    190
    Détails du profil
    Informations forums :
    Inscription : Mai 2002
    Messages : 190
    Points : 83
    Points
    83
    Par défaut
    Hélas, une maquette serait longue à mettre en place. Je vais voir comment je peux faire ça.

    A propos de WM_COPYDATA et JSON, pourquoi pas, mais ne s'agit-il pas d'une solution synchrone ?
    Pour mes développements, j'utilise :
    WinX-64bits, Delphi Tokyo 10.2.2
    Merci, merci, merci... moi aussi je vous aime, c'est trop d'émotions...
    Key user des blagues nulles

  6. #6
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 445
    Points
    28 445
    Par défaut
    Citation Envoyé par jbat Voir le message
    Hélas, une maquette serait longue à mettre en place. Je vais voir comment je peux faire ça.

    A propos de WM_COPYDATA et JSON, pourquoi pas, mais ne s'agit-il pas d'une solution synchrone ?
    oui et non, vu que la seule réponse que tu auras est la réponse à SendMessage(), soit un Integer.

    ce que je fais généralement c'est retourner un code d'acceptation ou de refus de la requête - ce qui permet notamment de détecter un HWnd devenu invalide (application fermée) - puis une réponse asynchrone via un WM_COPYDATA vers le HWnd d'origine.

    il y a fort longtemps j'avais fait une série de composants qui permettaient d'établir un lien entre deux applications, soit via un socket asynchrone (WSASelect) soit via un WM_COPYDATA, ça revenait exactement au même, j'avais un événement OnRead, sur un socket ça pouvait donner un message partiel, avec WM_COPYDATA j'avais forcément le message complet, mais c'était la seule différence
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  7. #7
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 694
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 694
    Points : 13 130
    Points
    13 130
    Par défaut
    Ça paraît tout de même surprenant que le blocage soit dû au pipe.

    Ça donne plutôt l'impression que le client et le serveur envoient une demande en même temps et se retrouvent ainsi dans l'impossibilité de répondre puisqu'ils attendent eux-mêmes une réponse de l'autre (pas sûr d'être clair !).
    On retrouve des problèmes plus ou moins similaires avec MSAA ; le client fait une demande le concernant et pour pouvoir lui répondre, le serveur doit faire une demande au client => deadlock !

    J'essayerais dans un premier temps de déporter HtkEvent_BtnOnClick dans un thread secondaire afin de ne pas bloquer les requêtes entrantes.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    procedure onBtnClick(Sender : TObject); 
    begin
      TThread.CreateAnonymousThread(procedure
                                    begin
                                      //Doit être adapté si usage de la VCL
                                      Hmi.m_fKernel.HtkEvent_BtnOnClick(...); 
                                    end).Start;
    end;

    ps: FillChar remplit un certain nombre d'octets. En Unicode, fillchar(l_acBufferCursorPipe, g_PipeBufferSizeCursors *SizeOf(Char), #0) ou fillchar(l_acBufferCursorPipe, SizeOf(l_acBufferCursorPipe), #0) puisque c'est un tableau statique.

Discussions similaires

  1. Réponses: 16
    Dernier message: 06/06/2007, 12h42
  2. [MFC] Objet COM et thread !?
    Par Kevgeii dans le forum MFC
    Réponses: 3
    Dernier message: 13/12/2004, 18h33
  3. Objet COM ou pas en réseau ?
    Par corwin_d_ambre dans le forum Web & réseau
    Réponses: 11
    Dernier message: 13/07/2004, 17h38
  4. [objets COM] "Catastrophic failure"
    Par Air'V dans le forum ASP
    Réponses: 5
    Dernier message: 10/09/2003, 11h45
  5. Désenregistrement d'Objets COM sous Windows
    Par barthelv dans le forum Windows
    Réponses: 2
    Dernier message: 21/05/2003, 15h11

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo