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

C++/CLI Discussion :

Fonction "atomique" en C++ ?


Sujet :

C++/CLI

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre chevronné Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    1 039
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 1 039
    Par défaut Fonction "atomique" en C++ ?
    Bonjour,

    Je suis confronté à un bug étrange

    J'ai une application graphique, qui dessine des rectangles de selection sur une image avec la technique bien connue NOT XOR PEN

    Le rectangle n'est pas assez visible.
    J'ai donc fait une petite modification pour en dessiner un plus épais :

    Le code original :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    					if (selektion_vorhanden>1) {
    						pDC->SetROP2(R2_NOTXORPEN);
    						pDC->Rectangle(&m_rect);
    					}
    					selektion_vorhanden = 1;
    					m_rect.top = y;
    					m_rect.left = x;
    Le code modifié :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    					if (selektion_vorhanden>1) {
    						pDC->SetROP2(R2_NOTXORPEN);
    						trace_rect_rect2(pDC); //pDC->Rectangle(&m_rect);
    					}
    					selektion_vorhanden = 1;
    					m_rect.top = y;
    					m_rect.left = x;
    J'ai remplacé partout les appels de

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    pDC->Rectangle(&m_rect);
    par

    qui est une fonction que j'ai créé dont voici le code :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void trace_rect_rect2(CDC* pDC) {
    	m_rect2.CopyRect(m_rect);
    	m_rect2.InflateRect(1, 1);
    	pDC->Rectangle(&m_rect);
    	pDC->Rectangle(&m_rect2);
    }
    Cette fonction est appelée par différentes procédures qui gèrent les évenements liés à la souris et à l'affichage :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void CShiftNView::OnDraw(CDC* pDC)
    void CShiftNView::OnLButtonDown(UINT nFlags, CPoint point) 
    void CShiftNView::OnMouseMove(UINT nFlags, CPoint point) 
    void CShiftNView::OnRButtonDown(UINT nFlags, CPoint point) 
    void CShiftNView::OnLButtonUp(UINT nFlags, CPoint point)
    void CShiftNView::OnRButtonUp(UINT nFlags, CPoint point)
    Le bug est le suivant : quand je manipule la souris un peu vite, les rectangles précédents se sont pas bien effacés :

    Nom : 2026_07_14_bug.jpg
