Delphi Injection Molding

I need an explanation on Delphi injection molding
I wrote an example with two classes: TClassA and TClassB, TClassB derived from TClassA.

The code is as follows:

program TEST; {$APPTYPE CONSOLE} uses System.SysUtils; type TClassA = class(TObject) public Member1:Integer; constructor Create(); function ToString():String; override; end; type TClassB = class(TClassA) public Member2:Integer; constructor Create(); function ToString():String; override; function MyToString():String; end; { TClassA } constructor TClassA.Create; begin Member1 := 0; end; function TClassA.ToString: String; begin Result := IntToStr(Member1); end; { TClassB } constructor TClassB.Create; begin Member1 := 0; Member2 := 10; end; function TClassB.MyToString: String; begin Result := Format('My Values is: %u AND %u',[Member1,Member2]); end; function TClassB.ToString: String; begin Result := IntToStr(Member1) + ' - ' + IntToStr(Member2); end; procedure ShowInstances(); var a: TClassA; b: TClassB; begin a := TClassA.Create; b := TClassB(a); // Casting (B and A point to the same Memory Address) b.Member1 := 5; b.Member2 := 150; // why no error? (1) Writeln(Format('ToString: a = %s, a = %s',[a.ToString,b.ToString])); // (2) Writeln(Format('Class Name: a=%s, b=%s',[a.ClassName,b.ClassName])); // (3) Writeln(Format('Address: a=%p, b=%p',[@a,@b])); // (4) Writeln(b.MyToString); // why no error? (5) readln; end; begin try ShowInstances; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end. 

Program Output:

 ToString: a = 5, a = 5 Class Name: a=TClassA, b=TClassA Address: a=0012FF44, b=0012FF40 My Values is: 5 AND 150 

(1) What is Member2's address? Is this a possible "access violation"?
(2) ok, the ToString () method points to the same address
(3) why do a and b have the same class name?
(4) ok, a and b are two different variables
(5) if b is TClassA, why can I use the "MyToString" method?

+6
source share
3 answers

You apply a hard type to a variable. When you do this, you tell the compiler that you know what you are doing, and the compiler trusts you.

(1) What is Member2's address? Is this a possible "access violation"?

When you assign a value to a class member, the class definition of the variable is used by the compiler to calculate the offset of this member in memory space, so if you have a class declaration as follows:

 type TMyClass = class(TObject) Member1: Integer; //4 bytes Member2: Integer; //4 bytes end; 

the representation in memory of this object is as follows:

 reference (Pointer) to the object | | --------> [VMT][Member1][Member 2][Monitor] Offset 0 4 8 12 

When you issue a statement like this:

 MyObject.Member2 := 20; 

The compiler simply uses this information to calculate the memory address for applying this purpose. In this case, the assignment compiler can be translated into

 PInteger(NativeUInt(MyObject) + 8)^ := 20; 

So, your assignment is successfully executed only because the memory manager is working (by default). When you try to access a memory address that is not part of your program, AV is created by the operating system. In this case, your program takes up more memory from the OS than is required. IMHO, when you do not receive AV, you are, in fact, out of luck, because your program memory can now be silently damaged. Any other variable located at this address can change its value (or metadata), and this will lead to undefined behavior.

(2) ToString () method points to the same address

Since the ToString () method is virtual, the address of this method is stored in VMT, and the call is determined at runtime. Take a look at What data is contained in TObject? and read the reference book section: Delphi Object Model .

(3) why do a and b have the same ClassName?

The class name is also part of the runtime metadata of the object. The fact that you apply an irregular shape to an object does not change the object itself.

(4) a and b are two different variables

Of course you declared it, look at your code:

 var a: TClassA; b: TClassB; 

Well, two different variables. In Delphi, object variables are links, so after some lines of code refer to the same address, but that's another thing.

(5) if b is TClassA, why use the "MyToString" method?

Because you tell the compiler that you are OK, and as said, the compiler trusts you. These are hacks, but Delphi is also a low-level language, and you are allowed to do a lot of crazy things if you want, but:

Play in safe mode

If you want (and you probably want most of the time) to be safe, do not use a hard trick like this in your code. Use as an operator :

The operator how performs the tested types of techniques. Expression

object as a class

returns a reference to the same object as the object, but with the type specified by the class. At run time, the object must be an instance of a class, denoted by a class or one of its descendants, or be nil; otherwise, an exception is thrown. If the declared type of the object is not associated with the class, that is, if the types are different, and one is not the ancestor of the other, a compilation error occurs.

So, with the as operator, you are safe, both at compile time and at runtime.

Change your code to:

 procedure ShowInstance(A: TClassA); var b: TClassB; begin b := A as TClassB; //runtime exception, the rest of the compiled code //won't be executed if a is not TClassB b.Member1 := 5; b.Member2 := 150; Writeln(Format('ToString: a = %s, a = %s',[a.ToString,b.ToString])); Writeln(Format('Class Name: a=%s, b=%s',[a.ClassName,b.ClassName])); Writeln(Format('Address: a=%p, b=%p',[@a,@b])); Writeln(b.MyToString); readln; end; procedure ShowInstances(); begin ShowInstance(TClassB.Create); //success ShowInstance(TClassA.Create); //runtime failure, no memory corrupted. end; 
+20
source
  • Member2 has an address that has not been assigned by the memory manager. A possible result of writing Member2 is a heap corruption with subsequent access violations in a completely different part of the program. This is a very nasty bug, and the compiler will not help you here. You should know what you are doing when creating an unsafe type.

  • This is because the ToString method is virtual, so its address is determined by the actual type of the instantiated class. If you replace the virtual method with static (in your case, replacing the override with reintroduce ), the result will be different.

  • Since the ClassName method ClassName also virtualized (not really a member of VMT, but this is a minor implementation detail).

  • Yes, a and b are two references to the same instance.

  • Because the ToMyString method ToMyString static. The actual instance type does not matter for static methods.

+4
source

(1) What is Member2's address? Is this a possible "access violation"?

Yes. AV is possible. In your case, you're in luck :)

ok, the ToString () method points to the same address

Yes, since VTable refers to creation time.

(3) why do a and b have the same ClassName?

the same answer as in (2).

(4) ok, a and b are two different variables

Not really. you printed the address from the stack :)

(5) if b is TClassA, why use the "MyToString" method?

b - TClassB, but mistakenly points to an instance of TClassA.

For such casts should be used as . In this case, it will fail.

+3
source

All Articles