Articles   Members Online:
-Article/Tip Search
-News Group Search over 21 Million news group articles.
-Delphi/Pascal
-CBuilder/C++
-C#Builder/C#
-JBuilder/Java
-Kylix
Member Area
-Home
-Account Center
-Top 10 NEW!!
-Submit Article/Tip
-Forums Upgraded!!
-My Articles
-Edit Information
-Login/Logout
-Become a Member
-Why sign up!
-Newsletter
-Chat Online!
-Indexes NEW!!
Employment
-Build your resume
-Find a job
-Post a job
-Resume Search
Contacts
-Contacts
-Feedbacks
-Link to us
-Privacy/Disclaimer
Embarcadero
Visit Embarcadero
Embarcadero Community
JEDI
Links
Inside Delphi's Classes and Interfaces Part II Turn on/off line numbers in source code. Switch to Orginial background IDE or DSP color Comment or reply to this aritlce/tip for discussion. Bookmark this article to my favorite article(s). Print this article
12-Nov-02
Category
Algorithm
Language
Delphi 5.x
Views
91
User Rating
No Votes
# Votes
0
Replies
0
Publisher:
DSP, Administrator
Reference URL:
DKB
			Author: Ezra Hoch

You've probably used classes & interfaces more than once in your delphi programs. 
Did you ever dtop to think how delphi implements this creatures ? 

Answer:

Inorder to understand this article, you must read the previous article (Inside 
Delphi's Classes and Interfaces Part I). 
In this article we'll finish covering Delphi's implementation of Interfaces, and 
review a few usefull conclusions. 

Let's start with an indepth example : 

1   type
2   
3     IInterface1 = interface
4       procedure ActA;
5       procedure ActB;
6     end;
7   
8     IInterface2 = interface(IInterface1)
9       procedure ActC;
10      procedure ActD; stdcall;
11    end;
12  
13    TSampleClass = class(TInterfacedObject, IInterface1, IInterface2)
14      procedure ActA;
15      procedure ActB;
16      procedure ActC;
17      procedure ActD; stdcall;
18    end;
19  
20  var
21    Interface1: IInterface1;
22    Interface2: IInterface2;
23    Sample: TSampleClass;
24  begin
25    Sample := TSampleClass.Create;
26    Interface1 := Sample;
27    Interface2 := Sample;
28    Interface1.ActA;
29    Interface1.ActB;
30    Interface2.ActA;
31    Interface2.ActB;
32    Interface2.ActC;
33    Interface2.ActD;
34  end;


Instead of looking at the compiled code for this example, I'll simlpy note the 
interesting aspects of it. First, when assigning a value to Interface1, we'd expect 
delphi to take the value of  what 'Sample' points to and add a specific amount 
($10) and be done with it. When assigning a value to Interface2, we'd expect delphi 
to do the same, just add a smaller amount ($0C) because the interfaces are stored 
in memory from the last to the first. 
But delphi doesn't do that. It assignes both Interface1 AND Interface2 the value 
that 'Sample' points to plus $0C. That's because IInterface2 inherites from 
IInterface1. Therefor,  IInterface2 includes IInterface1. Hence, any call to 
Interface1, will actually be executed through IInterface2's method list. 

Second, when we call Interface1.ActA, it calles the 4th (every interface inherites 
from IUnknown) method on IInterface2's method list (because IInterface2 inherites 
from IInterface1). When we call Interface1.ActB it calles the 5th method on 
IInterface2's method list. When we call Interface2.ActA it calles the 4th method on 
IInterface2's method list, just the same as Interface1.ActA. That's because 
IInterface2 inherites from IInterface1. 

Third, when we call Interface2.ActD delphi addes one additional instruction before 
calling the 7th method of IInterface2. That's because we've declared a different 
convention call to the method (stdcall). Notice that all of IUnknown's methods are 
defined with the stdcall directive. 

The structor of an interface's method list always follows the following rule : 

First Method 
. 
. 
Last Method 
The parent's interface's method list 

In our case, IInterface2's method list is as follows : 
   
35  ActC
36  ActD
37  // IInterface1's method list
38     ActA
39     ActB
40     // IUnknown's method List
41        QueryInterface
42       _AddRef
43       _Release


