Talking to the object mechanism of Delphi
Savetime2k@yahoo.com
2004-1-3
I started reading the VCL source code a few days ago, but the inheritance code of several base classes has a big head. After the Mustic asked several people, I was not very clear about the creation and method of the Delphi object. Finally, I had to take a compilation and investigated several key methods of Delphi object operation.
You can know why I have to do this with the following link:
http://www.delphibbs.com/delphibbs/dispq.asp?lid=2385681
This is the test result I spend a night. More details can only go to learn later.
The main test items are:
Test Goal: View the compiler implementation of TOBJECT.CREATE
⊙ Test Objective: Viewing Inherited Compiler Implementation in the Constructor Function
Test Objective: Implementation of Compiler with Object Reference and Class Reference Calling Constructor
Test Objective: Check Object and Class Implementation when calling Class Method
⊙ Test Goal: Check the function of the SHORTSTRING return value type does not assign the compiler
I record the details of the test in the following text, one is to stay for reference, the other is to give a reference to friends who are interested in this. In fact, more importantly, everyone can help check my analysis. I have always used Delphi components to drag and drop programming. The real skill is only to read Object Pascal Reference and VCL these days. Compilation is a temporary buddha, so it is inevitable. I know my own level, so I am very worried after writing. Despite this, my purpose is to learn, I hope you find a mistake to help me.
The main conclusion is:
(*) TOBJECT.CREATE is indeed an empty function, Borland does not hide the code of TOBJECT.CREATE. The assembly code for TOBJECT.CREATE is formed by the Constructor Directive instruction compiler, and the compiler has a colleague of each class.
(*) DL and EAX are the critical registers implemented by the Constructor Create. Borland is designed and clear (personal feeling because I don't know how other languages are implemented, such as how C is implemented).
(*) A normal creation of an object (OBJ: = TMYCLASS.CREATE) process is like this:
1. The compiler guarantees that before the first Constructor call DL = 1
The compiler guarantees that before the Inherited Create call DL = 0
2. DL = 1 Compiler to ensure that Eax = Pointer to Class VMT when CREATE
DL = 0 compiler to ensure CREATE EAX = Pointer to Current Object
3. Compiler to ensure that Eax = Pointer to Current Object after any level of constructor call
4. DL = 1 Compiler guarantees that CREATE calls System._ClassCreate and uses EAX in the same way as constructor.
DL = 1 When the compiler guarantees that CREATE calls system._afterconstruction and calls before and after calling Eax = Poin Current Object
DL = 0 compiler guarantees that CREATE does not call System._ClassCreate
DL = 0 When the compiler guarantees that CRETE does not call System._AFTERCONSTRUCTION5. System._ClassCreate Sets the structured exception handling in CREATE, closing the structured exception handling at the end of CREATE.
If an error will be (1) Release the memory (2) to restore the stack to the Create object (3) call tsomeclass.destroy before the compiler is allocated.
(*) The constructor call of the Object Reference method, the compiler attempts to be invoked for inherited, and the result is of course an error.
(*) Class Method calls implies parameter EAX to point to VMT, whether it is called with Class or Object mode, the compiler correctly passes the pointer to the Class VMT to EAX.
To read the test process of the following, you may need a related basis, recommend reading Object Pascal Reference The following sections:
Parameter Passing
Function Results
Calling Conventions (Register default call agreement, Constructor and Destructor functions must be used as the Register appointment)
Inline Assambly Code
"Delphi's Atom World" is worth reading.
The following is the test content:
================================================================================★
Test Goal: View the compiler implementation of TOBJECT.CREATE
================================================================================★
⊙ Test code and disassembly code:
Procedure test; register;
VAR
Obj: TOBJECT;
Begin
Push EBP; the first 2 sentences are used to set up stack pointers
MOV EBP, ESP
PUSH ECX; save ECX (useless statement)
Obj: = TOBJECT.CREATE;
MOV DL, $ 01; Setup DL = 1, notify TOBJECT.CREATE This is a call for a new object
MOV EAX, [$ 004010a0]; put the pointer to the TOBJECT CLASS VMT into Eax,
; As a TOBJECT.CREATE implicit Self parameter
Call Tobject.create; call the Tobject.create function
MOV [EBP- $ 04], EAX; TOBJECT.CREATE Returns the new object's pointer to OBJ
END;
POP ECX; Restore Stacks and Returns
POP EBP
RET
⊙ TOBJECT.CREATE disassembly code:
Eax = Pointer to VMT (DL = 1) when the function enters
EAX = Pointer to Instance (DL = 0)
Eax = Pointer to instance when the function returns
TEST DL, DL; check if DL is = 0
JZ $ 08; DL = 0 jumps to @@ 1
Add ESP, - $ 10; Increase the 16-byte stack, every time you call _classcreate,
; Use for system._classcreate to set structured exception processing
Call @classcreate; call system._classcreate
@@1:
TEST DL, DL; check if DL is = 0
JZ $ 0F; DL = 0 jumps to the end process
Call @AFTERCONSTRUCTION; DL <> 0 calls System._AFTERCONSTRUCTION
(Note Not TOBJECT.AFTERCONSTRUCTION)
POP DWORD PTR FS: [$ 00000000]; fs: [0] points to the function of structured exception processing, this is the last TRY..Except setting
This try..Except created in System._ClassCreate
; Automatically restore stack / release memory allocation / and call Tobject.free when an error
Add ESP, $ 0C; Restore Stack, pay attention to restore only 12-byte stack, and 4 bytes from the above sentence POP
RET
Note: Test DL, DL is repeated in the above assembly code, indicating that Borland does not have special TOBJECT.CREATEs, and TOBJECT.CREATE is indeed an empty function. The assembly code for TOBJECT.CREATE is formed by the Constructor Directive instruction compiler, and the compiler has a colleague of each class.
Note: This TOBJECT.CREATE code is a result of compiling on the PC, which is strictly that one of the implementations on the Win32 operating system. View System._ClassCreate knows that Borland has other exception handling mechanisms, and the resulting TOBJECT.CREATE code is also different.
⊙ System._afterContruction function code:
Function _AFTERCONSTRUCTION (Instance: TOBJECT): TOBJECT
Begin
Instance.AFTERCONSTRUCTION;
RESULT: = Instance;
END;
⊙ System._classcreate function code:
Function_ClassCreate (aclass: tclass; alloc: boolean): TOBJECT
ASM
{-> eax = pointer to vmt}
{<- eax = pointer to instance}
PUSH EDX; Save Register
Push ECX
Push EBX
TEST DL, DL; if DL = 0 does not call Tobject.newinstancejl @@ noalloc
Call DWORD PTR [EAX] VMTOFSET TOBJECT.NEWINSTANCE; call TOBJECT.NEWINSTANCE
@@ noalloc:
{$ IFNDEF PC_MAPPED_EXCEPTIONS}; setting up structured exception handling of PC architecture
XOR EDX, EDX
Lea ECX, [ESP 16]
MOV EBX, FS: [EDX]
MOV [ECX] .TexcFrame.Next, EBX
MOV [ECX] .Texcframe.hebp, EBP
MOV [ECX] .Texcframe.Desc, Offset @Desc
MOV [ECX] .Texcframe.constructedObject, Eax {Trick: Remember Copy To Instance}
MOV FS: [EDX], ECX
{$ ENDIF}
POP EBX; Restore Register
POP ECX
POP EDX
RET
{$ IFNDEF PC_MAPPED_EXCEPTIONS}; setting up a structured exception handling of non-PC architecture
@Desc:
JMP_HandleAnyException
{Destroy the object}
MOV EAX, [ESP 8 9 * 4]
Mov Eax, [EAX] .Texcframe.constructedObject
Test Eax, EAX
JE @@ Skip
MOV ECX, [EAX]
MOV DL, $ 81
Push EAX
Call DWORD PTR [ECX] VMTOFFSET TOBJECT.DESTROY
POP EAX
Call _classdestroy
@@ Skip:
{RERAISE THE EXCEPTION}
Call _raiseagain
{$ ENDIF}
END;
============================================================================================================================================================================================================= ============
⊙ Test Objective: Viewing Inherited Compiler Implementation in the Constructor Function
============================================================================================================================================================================================================= ============ ⊙ Test code and disassembly code:
Type
TMYCLASS = Class (TOBJECT)
Constructor crete;
END;
Constructor TMYCLASS.CREATE;
Begin
Inherited; // Check the implementation of this sentence
BEEP;
END;
Procedure test; register;
VAR
Obj: TMYCLASS;
Begin
Obj: = tmyclass.create;
MOV DL, $ 01; Class Reference, compiler settings DL = 1
MOV EAX, [$ 004600 "; setting Eax to point to TMT Pointer
Call tmyclass.create; call TMYCLASS.CREATE
MOV [EBP- $ 04], EAX; Pointer to save new objects
END;
Conntructor TMYCLASS.CREATE disassembly code:
Eax = Pointer to VMT (DL = 1) when the function enters
EAX = Pointer to Instance (DL = 0)
Eax = Pointer to instance when the function returns
Begin
Push EBP; This 3 sentence is used to save the stack pointer and create a stack
MOV EBP, ESP
Add ESP, - $ 08
TEST DL, DL; if DL = 0 jumps to @@ 1 execution after @classcreate
JZ $ 08
Add ESP, - $ 10; Prepare Stack for _ClassCreate
Call @classcreate; call system._classcreate, after execution, Eax = new object's pointer
@@1:
MOV [EBP- $ 05], DL; save the DL value into 1 byte in the stack, because the back inherited Tobject.create
May change the value of EDX
MOV [EBP- $ 04], EAX; save EAX to stack, eax = Pointer to instance
inherited;
XOR EDX, EDX; Clear EDX (DL = 0) to inform TOBJECT.CREATE No need to call
; _ClassCreate and AfterConstructor (compiler implementation)
MOV EAX, [EBP- $ 04]; restore EAX's value to the EAX value saved in the stack
(This sentence is redundant, but in other cases, this sentence may be required)
Call TOBJECT.CREATE; call TOBJECT.CREATEBEEP;
Call beep; functions implemented after inherited inherited in the class
MOV EAX, [EBP- $ 04]; restore EAX's value to the EAX value saved in the stack
CMP BYTE PTR [EBP- $ 05], $ 00; (Indirect) Check if DL is = 0
JZ $ 0F; DL = 0 Skip _AFTERCONSTRUCTION to @@ 2
Call @AFTERCONSTRUCTION; call system._afterconstruction
POP DWORD PTR FS: [$ 00000000]; This 2 sentence is restored to _classcreate created stack space
Add ESP, $ 0C
@@2:
Mov Eax, [EBP- $ 04]; Return Pointer to Instance
END;
POP ECX
POP ECX
POP EBP
RET
Conclusion: It's a subtle! The normal creation of an object (Obj: = Tmyobj.create, the relative to the abnormal calls) is like this:
1. The compiler guarantees that before the first Constructor call DL = 1
The compiler guarantees that before the Inherited Create call DL = 0
2. DL = 1 Compiler to ensure that Eax = Pointer to Class VMT when CREATE
DL = 0 compiler to ensure CREATE EAX = Pointer to Current Object
3. Compiler to ensure that Eax = Pointer to Current Object after any level of constructor call
4. DL = 1 Compiler guarantees that CREATE calls System._ClassCreate and uses EAX in the same way as constructor.
DL = 1 When the compiler guarantees that CREATE calls system._afterconstruction and calls before and after calling Eax = Poin Current Object
DL = 0 compiler guarantees that CREATE does not call System._ClassCreate
DL = 0 compiler guarantees that CREATE does not call System._AFTERCONSTRUCTION
5. Structured exception handling in System._ClassCreate, and turn off structural exceptions at CREATEs.
If an error will be (1) Release the memory (2) to restore the stack to the Create object (3) call tsomeclass.destroy before the compiler is allocated.
It looks a bit complicated, but if you read the top tobject.create and TmyObject.create, you will feel very clear.
============================================================================================================================================================================================================= ================================ ⊙ Test target: Implement the compiler that calls the constructor in Object Reference and Class Reference
============================================================================================================================================================================================================= =======================================================================================================================================================
⊙ static constructor test code and disassembly code (omitted the stack assignment code behind Begin and End):
Procedure test; register;
VAR
Obj: TOBJECT;
Begin
Obj: = TOBJECT.CREATE;
MOV DL, $ 01; compiler automatically set DL = 1 when using Class Reference
MOV EAX, [$ 004010a0]; putting a pointer to TOBJECT CLASS VMT into EAX, used for next line call
Call TOBJECT.CREATE
MOV [EBP- $ 04], EAX
Obj: = obj.create;
Or EDX, - $ 01; All Bit, which is automatically set by the compiler, using Object Reference, is 1
MOV EAX, [EBP- $ 04]; deposit the referred to the OBJ pointer (ie object memory space) in Eax, used for next line call
Call TOBJECT.CREATE
MOV [EBP- $ 04], EAX
END;
⊙ Virtual constructor test code and disassembly code (omitted the stack allocation code behind Begin and End):
Procedure test; register;
VAR
Comp: tComponent;
Begin
Comp: = tcomponent.create (nil);
XOR ECX, ECX; Setting Parameters = NIL
MOV DL, $ 01; Setup DL = 1
MOV EAX, [$ 00412EAC]; Set Eax = Class Vmt PointerCall Tcomponent.create; call tComponent.create
MOV [EBP- $ 04], EAX; Save New Objects to Comp
Comp: = comp.create (nil);
XOR ECX, ECX;
OR EDX, $ 01; Setting EDX All Bits 1
MOV EAX, [EBP- $ 04]; this sentence and the following sentence set EBX for the VMT Pointer for Tcomponent Class
MOV EBX, [EAX]; (if the CoM has been instantiated, the value of EBX is right)
Call DWORD PTR [EBX $ 2C] may call Tcomponent.create (CoMP, -1, NIL);
MOV [EBP- $ 04], EAX; Save New Objects to Comp
END;
Conclusion: The constructor call for the Object Reference mode, the compiler attempts to be invoked for inherited, and the result is of course an error.
============================================================================================================================================================================================================= =====================
Test Objective: Check Object and Class Implementation when calling Class Method
============================================================================================================================================================================================================= =====================
⊙ Test code and disassembly code (omitted the stack allocation code behind Begin and End):
Procedure test; register;
VAR
COM: tComponent;
Str: string [255];
Begin
COM: = Tcomponent.create (nil);
XOR ECX, ECX
MOV DL, $ 01
MOV EAX, [$ 00412EAC]; EAX = Pointer to Class VMT
Call tcomponent.create
MOV [EBP- $ 04], EAX
Str: = com.classname; Lea EDX, [EBP- $ 00000104]
Mov Eax, [EBP- $ 04]; EAX = Pointer to Object
MOV Eax, [EAX]; EAX = Pointer to VMT
Call Tobject.classname
Str: = tcomponent.classname;
Lea Edx, [EBP- $ 0000010]; EDX = Address Of STR
The return value of the shortstring type is transmitted by the variety of VAR types.
MOV EAX, [$ 00412EAC]; EAX = Pointer to Class VMT
Call Tobject.classname
END;
Conclusion: Class Method calls implies parameter EAX to point to VMT, whether it is called with Class or Object mode, the compiler correctly passes the pointer to the Class VMT to EAX.
============================================================================================================================================================================================================= ======================
⊙ Test Goal: Check the function of the SHORTSTRING return value type does not assign the compiler
============================================================================================================================================================================================================= ======================
Procedure test; register;
Begin
Tcomponent.classname;
Lea Edx, [EBP- $ 00000100]; the compiler creates 256 Byte's temporary space in the stack to ensure that EDX will not be illegal.
Mov Eax, [$ 00412EAC]
Call Tobject.classname
END;
⊙ TOBJECT.CLASSNAME Function Code:
Class function TOBJECT.CLASSNAME: shortstring;
{IFDEF PurePascal}
Begin
Result: = pshortstring (PPointer (Integer (Self) VMTClassName ^) ^
END;
{$ Else}
ASM
{-> EAX VMT} {Edx Pointer to Result String}
PUSH ESI
Push EDI
MOV EDI, EDX; EDX is a pointer to the return value string
Mov ESI, [EAX] .VMTClassName
XOR ECX, ECX
MOV CL, [ESI]; Setting Result String LENGTH
Inc ECX
REP MOVSB
POP EDI
POP ESI
END;
{$ ENDIF}
Conclusion: This is just how I want to know the way of passing the string return value.
===================
(Finish)
===================