dump interface - John COLIBRI. | - mots clé:affichage mémoire des Classes et Interfaces Delphi
- logiciel utilisé: Windows XP, Delphi 6.0
- matériel utilisé: Pentium 1.400Mhz, 256 M de mémoire
- champ d'application: Delphi 1 à 6 sur Windows, Kylix
- niveau: programmeur Delphi
- plan:
1 - Introduction
Nous allons présenter ici l'organisation mémoire des classes et des interfaces. Ce programme permet de confirmer les descriptions fournies dans les manuels Delphi. Nous examinerons: - la structure d'un objet
- les VMT
- les IMT (méthodes des interfaces) et les thunks associés
Nous supposons que le lecteur est familier avec: - l'utilisation des Classes contenant des procédures Virtual
- la définition d'Interfaces et leur implémentation à l'aide de Classes
- le surtypage d'objet par AS
Le programme complet sera capable d'afficher les différentes parties de la
mémoire en expliquant les liens entre ces éléments. 2 - Architecture 2.1 - Un objet ayant des méthodes VIRTUAL Comme nous l'avons présenté dans le livre
programmation objet, l'organisation mémoire d'un programme comportant des classes ayant des méthodes virtuelles est la suivante:
- le code contient le code des méthodes, virtuelles ou non
- la mémoire contient une table stockant les adresses des méthodes virtuelles (la Virtual Method Table)
- chaque objet comporte, au début de la zone des données, un pointeur vers la table des méthodes virtuelles chaque objet contient un pointeur vers la table des méthodes virtuelles de sa classe.
Dans la figure qui suit, nous supposons que:
- notre classe comporte 3 méthodes virtuelles (figurant en vert clair dans le code présenté en violet)
- la VMT est en jaune et contient des pointeurs vers chacune des méthodes
- nous avons présenté deux objets (en bleu clair), chacun contenant un pointeur vers la VMT de cette classe
Si notre programme contenait une seconde classe héritant de celle-ci, il faudrait ajouter: - les trois méthodes virtuelles de la classe descendante (si elles sont différentes de celles de son ancêtre)
- la VMT de cette nouvelle classe pointant vers les méthodes virtuelles
- pour chaque descendant, les données, avec le pointeur vers la VMT de la classe descendante
2.2 - Une classe avec des Interfaces
Voyons à présent ce qui se passe lorsqu'une classe implémente des Interfaces. Nous supposons: - que nous avons défini quelques Interfaces
- que ces Interfaces sont implémentées par une Classe Delphi descendant de tInterfacedObject
Dans ce cas: - le code comporte les méthodes de la classe (celles qui implémentent les Interfaces et les autres)
- l'objet est structuré pour pouvoir accéder aux méthodes virtuelles ainsi qu'aux méthodes des Interfaces
Pour les méthodes virtuelles "pure" (n'implémentant pas une Interface), la mécanique est celle décrite ci-dessus. Pour les méthodes implémentant une méthodes spécifiée dans une Interface:
- l'objet contient pour chaque Interface un pointeur vers une table permettant d'accéder aux méthodes de l'Interface:
- la table correspondant à tInterfacedObject, correspondant à l'Interface iUnknown
- une table par Interface supplémentaire
- chaque variable de type Interface pointera vers ces zones de l'objet.
Si une méthode de la classe est à la fois Virtual et implémente une méthode
de l'Interface, elle doit pouvoir être appelée à la fois par une variable objet, et un variable Interface. Dans le code de cette méthode, nous pourrons accéder aux données membres de la Classe. Pour cela, Delphi passe toujours en
paramètre implicite: - soit l'objet
- soit la variable Interface qui a appelé la méthode
C'est le paramètres Self (dont l'usage est facultatif pour accéder aux champs ou aux méthodes de l'objet).
Or si nous appelons à partir d'un objet, le pointeur désigne le début de la zone de l'objet, mais si nous appelons à partir d'une variable Interface, ce pointeur a une valeur différente (il pointe un peu plus loin). Pour résoudre
cette difficulté, Delphi effectue un ajustement de la façon suivante: - pour l'objet, l'adresse de la VMT est la bonne
- pour une variable Interface, l'adresse dans l'IMT désigne un petit
fragment de code qui ajuste la valeur de la variable Interface pour que cette valeur soit la même que celle de l'objet. Ce code (thunk en anglais) se contente d'ajouter la différence d'adresse, et appelle ensuite la méthode
(par un JMP assembleur).
Supposons que nous ayons: - une Interface comportant deux méthodes
- qui est implémentée par une Classe
- qui descend de tInterfacedObject et qui implémente une Interface
- qui contient
- une méthode Virtual pure (pas liée à l'Interface)
- les deux méthodes de l'Interface, l'une Virtual l'autre non
La mémoire comporte successivement: - le code des 3 procédures (en vert clair)
- les thunk de relocations pour IUnknown, et pour les deux méthodes implémentant l'Interface (en bleu sombre)
- les IMT pour iUnknown et pour l'Interface (en jaune)
- la VMT (en jaune)
- les données d'un objet, avec les pointeurs vers la VMT, vers l'IMT de iUnknown, et l'IMT de l'Interface (en bleu clair)
- les variables globales (en rouge)
Notez que: - l'ordre des données n'est pas tout à fait respecté (par exemple les VMT \ IMT sont à des adresses plus faibles que le code), mais la figure a été
dessinée pour correspondre à l'ordre de la présentation et pour simplifier le dessin des liens. De plus les VMT \ IMT sont actuellement dans le code 386 (et non dans les données 386)
- les procédures correspondant aux 3 méthodes de iUnknown ne sont pas présentées et les thunks correspondants non plus (ni pour iUnknown, ni pour l'Interface). En fait:
- l'IMT de iUnknown contient 3 entrées, et 3 thunks correspondant
- l'IMT de l'Interface contient 5 entrées (3 pour iUnknown et 2 pour nos méthodes) et donc 5 thunks
Le dump mémoire ci dessous permettra de présenter exactement où se trouvent toutes ces données 3 - Affichage Brut 3.1 - La mise en place Nous allons utiliser: - deux Interfaces
- une Class qui les implémente
- un objet de base
- trois variables d'Interface obtenues à partir de l'objet par AS
3.2 - Les définitions Voici les Interfaces:
type i_compute= Interface
['{0C5C2FE0-DD74-11D4-A354-005004125613}']
function set_side(p_side: Integer): HResult; stdcall;
function compute_area(var pv_area: Integer): HResult; stdcall;
end;
i_journal= Interface
['{C444E017-65B0-4AEB-B05C-6D473F1BE1EC}']
procedure write_to_journal;
procedure write_and_display;
end; // i_journal |
Voici la Class
type ci_square= class(TInterFacedObject, i_compute, i_journal)
m_side: Integer;
m_area: Integer;
constructor create;
function set_side(p_side: Integer): HResult; stdcall;
function compute_area(var pv_area: Integer): HResult; stdcall;
procedure write_to_journal;
procedure write_and_display; virtual;
procedure analyze;
procedure analyze_virtual; Virtual;
Destructor Destroy; Override;
end; // ci_square |
et son implémentation:
constructor ci_square.create; begin
m_side:= $1111; m_area:= $2222;
end; // create
function ci_square.set_side(p_side: Integer): HResult;
begin m_side:= p_side;
end; // set_side
function ci_square.compute_area(var pv_area: Integer): HResult;
begin
pv_area:= m_side* m_side;
end; // compute_area
procedure ci_square.write_to_journal; begin
display('ok');
end; // write_to_journal
procedure ci_square.write_and_display; begin
display('write_and_display');
end; // write_and_display
procedure ci_square.analyze; begin
m_side:= $7777; end; // analyze
procedure ci_square.analyze_virtual; begin
m_side:= $8888; end; // analyze_virtual
Destructor ci_square.Destroy; begin
Inherited; end; // Destroy |
L'ensemble se trouve dans l'unité u_ci_area téléchargeable ci-dessous. 3.3 - Les adresses des méthodes Pour afficher la position des méthodes, nous présentons au format hexadécimal
l'adresse de chaque méthode obtenue par @:
display(f_integer_to_hex(@ ci_area.set_side)); |
Pour les méthodes qui nous intéressent, nous obtenons ainsi:
463548: 55 8B EC 83 : <= ci_square.create
463594: 55 8B EC 51 : <= ci_square.set_side
4635AC: 55 8B EC 51 : <= ci_square.compute_area
4635D0: 55 8B EC 51 : <= ci_square.write_to_journal
4635F0: 55 8B EC 51 : <= ci_square.write_and_display
463620: 55 8B EC 51 : <= ci_square.analyze
463634: 55 8B EC 51 : <= ci_square.analyze_virtual
463648: 55 8B EC 83 : <= ci_square.destroy
| 3.4 - Le Dump Simple Nous commençons par afficher les groupes de données par une primitive simple qui présente l'adresse et les données hexadécimales. La primitive de base
provenant de u_display_hex_2 est:
function f_display_hex(p_pt: Pointer; p_count, p_columns: Integer;
p_set_of_hex_type: t_set_of_hex_type): String; |
le dernier paramètres indiquant simplement le type de données à afficher (l'adresse, la partie hexadécimale, la partie ASCII) Les variables sont déclarées par:
var g_ci_square: ci_square= Nil;
g_i_compute: i_compute;
g_i_journal: i_journal;
g_i_unknown: iUnknown; | et créées par:
procedure TForm1.create_Click(Sender: TObject);
begin
g_ci_square:= ci_square.create;
g_i_unknown:= g_ci_square as iUnknown;
g_i_compute:= g_ci_square as i_compute;
g_i_journal:= g_ci_square as i_journal;
end; // create_Click | Nous pouvons connaître les valeurs des pointeurs d'objet et d'Interface par
display('object '+ f_integer_to_hex(Integer(g_ci_square)));
display('iUnk '+ f_integer_to_hex(Integer(g_i_unknown)));
display('i2 '+ f_integer_to_hex(Integer(g_i_journal)));
display('i1 '+ f_integer_to_hex(Integer(g_i_compute)));
| Ce qui nous donne:
object 008D.6DE8
iUnk 008D.6DF0
i2 008D.6DFC
i1 008D.6E00
| Les données de l'objet g_ci_square sont affichées par:
display(f_display_hex(g_ci_square, 4+ 4+ 4+ 8+ 2* 4, 4, k_hex_address_hex));
| soit:
8D6DE8: 34 35 46 00 : <
8D6DEC: 03 00 00 00 : <
8D6DF0: BC 11 40 00 : <
8D6DF4: 11 11 00 00 : <
8D6DF8: 22 22 00 00 : <
8D6DFC: 84 34 46 00 : <
8D6E00: 98 34 46 00 : <
| Nous vérifions bien que: - l'objet pointe vers le début de la zone. C'est là que se trouve le pointeur vers la VMT ($46.3534)
- ensuite nous trouvons le compte de référence 3: nous avons créé trois
variables Interface)
- puis nous trouvons le pointeur vers l'IMT de iUnknown, les données, les pointeurs vers les deux autres IMTs
Nous affichons la VMT en utilisant le pointeur situé au début de l'objet:
type t_vmt= array[0..0] of Integer;
t_pt_vmt= ^t_vmt;
procedure display_the_vmt(p_pt_object: Pointer);
var l_pt_vmt: t_pt_vmt; begin
l_pt_vmt:= t_pt_vmt(p_pt_object^);
display(f_display_hex(Pointer(Integer(l_pt_vmt)- k_vmt_entries* 4),
k_vmt_entries* 4+ 4, 4, k_hex_address));
end; // display_the_vmt ...
display_the_vmt(g_ci_square); | La constante k_vmt_entries indique combien de données se trouvent dans la VMT
avant nos procédures: Delphi y places les données pour être compatible avec COM, ainsi que des données propres à la Class: en -10 la taille des instances ($1C) et en -1 l'adresse de Destroy, etc. Le nombre de ces entrées est de 19.
La VMT est alors la suivante:
4634E8: 34 35 46 00 : 45F.<
4634EC: AC 34 46 00 : ¬4F.<
4634F0: 00 00 00 00 : ....<
4634F4: 00 00 00 00 : ....<
4634F8: 00 00 00 00 : ....<
4634FC: 00 00 00 00 : ....<
463500: 00 00 00 00 : ....<
463504: 00 00 00 00 : ....<
463508: 3C 35 46 00 : <5F.<
46350C: 1C 00 00 00 : ....<
463510: E8 11 40 00 : è.@.<
463514: 1C 3C 40 00 : .<@.<
463518: 2C 65 40 00 : ,e@.<
46351C: 38 65 40 00 : 8e@.<
463520: 30 3C 40 00 : 0<@.<
463524: 24 3C 40 00 : $<@.<
463528: 48 65 40 00 : He@.<
46352C: 88 39 40 00 : ê9@.<
463530: 48 36 46 00 : H6F.<
463534: F0 35 46 00 : ð5F.<
| Et pour terminer les IMT. Le contenu d'une IMT se fait comme pour les VMT. Dans le cas de i_compute, par exemple, nous obtenons:
i1 008D.6E00
463498: 63 34 46 00 : <
46349C: 6D 34 46 00 : <
4634A0: 77 34 46 00 : <
4634A4: 4F 34 46 00 : <
4634A8: 59 34 46 00 : <
| Nous y trouvons: - les adresses des thunks de QueryInterface, Addref et Release
- les adresses des thunks des deux méthodes
Et pour les thunks c'est un peu plus compliqué:
- au début se trouve le calcul de différence d'adresse
- puis se trouve l'adresse de JMP: si nous additionnons l'adresse du début du JMP nous trouvons l'adresse de la méthode
Voici le résultat de ce calcul pour le thunk de notre première méthode:
method_3 0046.3594
46344F: 83 44 24 04 : âD$.<
463453: E8 E9 3B 01 : èé;.<
463457: 00 00 ______ : ..__<
jmp 0046.3594
| Mentionnons que le thunk présenté ci-dessus correspond à une FUNCTION ayant l'attribut StdCall. Pour une PROCEDURE normale les valeurs sont différentes (nous le présenterons ci-dessous).
Et comme au début du thunk se trouve l'addition, nous pouvons même calculer l'adresse de l'objet à partir de l'Interface:
itf_to_object
itf 008D.6E00
+ delta 0000.00E8
= 008D.6EE8
object 008D.6DE8
| 3.5 - Désassemblage Pour ceux qui souhaitent vérifier la partie assembleur, nous avons regardé le code d'appel des méthodes à l'aide des objet ou des variables Interface. Voici les appels:
procedure TForm1.call_Click(Sender: TObject);
begin g_ci_square.set_side($3333);
g_i_compute.set_side($4444);
g_ci_square.write_and_display;
g_i_journal.write_and_display;
end; // call_Click | Nous avons alors placé un point d'arrêt et examiné le code assembleur (Voir |
Débug | Registers ). Voici pour les appels: et pour les thunks de relocation: 4 - Affichage Commenté
4.1 - Affichage Commenté Ayant compris l'organisation générale, nous avons ajouté à notre programme un affichage avec commentaire en face de chaque ligne. Nous avons juste ajouté une unité qui permet de recalculer les thunks:
unit u_vmt_imt; interface
type t_vmt= array[0..0] of Integer;
t_pt_vmt= ^t_vmt;
const k_vmt_entries= 19;
k_com_vmt_index= - 19; k_com_imt_index= - 18;
k_object_size_index= - 10; k_destroy_index= -1;
procedure display_vmt(p_pt_object: Pointer; p_virtual_method_count: Integer);
function f_imt_function_jump_value(p_pt_imt: Pointer; p_method_index: Integer): Integer;
function f_imt_procedure_jump_value(p_pt_imt: Pointer; p_method_index: Integer): Integer;
implementation
uses SysUtils, u_c_display, u_display_hex_2;
procedure display_vmt(p_pt_object: Pointer; p_virtual_method_count: Integer);
begin
display(f_display_hex(Pointer(p_pt_object^), 4* p_virtual_method_count, 4, k_hex_address_hex));
end; // display_vmt
const k_add_byte_ptr_esp= $04244483;
k_jump= $E9;
type t_function_thunk= packed record
m_add_byte_ptr_esp: longint;
m_delta: byte;
m_jump: Byte;
m_jump_to_function_start: longint;
end;
t_pt_function_thunk= ^t_function_thunk;
function f_pt_function_thunk(p_pt_imt: Pointer; p_method_index: Integer): t_pt_function_thunk;
begin (*$r-*)
Result:= t_pt_function_thunk(t_pt_vmt(p_pt_imt^)[p_method_index]);
(*$r+*) end; // f_pt_function_thunk
function f_is_imt_function_thunk(p_pt_imt: Pointer; p_method_index: Integer): Boolean;
begin
with f_pt_function_thunk(p_pt_imt, p_method_index)^ do
Result:= (m_add_byte_ptr_esp= k_add_byte_ptr_esp) and (m_jump= k_jump);
end; // f_is_imt_function_thunk
function f_imt_function_jump_value(p_pt_imt: Pointer; p_method_index: Integer): Integer;
var l_pt_function_thunk: t_pt_function_thunk;
l_function_address: Integer; begin
if f_is_imt_function_thunk(p_pt_imt, p_method_index)
then begin
l_pt_function_thunk:= f_pt_function_thunk(p_pt_imt, p_method_index);
// -- compute the absolute adress, starting from the start of "JMP XXX"
Result:= $0A
+ Longint(l_pt_function_thunk)
+ l_pt_function_thunk^.m_jump_to_function_start;
end
else begin
display(' not_thunk');
Result:= 0; end;
end; // f_imt_function_jump_value // -- procedure
const k_add= $C083;
type t_procedure_thunk= packed record
m_add: Word;
m_delta: Byte;
m_jump: Byte;
m_jump_to_procedure_start: longint;
end;
t_pt_procedure_thunk= ^t_procedure_thunk;
function f_pt_procedure_thunk(p_pt_imt: Pointer; p_method_index: Integer): t_pt_procedure_thunk;
begin (*$r-*)
Result:= t_pt_procedure_thunk(t_pt_vmt(p_pt_imt^)[p_method_index]);
(*$r+*) end; // f_pt_procedure_thunk
function f_is_imt_procedure_thunk(p_pt_imt: Pointer; p_method_index: Integer): Boolean;
begin
with f_pt_procedure_thunk(p_pt_imt, p_method_index)^ do
Result:= (m_add= k_add) and (m_jump= k_jump);
end; // f_is_imt_procedure_thunk
function f_imt_procedure_jump_value(p_pt_imt: Pointer; p_method_index: Integer): Integer;
var l_pt_procedure_thunk: t_pt_procedure_thunk;
l_procedure_address: Integer; begin
if f_is_imt_procedure_thunk(p_pt_imt, p_method_index)
then begin
l_pt_procedure_thunk:= f_pt_procedure_thunk(p_pt_imt, p_method_index);
// -- compute the absolute adress, starting from the start of "JMP XXX"
Result:= $08
+ Longint(l_pt_procedure_thunk)
+ l_pt_procedure_thunk^.m_jump_to_procedure_start;
end
else begin
display(' not_thunk');
Result:= 0; end;
end; // f_imt_procedure_jump_value end. |
Pour afficher les IMTs, nous utilisons, par exemple:
type t_call_type= (e_function, e_procedure);
procedure display_imt_entry(p_pointer: Pointer; p_method_index: Integer;
p_call_type: t_call_type;
p_title: String);
var l_address: Integer;
l_thunk_address: Integer;
l_code_address: Integer; begin
l_address:= Integer(p_pointer^)+ p_method_index* 4;
l_thunk_address:= Integer(Pointer(l_address)^);
// -- now find the code address in the thunk
case p_call_type of
e_function : l_code_address:= f_imt_function_jump_value(p_pointer, p_method_index);
e_procedure : l_code_address:= f_imt_procedure_jump_value(p_pointer, p_method_index);
else l_code_address:= 0;
end; // p_call_type
display(f_display_hex(Pointer(l_address), 4, 4, k_hex_address_hex)
+ '= '+ f_integer_to_hex(l_thunk_address)
+ ' ['+ f_integer_to_hex(l_code_address)+ ']'
+ ' '+ p_title);
end; // display_imt_entry
procedure display_i_journal; begin
display_line;
display('i2 '+ f_integer_to_hex(Integer(g_i_journal)));
display_imt_entry(Pointer(g_i_journal), 0, e_function, 'i_journal.QueryInterface');
display_imt_entry(Pointer(g_i_journal), 1, e_function, 'i_journal.Addref');
display_imt_entry(Pointer(g_i_journal), 2, e_function, 'i_journal.Release');
display_imt_entry(Pointer(g_i_journal), 3, e_procedure, 'i_journal.write_to_log');
end; // display_i_journal | Le reste des procédures est similaire. Vous en trouverez le détail dans le
projet téléchargeable. Le résultat pour notre Classe est:
iUn 008D.6DF0
4011BC: 9D 11 40 00 : <= 0040.119D [0040.6558] iUnknown.QueryInterface
4011C0: A7 11 40 00 : <= 0040.11A7 [0040.6580] iUnknown.Addref
4011C4: B1 11 40 00 : <= 0040.11B1 [0040.6594] iUnknown.Release
i2 008D.6DFC
463484: 31 34 46 00 : <= 0046.3431 [0040.6558] i_journal.QueryInterface
463488: 3B 34 46 00 : <= 0046.343B [0040.6580] i_journal.Addref
46348C: 45 34 46 00 : <= 0046.3445 [0040.6594] i_journal.Release
463490: 1D 34 46 00 : <= 0046.341D [0046.35D0] i_journal.write_to_log
i1 008D.6E00
463498: 63 34 46 00 : <= 0046.3463 [0040.6558] g_i_compute.QueryInterface
46349C: 6D 34 46 00 : <= 0046.346D [0040.6580] g_i_compute.Addref
4634A0: 77 34 46 00 : <= 0046.3477 [0040.6594] g_i_compute.Release
4634A4: 4F 34 46 00 : <= 0046.344F [0046.3594] g_i_compute.set_side
4634A8: 59 34 46 00 : <= 0046.3459 [0046.35AC] g_i_compute.compute_area
methods
463548: 55 8B EC 83 : <= ci_square.create
463594: 55 8B EC 51 : <= ci_square.set_side
4635AC: 55 8B EC 51 : <= ci_square.compute_area
4635D0: 55 8B EC 51 : <= ci_square.write_to_journal
4635F0: 55 8B EC 51 : <= ci_square.write_and_display
463620: 55 8B EC 51 : <= ci_square.analyze
463634: 55 8B EC 51 : <= ci_square.analyze_virtual
463648: 55 8B EC 83 : <= ci_square.destroy
ci_square vmt 0046.3534
46350C: 1C 00 00 00 : <= ci_area_vmt[-$A0] object_size
...
463530: 48 36 46 00 : <= ci_area_vmt[-$01] vm 0 Destroy
463534: F0 35 46 00 : <= ci_area_vmt[ $00] vm 0 write_and_display
463538: 34 36 46 00 : <= ci_area_vmt[ $01] vm 0 analyze_virtual
object
8D6DE8: 34 35 46 00 : < 0046.3534 <= pt_vmt
8D6DEC: 03 00 00 00 : < 0000.0003 <= reference_count
8D6DF0: BC 11 40 00 : < 0040.11BC <= pt_imt_iUnknown
8D6DF4: 11 11 00 00 : < 0000.1111 <= m_side (= $1111)
8D6DF8: 22 22 00 00 : < 0000.2222 <= m_area (= $2222)
8D6DFC: 84 34 46 00 : < 0046.3484 <= pt_imt_journal
8D6E00: 98 34 46 00 : < 0046.3498 <= pt_imt_compute
object 008D.6DE8
iUnk 008D.6DF0
i2 008D.6DFC
i1 008D.6E00
| 5 - Améliorations Notre programme avait uniquement pour objectif de vérifier l'organisation mémoire des Classes et Interfaces Delphi.
Ce programme a été spécialisé pour analyser notre Classe particulière. Nous pourrions généraliser ce programme pour afficher le contenu mémoire correspondant à n'importe quelle Classe ou Interface qui serait fournie en paramètre.
6 - Télécharger le source Nous avons placé le projet dans des .ZIP qui comprend: - le .DPR, la forme principale, les formes annexes éventuelles
- les fichiers de paramètres (le schéma et le batch de création)
- dans chaque .ZIP, toutes les librairies (Unit) nécessaires à chaque projet (chaque .ZIP est autonaume)
Ces .ZIP contiennent des chemins RELATIFS. Par conséquent:
- créez un répertoire n'importe où sur votre machine
- placez le .ZIP dans ce répertoire
- dézippez et les sous-répertoires nécessaires seront créés
- compilez et exécutez
Au niveau sécurité: - les .ZIP:
- ne modifient pas votre PC (pas de changement de la Base de Registre, d'écrasement de DLL ou autre .BAT). Aucune modification de répertoire ou de contenu de répertoire ailleurs que dans celui où vous dézippez
- ne contiennent aucun programme qui s'exécuterait à la décompression (.EXE, .BAT, .SCR ou autre .VXD) ou qui seraient lancés plus tard (reboot)
- passez-les à l'antivirus avant décompression si vous êtes inquiets.
- les programmes ne changent pas la base de registre et ne modifient aucun autre répertoire de votre machine
- pour supprimer le projet, effacez simplement le répertoire.
Voici le .ZIP:
- dump_interface.zip: le projet d'affichage des Classes et Interfaces (3 K)
Ce zip contient - le projet
- l'unité u_ci_area avec la Classe et les deux Interfaces
- l'unité u_vmt_imt
- l'unité u_display_hex_2
- les autres unités auxiliaires (affichage etc)
Comme d'habitude: - nous vous remercions de nous signaler toute erreur, inexactitude ou
problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections qui en résulteront pourront aider les prochains lecteurs
- tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même les bienvenus à jcolibri@jcolibri.com.
- plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :
- et si vous avez apprécié cet article, faites connaître notre site, ajoutez un lien dans vos listes de liens ou citez-nous dans vos
blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.
7 - Conclusion Notre petit projet nous permet donc de vérifier que
- une variable objet est un pointeur. Ce pointeur pointe vers la zone des donnée, contenant au début le pointeur vers la VMT, puis les données membre de l'objet. Cette architecture est fort classique, et n'a pas bougé depuis 1991 !
- une variable de type Interface est un pointeur. Ce pointeur pointe à l'intérieur de la zone de l'objet qui implémente la variable Interface. Cette zone est un pointeur vers la table des méthodes correspondant à
l'Interface. D'où l'expression "un objet COM est un pointeur de pointeur désignant une VMT". Sauf que:
- la "VMT du COM" n'est pas la même que celle de l'objet: c'est une IMT
- il y a autant d'IMT que d'Interfaces implémentées par notre Class
- chacune de ces IMT reprend toutes les entrées de ses Interfaces ancêtre, et au minimum les 3 entrées de iUnknown
- le contenu des ces IMT ne sont pas des adresses de méthodes, mais des adresses de thunk de relocation qui désignent, eux, les méthodes
- le décalage contenu dans le thunk de relocation permet éventuellement de
calculer un pointeur vers l'objet à partir d'une variable Interface
- les IMT sont situés avant la VMT
- la VMT contient la taille de chaque instance
- contrairement à ce que je croyais:
- les méthodes d'une Interface ne sont pas obligatoirement et systématiquement implémentées sous forme de méthodes Virtual. L'Interface peut être considérée comme constituée de méthodes Virtual
Abstract, mais l'implémentation contient des méthodes qui sont Virtual ou non
- même pour les Interfaces COM, les méthodes ne sont pas systématiquement
des Function ayant pour résultat hResult et comme attribut StdCall. Nous pouvons utiliser des Procédures et le mode d'appel est indifférent.
8 - Références - Delphi Component Design
Danny THORPE - Addison Wesley Isbn 0-201-46136-6 - 1997 - Delphi COM
Eric HARMON - New Riders Isbn 1-57870-221-6 - 2000
- Programmation Objet Pascal
John COLIBRI - L'Institut Pascal - 1989
- Client et Serveur COM
John COLIBRI - Pascalissime 66 - Avril 1997 9 - L'auteur
John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le
conseil (composants, architecture, test) et la
formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et
UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client. |