In the process of using DELPHI to develop software, we are like a group of happy cows and sheep on the grassland, carefreely enjoying the sunshine brought to us by the Object Pascal language and the rich water plants provided by various VCL controls. Looking up at the boundless blue sky, looking down at the lush green grass on the earth, who would think about how big the universe is and what things are smaller than molecules and atoms? That's a matter for philosophers. At this time, the philosopher was sitting on the top of a high mountain, looking up at the changes in the nebulae of the universe, staring at the crawling of insects on the ground, suddenly turning back, nodding and smiling at our group of grazing cattle and sheep. He picked up a piece of grass, held it gently in his mouth, closed his eyes and tasted it carefully. I wonder what the taste of this piece of grass was in the philosopher's mouth? However, he always had a satisfied smile on his face.
Knowing and understanding the microscopic atomic world of DELPHI can enable us to thoroughly understand the macroscopic application structure of DELPHI, thereby developing our software in a broader ideological space. It's like Newton discovered the motion of macroscopic objects, but was troubled because he couldn't figure out why the objects moved like this. On the contrary, Einstein experienced the happy life of relativity between the laws of basic particles and the motion of macroscopic objects!
Section 1 TObject Atom
What is TObject?
It is the basic core of the Object Pascal language architecture and the origin of various VCL controls. We can think of TObject as one of the atoms that make up a DELPHI application. Of course, they are made up of more subtle particles such as basic Pascal syntax elements.
It is said that TObject is the atom of the DELPHI program because TObject is supported internally by the DELPHI compiler. All object classes are derived from TObject, even if you do not specify TObject as an ancestor class. TObject is defined in the System unit, which is part of the system. At the beginning of the System.pas unit, there is this comment text:
{PRedefined constants, types, procedures, }
{ and functions (such as True, Integer, or }
{Writeln) do not have actual declarations.}
{ Instead they are built into the compiler }
{ and are treated as if they were declared }
{ at the beginning of the System unit.}
It means that this unit contains predefined constants, types, procedures and functions (such as: True, Integer or Writeln). They are not actually declared, but are built-in by the compiler and are used at the beginning of compilation. Considered to be a stated definition. You can add other source program files such as Classes.pas or Windows.pas to your project file to compile and debug the source code, but you absolutely cannot add the System.pas source program file to your project file for compilation! DELPHI will report compilation errors for duplicate definitions of System!
Therefore, TObject is a definition provided internally by the compiler. For those of us who use DELPHI to develop programs, TObject is an atomic thing.
The definition of TObject in the System unit is as follows:
TObject = class
constructor Create;
procedure Free;
class function InitInstance(Instance: Pointer): TObject;
procedure CleanupInstance;
function ClassType: TClass;
class function ClassName: ShortString;
class function ClassNameIs(const Name: string): Boolean;
class function ClassParent: TClass;
class function ClassInfo: Pointer;
class function InstanceSize: Longint;
class function InheritsFrom(AClass: TClass): Boolean;
class function MethodAddress(const Name: ShortString): Pointer;
class function MethodName(Address: Pointer): ShortString;
function FieldAddress(const Name: ShortString): Pointer;
function GetInterface(const IID: TGUID; out Obj): Boolean;
class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
class function GetInterfaceTable: PInterfaceTable;
function SafeCallException(ExceptObject: TObject;
ExceptAddr: Pointer): HResult; virtual;
procedure AfterConstruction; virtual;
procedure BeforeDestruction; virtual;
procedure Dispatch(var Message); virtual;
procedure DefaultHandler(var Message); virtual;
class function NewInstance: TObject; virtual;
procedure FreeInstance; virtual;
destructor Destroy; virtual;
end;
Next, we will gradually knock on the door of TObject atoms to see what structure is inside.
We know that TObject is the basic class of all objects, so what exactly is an object?
Any object in DELPHI is a pointer, which indicates the space occupied by the object in memory! Although the object is a pointer, when we refer to the members of the object, we do not need to write the code MyObject^.GetName, but can only write MyObject.GetName. This is an expanded syntax of the Object Pascal language and is supported by the compiler. Friends who use C++ Builder are very clear about the relationship between objects and pointers, because objects in C++ Builder must be defined as pointers. The place pointed by the object pointer is the object space where the object stores data. Let's analyze the data structure of the memory space pointed by the object pointer.
The first 4 bytes of the object space point to the virtual method address table (VMT?C Virtual Method Table) of the object class. The next space is the space to store the member data of the object itself, and is stored in the total order from the data members of the object's most primitive ancestor class to the data members of the object class, and in the order in which the data members are defined in each level of class.
A class's virtual method table (VMT) holds the procedure addresses of the virtual methods of all classes derived from the class's original ancestor class. The virtual method of a class is a method declared with the reserved word virtual. Virtual method is the basic mechanism to achieve object polymorphism. Although dynamic methods declared with the reserved word dynamic can also achieve object polymorphism, such methods are not stored in the virtual method address table (VMT). It is just another method provided by Object Pascal that can save class storage space. Polymorphic implementation mechanism, but at the expense of calling speed.
Even if we do not define any virtual method of the class ourselves, the object of the class still has a pointer to the virtual method address table, but the length of the address entry is zero. However, where are the virtual methods defined in TObject, such as Destroy, FreeInstance, etc., stored? It turns out that their method addresses are stored in a space offset in the negative direction relative to the VMT pointer. In fact, the data space offset by 76 bytes in the negative direction of the VMT table is the system data structure of the object class. These data structures are compiler-related and may be changed in future DELPHI versions.
Therefore, you can think that VMT is a data structure starting from the negative offset address space. The negative offset data area is the system data area of VMT, and the positive offset data of VMT is the user data area (customized virtual method address table ). The functions and procedures related to class information or object runtime information defined in TObject are generally related to the system data of VMT.
A VMT data represents a class. In fact, VMT is a class! In Object Pascal, we use identifiers such as TObject, TComponent, etc. to represent classes, which are implemented as their respective VMT data internally in DELPHI. The type of the class defined with the class of reserved word is actually a pointer to the relevant VMT data.
For our application, VMT data is static data. After the compiler compiles our application, this data information has been determined and initialized. The program statements we write can access VMT-related information, obtain information such as the size of the object, class name or run-time attribute data, or call virtual methods or read the name and address of the method, etc.
When an object is generated, the system will allocate a memory space for the object and associate the object with the relevant class. Therefore, the first 4 bytes in the data space allocated for the object become pointers to class VMT data. pointer.
Let's take a look at how objects are born and die. Watching my three-year-old son jumping around on the grass, it is precisely because I have witnessed the birth process of life that I can truly understand the meaning and greatness of life. Only those who have experienced death will understand and cherish life more. So, let us understand the process of the creation and destruction of objects!
We all know that the simplest object can be constructed using the following statement:
AnObject := TObject.Create;
The compiler implements its compilation as:
Based on the VMT corresponding to TObject, call the Create constructor of TObject. The Create constructor calls the system's ClassCreate process, and the system's ClassCreate process calls the NewInstance virtual method through the class VMT stored in it. The purpose of calling the NewInstance method is to establish the instance space of the object. Because we have not overloaded this method, it is the NewInstance of the TObject class. The NewInstance method of the TObjec class will call the GetMem procedure to allocate memory for the object based on the object instance size (InstanceSize) initialized by the compiler in the VMT table, and then call the InitInstance method to initialize the allocated space. The InitInstance method first initializes the first 4 bytes of the object space as a pointer to the VMT corresponding to the object class, and then clears the remaining space. After establishing the object instance, a virtual method AfterConstruction is also called. Finally, save the address pointer of the object instance data to the AnObject variable, and in this way, the AnObject object is born.
Similarly, an object can be destroyed using the following statement:
AnObject.Destroy;
The destructor of TObject, Destroy, is declared as a virtual method, which is also one of the inherent virtual methods of the system. The Destory method first calls the BeforeDestruction virtual method, and then calls the system's ClassDestroy process. The ClassDestory process calls the FreeInstance virtual method through the class VMT, and the FreeInstance method calls the FreeMem process to release the object's memory space. Just like that, an object disappears from the system.
The destruction process of objects is simpler than the construction process of objects, just like the birth of life is a long gestation process, but death is relatively short-lived. This seems to be an inevitable rule.
During the object's construction and destruction process, two virtual functions, NewInstance and FreeInstance, are called to create and release the memory space of the object instance. The reason why these two functions are declared as virtual functions is to allow users to have room for expansion when writing special object classes that require users to manage their own memory (such as in some special industrial control programs).
Declaring AfterConstruction and BeforeDestruction as virtual functions is also to give the derived class in the future the opportunity to let the newly born object breathe the first breath of fresh air after generating the object, and to allow the object to complete the aftermath before the object dies. This is all Something that makes sense. In fact, the OnCreate event and OnDestroy event of the TForm object and TDataModule object are triggered respectively in the two virtual function processes of TForm and TDataModule overload.
In addition, TObjec also provides a Free method, which is not a virtual method. It is specially provided to safely release the object when it is unclear whether the object is empty (nil). In fact, if you can't figure out whether the object is empty, there is a problem of unclear program logic. However, no one is perfect and can make mistakes. It is also a good thing to use Free to avoid accidental mistakes. However, writing correct programs cannot rely solely on such solutions. The first goal of programming should be to ensure the logical correctness of the program!
Interested friends can read the original code of the System unit, where a large amount of code is written in assembly language. Careful friends can find that TObject's constructor Create and destructor Destory have not written any code. In fact, through the Debug CPU window in the debugging state, the assembly code of Create and Destory can be clearly reflected. Because the masters who created DELPHI did not want to provide users with too many complicated things. They wanted users to write applications based on simple concepts and hide the complex work inside the system for them to undertake. Therefore, when publishing the System.pas unit, the codes of these two functions are specially removed to make users think that TObject is the source of all things, and user-derived classes completely start from nothingness. This is not wrong in itself. Although reading these most essential codes of DELPHI requires a small amount of assembly language knowledge, reading such codes can give us a deeper understanding of the origin and development of the DELPHI world. Even if you don't understand much, being able to at least understand some basic things will be of great help to us in writing DELPHI programs.
Section 2 TClass Atom
In the System.pas unit, TClass is defined like this:
TClass = class of TObject;
It means that TClass is the class of TObject. Because TObject itself is a class, TClass is the so-called class of classes.
Conceptually, TClass is a type of class, that is, a class. However, we know that a class of DELPHI represents a piece of VMT data. Therefore, the class can be considered as the type defined for the VMT data item. In fact, it is a pointer type pointing to the VMT data!
In the previous traditional C++ language, the type of a class could not be defined. Once the object is compiled, it is fixed, the structural information of the class has been converted into absolute machine code, and the complete class information will not exist in the memory. Some higher-level object-oriented languages can support dynamic access and invocation of class information, but they often require a complex internal interpretation mechanism and more system resources. DELPHI's Object Pascal language absorbs some of the excellent features of high-level object-oriented languages, while retaining the traditional advantage of directly compiling programs into machine code, which perfectly solves the problems of advanced functions and program efficiency.
It is precisely because DELPHI retains complete class information in the application that it can provide advanced object-oriented functions such as as and is to convert and identify classes at runtime, in which the VMT data of the class plays a key core role. Interested friends can read the two assembly processes of AsClass and IsClass in the System unit. They are the implementation codes of the as and is operators to deepen their understanding of classes and VMT data.
...
The following content also includes the understanding of fictitious constructors, the implementation mechanism of Interface and the implementation mechanism of exception handling, etc. The basic principles of DLPHI. I hope I can finish it after May Day and contribute it to everyone.