Talking to the object mechanism of Delphi

xiaoxiao2021-03-06  120

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)

===================

转载请注明原文地址:https://www.9cbs.com/read-125527.html

New Post(0)