Affichages : 146
Taille : 1,19 Mo

    Le bug ne se produit pas si je trace pas m_rect tout seul :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void trace_rect_rect2(CDC* pDC) {
    	m_rect2.CopyRect(m_rect);
    	m_rect2.InflateRect(1, 1);
    	pDC->Rectangle(&m_rect); // affichage correct sans bug
    	//pDC->Rectangle(&m_rect2);
    }
    mais le bug se produit aussi si je trace m_rect2 tout seul :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void trace_rect_rect2(CDC* pDC) {
    	m_rect2.CopyRect(m_rect);
    	m_rect2.InflateRect(1, 1);
    	//pDC->Rectangle(&m_rect);
    	pDC->Rectangle(&m_rect2); // le bug se produit
    }
    C'est comme si m_rect était modifié par le reste du code pendant que la fonction trace_rect_rect2(CDC* pDC) était executée

    Le bug est aussi présent si je déclare la fonction inline, même en ne traçant que m_rect2 :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    inline void trace_rect_rect2(CDC* pDC) {
    	m_rect2.CopyRect(m_rect);
    	m_rect2.InflateRect(1, 1);
    	//pDC->Rectangle(&m_rect);
    	pDC->Rectangle(&m_rect2);
    }
    Le bug se produit aussi avec cette variante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    inline void trace_rect_rect2(CDC* pDC) {
    	pDC->Rectangle(&m_rect);
    	pDC->Rectangle(m_rect.left-1, m_rect.top-1, m_rect.right+1, m_rect.bottom+1);
    }
    Comme mon code ne fait qu'agrandir le rectangle, le problème n'est pas lié à un rectangle de taille nulle ou négative

    A bientôt
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  2. #2
    Membre chevronné Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    1 039
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 1 039
    Par défaut
    J'ai résolut le problème de cette manière :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    inline void trace_rect_rect2(CDC* pDC, CRect rect) {
    	CPen NewPen(PS_SOLID, 2, RGB(0, 0, 0));
    	pDC->SelectObject(&NewPen);
    	pDC->Rectangle(&rect);
    	//pDC->Rectangle(rect.left-1, rect.top-1, rect.right+1, rect.bottom+1);
    }
    Ca m'embête un peu de créer un nouveau CPen à chaque appel,

    mais il n'y a aucun impact perceptible sur la rapidité,

    ni sur le reste du programme (c'est un projet open source que j'ai repris)

    Cette manière de faire permet seulement de dessiner un trait plus épais ; j'aurais voulu dessiner un rectangle épais mais avec deux couleurs, ce n'est pas possible
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  3. #3
    Expert confirmé
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    4 962
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 4 962
    Par défaut
    Hello,

    Je me permets de rebondir sur votre analyse qui est tout à fait pertinente. Votre diagnostic concernant le manque d'atomicité entre l'événement MouseMove et votre fonction de dessin est parfaitement exact.

    1. Pourquoi votre problème initial se produisait-il ?
    Le phénomène des "lignes fantômes" (ou du déséquilibre lors du rendu XOR) survient car la variable membre de votre classe (m_rect) est lue plusieurs fois au sein de la fonction trace_rect_rect2. Si le système d'événements de Windows traite un nouveau MouseMove entre les différentes instructions de votre fonction de dessin, la valeur de m_rect change "en vol". Votre fonction dessine alors un rectangle basé sur d'anciennes coordonnées et un autre basé sur de nouvelles, ce qui casse l'effet d'effacement.

    2. Pourquoi votre solution fonctionne-t-elle visuellement ?
    Votre correctif, qui consiste à passer l'objet CRect par valeur (inline void trace_rect_rect2(CDC* pDC, CRect rect)), est une excellente approche pour régler le problème d'affichage. En passant le paramètre par valeur, le compilateur effectue une copie immédiate des coordonnées au moment de l'appel. La fonction de dessin travaille alors sur cet "instantané" (snapshot) local, qui est immunisé contre les modifications extérieures générées par la souris pendant l'exécution de la fonction. Le dessin reste donc cohérent du début à la fin.

    3. Le piège caché : Ce que disent les outils d'analyse
    Pour aller plus loin, j'ai reproduit la mécanique de votre code dans un environnement de test isolé et je l'ai passé au crible avec l'outil d'analyse Valgrind (spécifiquement l'outil Helgrind, qui détecte les problèmes de concurrence).

    Bien que votre solution règle l'incohérence visuelle, l'outil lève toujours des erreurs de type Data Race (conflit de données). En effet, bien que le passage par valeur crée une copie sécurisée, l'opération de copie elle-même n'est pas atomique. Si le thread gérant la file de messages modifie m_rect au moment exact où la copie est effectuée pour l'appel de fonction, vous risquez théoriquement d'obtenir un rectangle avec des coordonnées "hybrides" (par exemple, avec le left de l'ancienne position et le right de la nouvelle position).

    4. La solution robuste en C++ Moderne
    Pour garantir une véritable atomicité et vous prémunir contre tout comportement indéfini (Undefined Behavior), la bonne pratique en C++ consiste à utiliser un verrou (std::mutex) lors de la lecture et de l'écriture de cette ressource partagée.

    Voici comment sécuriser totalement votre mécanique vis-à-vis du processeur :

    Code cpp : 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
     
    #include <mutex>
     
    // Dans la déclaration de votre classe
    std::mutex m_rect_mutex;
    CRect m_rect;
     
    // ---------------------------------------------------
    // Lors de la mise à jour (ex: événement MouseMove) :
    {
        std::lock_guard<std::mutex> lock(m_rect_mutex);
        m_rect.left = ...;
        // ...
    }
     
    // ---------------------------------------------------
    // Avant d'appeler la fonction de dessin :
    CRect snapshot;
    {
        // On verrouille juste le temps de faire la copie
        std::lock_guard<std::mutex> lock(m_rect_mutex);
        snapshot = m_rect; 
    }
    // On appelle la fonction avec la copie propre
    trace_rect_rect2(pDC, snapshot);

    En procédant ainsi, vous conservez les bénéfices de votre passage par valeur tout en garantissant au compilateur qu'aucune donnée ne sera corrompue au moment de l'initialisation du paramètre.

    En espérant que ces détails techniques vous seront utiles pour la suite de votre projet. Bonne continuation !
    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

  4. #4
    Membre chevronné Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    1 039
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 1 039
    Par défaut
    Merci d'avoir pris le temps de me répondre

    Alors je ne passe pas par valeur

    Voici la fonction dans la version finale :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    CPen pen_rect_epais(PS_SOLID, 3, RGB(0, 0, 0));
    ...
    inline void trace_rect_epais(CDC* pDC, CRect rect) {
    	pDC->SelectObject(&pen_rect_epais);
    	pDC->Rectangle(&rect);
    }

    Et voici le code qui l'appelle

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    trace_rect_epais(pDC, &m_rect); //pDC->Rectangle(&m_rect);
    Je passe par référence et non par valeur (avec le '&') - et ça fonctionne

    Mais le code qui plantait utilisait non pas UNE mais DEUX fonctions graphiques successives

    Donc c'est plus lent et ça laisserait le temps à un autre appel de fonction MouseMove de modifier m_rect entre les deux

    D'après vous, la solution serait donc de passer m_rect par valeur et non pas par référence

    J'ai essayé le code suivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    inline void trace_rect_epais(CDC* pDC, CRect rect) {
    	pDC->Rectangle(&rect);
    	pDC->Rectangle(m_rect.left - 1, m_rect.top - 1, m_rect.right + 1, m_rect.bottom + 1);
    }
    Avec des appels de fonction comme ceci

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    trace_rect_epais(pDC, m_rect); //pDC->Rectangle(&m_rect);
    Mais le bug est toujours là - c'est bizarre

    En revanche, ce code-là fonctionne, peut importe que m_rect() soit passé par valeur ou par référence :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    CPen pen_rect_epais(PS_SOLID, 3, RGB(0, 0, 0));
    ...
    inline void trace_rect_epais(CDC* pDC, CRect rect) {
    	pDC->SelectObject(&pen_rect_epais);
    	pDC->Rectangle(&rect);
    }
    Je pourrais optimiser le code en évitant d'appeler à chaque fois SelectObject, mais cela impose des modifications à de nombreux endroits du code pour ajouter les SelectObject aux bons endroits.


    Concernant la "vraie" solution, c'est à dire le mutex, comme vous dites c'est du C++ "moderne"

    Et justement, le programme sur lequel je travaille est un vieux projet open source C++ WIN32 obsolète que j'ai repris, je ne sais pas si le mutex sera accepté,
    et si ça marche j'ai peur de créer d'autres bugs ailleurs que je ne verais pas tout de suite

    Cela me rappelle de mauvais souvenir avec Visual Basic.
    En Visual Basic on arrivait à gérer ces problèmes de façon plus ou moins sale avec DoEvents, et aussi en verrouillant "manuellement" les fonctions avec une variable globale "autorisation_paint" gérée de la manière suivante :
    - au debut de chaque fonction, un test pour voir si autorisation_paint est VRAI, sinon on quitte la fonction
    - juste après, une instruction met autorisation_paint à FALSE
    - à la fin de la fonction, la dernière instruction remet autorisation_paint à TRUE
    Ce n'était pas facile, selon les possibilités d'édition à la souris du programme, il fallait plusieurs variables "autorisation_xxx" et la fonction MouseMove n'était pas traité de la même manière que MouseDown et MouseUp, car "rater" un MouseMove n'était pas grave (et même souhaitable) en revanche il ne fallait pas rater un appel de MouseDown ou MouseUp.

    Ces variables "autorisation_xxx" permettaient aussi d'éviter les ralentissements du à une succession d'appel de fonction d'affichages quand une action unique de l'utilisateur sur l'interface engendre plusieurs évenements (typiquement, le redimenssionnement de la fenêtre qui ajuste le mini, le maxi et la valeur des barres de défilement)

    Je n'ai jamais compris pourquoi WINDOWS et/ou les langages IDE Visual Basic / Visual C++ :
    - n'empêchaient pas de nouveaux appels de MouveMove tant que les précédents ne sont pas terminés
    - ne mettaient pas en attente l'appel de MouseDown ou MouseUp tant qu'un appel précédent de MouseMove n'était pas terminé

    A cause de la manière dont le système d'exploitation gère les évenements de l'interface graphique, on se retrouve à devoir gérer des problèmes de Thread dans un programme qui ne les utilise pas à l'origine
    Ces bugs sont très emmerdants car ils n'apparaissent pas tout de suite.
    Quand le programme est simple (donc rapide) et que l'utilisateur est lent tout fonctionne correctement.1
    Mais quand le programme s'enrichi en fonctionnalités et que le code dans les fonctions devient plus gros et plus lent, et qu'en même temps l'utilisateur maitrise le logiciel et va plus vite, les soucis apparaissent...


    Au risque de choquer les gens, la programmation "à l'ancienne" sous MS-DOS (ou sur un microcontrôleur), où il fallait gérer manuellement une souris, une manette de jeux ou un clavier avec une boucle, était presque plus simple.
    Une interruption mettait à jours les coordonnées souris ou actions de l'utilisateur. On pouvait même faire une pile pour enregistrer plusieurs frappes rapides au clavier successives.
    La boucle principale du programme scrutait si les valeurs avaient changés, et si oui, les traitait.
    Pas de problème de recouvrement.


    La solution que j'ai trouvée, qui dessine un seul rectangle épais via un CPen fonctionne, je vais m'en contenter.
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  5. #5
    Membre chevronné Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    1 039
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 1 039
    Par défaut
    Bonjour,

    Voici une remarque :

    Avec la technique NOT XOR PEN, si on dessine avec une couleur noire, il sera tracé avec un négatif de l'image originale

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CPen pen_rect_epais(PS_SOLID, 3, RGB(0, 0, 0));
    La visibilité est maximale si les composantes RGB de l'image originale sont proches de 0 ou 255 (ce qui est le cas des images correspondant à des plans, des schémas ou du texte)

    Mais si c'est proche du gris (128), la visibilité est très faible voire nulle. En effet, le négatif du gris... c'est le gris

    Or les photos contiennent beaucoup de tons intermédiaires, c'est à dire assez proche du gris, notamment avant qu'on les retravaille pour améliorer le contraste et la dynamique.

    En utilisant comme couleur RGB(127, 127, 127), le dessin sera tracé en inversant uniquement le bit de poids fort de chaque composante RGB de l'image originale.

    Autrement dit, chaque pixel sera tracé avec un "décalage" positif ou négatif d'une amplitude constante de 128.

    La visibilité est donc constante, quelque soit les couleurs de l'image originale :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CPen pen_rect_epais(PS_SOLID, 3, RGB(127, 127, 127));
    A bientôt
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  6. #6
    Invité de passage
    Homme Profil pro
         ​​​  
    Inscrit en
    Décembre 2025
    Messages
    81
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Autre

    Informations professionnelles :
    Activité :      ​​​  

    Informations forums :
    Inscription : Décembre 2025
    Messages : 81
    Par défaut
    Bonsoi,

    J'ai testé ton algo basé sur le dessin deux rectangles, je n'ai pas C++ mais en Delphi, l'algo fonctionne sans problème, pour eviter les rebonds de la souris et les événements aléatoires j'ai placé les trois événements Mouse Down Move Up dans une seule fonction avec boucle..
    Ma remarque perso et que je n'ai pas différence notable entre cette méthode et celle d'utilisation d un Pen plus large, j'ai tenté deux couleurs et la valeur préconisée RGB(127,127,127) mais la différence est marginale:




    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
    var
      FMovePt: TPoint;
      FDown: boolean;
     
    procedure TForm1.DrawRect2(X1, Y1, X2, Y2: integer);
    var
     Rc: TRect;
    begin
       with image1 do
       begin
          if (abs(X1 - X2) < 3) or (abs(Y1 - Y2) <3) then
             Exit;
          Rc := Rect(X1, Y1, X2, Y2);
          Canvas.Rectangle(Rc);
          Rc.Inflate(1,1);
          Canvas.Rectangle(Rc);
       end;
    end;
     
    procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    var
       PrvPt, Dwn: TPoint;
    begin
       if FDown then exit;
       FDown:= true;
       Dwn := Point(X,Y);
       FMovePt  := Dwn;
       PrvPt := Dwn;
       with image1 do
       begin
           Canvas.Brush.Style := bsClear;
           Canvas.Pen.Width := 1;
           Canvas.Pen.Color := rgb(127,127,127);
           Canvas.Pen.Mode  := pmNotXor;
           while GetAsyncKeyState(VK_LBUTTON) < 0 do
           begin
              if FMovePt <> PrvPt then
              begin
                 DrawRect2(Dwn.X, Dwn.Y, PrvPt.x, PrvPt.y);
                 DrawRect2(Dwn.X, Dwn.Y, FMovePt.x, FMovePt.y);
                 PrvPt := FMovePt;
              end;
              Application.HandleMessage;
           end;
           DrawRect2(Dwn.X, Dwn.Y, PrvPt.x, PrvPt.y);
       end;
       FDown:= False;
     
    end;
     
    procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    begin
      FMovePt := Point(X,Y);
    end;

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Evaluation de la fonction quote
    Par Toitoine dans le forum Lisp
    Réponses: 2
    Dernier message: 05/05/2007, 19h13
  2. [Fonction] Quote et guillemet dans un textarea
    Par ddelec24 dans le forum PHP & Base de données
    Réponses: 2
    Dernier message: 11/03/2007, 15h51
  3. Fonction Quoted printable qui ne fonctionne pas.
    Par leCcsympas dans le forum C
    Réponses: 3
    Dernier message: 13/01/2007, 18h54
  4. Inverse de la fonction QUOTE() ?
    Par __fabrice dans le forum SQL Procédural
    Réponses: 2
    Dernier message: 13/07/2006, 10h39

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