NOTE : The structor above is how the methods' code is organized in memory. The 
first entry in any interface's method list will belong to QueryInterface (the first 
method of IUnknown) but it will point to a place in memory (the implementation of 
that specific interface's QueryItnerface method) that is higher than the 
interfaces' own methods' implementation - as shown in the structor above. In our 
case, IInterface2's QueryInterface's implementation is higher in memory than 
IInterface2's ActB's implementation, which is higher in memory than ActD's 
implementation. Thou ActD is the 7th entry, ActB is the 5th entry and 
QueryInterface is the 1st entry in IInterface2's method list. 

To fully understand what happens when delphi calls an interface's method, lets have 
a look at the compiled method list of IInterface2 in the example above. The 
following code is an exact copy of the compiled code (except for the comments) : 

44  // ActC
45  add eax, -$0C
46  jmp TSampleClass.ActC
47  // ActD
48  add dword ptr[esp + $04], -$0C
49  jmp TSampleClass.ActD
50  // ActA
51  add eax, -$0C
52  jmp TSampleClass.ActA
53  // ActB
54  add eax, -$0C
55  jmp TSampleClass.ActB
56  // QueryInterface
57  add dword ptr[esp + $04], -$0C
58  jmp TInterfacedObject.QueryInterface
59  // _AddRef
60  add dword ptr[esp + $04], -$0C
61  jmp TInterfacedObject._AddRef
62  // _Release
63  add dword ptr[esp + $04], -$0C
64  jmp TInterfacedObject._Release


As you remember, an object's method is actually a regular function/procedure that 
accepts as a parameter an instance of the method's class. As you can notice, before 
each call to the real method ('TSampleClass.ActD' for example) there is one line of 
code that changes the value of either 'eax', or 'dword ptr [esp + $04]', depending 
on the calling convention. As you can notice, in all cases we subtract $0C form a 
variable. But, why 12 ($0C = 12) ? That's because this interface (IInterface2) is 
in the 3rd (FRefcount, IUnknown are before it) place after the pointer to VMT of 
the clasS TSampleClass. Therefore, the value of any instance of IInterface2 of 
TSampleClass (Interface2 for example) is actually the value of the pointer to that 
class' instance plus 12. 

Here is another example that will help understand the section above. The following 
code continues the defenitions from the above code : 

65  type
66  
67    IAnotherInterface = interface
68      procedure ActE;
69    end;
70  
71    TAnotherSample = class(TInterfacedObject, IInterface2, IAnotherInterface)
72      procedure ActA;
73      procedure ActB;
74      procedure ActC;
75      procedure ActD; stdcall;
76      procedure ActE;
77    end;
78  
79  var
80    Interface2: IInterface2;
81  begin
82    Interface2 := TAnotherSample.Create;
83    Interface2.ActC;
84  end;


Now, let's compare the entry for this example's IInterface2 and the previous' one : 

IInterface2 of TAnotherSample:
add eax, -$10
jmp TAnotherSample.ActC

IInterface2 of TSampleClass:
add eax, -$0C
jmp TSampleClass.ActC

There are two obvious changes : 

The actuall function that is called (either TAnotherSample.ActC or 
TSampleClass.ActC)   
The amount that 'eax' is changed by. Notice that when calling IInterface2 of 
TAnotherSample, 'eax' is changed by 16 ($10 = 16) as opposed to being changed by 
12. That's because on TAnotherSample, the IInterface2 is the second interface in 
the instance's structor in memory, and therefor it is "farther away" from the 
instance itself and needs to be changed by additional 4 bytes. 

And now to some usefull sutff : 

First, if you want to check if 2 (or more) interface variables are of the same 
instance, you cannot simply compare them, even if they are of the same type. You 
must QueryInterface them to a single interface type, and then compare. As a general 
rule, if you want to compare interfaces, QueryInterface them to IUnknown and then 
compare. 

Example : 

85  type
86  
87    IBooA = interface
88    end;
89  
90    IBooB = interface
91    end;
92  
93    TBoo = class(TInterfacedObject, IBooA, IBooB)
94    end;
95  
96  var
97    Boo: TBoo;
98    BooA: IBooA;
99    BooB: IBooB;
100 begin
101   Boo := TBoo.create;
102   BooA := Boo;
103   BooB := Boo;
104 
105   // This won't complie
106   if BooA = BooB then
107   begin
108     Beep;
109   end;
110 
111   if Integer(BooA) = Integer(BooB) then
112   begin
113     // will never get here
114     Beep;
115   end;
116 
117   if IUnknown(BooA) = IUnknown(BooB) then
118   begin
119     // will never get here
120     Beep;
121   end;
122 
123   // the 'as' word is the same as QueryInterface when acting on interfaces
124   if (BooA as IUnknown) = (BooB as IUnknown) then
125   begin
126     // Will always get here
127     Beep;
128   end;
129 end;


Explaination : The first comparing won't complie, becuase BooA and BooB are of 2 
different types. The Second and third comparings will complie but never return 
true. That's because type casting doesn't change the value of the variable that's 
being type casted. It only allows the complier to complie the code though there are 
two different types involved. Hence, if BooA is different from BooB, comparing them 
will never return true, no matter what type casting is done to them. 
But why do BooA and BooB have different values ? They were both assigned using the 
":= Boo;" statment. The answer is simple. Remeber that I said that an interface's 
variable's value is actually the value of the instance itself (or at least the 
value of the pointer to the instance) plus a different number for each interface ? 
In our case, BooA is the same as what Boo points to, added 16. And BooB is the same 
as what Boo points to, added 12. That's why BooA and BooB are not that same. 
The Forth comparing actually works. That's because if an interface is from the same 
type, then comparing it to an interface of that type will always return the 
expected result (if both interfaces were aquired via QueryInterface, not by type 
casting). That's because if they are of the same type, then the difference between 
them and the instance is the same. And if they are of the same instance, then they 
must be equal. 
That is, each interface is equal to it's instance + a specific Delta (the Delta 
depeneds on the interface). In other words, Interface = Instace + Delta. If 
'Instance' is the same for both interfaces, and the 'Delta' is the same (cause they 
are of the same interface type), then both interfaces must be equal. 

