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

Composants VCL Delphi Discussion :

Utilisation d'un TStringGrid dans une DLL


Sujet :

Composants VCL Delphi

  1. #1
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 287
    Points : 164
    Points
    164
    Billets dans le blog
    1
    Par défaut Utilisation d'un TStringGrid dans une DLL
    J'utilise Delphi 6 Personal Edition pour créer des DLLs avec des fonctions destinées à étendre les capacités d'un langage de programmation différent. A ce jour, j'ai réalisé plusieurs centaines de fonctions dans des domaines très divers. En ce moment, j'essaie de donner plus de souplesse au langage en question,, pour gérer des TStringGrid.

    Il s'agit d'un TStringGrid créé dans le programme principal. Cet objet est ensuite passé en paramètre à des fonctions de la DLL pour effectuer certaines opérations qui ne sont pas actuellement accessibles dans ce langage. Beaucoup de choses marchent bien: passer en mode "saisie directe" dans une cellule, choisir des couleurs personnalisées pour des lignes, des colonnes et/ou des cellules, retourner les coordonnées du rectangle de cellules sélectionnées, retourner l'ensemble du contenu des cellules d'un rectangle de sélection, sous forme d'un string formaté, etc.

    Mais je me heurte à un problème lorsque je veux modifier le contenu des cellules d'un rectangle de cellules donné. Cela marche bien au premier appel, quelque fois même au deuxième. Mais au plus tard au troisième appel de la fonction, j'ai une violation de mémoire, au moment où j'affecte une chaîne de caractères à une cellule du TStringGrid. Quelque soient ses coordonnées, son ancien ou nouveau contenu, etc.

    J'ai fait un fichier ZIP contenant deux projets Delphi 6 Personal Edition:
    KGF_GRID.dpr qui est le projet pour KGF_GRID.dll
    test_KGF_GRID.dpr qui est le projet pour le programme de test qui utilise la DLL

    KGF_GRID.dll contient une seule fonction: celle qui pose problème. Tout est concentré sur le problème, et test_KGF_GRIG ne contient que le strict nécessaire pour exécuter la démo: un objet TStringGrid, un bouton pour lancer le test, et une checkbox pour afficher ou non des messages de traçage.

    Il suffit de cliquer deux ou 3 fois sur le bouton pour provoquer le crash. Il a lieu dans la ligne suivante:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
            TStringGrid(grid).Cells[x,y] := sa[i];   // remplacer les des données dans le TStringGrid
    alors qu'au premier passage, ça passe bien et le résultat affiché est correct. Tout se passe comme si le fait de modifier les données changeait des pointeurs qui ne sont pas "repris" par l'objet dans le programme principal.

    Qu'est-ce que j'ai raté ? Car j'ai forcément négligé un détail technique significatif.
    Fichiers attachés Fichiers attachés

  2. #2
    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 430
    Points
    28 430
    Par défaut
    alors il n'est tout simplement pas possible de manipuler un objet delphi créé par un EXE dans un DLL ou vis et versa...ni des String d'ailleurs si ce n'est en utilisant l'unité ShareMem.

    la seule solution pour faire cela sous Delphi c'est d'utiliser un Package; DLL spécifique qui permet de partager des objets...elles sont tellement spécifiques qu'il faut que l'EXE et la DLL soient compilées avec la même version du compilateur.

    sinon il faut passer par une Interface
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    library exemple;
     
    type
      IStringGrid = Interface
        procedure SetCell(x, y: Integer; Value: PChar);
      end;
     
    function WriteGridCells(grid: IStringGrid; x1,y1,x2,y2: integer; xs: PChar; dbg: integer):integer; stdcall; 
    begin
      ...
    end;
    et au niveau de l'EXE il faut proposer une classe proxy si tu ne veux pas dériver TStringGrid, par exemple :

    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
     
    type
      TStringGridProxy = class(TInterfacedObject, IStringGrid)
        Grid: TStringGrid;
        procedure SetCell(x, y: Integer; Value: PChar);
      end;
     
    procedure TStringGridProxy.SetCell(x, y: Integer; Value: PChar);
    begin
      Grid.Cells[x, y] := Value;
    end;
     
    var
      Proxy: TStringGridProxy;
    begin
      Proxy := TStringGridProxy.Create;
      Proxy.Grid := StringGrid1;
      WriteGridCells(Proxy, 1, 1, 3, 2, PChar(celllules), dbg);
     
    end;
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  3. #3
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 287
    Points : 164
    Points
    164
    Billets dans le blog
    1
    Par défaut
    Aïe... ça fait mal ! J'espérais un moyen de contourner ce problème. En cherchant sur le net, j'avais bien vu ce problème de compatibilité entre les VCLs de la DLL et du programme principal. Malheureusement, je n'ai pas accès aux sources du programme principal, et donc, je ne peux pas utiliser, ni les packages, ni ShareMem, ni même ta solution avec les interfaces. Dommage.

    Mais en cherchant plus loin, je viens de tomber sur un truc qui s'appelle HookSG.dll. C'est une DLL écrite en Delphi et dont je viens enfin de récupérer les sources. Elle permet d'injecter un thread dans le programme principal, et d'accéder le TStringGrid dans le contexe. Je l'ai faite marcher, et le programme de démo fonctionne. Le hic, ou plutôt les deux hics: il n'y a que la partie "lecture" d'une cellule, et il faut donc que j'implémente la partie "écriture". Et d'autre part, tous les textes des messages sont en chinois ou japonais, et il faut que je trouve la traduction correspondante.

    Tout cela est relativement simple, et lorsque tout fonctionnera, je reviendrai publier ici le résultat. En attendant, un grand MERCI d'avoir bien voulu jeter un regard sur mes codes, car ta réponse m'a permis de chercher de façon plus ciblée, ce qui à conduit à la découverte de HookSG.dll.

  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 430
    Points
    28 430
    Par défaut
    Je ne comprends pas, dans ton exemple l'exe fait appel à la DLL...à moins qu'elle aussi soit injectée ?

    Ceci dit il reste possible d'invoquer les fonctions virtuelles, une interface ce n'est qu'une liste de fonctions virtuelles après tout. Donc avec une bonne connaissance de la structure de l'objet (facile si tu as la même version du compilateur) et un peu de chance (il faut que les fonctions que tu veux invoquer soient virtuelles) tu peux manipuler l'objet depuis la DLL
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  5. #5
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 287
    Points : 164
    Points
    164
    Billets dans le blog
    1
    Par défaut
    Alors, voici le contexte précis.

    Je développe und DLL en Delphi 6 Personal édition. Cette DLL contient une collection de fonctions, très diverses, qui sont toutes destinées à être utilisées par un exécutable qui est issu d'un clone de Basic. Ce langage est interprété, mais peut construire des EXE. Cependant, iln ne s'agit pas d'une compilation suivi d'un link classique. En fait, le programme fait une copie du moteur d'interprétation, et copie le source à interpréter, en tant que données, à la fin de l'exécutable. Donc, on obtient en fait toujours le même EXE, avec des données intégrées différentes.

    Ce programme, actuellement réalisé en Delphi (mais je ne connais pas la version exacte), sera dans un avenir plus ou moins proche remplacé par un interpréteur réalisé autour de FreeBasic. Mais le code interprété (et donc nos "programmes") reste identique. Dans ce langage style Basic, il y a une fonction qui permet d'appeler une fonction DLL, en lui passant des paramètres uniquement du type integer, "by value". Et la valeur retournée est obligatoirement un integer. J'indique ceci pour montrer que l'interface entre ce langage interprété et ma DLL est extrêmement rigide, et il faut déployer des trésors d'imagination pour arriver à ses fins.

    La seule chose dont je sois sûr, c'est que les fonctions de ma DLL sont appelées dans le même thread que le thread principal (et unique) du programme principal (de l'interpréteur).

    Revenons-en à mon problème:
    Je ne comprends pas, dans ton exemple l'exe fait appel à la DLL...à moins qu'elle aussi soit injectée ?
    Dans mon exemple, j'ai fait KGF_GRID.dll qui est un micro-extrait de ma véritable DLL, juste pour mettre le problème en évidence. Cette DLL n'est pas "injectée", ni pour KGF_GRID.dll ni pour la vraie DLL en production. Elle est uniquement chargée dynamiquement, par LoadLibrary. Et le programme test_KGF_GRID est une sorte de "simulation" de ce que fait l'interpréteur réellement lorsqu'il appelle ma DLL: il crée l'objet TStringGrid, il charge la DLL par LoadLibrary, trouve l'adresse de la fonction par son nom et appelle la fonction.
    Ceci dit il reste possible d'invoquer les fonctions virtuelles, une interface ce n'est qu'une liste de fonctions virtuelles après tout.
    J'avoue que je ne me suis jamais occupé des notions de "fonctions virtuelles" et des "interfaces". Je ne sais donc pas vraiment de quoi on parle, dans ce contexte.
    facile si tu as la même version du compilateur
    Justement, je ne le sais pas, et de toutes façons, ça ne durera pas - voir ci-dessus. Ceci dit, en absence d'informations et d'échéancier plus précis, je m'en tiens uniquement à la version Delphi. Pour la suite, de toutes façons, il faudra certainement revoir beaucoup de choses.

    J'ai vu que de façon interne, Delphi gère les cellules d'un TStringGrid dans une TList, selon on mécanisme interne et non accessible à l'extérieur. Et je ne souhaite pas "trafiquer" l'objet. D'ailleurs, je ne pense pas que ce soit nécessaire. Dans ma DLL, je peux lire librement les cellules du TStringGrid, je peux accéder au Canvas pour changer la couleur de font dans un évènement 0nDrawCell pour lequel je peux passer l'adresse de ma propre routine au TStringGrid, et je peux même remplacer la WndProc du TStringGrid pour intercepter certains messages de Windows. HookSG.dll que j'expérimente en ce moment, utilise cette astuce pour définir des messages utilisateur afin de déclencher des actions spécifiques. J'arrive à écrire dans plusieurs cellules du TSTringGrid - les données passent et l'affichent à l'écran. Mais, par la suite, lors d'un évènemenr Repaint, il y a une violation de mémoire, et d'après l'état affiché à l'écran, ça se passe lors de l'affichage de la première cellule modifiée. Non pas chronologiquement, mais dans l'ordre des indices. Quelque part, l'affectation d'une donnée à la propriété Cells[X,Y] di TStringGrid affiche initialement ce qu'il faut à l'écran, mais perturbe la gestion des pointers (strings ? listes ?) du TStringList.

    Désolé d'avoir été si long, mais je pense qu'il était utile que je plante le décor.

  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 430
    Points
    28 430
    Par défaut
    ok mais il y a encore quelque chose qui m'échappe...l'interpréteur actuellement développé sous Delphi utilise l'objet TStringGrid que tu peux passer en paramètre à ta DLL si je comprend bien...mais si demain l'appli utilise FreeBasic, il n'y aura plus de StringGrid...en tout cas plus celle de Delphi non ?

    ne peux-tu pas simplement demander à la DLL de créer la StringGrid ?

    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
     
    function CreateStringGrid(Parent: HWnd): TStringGrid; stdcall; 
    begin
      Result := TStringGrid.CreateParented(Parent);
    end;
     
    function DestroyStringGrid(Grid: TStringGrid); stdcall; 
    begin
      Grid.Free;
    end;
     
    procedure WriteStringGrid(Grid: TStringGrid; x, y: Integer; Str: PChar); stcall; 
    begin
      Grid.Cells[x, y] := Str;
    end;
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  7. #7
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 287
    Points : 164
    Points
    164
    Billets dans le blog
    1
    Par défaut
    mais si demain l'appli utilise FreeBasic, il n'y aura plus de StringGrid
    Exact. Un objet de ce type existera certainement, mais ce ne sera pas le même. De toutes façons, même si cette nouvelle version sort, l'actuelle coexistera encore pendant des années. Et j'avais bien dit:
    Pour la suite, de toutes façons, il faudra certainement revoir beaucoup de choses.
    Donc, pour le moment, je me concentre uniquement sur cette version Delphi/Delphi.

    ne peux-tu pas simplement demander à la DLL de créer la StringGrid ?
    Oui, je peux. Et pour toute une série de composants, je le fais -je les crée, le les injecte dans une form, un panel ou autre créé par le programme principal et je les gère par des fonctions DLL. Mais le hic, c'est que ces objets sont alors inconnus en réalité du programme principal (l'interpréteur), et je dois tout gérer dans la DLL, y compris les évènements. Et je ne peux plus du tout utiliser la gestion des évènements par le langage de l'interpréteur. Donc, je ne fais ceci que pour des objets actuellement non disponibles dans cet interpréteur, et je retire ces fonctions dès que cet objet est rendu accessible dans l'interpréteur, par l'auteur de celui-ci. Ce sera le cas pour l'objet TPageControl que j'ai rendu accessible par la DLL et qui prochainement sera dans une nouvelle version de l'interpréteur.

    Entretemps, j'ai avancé dans l'étude du problème, à l'aide de HookSG.dll. Cette DLL fonctionne parfaitement, mais ne donne que la possibilité de lecture de cellules. Elle a néanmoins le mécanisme pour "hooker" un TStringGrid et de gérer des "user messages" pour faire des opérations spécifiques. J'ai donc défini deux messages de mon cru:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    const
      CM_SETCELL  = WM_USER + 1004;
      CM_SETDATA  = WM_USER + 1005;
    et je les intercepte dans la WndProc de substitution pour le TSTringGrid. Ces messages sont envoyés par l'API SendMessage sur le handle du TSTringGrid.

    Le message CM_SETDATA transmet un caractère an valeur ASCII dans WPARAM, et un offset dans un buffeur dans LPARAM. La DLL a de façon interne une section de mémoire partagée qui est définie ainsi:
    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
    unit UShare;
     
    interface
     
    uses Windows, Messages;
     
    const
      cMapFileName = 'HookSG_SharedData';
     
    const
      CM_MSGWNDCREATED = WM_USER + 1000;
      CM_QUERYROW = WM_USER + 1001;
      CM_QUERYCOL = WM_USER + 1002;
      CM_HOOKCELL = WM_USER + 1003;
      CM_SETCELL  = WM_USER + 1004;
      CM_SETDATA  = WM_USER + 1005;
     
    type
      PShareData = ^TShareData;
      TShareData = record
      hkMsg: HHook;
      HostPID: DWORD;
      HostWnd: HWND;
      DestWnd: HWND;
      MsgWnd: HWND;
      Text: array[0..1024] of Char;
    end;
     
    var
      hMapFile: THandle;
      P: PShareData;
     
    implementation
     
    end.
    Et l'offset dans LPARAM pointe dans P^.Test[offset]. Je dépose ainsi le caractère contenu dans WPARAM à cet endroit, et je dépose un #0 dans l'octet suivant.
    Je transmets ainsi le texte à écrire dans une cellule, par une boucle sur tous les caractères du texte:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
      if Length(s)=0 then begin
        Len := SendMessage(MsgWnd, CM_SETDATA, 0, 0);   // signaler "vide" par SendMessage en LParam et WParam
      end else begin
        for i:=1 to length(s) do begin
          Len := SendMessage(MsgWnd, CM_SETDATA, ord(s[i]), i-1);   // passer le string par SendMessage en LParam et WParam
        end;
      end;
      Len := SendMessage(MsgWnd, CM_SETCELL, C, R);   // passer Column et Row par SendMessage en LParam et WParam
    et à la fin, j'envoie le message CM_SETCELL pour mettre les nouvelles données en place.Ca marche bien: les données sont bien transmises, et le buffeur P^.Text contient bien la bonne information. Donc, pour CM_SETDATA, pas de problème.

    Voici la séquence de WndProc concernée:
    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
        CM_SETCELL: begin
                       Result := -1;
                       if P^.DestWnd <> 0 then begin
                         SG := Pointer(FindControl(P^.DestWnd));
                         if SG <> nil then begin
                           X := WParam;
                           Y := LParam;
                           if (X >= 0) and (X < SG.ColCount) and (Y >= 0) and (Y < SG.RowCount) then begin
                             Result := Length(P^.Text);
                             if Result > 0 then begin
                               CellBuff := StrPas(P^.Text);
    showmessage('CM_SETCELL: ['+CellBuff+']');                   // <== prouve que les données sont bien transmises !
                               SG.Cells[X, Y] := '123456';                                 // <== ok
    //                           SG.Cells[X, Y] := MidStr('A'+CellBuff,2,Length(CellBuff));  // <== crash
    //                           SG.Cells[X, Y] := CellBuff;                                 // <== crash
                             end;
                           end;
                         end;
                       end;
                       Exit;
                     end;
        CM_SETDATA: begin
                       Result := -1;
                       b := Wparam;
                       i := LParam;
                       P^.Text[i] := chr(b);
                       if b<>0 then P^.Text[i+1] := chr(0);
                       Result := i;
                       Exit;
                     end;
    Le message affichant le contenu de CellBuff montre bien que les données sont correctes. Mais, c'est la ligne suivante qui pose problème. Tant que j'affectue uen constante '12345' à la cellule, tout va bien. Pas de crash. Mais dès que j'essaie de mettre le contenu de CellBuff, d'une manière ou d'une autre, la valeur s'affiche bien dans le TStringGrid, on peut même renouveler l'opération plusieurs fois, puis il y a une violation de mémoire. Je suis sûr que c'est lié à la façon dont le string est copié (ou pas, d'ailleurs...), mais je n'arrive pas à voir où est mon erreur.

    Si besoin, je peux poster un ZIP avec le code complet et les binaires.

  8. #8
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 287
    Points : 164
    Points
    164
    Billets dans le blog
    1
    Par défaut
    Voici, en pièce jointe, un fichier ZIP contenant les sources et exécutables complets, pour le test dont j'ai parle dans mon post ci-dessus. Il y a 3 projets:
    Project1.dpr = programme contenant le TSTringGrid dont je veux changer certaines cellules
    HookSG.dpr = dll qui réalise le travail
    test.dbr = programme "principal" appelant la DLL HookSG pour modifier le TStringGrid contenu dans Project1

    Manipulation à faire:
    1. lancer Project1.exe
    2. lancer test.exe et le déplacer à l'écran de sorte que la fenêtre de Project1.exe soit entièrement visible (pour le confort...)
    3. dans test.exe, cliquer sur le bouton Hook ==> le TStringGrid de Project1.exe sera "hooké"
    4. le bouton Rows/Cols affiche le nombre de lignes et colonnes du TSTringGrid
    5. les boutons Cell[X,Y], Row[Y] et Col[Y] récupèrent les données correspondantes (saisir le numéro de ligne et/ou colonne dans les deux EDIT à gauche)
    Jusque là, tout va bien.
    6. Saisir les nouvelles données dans New Data, puis cliquer sur Update[X,Y] ==> un message de traçage provenant de la DLL montre que les nouvelles données sont bien arrivées dans la WndProc du TStringGrid, mais dans la fenêtre de Project1, la cellule adressée est remplie par "123456". Normal, car:
    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
        CM_SETCELL: begin
                       Result := -1;
                       if P^.DestWnd <> 0 then begin
                         SG := Pointer(FindControl(P^.DestWnd));
                         if SG <> nil then begin
                           X := WParam;
                           Y := LParam;
                           if (X >= 0) and (X < SG.ColCount) and (Y >= 0) and (Y < SG.RowCount) then begin
                             Result := Length(P^.Text);
                             if Result > 0 then begin
                               CellBuff := StrPas(P^.Text);
    showmessage('CM_SETCELL: ['+CellBuff+']');                   // <== prouve que les données sont bien transmises !
                               SG.Cells[X, Y] := '123456';                                 // <== ok
    //                           SG.Cells[X, Y] := MidStr('A'+CellBuff,2,Length(CellBuff));  // <== crash
    //                           SG.Cells[X, Y] := CellBuff;                                 // <== crash
                             end;
                           end;
                         end;
                       end;
                       Exit;
                     end;
    On peut refaire l'opération, tout marche bien.

    Maintenant, on arrête les deux programmes, on modifie HookSG.dbr pour activer une des deux autres solutions (de préférence SG.Cells[X, Y] := CellBuff, et on reconstruit HookSG.dll. En relançant l'essai selon le même protocole, on obtient apparemment, au premier clic sur le bouton Update, le bon résultat, mais en recliquant dessus 2 ou 3 fois, on a une violation de mémoire. Et même si elle ne se produit pas immédiatement, elle va se produire lorsqu'un arrête le programme test.exe ou en cliquant sur son bouton UnHook.

    Question: comment faut-il faire pour charger le texte dans SG.Cells[X, Y], alors qu'on sait que le texte est bien présent dans la DLL, dans l'unité partagée ?
    Fichiers attachés Fichiers attachés

  9. #9
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 287
    Points : 164
    Points
    164
    Billets dans le blog
    1
    Par défaut
    J'arrive à faire tout ce que je veux, avec un TStringGrid, sauf remplacer le contenu de cellules. Je peux:
    - lire n'importe quelle cellule
    - récupérer les coordonnées du rectangle de cellules sélectionnées
    - récupérer l'ensemble des données des cellules sélectionnées
    - créer et gérer une liste d'attributs graphiques, par ligne, par colonne et/ou par cellule, avec autant d'entrées que nécessaire
    - parmi ces attributs, je peux gérer:
    - couleur de fond
    - nom de police
    - taille de police
    - couleur de police
    - attributs du texte (gras, italique, souligné, barré)
    Tout cela marche sans problème. Mais dès que je remplace le contenu d'une ou plusieurs cellules, cela semble marcher, en apparence, car les nouvelles données s'affichent bien dans le TStringGrid. Mais en renouvelant l'opération une, deux ou trois fois, il y a une violation de mémoire en REPAINT de l'objet, dont le contenu est alors affiché jusqu'à la première cellule modifiée (dans l'ordre x,y), le reste étant blanc. Les deux ZIP que j'ai postés, montrent ce problème. Le code est reduit à ce qui est nécessaire pour mettre le problème en évidence. Les fonctions réelles dans la DLL sont bien sûr beaucoup plus grandes et plus nombreuses.

  10. #10
    Modérateur
    Avatar de tourlourou
    Homme Profil pro
    Biologiste ; Progr(amateur)
    Inscrit en
    Mars 2005
    Messages
    3 858
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Biologiste ; Progr(amateur)

    Informations forums :
    Inscription : Mars 2005
    Messages : 3 858
    Points : 11 301
    Points
    11 301
    Billets dans le blog
    6
    Par défaut
    Est-il envisageable d'utiliser une fonction CallBack pour que l'EXE prenne en charge la modification de la cellule ?
    Delphi 5 Pro - Delphi 11.3 Alexandria Community Edition - CodeTyphon 6.90 sous Windows 10 ; CT 6.40 sous Ubuntu 18.04 (VM)
    . Ignorer la FAQ Delphi et les Cours et Tutoriels Delphi nuit gravement à notre code !

  11. #11
    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 430
    Points
    28 430
    Par défaut
    ton problème bien de l'allocation mémoire de la chaîne qui se fait par le gestionnaire de mémoire de ta DLL, puis elle est transmise au gestionnaire de mémoire de l'exe et il arrive un moment ou celui qui veux libérer la chaîne n'est pas celui qui l'a créé. C'est pour cela que dans ces cas il faut utiliser ShareMem.

    exemple:
    CellBuff := StrPas(P^.Text); <-- la DLL alloue la chaîne avec un compteur d'usage à 1
    SG.Cells[X, Y] := CellBuff; <-- l'EXE récupère la chaîne et passe son compteur d'usage à 2
    fin de procédure <-- la DLL passe le compteur d'usage à 1 et n'a plus aucun lien vers la chaîne.

    au second appel, l'EXE tentera de libérer la chaîne devenue inutile alors qu'il ne la possède pas...probable plantage.

    quand tu utilises un valeur constante, pas de problème car elle n'est jamais libérée.

    Un solution (tant que tu es dans les rustines) serait de conserver la chaîne dans un TStringList à l'intérieur de la DLL afin d'empêcher que son compteur d'usage tombe à zéro.
    et de temps en temps, il faut vérifier qu'il n'y a pas des chaînes dont le compteur est retombé à 1 et qu'on peux retirer de la liste (façon garbage collector).
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  12. #12
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 287
    Points : 164
    Points
    164
    Billets dans le blog
    1
    Par défaut
    @Toutlourou:
    Malheureusement pas - je n'ai aucun accès au programme principal qui est simplement fourni sous forme d'un binaire.

    @Paul Toth:
    Ca y est - j'ai enfin compris la cause (probable) du plantage concernant la transmission du string. Merci !
    Je fais faire l'essai avec une TStringList - on verra bien.

  13. #13
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 287
    Points : 164
    Points
    164
    Billets dans le blog
    1
    Par défaut
    Bon, les essais sont faits. Malheureusement, non concluants.

    A. dans le cadre de mon dernier ZIP, avec HookSG.dll. J'ai créé une stringlist dans l'unité UShare.pas:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    ...
    uses Windows, Messages, Classes;   // <========= ajout Classes ici
    ...
    var
      hMapFile: THandle;
      P: PShareData;
      MySL: TStringList;    // <============== ajout ici
     
    implementation
     
    end.
    Ensuite, danns l'unité UHook.bas:
    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
     
    ...
    uses Classes, Windows, Messages, SysUtils, Controls, Grids, StrUtils, Dialogs;   // <========== ajout  de Classes comme premier élément de la liste
    ...
        CM_SETCELL: begin
                       Result := -1;
                       if P^.DestWnd <> 0 then begin
                         SG := Pointer(FindControl(P^.DestWnd));
                         if SG <> nil then begin
                           X := WParam;
                           Y := LParam;
                           if (X >= 0) and (X < SG.ColCount) and (Y >= 0) and (Y < SG.RowCount) then begin
                             Result := Length(P^.Text);
                             if Result > 0 then begin
                               CellBuff := StrPas(P^.Text);
    showmessage('CM_SETCELL: ['+CellBuff+']');                   // <== prouve que les données sont bien transmises !
    //                           SG.Cells[X, Y] := '123456';         // <== ok
    showmessage('CM_SETCELL - count='+inttostr(MySL.Count));         // <=========== ajout ici crash !!!
                               MySL.Delete(0);                       // <=========== ajout ici crash !!!
    showmessage('CM_SETCELL - clear ok');                            // <=========== ajout ici
                               MySL.Add(StrPas(P^.Text));            // <=========== ajout ici
    showmessage('CM_SETCELL - MySL[Strings[0]='+MySL.Strings[0]);    // <=========== ajout ici
                               SG.Cells[X, Y] := MySL.Strings[0];    // <=========== ajout ici
                             end;
                           end;
                         end;
                       end;
                       Exit;
                     end;
    ...
    procedure InstallHook(MainWnd, DestWnd: HWND); stdcall;
    begin
      if P^.hkMsg = 0 then
            P^.hkMsg := SetWindowsHookEx(WH_GETMESSAGE, @GetMsgProc, HInstance, 0);
      P^.HostWnd := MainWnd;
      P^.HostPID := GetCurrentProcessId;
      P^.DestWnd := DestWnd;
      MySL := TStringList.Create;         // <=========== ajout ici
      MySL.Add('bidon');                  // <=========== ajout ici pour créer l'élément initial
    showmessage('MySL créé');             // <=========== ajout ici
    end;
     
    procedure UninstallHook; stdcall;
    begin
      if P^.hkMsg <> 0 then begin
        UnhookWindowsHookEx(P^.hkMsg);
        P^.hkMsg := 0;
        MySL.Free;                        // <=========== ajout ici
    showmessage('MySL libéré');           // <=========== ajout ici
      end;
    end;
    ...
    Les procédures InstallHook et UninstallHook passent bien: MySL est créée puis supprimée.
    Mais le problème réside dans MsgWndProc, code CM_SETCELL: on ne peut pas y accéder MySL : plantage même si je veux afficher le nombre d'éléments.

    Ce que j'ai essayé de faire:
    1. créer MySL au début, en ajoutant un élément unique bidon
    2. pour chaque cellule du TSTringGrid à modifier, je supprime d'abord l'enregistrement existant dans MySL, puis j'ajoute dans MySL le texte à ajouter dans le TStringGrid, puis je charge la cellule du TSTringGrid par l'élément 0 de MySL.
    3. à la fin, je supprime MySL.
    Sauf que, en (2), je ne peux pas accéder à MySL, certainement parce qu'il s'agit d'une procédure WndProc qui est (peut-être) exécutée dans un contexte du programme principal et non celui de la DLL...

    B. même technique, mais sans passer par HookSG.dll (contexte de mon premier ZIP). J'emploie la même technique:
    dans la KGF_GRID.dpr (la DLL), dans la fonction WriteGridCells, je fais:
    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
    ...
        Mysl := TStringList.Create;
        Mysl.Add('bidon');
        for y:=y1 to y2 do begin           // boucle sur les lignes du rectangle
          for x:=x1 to x2 do begin            // boucle sur les colonnes du rectangle
    if dbg=1 then showmessage('c1: cells['+inttostr(x)+','+inttostr(y)+']='+TStringGrid(grid).Cells[x,y]+'<=='+sa[i]);
    //        TStringGrid(grid).Cells[x,y] := sa[i];   // remplacer les des données dans le TStringGrid
    showmessage('WriteGridCells - count='+inttostr(MySL.Count));
            MySL.Delete(0);
            MySL.Add(sa[i]);
            TStringGrid(grid).Cells[x,y] := MySL.Strings[0];
    if dbg=1 then showmessage('c1: cells['+inttostr(x)+','+inttostr(y)+']='+TStringGrid(grid).Cells[x,y]);
            i := i + 1;                       // pointer sur la donnée suivangte
          end;                                // fin de la boucle sur les colonnes du rectanle
        end;                               // fin de la boucle sur les lignes du rectagle
        MySL.Free;
    ...
    ayant déclaré MySL localement dans cette fonction. Résultat: deux, trois ou quatre exécutions se passent apparemment bien, mais ensuite, il y a la fameuse violation de mémoire.

    Conclusion: Delphi semble ne pas se laisser abuser par l'astuce d'affectation d'un élément d'une TStringList. Sûrement parce qu'il crée un string interne pour lequel les mêmes raisonnement s'appliquent, par rapport au compteur d'utilisation ?

  14. #14
    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 430
    Points
    28 430
    Par défaut
    lol, tu ne vas pas assez loin dans le raisonnement. si tu libères ta StringList, tu laisses de nouveau à l'EXE la responsabilité de la durée de vie de la string.

    ta SL doit être globale et conserver la chaîne aussi longtemps qu'elle est référencée par la StringGrid.

    pour tester, crée la SL dans l'initialization de l'unité et laisse les chaînes dedans sans jamais les supprimer...c'est pas à faire mais ça devrait permettre de vérifier ma théorie, si plus de plantage on passe à l'étape 2
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  15. #15
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 287
    Points : 164
    Points
    164
    Billets dans le blog
    1
    Par défaut
    YESSSSSSSS !!!!!!!!!!!!!!!

    Ca marche. Plus de violations d'accès ! Super !

    Mais maintenant, il y a une autre question qui se pose: comment éviter la fuite de mémoire ?,Je peux faire certaines choses assez facilement:
    - ajouter une marque identifiant le TStringGrid visée, sa ligne et sa colonne, à chaque chaîne que le dépose dans la StringList
    - lorsque supprime le TStringGrid, je vide également toutes les lignes de la StringList concernant ce TStringGrid.

    Mais, lorsque je remplace une deuxième fois le texte d'une même cellule du TStringGrid, est-ce que je peux supprimer l'élément correspondant dans la StringList, sans risque ? Je suppose que je devrai le faire après avoir remplacé le contenu de la cellule...

    Cependant, de toutes façons, la StringList de "bidouillage" va croitre rapidement. Est-ce qu'il y a un moyen d'éviter cela ? Tu as parlé d'une "phase 2"...

    Pour tester, je fais comme ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        i := 0;
        if not assigned(SG_helpSL) then SG_helpSL := TStringList.Create;
        for y:=y1 to y2 do begin
          for x:=x1 to x2 do begin
    showmessage('count='+inttostr(SG_helpSL.Count));
            SG_helpSL.Add(sa[i]);                                                    // <===== sauver le texte dans une StringList
            TStringGrid(grid).Cells[x,y] := SG_helpSL.Strings[SG_helpSL.Count-1];    // <===== charger le StringGrid à partie de la StringList
            i := i + 1;
          end;
        end;

  16. #16
    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 430
    Points
    28 430
    Par défaut
    oui il y a une solution

    comme évoqué plus haut, ta StringList contient une liste de chaînes dont le compteur d'usage ne tombera jamais à 0 du fait même de la liste. Il est possible de consulter la valeur de ce compteur d'usage...et pour toutes les chaînes pour lequel il vaut 1, tu peux la supprimer car seul la StringList y fait référence

    Dans un Delphi récent tu as la fonction StringRefCount() qui permet de connaître le nombre d'usages d'une chaîne. Sinon cette information est stockée à l'offset -8 de la chaîne (en -4 on a sa longueur)
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  17. #17
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 287
    Points : 164
    Points
    164
    Billets dans le blog
    1
    Par défaut
    Intéressant ! Je vais essayer cela.

  18. #18
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 287
    Points : 164
    Points
    164
    Billets dans le blog
    1
    Par défaut
    Je n'y arrive pas au niveau de la syntaxe. Comment fait-on pour trouver l'adresse d'un élément d'une StringList ? J'essaie comme ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      function RefCount(n: integer):integer; stdcall;
      var
        adr: pinteger;
        cnt: integer;
      begin
         adr := @SG_helpSL[k];   // <=========== erreur: "variable requise"
         cnt := (adr-8)^;       
         showmessage('k='+inttostr(k)+'  cnt='+inttostr(cnt));
         result := cnt;
      end;
    Le compilateur me jette sur la ligne indiquée. Je précise que SG_helpSL est une TStringList définie globalement, et elle contient toutes les chaînes que je mets en place dans le TSTringGrid.

  19. #19
    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 430
    Points
    28 430
    Par défaut
    alors, le plus simple est de passer par une variable intermédiaire (qui incrément du coup le compteur)

    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
     
    function StringRefCount(const: string): Integer;
    var
      p: PInteger;
    begin
      p := Pointer(s);
      if p = nil then
        Result := 0
      else begin
        Dec(p, 2); // 2 * 4 octets par Integer = 8 octets
        Result := p^;
      end;
    end;
     
    procedure TForm1.FormCreate(Sender: TObject);
    var
      s : string;
      sl: TStringList;
      s2: string;
    begin
      ShowMessage('s vide => ' + IntToStr(StringRefCount(s))); // 0, c'est le cas "nil"
      s := 'un';
      ShowMessage('s constant => ' + IntToStr(StringRefCount(s))); // -1, c'est une constante
      s := s + ' deux'; // force la création d'une variable dynamique
      ShowMessage('s dynamique => ' + IntToStr(StringRefCount(s))); // 1 : une seule référence
      sl := TStringList.create;
      sl.Add(s);
      ShowMessage('s dans SL => ' + IntToStr(StringRefCount(s))); // 2 : s et sl[0]
      s2 := sl[0];
      ShowMessage('s via SL => ' + IntToStr(StringRefCount(s2))); // 3 : s, sl[0] et s2
      sl.Clear;
      ShowMessage('s hors SL => ' + IntToStr(StringRefCount(s))); // 2 : s et s2
      sl.Add(s);
      ShowMessage('s dans SL => ' + IntToStr(StringRefCount(s))); // 3 : s, s2 et sl[0]
      s := 'trois';
      s2 := sl[0];
      ShowMessage('s vidé SL => ' + IntToStr(StringRefCount(s2))); // 2: sl[0] et s2
      sl.Clear;
      ShowMessage('s2  => ' + IntToStr(StringRefCount(s2))); // 1: s2
    end;
    tu peux passer par StringRefCount(sl[0]) mais il y a une variable invisible qui n'est libérée qu'en fin de procédure; avec s2 on lève toute ambiguïté.
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  20. #20
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 287
    Points : 164
    Points
    164
    Billets dans le blog
    1
    Par défaut
    J'ai pris un peu de temps, car les essais n'étaient pas évidents. Certes, dans le petit modèle que j'avais envoyé, j'arrivais à appliquer cette méthode. Donc, elle est valide. Cependant, en contexte de production, la situation est beaucoup plus complexe, et je n'ai pas réussi à adapter cette technique à la situation réelle, tout simplement parce que le compteur de référence pour les chaînes de caractères en question est très variable, au moment de l'appel de la fonction. Je ne peux donc pas me baser du tout sur ce nombre, même en supposant un décalage de 4, 5 ou 6...

    J'ai complètement changé de stratégie. Je me suis dit qu'en tapant les caractères à l'écran, en mode edition du TSTringGrid, le contenu des cellules est bien remplacé, sans que cela pose problème. Et j'ai donc commencé à simuler ce comportement dans ma fonction (résidant dans la DLL). Pour cela, il faut plusieurs étapes:
    1. mémoriser l'état de certaines options du TSTringGrid
    2. forcer le TSTringGrid dans le mode voulu (mode "édition")
    3. sélectionner la totalité des données de la cellule visée par l'écriture
    4. envoyer les nouvelles données sous forme de simulation de touches frappées
    5. restaurer le mode initial du TSTringGrid

    Deux problèmes principaux:
    1. le point (3) ci-dessus pose problème. Car si avant l'appel de la fonction, la cellule visée était la cellule sélectionnée (cellule en bleu), alors le fait de passer le TSTringGrid en mode "édition" et de resélectionner la cellule sélectionne bien la cellule (avec un rectangle pointillé autour), mais ne sélectionne pas les données. Au contraire, le curseur vient se positionner derrière, et les nouvelles données seront ajoutées à la suite. Il faut donc d'abord sélectionner une autre cellule, quelconque, puis sélectionner la cellule visée pour de bon. Et là, les données existantes sont bien sélectionnées et remplacées par le texte envoyé en simulation de touches.
    2. Pour éviter tout problème de chaînes de caractères avec leur compteur de référence, je suis passé par des pointeurs. Ma contrainte était que le texte est passé à la fonction via un PSTRING, donc l'addresse d'un pointeur vers le texte (une double indirection). Je construis donc un premier pointeur dans lequel je déréférence le PSTRING une fois, sous forme de PINTEGER, et j'obtiens l'adresse du premier caractère du texte. Je copie ce pointeur dans un autre pour garder l'adresse de début. Puis je laisse courir le premier pointeur jusqu'au premier caractère #0 qui signale la fin de la chaîne de caractères. Je peux maintenant, à l'aide du pointeur "début" et "fin", faire une boucle sur l'API POSTMESSAGE avec WM_CHAR pour envoyer mes caractères au TSTringGrid.

    Et mon problème est résolu !

    Voici le code final:
    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
    function WriteGridCells(grid: integer; x,y: integer; xs: pstring):integer; stdcall; export;
    var
      k: integer;
      actedit, acteditor, editmode: boolean;      // pour mémoriser l'état des options
      ps: PChar;       // pointeur vers la fin du texte à copier
      ps1: PChar;      // pointeur vers le début du texte à copier
    begin
      result := -1;                  // la valeur de retour de -1 est signal d'erreur
      try
        if TControl(grid).ClassName<>'TStringGrid' then exit;                // objet non créé ?
        ps := pointer(pinteger(xs)^);                                        // pointer dans le texte transmis
        ps1 := ps;                                                           // et garder une copie
     
        // mémoriser l'état des options du TSTringGrid
        actedit := (goEditing in TStringGrid(grid).Options);
        acteditor := (goAlwaysShowEditor in TStringGrid(grid).Options);
        editmode := TStringGrid(grid).EditorMode;
        // imposer les options nécessaires
        if not actedit then TStringGrid(grid).Options:= TStringGrid(grid).Options + [goEditing];
        if not acteditor then TStringGrid(grid).Options:= TStringGrid(grid).Options + [goAlwaysShowEditor];
        TStringGrid(grid).EditorMode := true;
     
        while ps^<>#0 do inc(ps);                // chercher la fin du texte à transmettre
     
        // le IF..THEN..ELSE suivant est nécessaire pour permettre une
        // sélection inconditionnelle du texte de la cellule, si cette
        // dernière est la cellule actuellement sélectionnée. Alors,
        // il faut provisoirement en sélectionner une autre, puis
        // on peut sélectionner la cellule ciblée.
        if y>1 then begin
          TStringGrid(grid).Col := x;
          TStringGrid(grid).Row := y-1;
          TStringGrid(grid).SetFocus;
        end else begin
          TStringGrid(grid).Col := x;
          TStringGrid(grid).Row := y+1;
          TStringGrid(grid).SetFocus;
        end;
     
        // sélectionner la cellule ciblée
        TStringGrid(grid).Col := x;
        TStringGrid(grid).Row := y;
        TStringGrid(grid).SetFocus;
     
        // copier le texte dans la cellule, en simulant des frappes au clavier.
        // comme le texte actuel de la cellule est sélectionné, il sera remplacé.
        for k:=0 to (ps-ps1-1) do begin
          PostMessage(TStringGrid(grid).Handle, WM_CHAR, Ord(ps1^), 0);
          inc(ps1);
        end;
        delay(100);     // délai important de 100 ms pour laisser le temps d'agir...
     
        // restaurer les options du TStringGrid si nécessaire
        if not acteditor then TStringGrid(grid).Options:= TStringGrid(grid).Options - [goAlwaysShowEditor];
        if not actedit then TStringGrid(grid).Options:= TStringGrid(grid).Options - [goEditing];
        if not editmode then TStringGrid(grid).EditorMode := false;
     
        result := 0;    // la valeur de retour de 0 est signal de terminaison correcte.
      except
      end;
    end;
    Seul "hic": il faut laisser un délai de 100 ms (peut-être possible de le diminuer, mas pas de le supprimer), sinon la sélection d'une cellule suivante opère avant l'arrivée de tous les caractères pour la cellule précédente, en cas d'utilisation en boucle.

    Voilà, pour ceux que cela pourrait intéresser. Merci mille fois pour ton soutien, Paul, car tu m'as fait prendre conscience du problème des compteurs de référence qui m"échappait complètement.

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. [Débutant] Utiliser les fonctions contenues dans une DLL écrite en VB.NET
    Par vg-matrix dans le forum VB.NET
    Réponses: 1
    Dernier message: 26/11/2012, 23h35
  2. Utilisation d'un Thread dans une DLL
    Par colorid dans le forum Langage
    Réponses: 7
    Dernier message: 14/03/2009, 11h05
  3. Utilisation d'un activex dans une dll mfc
    Par regdobey dans le forum MFC
    Réponses: 2
    Dernier message: 20/11/2008, 13h19
  4. [COM] utiliser la librairie standard dans une dll COM
    Par kacedda dans le forum Visual C++
    Réponses: 5
    Dernier message: 13/03/2008, 14h57
  5. TForm dans une DLL avec utilisation d'Interface
    Par guedelmalin dans le forum Langage
    Réponses: 13
    Dernier message: 17/06/2005, 11h58

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