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 :

Problème en dérivant une TForm sous Delphi 6 PE


Sujet :

Composants VCL Delphi

  1. #1
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    309
    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 : 309
    Points : 172
    Points
    172
    Billets dans le blog
    1
    Par défaut Problème en dérivant une TForm sous Delphi 6 PE
    Bonjour,

    Je suis sous Delphi 6 PE (oui, je sais - c'est vieux...) et je développe une vaste DLL avec des fonctions de support pour un autre angage.

    Actuellement, je développe un système d'impression paramétrée auquel je veux ajouter une fonction de prévisualisation.
    Je crée donc dynamiquement une TForm à laquelle j'ajoute une TPaintBox (pour l'image d'une page d'impression en prévisualisation d'un des fichiers EMF par page).
    Je crée aussi une série de TButton pour la navigation. J'affiche cette fenêtre en mode modal, et tout fonctionne très bien, mes boutons réagissent - pas de problème.

    Maintenant, je voudrais aller plus loin. J'ai créé un colmposant TPreview dans lequel je réunis toutes les informations ayant trait à la prévisualisation.
    Ces informations sont, entre autres:
    - l'instanciation de ma fenêtre de présélection
    - une TList contenant la liste de tous les fichiers EMF, un par page (indexes 1...n)
    Dans l'élément avec indexe 0, je place un fichier EMF affichant juste un fond blanc avec un texte "NO DATA".
    Ceci sera affiché dans la TPaintBox au début, lorsqu'aucune page n'est visualisée dans la fenêtre.
    - une variable contenant l'indexe de la page actuellement visible dans la TPaintbox de ma fenêtre de visialisation
    (et ayant donc la valeur 0 au départ).

    La creation de ces composants ne pose pas de problème. Le compiler accepte mon code.
    Mais à l'exécution, je reçois une erreur de violation de mémoire lors de la commande inherited dans le constructeur de la classe TPreviewForm.

    J'ai créé un extrait très simplifié de mon code, juste le stricte nécessaire pour reproduire l'erreur, et j'ai tout mis dans une unite que voici:
    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
    unit KGF_unit_test_1;
     
    interface
     
      uses Forms, ExtCtrls, Classes, Dialogs;
     
      type TPreviewForm = class(TForm)
        PreviewPage: TPaintBox;
        private
        published
          constructor Create(AOwner: TComponent); reintroduce;
          destructor Destroy; override;
      end;
     
      type TPreview = class
        PreviewForm: TPreviewForm;
        published
        private
          constructor Create;
          destructor Destroy; override;
        end;
     
     
    implementation
     
    constructor TPreviewForm.Create(AOwner: TComponent);
    begin
    showmessage('TPreviewForm.Create 1');
      inherited Create(AOwner);               // <========== violation de mémoire ici !!!!
    showmessage('TPreviewForm.Create 2');
    end;
     
    destructor TPreviewForm.Destroy;
    begin
      inherited;
    end;
     
    constructor TPreview.Create;
    begin
      inherited;
    showmessage('TPreview.Create 1');
      PreviewForm := TPreviewForm.Create(nil);
    showmessage('TPreview.Create 2');
    end;
     
    destructor TPreview.Destroy;
    begin
      inherited;
    end;
     
     
    function Test_derived_TForm: integer; stdcall; export;
    var
      Preview: TPreview;
    begin
    showmessage('start test');
      Preview := TPreview.Create;
    showmessage('test ok');
    end;
    exports Test_derived_TForm;
     
     
    end.
    Un appel de la fonction Test_derived_TForm provoque une violation de mémoire à l'endroit marqué par le commenteire.
    Il m'affiche aussi "ressource TPreviewForm non trouvé". Et ce message vient meme si je remplacela méthode Create par CreateNew our TPreviewForm.
    Je ne comprends pas du tout pourquoi.

    Est-ce que vous pourriez l'aider ? Je suis bloqué. Un grand MERCI d'avance
    KlausGunther

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

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 834
    Points : 13 587
    Points
    13 587
    Par défaut
    Exe et dll ont leur propre objet TApplication, il faut les lier.

    Exporte une procédure supplémentaire, que tu appelleras dès chargement de la dll, avec en paramètre Application.Handle de l'exe.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    procedure InitApplicationHandle(aHandle :THandle); stdcall; export;
    begin
      Application.Handle := aHandle;
    end;

  3. #3
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    309
    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 : 309
    Points : 172
    Points
    172
    Billets dans le blog
    1
    Par défaut
    Merci pour ton avis, Andnotor.

    Si je lis bien la doc, je trouve ceci, concernant TApplication.Handle (source: https://docwiki.embarcadero.com/Libr...ation.Handle):
    Description

    Provides access to the window handle of the main form (window) of the application.

    Use Handle when calling Windows API functions that require a parent window handle. For example, a DLL that displays its own top-level pop-up windows needs a parent window to display its windows in the application. Using the Handle property makes such windows part of the application, so that they are minimized, restored, enabled, and disabled with the application.

    Note: When writing a DLL that uses VCL forms, assign the window handle of the host EXE's main window to the Handle property of the DLL's global Application variable. This makes the DLL's form part of the host application. Never assign to the Handle property in an EXE.
    Donc, TApplicationHandle est identique au handle de la Form 0 du programme principal.

    Je récupère ce handle depuis longtemps dans une procédure que j'appelle systématiquement à l'intérieur de ma DLL lors de son chargement. Je me sers souvent de ce handle que je stocke naïvement dans une variable globale. Bon, mainenant, je l'affecte en plus à Application.Handle dans ma DLL.

    Mais malheureusement, le résultat est identique: "Ressource TPreviewForm non trouvée".

    Pourquoi Delphi cherche-t-il un fichier *.res lors de la commande "Inherited" dans le constructeur de maform que je crée de toutes pièces dans mon code ?
    Est-ce qu'il y a un moyen de lui "fournir" ce faeux fichier .res ?

  4. #4
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    309
    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 : 309
    Points : 172
    Points
    172
    Billets dans le blog
    1
    Par défaut
    Ok, j'ai compris ! J'ai résulu le problème.

    J'ai encore voulu être plus "technique" que nécessaire. J'ai simplifié mon code:
    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
    unit KGF_unit_test_1;
     
    interface
     
      uses Forms, ExtCtrls, Classes, Dialogs;
     
     
      type TPreview = class
        PreviewForm: TForm;
        published
        private
          constructor Create;
          destructor Destroy; override;
        end;
     
     
    implementation
     
    constructor TPreview.Create;
    begin
      inherited;
    showmessage('TPreview.Create 1');
      PreviewForm := TForm.Create(nil);
    showmessage('TPreview.Create 2');
    end;
     
    destructor TPreview.Destroy;
    begin
      inherited;
    end;
     
     
    function Test_derived_TForm: integer; stdcall; export;
    var
      Preview: TPreview;
    begin
    showmessage('start test');
      Preview := TPreview.Create;
    showmessage('test ok');
      Preview.PreviewForm.ShowModal;
    end;
    exports Test_derived_TForm;
     
    end.
    Et maintenant, il n'y a plus d'erreur, et ma fenêtre est créée et s'affiche à la demande.
    Je peux également la peupler d'objets tous créés programmatiquement: une TPaintBox et une série de TButton), ainsi que les procédures ON_CLICK de ces boutons, et tout est fonctionnel, sans besoin d'un fichier de ressources. L'astuce est de placer tout ce code dans le constructeur de TPreview, juste après PreviewForm := TForm.Create(nil); mais SANS déclarer un constructeur sur cette form créée programmatiquement. Et tout fonctionne.

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

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 834
    Points : 13 587
    Points
    13 587
    Par défaut
    J'avais pas bien lu jusqu'au bout mais l'erreur est parce qu'il manque la ligne {$R *.dfm}.

  6. #6
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    309
    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 : 309
    Points : 172
    Points
    172
    Billets dans le blog
    1
    Par défaut
    Oups... j'avais oulié ça... je vais noter cela pour la suite ! Merci !

  7. #7
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 717
    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 717
    Points : 25 613
    Points
    25 613
    Par défaut
    Petite info sur CreateNew, c'est pour instancier un descendant de TForm sans ressource

    Creates and initializes a new form. Use CreateNew instead of Create to create a form without using the associated .DFM file to initialize it.
    C'est une autre approche pour résoudre le problème de "ressource TPreviewForm non trouvé" mais cela implique de créer tous les contrôles manuellement mais si tu utilises une TForm, cela en revient au même.
    Tous les champs du TPreviewForm instancié par CreateNew sont à nil


    Cependant cela n'était pas la solution pour le problème de Violation d'Accès, sauf si D6 avait un bug si la ressource manquait.
    Cela m'étonne la Violation d'Accès, en C++, le mélange de Create et CreateNew (ce dernier n'existe pas vraiment, c'est le constructeur avec le paramètre Dummy) pouvait provoquer un Stack Overflow et non une Violation d'Accès.
    En Delphi, une Violation d'Accès sur un constructeur cache autre chose que le code que l'on dispose.

    Dans le code complet, car dans le code ci-dessus, je ne vois pas ce qui peut causer une Violation d'Accès, je regarderais aussi du côte de OldCreateOrder
    Si OldCreateOrder est à True
    Ne pas mettre dans le gestionnaire de OnCreate, la DFM n'est pas chargé, les références des champs publiés sont encore non associées
    Si OldCreateOrder est à False (par défaut)
    Le gestionnaire de OnCreate n'est plus appelé au moment du constructor mais au moment du Loaded() ce qui permet d'avoir les contrôles chargés ...
    tout ceux qui ont bossée en D3 se souviennent de l'astuce d'un OnActivate First pour gérer ce soucis.


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    constructor Create(AOwner: TComponent); reintroduce;
    c'est tout simplement override et non reintroduce !
    le inherited ne doit pas apprécier le reintroduce dans ce cas car reintroduce casse le mécanisme de virtualisation.

    A vérifier mais je dirais que tu aurais au moins de problème avec constructor Create(); reintroduce; et

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    constructor TPreviewForm.Create();
    begin
      inherited Create(nil);  
    end;
    Car tu change bien le prototype Create(AOwner) par Create() par reintroduce en changeant la liste des paramètres

    C'est le but de reintroduce , rompre la virtualisation pour fournir un constructeur différent.

    Mais faire un reintroduce avec la même liste de paramètres, c'est un non-sens !
    A tester, en D10 ça passe mais en D6 est-ce différent ?



    constructor et destructor sont public, les mettre en published est aussi une anomalie.



    Enfin avec le code fourni, Preview.PreviewForm n'est jamais libérée

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function Test_derived_TForm: integer; stdcall; export;
    var
      Preview: TPreview;
    begin
      Preview := TPreview.Create();
      try
        Preview.PreviewForm.ShowModal();
      finally
        Preview.Free();
      end;
    end;
    avec le code

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    constructor TPreview.Create();
    begin
      inherited Create();
     
      FPreviewForm := TPreviewForm.Create(nil);
    end;
     
    destructor TPreview.Destroy();
    begin
      FreeAndNil(FPreviewForm);
     
      inherited Destroy();
    end;
    j'aurais tendance à renommer PreviewForm en FPreviewForm déplacé en private et au besoin ajouter en public un property en lecture seule.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type 
      TPreview = class(TObject)
        private
          FPreviewForm: TForm;
        public   
          constructor Create();
          destructor Destroy(); override;
     
          property PreviewForm: TForm read FPreviewForm;
        end;
    Au moins, tu es sûr que ton code n'ira pas modifier la référence de PreviewForm par erreur
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  8. #8
    Membre habitué

    Homme Profil pro
    Informaticien retraité
    Inscrit en
    Mars 2010
    Messages
    309
    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 : 309
    Points : 172
    Points
    172
    Billets dans le blog
    1
    Par défaut
    Merci beaucoup !

    Je garde ce texte dans ma liste de "consignes" à appliquer. Cela rend le rôle de ces directives nettement plus clair.

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

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 834
    Points : 13 587
    Points
    13 587
    Par défaut
    Il est clair que reintroduce n'est pas logique ici mais il n'a aucune influence sur l'héritage puisqu'il ne sert à rien si ce n'est éliminer l'avertissement "masque la méthode précédente".
    Ne pas spécifier override pourrait par contre être un problème si on souhaitait un comportement polymorphe ; instancier un objet par une référence de classe ancêtre.

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

Discussions similaires

  1. Visualisation du contenu d'une table sous delphi 7
    Par Saddek dans le forum Bases de données
    Réponses: 3
    Dernier message: 22/07/2006, 20h10
  2. Requete insertion dans une table sous delphi?
    Par EssaiEncore dans le forum Bases de données
    Réponses: 5
    Dernier message: 09/01/2006, 16h12
  3. Sauvegarde du contenu d'une paintbox sous delphi 5
    Par TISSEYRE dans le forum Composants VCL
    Réponses: 1
    Dernier message: 23/06/2005, 12h41
  4. Comment créér une collection sous Delphi
    Par PsyKroPack dans le forum Langage
    Réponses: 6
    Dernier message: 11/02/2003, 14h20

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