Note : This is the way delphi works, for good and for bad. You should take this in 
mind when writing code for propertys of interface type. The following code wouldn't 
work properly : 

130 TSample = class
131 private
132   FData: IUnknown;
133   procedure SetData(Value: IUnknown);
134 protected
135   procedure Changed; virtual; abstract;
136 public
137   property Data: IUnknown read FData write SetData;
138 end;
139 
140 procedure TSample.SetData(Value: IUnknown);
141 begin
142   // This is incorrect.
143   if Value <> FData then
144   begin
145     FData := Value;
146     Changed;
147   end;
148 end;


It might seem that this code should work, but it might not work when someone would 
assgin the property 'Data' with an IUnknown retreived by a type cast. The correct 
code should be : 
149 
150 procedure TSample.SetData(Value: IUnknown);
151 begin
152   if (Value as IUnknown) <> (FData as IUnknown) then
153   begin
154     FData := Value;
155     Changed;
156   end;
157 end;


Second, each interface you declare that a class implements (with exception of 
interfaces that inherite from other interfaces) means that each instance of that 
class will take up 4 more byte of memory. That might seem like nothing (and 
probably is) except for one case. Consider the following code : 

158 IInterfaceA = interface
159 end;
160 
161 IInterfaceB = interface
162 end;
163 
164 TSampleClass1 = class(TInterfacedObject, IInterfaceA)
165 end;
166 
167 TSampleClass2 = class(TSampleClass1, IInterfaceA, IInterfaceB)
168 end;


It would seem that each instance of TSampleClass1 should take up 16 bytes, and each 
instance of TSampleClass2 should take up 20 bytes (4 bytes more, because it 
supports one more interface). That is not true. Each instance of TSampleClass1 does 
take up 16 byte. But, each instance of TSampleClass2 takes up 24 bytes ! That's 
because delphi creates an interface entry even for interfaces that are already 
implemented by parent classes. 
The solution to this is simple. Just remove the decleration of IInterfaceA from 
TSampleClass2. This will not change the fact that TSampleClass2 implements 
IInterfaceA, cause TSamlpeClass2 inherites from TSamlpeClass1, which implements 
IInterface1. This wouldn't have happened if IInterfaceB was a decendant of 
IInterfaceA. 
   
This might add up to quit alot if you do your inheritence improporely. For example 
: 

169 TSampleClass1 = class(TInterfacedObject, IUnknown)
170 end;
171 
172 TSampleClass2 = class(TSampleClass1, IUnknown)
173 end;
174 
175 TSampleClass3 = class(TSampleClass2, IUnknown)
176 end;
177 
178 TSampleClass4 = class(TSampleClass3, IUnknown)
179 end;
180 
181 TSampleClass5 = class(TSampleClass4, IUnknown)
182 end;


Each instance of TSampleClass5 takes up 32 bytes of memory, though it has no real data (except for FRefCount of TItnerfacedObject). 

			
Vote: How useful do you find this Article/Tip?
Bad Excellent
1 2 3 4 5 6 7 8 9 10

 

Advertisement
Share this page
Advertisement
Download from Google

Copyright © Mendozi Enterprises LLC