Analysis of the structure and destruction in Delphi
1 Object model in Delphi: 2
1.1 What does the object name mean? 2
1.2 Where are the objects stored? 2
1.3 What is stored in the object? How are they stored? 3
2 Constructor and creating object 5
2.1 What is a constructor? ("Special" class method) 5
2.2 The whole process of creating an object 5
2.3 Alternative usage of constructors (using class references to implement the polymorphism of constructors) 6
3 Destructor and Destruction Object 7
3.1 What is a destructor ("naturally" virtual method) 7
3.2 The whole process of object destruction 7
3.3 destroy, free, freeAndNil, release usage and difference 7
4 VCL construction & destructuring architecture 8
5 Correctly use constructors and destructors 9
Analysis of the structure and destruction in Delphi
Abstract: Through the study of VCL/RTL, this paper analyzes the implementation mechanism of constructors and destructors and the architecture of objects in VCL, and explains how to correctly create and release objects.
Keywords: construct, destruct, create objects, destroy objects, heap, stack, polymorphism.
Author: majorsoft
question
What is the implementation mechanism of constructors and destructors in Delphi? What is the difference from C++? How to correctly create and release objects?
Solution
How to correctly use construction and destructuring is a common problem we encounter in the process of using Delphi. There are many related posts in the Oriented Pascal column in the Monopoly Forum (see related questions for details). I have also encountered similar problems. , The following is to understand the implementation mechanism of constructors and destructors through the study of VCL/RTL source code.
1 Object model in Delphi:
1.1 What does the object name mean?
Unlike C++, the object name (also called a variable) in Delphi represents the reference of an object, and does not represent the object itself, which is equivalent to a pointer to the object, which is called the "object reference model". As shown in the figure:
Obj (object name) The actual object
Vmt entry address
Data Members
Figure 1 Object name refers to an object in memory
1.2 Where are the objects stored?
Each application divides the memory allocated to it to run into four areas:
Code area
Global data area
Heap area
Stack area
Figure 2 Program memory space
Code area: Store program code in the program, including all function codes
Global data area: Stores global data.
Heap area: also known as "free storage area", which stores dynamic data (including objects and strings in Delphi). Scope is the entire life cycle of the application until the destructor is called.
Stack area: also known as "automatic storage area" to store local data in a program. In C++, local variables are actually variables of type auto. The scope is inside the function, and the system immediately recycles the stack space after the function is called.
In C++, objects can be created on the heap, or on the stack, or in the global data. Therefore, C++ has four types: global objects, local objects, static objects and heap objects. The theory of object. In Delphi, all objects are built on the heap storage area, so the Delphi constructor cannot be called automatically, but must be called by the programmer himself (drag the component in the designer, and the object is created by Delphi) . The following program explains the difference between creating objects in Delphi and C++:
In Delphi:
PRocedure CreateObject(var FooObjRef:TFooObject);
Begin
FooObjRef:=TfooObject.create;
//Called by the programmer, after the procedure is called, the object still exists. There is no need to copy it.
FooObject.caption='I am created in stack of CreateObject()';
End;
And in C++:
TfooObject CreateObject (void);
{
TfooObject FooObject;//Create local objects
// static TfooObject FooObject;//Create static local object
//The object is automatically created by calling the default constructor, and the object is created in the function stack at this time.
FooObject.caption='I am created in stack of CreateObject()';
return FooObject;
//The object is copied when returning, and the original created object will be automatically destroyed after the function call is finished}
TfooObject fooObject2;//Create global object.
void main();
{ TFooObject* PfooObject=new TfooObject;
//Create a heap object. After the function is called, the object still exists and does not need to be copied. }
1.3 What is stored in the object? How are they stored?
Unlike C++, objects in Delphi only store the entry addresses of data members and virtual method tables (vmts), but do not store methods, as shown in the figure:
Object virtual method table code segment
Vmt address
name:String
width:integer;
ch1:char;
…
Proc1
Func1
…
procn
funcn
…
Figure 3 The structure of the object...
Maybe you have some questions about the above statement, please see the following procedure:
TsizeAlignTest=class
Private
i:integer;
ch1,ch2:char;
j:integer;
public
procedure showMsg;
procedure virtMtd; virtual;
end;
memo1.Lines.Add(inttostr(sizeTest.InstanceSize)+':InstanceSize');
memo1.Lines.Add(inttostr(integer(sizeTest))+'<-start Addr');
memo1.Lines.Add(inttostr(integer(@(sizeTest.i)))+'<-sizeTest.i');
memo1.Lines.Add(inttostr(integer(@(sizeTest.ch1)))+'<-sizeTest.ch1');
memo1.Lines.Add(inttostr(integer(@(sizeTest.ch2)))+'<-sizeTest.ch2');
memo1.Lines.Add(inttostr(integer(@(sizeTest.j)))+'<-sizeTest.j');
The results show:
16:InstanceSize
14630724<-start Addr
14630728<-sizeTest.i
14630732<-sizeTest.ch1
14630733<-sizeTest.ch2
14630736<-sizeTest.j
The data member and the vmt entry address occupy 16 bytes! The two member functions showMsg and virtMtd do not occupy space at all in the object's storage area.
So where are the member functions stored? Since Delphi is based on RTL (Runtime Type Library), all member functions are stored in the class. Member functions are actually method pointers, which point to the entry address of the member function, and all objects of the class share these member functions. So how to find the entry address of the member function? For static functions, this work is done by the compiler. During the compilation process, the entry address of the member function is directly found in the class according to the type of the class object reference/pointer (the object does not need to exist at this time). That is, the so-called static binding; for virtual methods (including dynamic methods), the virtual method table of the object at runtime table vmt entry address (that is, the first four bytes of the object, the object must exist at this time. Otherwise, it will cause a pointer access error) to find the entry address of the member function, which is the so-called dynamic binding.
Notice
As mentioned above, all member functions are stored in the class, and actually include the virtual method table Vmt. From Delphi's code autocomplete function (it depends on the compilation information), we can see that after we enter the object name and then enter ".", Delphi recompiles it, listing all data members and all Static methods, all virtual methods, all class methods, all constructors and destructors, you can try it out if this is the case.
Vmt entry address of class virtual method table
Data member template information
Static method table, etc.
Virtual method table vmt
Object
Vmt entry address
Data Members
The above program also demonstrates the alignment of object data members (physical data structure), which is aligned in 4 bytes (windows default alignment), as shown in the figure below:
Vmt Entrance Addr
i
Ch1Ch2
j
2 Constructor and creating objects
2.1 What is a constructor? ("Special" method)
From the semantics of OO (object-oriented) idea, the constructor is responsible for the creation of the object, but in terms of the implementation of the OOP language, whether Delphi or C++, the constructor only does the initialization of the object (including calling the internal sub-objects). constructor) is not responsible for the entire process of creating objects (refer to 2.2).
In addition, unlike in C++, Delphi defines another method type for the constructor (mkConstructor, see line /Source/RTL/Common/typInfo.pas, line 125 in the Delphi installation directory), which we can understand as "Special" class method. It can only be called through the class (class name/class reference/class pointer), while general class methods can be called through both classes and objects; another special thing is that the built-in self parameter in the constructor points to the object , and in class methods, self points to the class, where we usually initialize its data members to make it a real object, which is all thanks to the self parameter.
By default, the constructor is a static function. We can set it as a virtual method and override it in its derived class. This can realize the polymorphism of the constructor (see 2.4), or it can also be done. It is overloaded, creates multiple constructors, and can also directly overlay the constructor of the parent class in the derived class. In this way, the constructor of the parent class is blocked in the derived class, and these technologies are used in VCL , forming a "architecture" of structure & destructuring (see 4)
2.2 The entire process of object creation
The complete process of creating an object should include allocating space, constructing physical data structures, initializing, and creating internal sub-objects. As mentioned above, the constructor is only responsible for initialization and calling the constructor of the internal sub-objects. So how do you complete the allocation of space and construct physical structures? This is because the compiler is doing extra things, we don't know. When compiling to the constructor, before constructing the function, a line of "call @ClassCreate" assembly code will be inserted. It is actually the _ClassCreate function in the system unit. Let's take a look at some of the source code of the _ClassCreate function:
function _ClassCreate(AClass: TClass; Alloc: Boolean): TObject;
asm
{ -> EAX = pointer to VMT }
{ <- EAX = pointer to instance }
…
CALL dWord ptr [EAX].vmtNewInstance //Calling NewInstance
…
End; {/Source/RTL/sys/system.pas, line 8939}
VmtNewInstance=-12; It is the offset of the NewInstance function in the class, then "CALL dword ptr [EAX].vmtNewInstance" is actually calling NewInstance. Please see TObject.NewInstance: Source code:
class function NewInstance: TObject; virtual;
class function TObject.NewInstance: TObject;
Begin
Result := InitInstance(_GetMem(InstanceSize));
end;
"InitInstance(_GetMem(InstanceSize))" calls three functions in turn:
1) First call InstanceSize() to return the object size of the actual class
class function TObject.InstanceSize: Longint; //Equivalent to a virtual method
Begin
Result := PInteger(Integer(Self) + vmtInstanceSize)^;//Return the object size of the actual class
end;
2) Call _GetMem() to allocate the Instance-sized memory on the heap and return the object's reference
3) Call InitInstance() to construct the physical data structure and set the default value of the member, such as setting the value of the integer data member to 0, setting the pointer to nil, etc. If there is a virtual method, assign the entry address of the virtual method table Vmt to the first four bytes of the object.
After calling NewInstance, the object at this time only has an "empty shell" and no actual "content", so it is necessary to call a customized constructor to initialize the object meaningfully, and call the constructor of the internal sub-object , enable objects in the program to truly reflect objects in the real world. This is the whole process of object creation.
2.3 Alternative usage of constructors (using class references to implement the polymorphism of constructors)
In Delphi, classes are also stored as objects, so there is also polymorphism. It is implemented with class references and virtual class methods, which provides class-level polymorphism implementation. Set the class method as a virtual method, override it in its derived class, and then call it through the reference/pointer of the base class, so that the object is constructed based on the class reference/pointer pointing to the actual class. Please see the following program:
TmyClass=class
constructor create;virtual;
end;
Ttmyclass=class of TmyClass;//Class reference of base class
TmyClassSub=class(TmyClass)
constructor create; override;
end;
procedure CreateObj(Aclass:TTMyClass;var Ref);
Begin
Tobject(Ref):=Aclass.create;
//ref is of no type and is incompatible with any type, so it must be explicitly cast when used (cast)
//Aclass is a class reference, a unified function interface, and different implementations.
//It will construct objects based on the actual class referenced/pointed to by Aclass.
End;
…
CreateObj(TmyClass,Obj);
CreateObj(TmyClassSub,subObj);
3 Destructor and destroy objects
3.1 What is a destructor ("naturally" virtual method)
Semantically speaking, the destructor is responsible for destroying objects and releasing resources. In Delphi, synonymous.
Delphi also defines a method type for the destructor (mkConstructor, see line /Source/RTL/Common/typInfo.pas, 125 in the Delphi installation directory). In VCL, it is actually a "natural" virtual Method, defines "destructor Destroy; virtual;" in all ancestors of VCL class - Tobject. Why does VCL do this? Because it ensures that the object can be correctly destructed in polymorphic situations. If virtual methods are not used, you may only destruct the base class subobject, resulting in the so-called "memory leak". Therefore, in order to ensure the correct destructor, the destructor needs to add an override declaration.
3.2 The entire process of object destruction
First destroy the derived class subobject, and then destroy the base class subobject.
hint
In a derived class, the base class sub-object refers to the part inherited from the base class, and the child object in the derived class refers to the newly added part.
3.3 destroy, free, freeAndNil, release usage and differences
destroy: virtual method
Release memory, declare it as virtual in Tobject, usually override it in its subclass, and add the inherited keyword to ensure that the derived class object is correctly destroyed;
But destroy cannot be used directly, why?
If an object is nil, we still call destroy, an error will occur. Because destroy is a virtual method, it needs to find the entrance address of the virtual method table Vmt based on the first four bytes in the object, so as to find the entrance address of destroy, so the object must exist at this time. But free is a static method. It only needs to be determined based on the type of object reference/pointer. Even if the object itself does not exist, it is fine. There is an operation in free to determine whether the object exists, so using free is safer than using destroy.
2)free: Static method
Test whether the object is nil, and destroy is called if it is not nil. Here is the Delphi code for free:
procedure Tobject.Free;
Begin
if Self <> nil then
Destroy;
end;
Isn’t it great to learn from one’s strengths and one’s weaknesses?
However, calling Destroy just destroys the object, but does not set the object's reference to nil, which needs to be done by the programmer. However, since Delphi5, a freeAndNil is provided in the sysUtils unit.
3) freeAndNil; general method, non-object method, non-class method.
FreeAndNil definition in SysUtils unit
procedure FreeAndNil(var Obj);
var
Temp: TObject;
Begin
Temp := TObject(Obj);
Pointer(Obj) := nil;
Temp.Free;
end;
We recommend that you use it instead of free/Destroy to ensure that the object is released correctly.
4) release; static method defined in TcustomForm.
The free function is called after all events in the window are processed. It is often used to destroy windows, and when event processing takes a certain amount of time in this window, this method can ensure that the window is destroyed only after the window event is processed. Here is the Delphi source code of TCustomForm.Release:
procedure TCustomForm.Release;
Begin
PostMessage(Handle, CM_RELEASE, 0, 0);
//Send CM_RELEASE message to the window to the message queue. After all window event messages are processed,
//Call CM_RELEASE message processing process CMRelease again
end;
Let’s take a look at the following definition of CMRelease for CM_RELEASE message processing:
procedure CMRelease(var Message: TMessage); message CM_RELEASE;
procedure TCustomForm.CMRelease;
Begin
Free; //In the end, it's free;
end;
4 VCL construction & destructuring architecture
TObject
constructor Create;//static method
destructor Destroy; virtual;
TPersistent
destructor Destroy; override;
TComponent
constructor Create(AOwner: TComponent); virtual;
destructor Destroy; override;
TControl
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
…
The following analyzes the source code of the construction and destructuring in VCL, taking Tcontrol as an example:
constructor TControl.Create(AOwner: TComponent);
Begin
inherited Create(AOwner);//Create a base class subobject and hand over the destructuring rights to AOwner. Put it in front
//This ensures the order of "creating base class subobject first, then creating derived class subobjects"
…//Initialize, and call the constructor of the internal subobject
end;
destructor TControl.Destroy;
Begin
…//Destruct internal subobjects in derived classes
inherited Destroy;//Destruct the base class object and put it at the end
//This ensures the order of "first destruction of derived class subobjects, then destruction of base class subobjects"
end;
5 Use constructors and destructors correctly
After the above analysis, the following summarizes the principles of using constructors and destructors:
Before using an object, you must first create an object and destroy the object in time to free up resources.
When two objects refer to assignments, make sure that the nameless object (referring to objects that are not referenced) that appear can be released.
When creating a component, it is recommended to set up a host component (that is, use the AOwner parameter, usually a form), and Aowner manages the destruction of objects, so there is no need to worry about destroying the component. This is Delphi on the form/data Module design and create components is the method taken. So we don't have to write the destructor that calls the component.
When the return type of the function is an object, Result is also a reference to the object, ensuring that the object referenced by Result must exist.
To use obj<>nil or assigned(nil) to test that the object exists, obj:=nil should also be called after obj:=nil.
Please refer to the source code of the demo program
Instructions (recommended)
All Delphi programs have been passed on win2k+Delphi6 sp2. For C++ programs, it is just to explain that they are different from those in Delphi and are not guaranteed to run directly. In order to deepen your understanding of this article, it is recommended to refer to the demonstration program.
This article includes some of my experiences and experiences in learning VCL/RTL. In addition, my personal abilities are limited, so mistakes are inevitable. Please correct me!
Before reading this article, readers need to have a certain understanding of the Oriented Pascal language and be able to understand polymorphism. If you are not very clear about some of these concepts, please refer to the relevant articles.
Through this article, you should be able to understand the object model, construction & destructuring implementation mechanism in Delphi, and the construction & destructuring architecture in VCL, and be able to master the use of construction & destructuring methods. The structure & destruction in Delphi is much simpler than that in C++, and we should be able to master it.