Quelqu'un sait-il comment créer une classe héritée d'une autre classe en dynamique?
Cloner la VMT avec un nouveau TClass pour la nouvelle classe.
J'ai trouvé un source sur le net mais il ne me parait pas "sans bug" ni complet.
Quelqu'un sait-il comment créer une classe héritée d'une autre classe en dynamique?
Cloner la VMT avec un nouveau TClass pour la nouvelle classe.
J'ai trouvé un source sur le net mais il ne me parait pas "sans bug" ni complet.
La VMT étant en réalité une constante ...
J'ai des gros doutes sur ce fameux code sur le net, tu aurais pu fournir le lien pour que l'on puisse le voir et donner son avis !
Quelle est la problématique d'origine ?
Il y a peut-être plus simple ?
Tu veux en fait créer des classes à la volée, Delphi n'est pas du JavaScript ou du PHP et les accesseurs Magiques, c'est un langage compilé, on produit pas du code comme ça (bon en fait, si certains ont le courage d'écrire dans une zone mémoire des Codop et de les exécuter mais rare ont assez de génie (folie) pour l'utiliser en réalité, surtout dans un projet professionnel qui un jour sera repris par un stagiaire)
Peut-être qu'un tableau associatif <String, Variant> pour te permettre d'étendre virtuellement les propriétés d'un objet
Ce n'est pas loin d'un TDataSet finalement dans l'idée avec un nombre de colonne variable ... tu pourras reprendre l'idée d'une collection de Variant en plus léger que le TClientDataSet
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
voili voilou pour le lien
http://www.koders.com/delphi/fidCAC2...273.aspx?s=pos
c'est une idée que j'ai eu avec cet article :
http://hallvards.blogspot.com/2007/0...s-part-ii.html
par exemple :
j'ai une table en base de donnees.
avec le desciptif de cette table cad la liste des champs et ses types
exemple
table Produits
champ NomProduit string (30)
champ couleurproduit string (5)
je veux creer
1)une table de description qui contient la description de la table : le nom et le type de champs (cidessus)
et
2)une table qui contient les donnees d'1 enregistrement (une ligne dans la table) cad les valeurs ('PANTOUFLE', 'NOIRE')
cette table pointe sur la table descriptif (1) pour connaitre le desc de chaque champ sans devoir le dupliquer à chaque fois
mais comme c'est un objet par ligne dans la table, je ne veux pas dupliquer pour chaque objet le pointeur qui pointe sur la table descriptif
je veux mettre ce pointeur comme variable statique à la classe
OR cette variable sera commune a chaque objet de cette classe
c'est pourquoi je veux pouvoir dupliquer cette classe en dynamique
comme cela chaque table aura sa PROPRE classe
mais des descriptif differents
suis-je clair?
la classe produit aura sa propre classe produit (meme si elle derive de la meme base), la classe article aura sa propre classe...
chaque classe aura les mêmes fonctions mais elles auront chacunnes des descripteur different et statique.
euh, class property, ça doit exister dans les Delphi récent !
sinon, une variable globale dans la section Implementation, et une class function Get... pour D7 et moins
ou encore un tableau associatif <TPersistentClass, TPropertyDescription>.
TPersistentClass est une MetaClass
TPropertyDescription serait l'instance qui décrit les propriétés
Selon les Delphi, tu dois avoir les Templates (Génériques)
Sinon, une TStringList permet d'avoir un tableau associatif assez rapidement !
Enfin le TDataSet et FieldsDefs, cela fonctionne très bien aussi !
Tu devrais commencer par savoir les bases du langage Delphi avant de vouloir utiliser du code ASM que tu ne comprends pas !
Tu es en train de concevoir une couche OR Persistance ?
Comme Bold InstantObjects, ECO, le modèle de phplive, ... il est possible que SJRD ait produit des objets qui pourrait t'aider dans les RTTI (peut-être même pouvant aller à du Scripting)
D'ailleurs, tu pourrais voir chez TMS si leur outil de PascalScript pourrait te servir !
Mais tu peux aussi le faire avec des objets sans solution extrème !
Tu peux gérer une Collection qui associe une TClass à une Description de structure, ton objet pour accéder à la collection pour sa Description
J'ai conçu une couche de persistance aussi, j'avais une bonne trentaine de classe ! J'ai refait une mini couche OR en C++Builder, j'ai pour le moment, le minimum
Si tu es curieux, juste la partie Interface (en Delphi) d'une des 10 unités de la EpcPersistance que j'ai conçu chez un ancien employeur, juste 335 lignes pour la partie interface, je te laisse imaginer le code de la partie implémentation ... ça montait à entre 15K et 20K lignes pour la lib entière
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 class DELPHICLASS TShaïORPersistentManager; class DELPHICLASS TShaïORPersistentClassMetaData; class DELPHICLASS TShaïORPersistentClassMetaDataItem; class DELPHICLASS TShaïORPersistent; typedef TClass TShaïORPersistentClass;
Evidemment ces objets sont abstraits, ils servent d'objet de base pour l'implémentation d'objet métier contenant des propriétés publiées
Dans la couche Delphi, c'est un XML qui donne la structure de la Table MySQL, ainsi que la correspond nom de champ et propriété, les relations (1-1, 1-N, N-N).
Dans le code, j'utilise les objets métiers fortement typés qui héritent du type abstaits (exemple TClient, TFournisseur, TCommande, TCompta...) qui fournissent des fonctions liées au métier et à la cohérence des données
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
Salut Shai,
non en fait il y a une subtile nuance mieux expliquée dans la partie I du blog. Il est question d'une propriété de classe spécifique à chaque classe, y compris chaque dérivé.
j'en ai eu besoin dans un cas de figure particulier, j'avais une série d'objets créés et détruits assez fréquemment ce qui est très pénalisant au niveau performances. J'ai donc cherché a conservé les instances libérées pour les réutiliser après coup.
Au départ j'étais parti sur une chose comme ça
mis à part que Pool peut être placé en variable de classe, cette approche présente deux gros inconvénients, le constructeur n'accepte aucun paramètre, et pire encore le Pool mélange les classes dérivées, on ne peut donc en réalité utiliser qu'un seul dérivé.
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 type TCollectedObject = class private FNext: TCollectedObject; public class function New(): TCollectedObject; procedure Release; end; var Pool: TCollectedObject: class function TCollectedObject.New(): TCollectedObject: begin if Pool = nil then begin Result := Pool; Pool := Result.FNext; end else begin Result := TCollectedObject.Create(); end; end; procedure TCollectedObject.Release; begin FNext := Pool; Pool := Self; end;
Je suis donc parti sur une autre approche qui surchage directement NewInstance et FreeInstance (plus de problème de constructeur) et qui exploite ClassInfo pour identifier la classe en cours ({$M+} obligatoire).
Au lieu d'avoir un Pool simple comme au dessus j'ai un truc comme ça
Notez au passage que j'ai supprimé les appels à InitInstance et CleanupInstance qui sont aussi très consommateur de temps, la seule conséquence et que l'objet ressortant du Garbage n'est pas initialisé (il conserve les anciennes valeurs), il faut donc le faire explicitement soit dans le constructor ou plus logiquement dans le destructor..
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 type {$M+} TCollectedObject = class(TObject) private FNext: TCollectedObject; public class function NewInstance: TObject; override; procedure FreeInstance; override; end; {$M-} type PGarbage = ^TGarbage; TGarbage = record Cls : TClass; List : TCollectedObject; Next : PGarbage; end; var Garbage: PGarbage; class function TCollectedObject.NewInstance: TObject; var Hook : PGarbage; begin // $M+ obligatoire sinn ClassInfo est vide Assert(ClassInfo <> nil); // Liste des Garbage Hook := Garbage; // On recherche celui de notre classe while (Hook <> nil) and (Hook.Cls <> ClassInfo) do Hook := Hook.Next; // S'il n'existe pas, laisser Delphi faire son boulot normalement if (Hook = nil) or (Hook.List = nil) then begin Result := inherited NewInstance; end else begin // Sinon prendre la première instance disponible Result := Hook.List; // Et la retirer de la liste Hook.List := TCollectedObject(Result).FNext; // InitInstance(Result); // <- very slow ! end; end; procedure TCollectedObject.FreeInstance; var Hook : PGarbage; begin //CleanupInstance; // you have to clean everything in the destructor // Recherche le Garbage propre à la classe Hook := Garbage; while (Hook <> nil) and (Hook.Cls <> ClassInfo) do Hook := Hook.Next; // S'il n'existe pas, le créer if Hook = nil then begin New(Hook); Hook.Cls := ClassInfo; Hook.List := nil; Hook.Next := Garbage; Garbage := Hook; end; // Ajouter l'objet à la liste FNext := Hook.List; Hook.List := Self; end;
"Tu devrais commencer par savoir les bases du langage Delphi avant de vouloir utiliser du code ASM que tu ne comprends pas !"
t'es gentil!
20ans de dev, C++,Asm,SQL, 10 ans de delphi, j'ose croire que je ne suis pas si débutant que ça...
je trouve simplement que le code du rttimaker me semble léger et pas complet c'est tout. je ne jamais dis que je le comprennais pas.
L'idée me semblait simplement interessante à creuser!
Paul TOTH> interessant le concept!![]()
zorglub59, cela te coûte à ce point de "dupliquer pour chaque objet le pointeur qui pointe sur la table descriptif"
Perso, je l'ai fait récemment en C++ dans ma mini-couche
Code c++ : 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 //--------------------------------------------------------------------------- // TCensureORPersistent - //--------------------------------------------------------------------------- /** * constructor create an empty objet */ /*constructor*/__fastcall TCensureORPersistent::TCensureORPersistent() { InternalCreate(); } //--------------------------------------------------------------------------- /** * constructor create an objet and fill published properties with dataset * @param[in] ADataSet contains fields associate with properties, reference not stored */ /*constructor*/__fastcall TCensureORPersistent::TCensureORPersistent(TDataSet* ADataSet) { InternalCreate(); SetPublishedPropertiesFromDataSet(ADataSet); } //--------------------------------------------------------------------------- void TCensureORPersistent::InternalCreate() { FMetaData = TCensureORPersistentManager::GetInstance()->InitializeClassMetaData(this->ClassType(), this->IsRelation()); }
Effectivement, chaque instance contient un FMetaData qui pointe sur un TSygalORPersistentClassMetaDataItem (ces objets étant stockés dans une TObjectList)
Après tout, le stockage de la référence est un gain sur la recherche dans le tableau associatif (InitializeClassMetaData en plus fonctionne en Lazy Loading)
Pour ta gestion des objets par ligne d'une table !
Tu pourrais le gérer autrement !
Mes objets soit utilise un DataSet avec deux modes
soit une copie vers les propriétés publiqués (le dataset peut être ainsi libérer)
soit on affecte un DataSource à l'objet, et dans ce cas, il fonctionne comme un Curseur !
Ainsi, j'ai une instance unique de l'objet, je me déplace dans le DataSet (typiquement relié à un DBGrid ou DBEdit), le contenu du l'objet évolue (parce qu'en interne, il utilise le DataSource)
En général, avec un DBGrid c'est en lecture seule (souvent, il manque même des Fields, je peux avoir que 2-3 Fields renvoyé par le SELECT alors qu'il y 10 published, l'écran n'a aucune raison d'aller voir d'autres champs, donc l'objet ne fait un SELECT que sur les colonnes nécessaires)
Pour modifier, cela passe par un autre Formulaire, le SELECT généré par l'objet recupère tous les champs ce coup-ci, idem l'objet utilise le DataSource connecté au DBEdit... et les UPDATE\INSERT seront générés par une méthode Save (oui, pas de Post sur le DataSet utilisé juste comme intermédiaire de saisie, vivement les LiveBindings)
A chaque fois l'objet est utilisé comme encaspulation du DataSource\DataSet
Par contre, dans d'autres sections du code où l'objet est utilisé pour des calculs, le DataSet est Ouvert, Copié et libéré !
Il faut éviter les collections de 10000 objets (d'autres sur le forum se sont mordus les doigts avec ces mauvaises pratiques !)
Sur ma précédent couche, j'ai fait un comportement proche mais encore plus poussé
Je lançais un Select sur l'objet qui renvoie une collection d'objet !
A ce moment, la collection ne contient aucun objet en réalité, juste le DataSet, un accesseur fournissait le même principe de curseur, tant que l'on utilisait l'objet en lecture, c'était le même, mais à partir du moment où l'objet était modifié ou que sa référence pouvait être utilisé par un autre objet, automatiquement cela créait un clone qui copiait les données et était conservé dans un cache
Dans la plupart des traitements, on avait donc que le curseur !
Le besoin d'instance spécifique étant finalement assez rare !
Exemple d'accesseur standardisé :
Code c++ : 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 //--------------------------------------------------------------------------- int __fastcall TSygalORPersistent::GetIntegerProp(int &AIntegerMember, const AnsiString &PropName/* = ""*/) { if (DataSource) AIntegerMember = FieldByPropertyName(PropName)->AsInteger; return AIntegerMember; } //--------------------------------------------------------------------------- void __fastcall TSygalORPersistent::SetIntegerProp(int &AIntegerMember, const int Value, const AnsiString &PropName/* = ""*/) { AIntegerMember = Value; if (DataSource) FieldByPropertyName(PropName)->AsInteger = AIntegerMember; }
Désolé, si je t'ai vexé !
Mais ta question à la lecture des articles cités, me semblait déjà être résolu par des méthodes basiques !
Mais j'avais bien compris, d'où l'utilisation d'un tableau associatif !
En fait je pensais à class property utilisant un Accesseur Abstrait redéfini pour chaque classe héritée !
Je ne comprends pas bien le problème et le besoin d'un code aussi retord pour quelques choses d'aussi simple !
Personnellement, n'ayant pas dépassé Delphi 7 (en ce moment, je suis en C++Builder 2007), il n'y aucune supprise à la lecture du Blog
Ce qui je trouve dingue c'est d'avoir autant de code (de malade de la partie 2) pour une chose aussi simple !
Reprenons l'exemple du blog
Version D7 / D2007 !
Pour D2007 ,c'est un code supposé, est-ce possible, je l'espère, cela reprend la section "Explicit per-class class variables implementation")
Dans le blog, je ne comprends pas pourquoi, il n'y a pas eu l'utilisation d'un Accesseur Get au lieu d'utiliser directement la variable !
Il manque pour moi, une variante importante avant d'aller dans un truc aussi dingue
Si c'est un projet professionnel qui maintient le code en cas d'erreur ou d'évolution du compilateur qui change la gestion du code ASM comme le 64bits ou tout simplement une modification de l'adresse de la VMT
Le code reste simple, certe un petit peu de "copier-coller" à chaque classe héritée, suffit de se faire un fichier exemple d'une classe minimale (le abstract forcera l'implémentation au pire)
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 type PDescriptor = ^TDescriptor; TDescriptor = class... TFruit = {abstract} class proctected class function DescriptorStorage: PDescriptor; virtual; abstract; public constructor Create; {$IFDEF D2007UP} class function GetDescriptor: TDescriptor; {$ELSE} class property Descriptor: TDescriptor read GetDescriptor; {$ENDIF} end; TApple = class(TFruit) {$IFDEF D2007UP} class var FDescriptor: TDescriptor; {$ENDIF} class function DescriptorStorage: PDescriptor; override; end; TOrange = class(TFruit) {$IFDEF D2007UP} class var FDescriptor: TDescriptor; {$ENDIF} class function DescriptorStorage: PDescriptor; override; end; implementation var {$IFNDEF D2007UP} _TApple_Descriptor: TDescriptor = nil; _TOrange_Descriptor: TDescriptor = nil; {$ENDIF} class function TFruit.GetDescriptor: TDescriptor; var Ptr: PDescriptor; begin Ptr := Self.DescriptorStorage; if not Assigned(Ptr^) then Ptr^ := TDescriptor.DescriptorRegistry.Items[Self.ClassType]; // ça je te laisse gérer ton Instance Registry et Factory de ton objet de Description Result := Ptr^; end; class function TApple.DescriptorStorage: PDescriptor; begin {$IFDEF D2007UP} Result := FDescriptor; {$ELSE} Result := @_TApple_Descriptor; {$ENDIF} end; class function TOrange.DescriptorStorage: PDescriptor; begin {$IFDEF D2007UP} Result := FDescriptor; {$ELSE} Result := @_TOrange_Descriptor; {$ENDIF} end;
Mieux vaut ça que trop de délire !
Personnellement, je suis allé parfois trop loin dans les délires, conséquence, aucun de mes collègues ne comprenaient le code (mon remplaçant s'arrache les cheveux !)
Je suppose que chaque classe métier aura sa propre unité (ce que j'ai fait pour les objets de chaque application utilisant la couche de persistance), le code pouvant être long, cela évite de tout mélanger (plusieurs classes pouvant être ensemble si fortement lié comme Commande et LigneDeCommande)
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
Partager