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

Langage Delphi Discussion :

DLL D7 String + EXE XE4 AnsiString


Sujet :

Langage Delphi

  1. #1
    Membre expérimenté Avatar de guillemouze
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    876
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations forums :
    Inscription : Novembre 2004
    Messages : 876
    Points : 1 448
    Points
    1 448
    Par défaut DLL D7 String + EXE XE4 AnsiString
    Salut a tous,
    je suis en train de migrer une application D7 en XE4, et je me trouve logiquement confronté à un problème de string, mais je n'arrive pas a comprendre pourquoi.

    Le contexte de base :
    Une application D7 (en migration) utilise une dll D7 d'une autre application (non migrée) pour obtenir des informations. Ces 2 parties utilisent SharMem.
    Là où ca se corse, c'est sur le prototype des fonctions (ce n'est pas le vrai proto, mais le problème est présent):
    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
    type
      TInfo = record
        txt: string;
      end;
      PInfo = ^TInfo;
     
    function GetInfo(): PInfo; external 'madll.dll'; 
     
    ...
    var
      p: PInfo;
    begin
      p := GetInfo;
      ShowMessage(PAnsiChar(p^.txt));
      Dispose(p);
    end;
    J'aimerai, dans la mesure du possible, ne pas avoir a toucher a la dll car elle est fournie avec un autre produit et je préférerai qu'elle reste telle quelle.

    Pour la migration, j'ai donc renommé String en AnsiString.
    Lorsque j’exécute cette fonction en XE4, j'obtiens une violation d'accès lors du dispose (lecture de l'adresse 0), alors que je n'ai pas le problème en D7.
    Le code de la fonction de la dll (D7) est le suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    function GetInfo(): PInfo; 
    var
      s: string;
    begin
      New(Result);
      s := stringofchar('c', 4) + inttostr(4); //pour etre sur de ne pas avoir une chaine constante
      Result^.txt := s;
    end;
    Si je passe la variable s en globale, je n'ai plus la V.A. dans l'exe, j'en déduis que si j'ai encore une référence sur ma chaine au moment du Dispose, ca ne pose pas de problème
    Je soupçonne donc
    * soit une libération impromptue de la chaine qui fait qu'elle est libérée une deuxième fois lors du Dispose,
    * soit que la libération par l'exe de la chaine allouée par la dll pose problème.

    Quelqu'un aurait-il une piste à me donner pour comprendre cette erreur (une autre réponse que "ne pas retourner pointeur avec des string", car ca je n'ai pas trop la main dessus).
    Petite remarque : même après lecture de l'article le Paul, et plusieurs tentatives de cast/encode de ma chaine, je n'ai réussi a voir la chaine que en castant en PAnsiChar ou dans l'evaluateur, sinon elle apparait vide.
    Merci.

  2. #2
    Modérateur
    Avatar de tourlourou
    Homme Profil pro
    Biologiste ; Progr(amateur)
    Inscrit en
    Mars 2005
    Messages
    3 879
    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 879
    Points : 11 377
    Points
    11 377
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    As-tu essayé de libérer la chaîne d'abord ? Ou de finaliser le record ?

  3. #3
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 665
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 665
    Points : 25 459
    Points
    25 459
    Par défaut
    S étant une variable locale, il n'y a pas de compteur de référence, c'est libéré à sa sortie de la fonction
    Est-ce que ce code fait bien une copie de la chaine ?
    Si oui, si S disparait, alors Txt est toujours là
    Si non, si S disparait, sans le compteur de référence, je ne sais pas (je me suis arrêté à D7 concernant le fonctionnement du compteur)
    Le passage en global, indiquerait qu'il n'y a pas de copie de la chaine mais un jeu de copie de référence

    Pense que XE4 utilise un autre modèle de Gestion de mémoire, c'est FastMM qui rend je crois ShareMem obsolète (voir aussi FastShareMem et SimpleShareMem)

    En cas normal, c'est allocation et libération du même côté
    Ce que tu explique ressemble au cas où ShareMem n'est pas pris en compte et donc la libération côté EXE d'une valeur alloué côté DLL est refusée
    Quoi que l'exception en 00000000 c'est étrange, passer une valeur à nil cela ne se fait pas tout seul


    je note que tu es en external, idem, si je fais une DLL, c'est toujours en Allocation dynamique car c'est principalement pour créer des plugins que j'utilise des DLL


    Je suis justement en train de faire une DLL, cela fait longtemps que je ne l'ai pas fait (ça remonte à bien 6 ans en C++Builder )
    Je gère tout avec des interfaces et WideString
    Il n'y a que les Exception qui fonctionne mais j'ignore si ma pratique est bonne


    Enfin la AnsiString et la String Delphi sont-elles pleinement compatible ?
    Quelle est la longueur de StrRec sur une AnsiString et une String ?

    structure AnsiString contient un indicateur de longueur sur 32 bits, un compteur de références sur 32 bits, une longueur de données sur 16 bits indiquant le nombre d'octets par caractère, et une page de code sur 16 bits.
    Ce passage indique que la AnsiString a le même entête que l'UnicodeString
    Ce qui rend la zone du compteur au mauvais endroit en D7 et DXE4

    C'est pour cela que tu as 00000000 car cela doit tenter le lire une mauvaise section pour libérer la chaine
    En D7 c'est 4+4+Pointer
    En DXE4 c'est 2+2+4+4+Pointer
    En toute logique, D7 tente de libérer l'adresse en -8 alors qu'elle a été alloué en -12


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    type
      PStrRec = ^StrRec;
      StrRec = packed record
      {$IF defined(CPUX64)}
        _Padding: LongInt; // Make 16 byte align for payload..
      {$IFEND}
        codePage: Word;
        elemSize: Word;
        refCnt: Longint;
        length: Longint;
      end;

  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 : 55
    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 457
    Points
    28 457
    Par défaut
    Hello,

    et oui, comme je l'explique dans mon article, depuis Delphi 2009 le type String possède deux informations supplémentaires aux offset -10 et -12, dès lors il n'est pas possible de manipuler une chaîne D7 sous XE4, il manque des informations et l'adresse allouée n'est pas la même, sous D7 l'allocation mémoire se fait entre l'offset - 8 et la fin de la chaîne, sous XE4 elle va de l'offset -12 à la fin de la chaîne.

    si tu veux utiliser la DLL D7 sous XE4 il va falloir déclarer un type string maison et gérer tout à la main...ou tout au moins sa lecture, et il faudra évidemment utiliser ShareMem pour gérer l'allocation.

    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
     
    // taper dans le forum, sans test réel
     
    type
      TInfo = record
        txt: PAnsiChar;
      end;
      PInfo = ^TInfo;
     
      TStringD7 = record
        RefCount: Integer;
        StrLength: Integer;
      end;
     
    function GetInfo(): PInfo; external 'madll.dll';
     
    function GetInfoXE4: string;
    var
      info: PInfo;
      Str : ^TStringD7;
    begin
      Info := GetInfo;
      Result := string(Info.txt); // on traite comme un PAnsiChar 
      Str := Pointer(Info.Text); // information sur la chaîne
      Dec(Str); // se placer à l'offset - 8
      Dec(Str.RefCount); // réduire le compteur de référence
      if Str.RefCount = 0 then // s'il est tombé à zéro
        FreeMem(Str); // libérer la chaîne (via ShareMem !)
      Dispose(info); // libérer PInfo (via ShareMem !)
    end;

  5. #5
    Membre expérimenté Avatar de guillemouze
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    876
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations forums :
    Inscription : Novembre 2004
    Messages : 876
    Points : 1 448
    Points
    1 448
    Par défaut
    Merci a tous de votre aide.
    Désolé pour la longueur du message, j'ai mis l'explication et les fonctions permettant de répondre au problème.

    @Tourlourou :
    Je ne savais pas qu'on pouvait utiliser manuellement Finalize(). Intéressant, mais ca ne résout pas mon problème (si ce n'est que j'ai repris ce principe pour mon record)

    @Shai :
    Citation Envoyé par ShaiLeTroll Voir le message
    Pense que XE4 utilise un autre modèle de Gestion de mémoire, c'est FastMM qui rend je crois ShareMem obsolète (voir aussi FastShareMem et SimpleShareMem)
    (...)
    je note que tu es en external, idem, si je fais une DLL, c'est toujours en Allocation dynamique car c'est principalement pour créer des plugins que j'utilise des DLL
    D'après la doc,
    Il existe deux méthodes mutuellement exclusives via lesquelles le gestionnaire de mémoire peut être partagé entre une application et ses bibliothèques : ShareMem et SimpleShareMem.
    les 2 sont incompatibles, donc si je ne modifie pas ma dll, il faut que je garde ShareMem aussi dans mon exe.
    L'external c'est pour l'exemple, je suis aussi en dynamique dans la réalité

    @Paul & Shai :
    C'est bien ça, la libération ce fait a l'octet -12 alors que l'alllocation s'est faite au -8.
    Paul, ton code étant parfait, je l'ai repris tel quel J'ai juste eu d'autre modif à faire parce que pour les chaines en résultat de fonction, c'est encore pire.
    J'explique :
    Si le Result d'une fonction de la dll est une string, et qu'on la stock dans ce que Paul appelle une StringD7 (un PAnsiChar), le compteur de référence n'est pas incrémenté par l'exe, et le résultat est donc libéré à la mode XE, donc au mauvais endroit.
    Il a donc fallu que je le récupère dans une AnsiString pour que le compteur soit incrémenté, mais qu'ensuite je relache cette AnsiString sans qu'elle libère la chaine car c'est une string D7. J'incrémente doncc manuellement le compteur, relache l'AnsiString (qui ne se libère donc pas), puis relache à la mode D7 (qui libère si c'était la derniere ref dessus).
    Voici, pour ceux que ca intéresse, le résultat des mes galères:
    Fonctions génériques utilisables n'importe ou
    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
    //Je me base sur le fait qu'on soit en widechar pour savoir si l'entete est différent, ce n'est peut etre pas la bonne façon de vérifier
    {$IF SizeOf(Char)=2}
      {$DEFINE NEED_STRING_LEGACY} //exe XE et dll D7 : on doit traiter particulierement les chaines
    {$IFEND}
     
    type
    {$IFDEF NEED_STRING_LEGACY}
      StringD7 = type PAnsiChar;
      ResultStringD7 = type AnsiString; // on doit incrementer le compteur de reference quand on recoit une string
    {$ELSE}
      StringD7 = string;
      ResultStringD7 = string;
    {$ENDIF}
    procedure ReleaseStr(const AString: StringD7);
    function ConvertResultStr(var AResult: ResultStringD7): string;
     
    ...
     
    {$IFDEF NEED_STRING_LEGACY}
    type
      PStrHeaderD7 = ^TStrHeaderD7;
      TStrHeaderD7 = record
        RefCount: Integer;
        StrLength: Integer;
      end;
     
    procedure ReleaseStr(const AString: StringD7);
    var
      h: PStrHeaderD7;
    begin
      h := Pointer(AString); // information sur la chaîne
      if h <> nil then
      begin
        Dec(h); // se placer à l'offset - 8
        Dec(h.RefCount); // réduire le compteur de référence
        if h.RefCount = 0 then // s'il est tombé à zéro
          FreeMem(h); // libérer la chaîne (via ShareMem !)
      end;
    end;
     
    procedure AddRefStr(const AString: StringD7);
    var
      h: PStrHeaderD7;
    begin
      h := Pointer(AString); // information sur la chaîne
      if h <> nil then
      begin
        Dec(h); // se placer à l'offset - 8
        Inc(h.RefCount); // incrémente le compteur de référence
      end;
    end;
     
    function ConvertResultStr(var AResult: ResultStringD7): string;
    var
      res: StringD7;
    begin
      res := StringD7(AResult);
      AddRefStr(res); //on incremente le compteur pour ne pas la supprimer à la ligne suivante
      AResult := ''; //on retire le lien avec la ResultStringD7, pour qu'elle ne soit pas libérée comme ansistring XE
      Result := res; //on la copie dans une string unicode
      ReleaseStr(res); //et on libere la chaine passée en paramêtre comme une chaine D7
    end;
     
    {$ELSE} // ! NEED_STRING_LEGACY
    procedure ReleaseStr(const AString: StringD7);
    begin
    end;
    function ConvertResultStr(var AResult: ResultStringD7): string;
    begin
      Result := AResult;
    end;
    {$ENDIF}// NEED_STRING_LEGACY
    Et mon cas particulier :
    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
    type
      TInfo = record
        txt: stringD7; //on manipule des PAnsiChar en D7
      end;
      PInfo = ^TInfo;
     
    function GetInfo(): PInfo; external 'madll.dll'; 
     
    procedure FinalizeInfo(p: PInfo);
    begin
      ReleaseStr(p.txt); //en réalité, j'ai une 40aine de string !
    end;
     
    procedure DisposeInfo(p: PInfo);
    begin
      FinalizeInfo(p);
      Dispose(p);
    end;
     
    ...
    var
      p: PInfo;
    begin
      p := GetInfo;
      ShowMessage(PAnsiChar(p^.txt));
      DisposeInfo(p);
    end;
    et pour les fonctions avec retour string (que je n'avais pas présenté dans le sujet original) :
    Code avant adaptation :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    interface
     
    function GetStr(): string; external 'madll.dll'; 
     
    implementation
     
    procedure Test;
    var
      s: string;
    begin
      s := GetStr;
      ShowMessage(s);
    end;
    Code adapté :
    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
    interface
    function GetStr(): string;
     
    implementation
     
    function FGetStr(): ResultStringD7; external 'madll.dll' name 'GetStr';
     
    function GetStr(): string;
    var
      res: ResultStringD7;
    begin
      res := FGetStr();
      Result := ConvertResultStr(res);
    end;
     
    procedure Test; //pas besoin de modif
    var
      s: string;
    begin
      s := GetStr;
      ShowMessage(s);
    end;

  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 : 55
    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 457
    Points
    28 457
    Par défaut
    Salut,

    tu peux aussi écrire ceci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    begin
      // AddRefStr(res); //on incremente le compteur pour ne pas la supprimer à la ligne suivante
      // AResult := ''; //on retire le lien avec la ResultStringD7, pour qu'elle ne soit pas libérée comme ansistring XE
      Pointer(AResult) := nil;
    end;

  7. #7
    Membre expérimenté Avatar de guillemouze
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    876
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations forums :
    Inscription : Novembre 2004
    Messages : 876
    Points : 1 448
    Points
    1 448
    Par défaut
    Effectivement c'est plus simple.
    Par hasard, tu n'aurais pas une méthode alternative plus élégante pour gérer les string en retour de fonction D7 ? Je suppose que non sinon tu l'aurait donnée plutôt que d'améliorer ma bidouille
    Merci Paul

  8. #8
    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 : 55
    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 457
    Points
    28 457
    Par défaut
    Citation Envoyé par guillemouze Voir le message
    Effectivement c'est plus simple.
    Par hasard, tu n'aurais pas une méthode alternative plus élégante pour gérer les string en retour de fonction D7 ? Je suppose que non sinon tu l'aurait donnée plutôt que d'améliorer ma bidouille
    Merci Paul
    l'alternative c'est de ne pas utiliser des Strings mais des P(Ansi)Char au niveau interne ça peut rester un String, mais de l'autre côté on considère que c'est un PChar avec éventuellement une fonction FreeString()

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    function getString:string;
    begin
     Result :=  ...
    end;
     
    procedure FreeString(var s: string);
    begin
      s := '';
    end;
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    function getString: PChar; external;
     
    procedure FreeString(var p: PChar); external;
    on peut faire la même chose avec AnsiString et PAnsiChar

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

Discussions similaires

  1. Réponses: 15
    Dernier message: 21/12/2017, 11h49
  2. Intégrer une dll dans un EXE
    Par Akim13 dans le forum Langage
    Réponses: 5
    Dernier message: 15/09/2005, 02h11
  3. Inclusion d'une DLL dans un exe?
    Par luareon22 dans le forum MFC
    Réponses: 10
    Dernier message: 29/08/2005, 12h08
  4. Inclure une DLL dans le .exe final?? possible?
    Par xavmax dans le forum C++Builder
    Réponses: 9
    Dernier message: 22/08/2005, 17h00
  5. modifier l'adresse d'un dll dans un .exe
    Par Mr Meuble dans le forum Windows
    Réponses: 4
    Dernier message: 02/03/2004, 16h39

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