Two traps: Delphi interface and Programming

Source: Internet
Author: User

Two traps in Delphi Interface Programming

Some time ago I wrote an example of extended functions through interfaces. At that time, the conversion of pointers and interfaces caused many errors. Recently I came into contact with an example of mixed classes and interfaces, as a result, the pointer of the program changes the address or content during transmission, and the incorrect address is read. Now, the situation between interfaces and classes is summarized.

Trap 1. interface type conversion trap

(1) You cannot forcibly convert an object reference to an interface that is not declared for the referenced type, even if the object actually implements this interface (haha, advantage interface ).
(2) When an object variable is assigned to an interface variable and this interface variable is assigned to an object variable, the address of this object variable has changed, that is, it is no longer the original object, but a wrong address.
For example:
I1 = interface
Function Do: Boolean;

End;

TC1 = class
ATT1: Integer;

End;

TC2 = Class (TC1, I1)
ATT2: Integer;
Function Do: Boolean;
End;

Intf1: I1;
OBJ1: TC !;

OBJ2: TC2;

OBJ2: = TC2.Create;
OBJ1: = OBJ2.
I1 (obj2). Do; correct.
I1 (obj1). Do; Compilation failed

Because the obj1 type TC1 is not declared to implement I1, it cannot be converted to I1, even if obj1 does implement i1.

If you convert an object to an interface and then convert it back, the problem also occurs.

Obj2: = tc2.create;
Obj2.att1: = 0;
Intf1: = obj2; // correct.
Obj2: = intf1;
TC2 (intf1). att1: = 0; // Invalid Address Access error during runtime.
Obj2.att1: = 0; // Invalid Address Access error during runtime.

That is, after converting from object reference to pointer reference, the address is changed, but the address is not changed when the pointer reference is switched back to object reference (Delphi bug ?).

 

Trap 2. Interface lifecycle management 

In my opinion, interfaces do not require lifetime management, because interfaces cannot generate real objects. But Delphi has once again cracked down on my common sense (Why ?), Its interface has a lifetime and must implement the following three methods:
Function QueryInterface (const IID: tguid; out OBJ): hresult; stdcall;
Function _ addref: integer; stdcall;
Function _ release: integer; stdcall;
It is troublesome to implement these three methods each time. More importantly, I don't know when Delphi will use and how to use these three methods? So I don't know how to implement these three methods.
If you do not want to implement these three methods by yourself, you can use TComponent. Because TComponent has implemented these three methods, you can inherit from them, so you don't need to implement these three methods.
So that you can rest assured to use it? The answer is no. Because Delphi secretly calls _ Release when you set the interface variable to nil (because it is unexpected.
Function _ IntfClear (var Dest: IInterface): Pointer;
Var
P: Pointer;
Begin
Result: = @ Dest;
If Dest <> nil then
Begin
P: = Pointer (Dest );
Pointer (Dest): = nil;
IInterface (P). _ Release;
End;
End;
What did _ Release do?

Function TComponent. _ Release: Integer;
Begin
If FVCLComObject = nil then
Result: =-1 //-1 indicates no reference counting is taking place
Else
Result: = ivclcomobject (fvclcomobject). _ release;
End;
If it is not a COM Object, nothing will be done. We are not working on a COM Object. Is there no problem? The answer is still no. Consider the following:

Obj2: = tc2.create;
Try
Intf1: = obj2;
Intf1.do;
Finally
Obj2.free;
Intf1: = nil;
End;

What will happen? An invalid address access error occurs. Why? As mentioned above, when the interface reference is set to nil, _ intfclear will be called, and _ intfclear will call _ release of the object, and this object has been released, naturally, an invalid address access error occurs.

Does anyone say that this is the case? The interface reference is just an address and it is not necessary to manually set it to nil.

Obj2: = tc2.create;
Try
Intf1: = obj2;
Intf1.do;
Finally
Obj2.free;
End;

The result may be unexpected, or an invalid address access error. Why? Because the Delphi compiler is clever, it thinks that you forgot to reference this address as nil, so you will automatically add it to you. It seems that the Delphi compiler is too clever. J.

How can this problem be solved?

Method 1: first set the interface reference to nil and then release the object.
Intf1: = nil;
OBJ2.Free;
Method 2: forcibly convert the interface reference to the pointer type and then set it to nil.
Pointer (Intf1): = nil;
In this case, the address is directly cleared and _ IntfClear is not called.
I tend to use the second method, so you don't have to consider who to release first. In some design patterns, you may only hold interface references, and you do not know when to release the referenced object. In this case, you must use method 2.
For example, consider the Composite mode.
TComposite = class (TComponent, I1)
Private
InterList: TXContainer; // a container class that stores "leaf" interface references.
Public
Procedure Add (AIntf: I1 );
Function DO: Boolean;
End;
Should it release its "leaves? Apparently not. Will the "leaf" be released later than this "merging object? I don't think so. If this rule is enforced, a lot of flexibility will be lost. Therefore, we are sure that when these interfaces reference nil, they will not have any relationship with the original object, so as to avoid Invalid Address Access errors after the object is released. What containers are used for consideration? Array? TList? TInterfaceList?
The first thought must be TInterfaceList, because we want to accommodate interfaces. But when we set it Free, it will set all the interfaces It holds to nil, which is exactly what we don't want. Alternatively, we can convert the API reference stored in Oss to a pointer before changing it to nil.
For I: = 0 to interList. Count-1 do
Pointer (interList. Items [I]): = nil;
Unfortunately, the compilation Error is "[Error] XXXX. pas (XX): Left side cannot be assigned ".
Then let's try array.
InterList: Array of I1;
Do not release dynamic arrays. It seems to be useful, but the compiler still sets each element as nil when releasing it, and as an interface, there is still the possibility of Invalid Address Access errors. Yes.
For I: = Low (arr) to High (arr) do
Pointer (arr [I]): = nil;
However, this is a violation of coding habits, and you must remember to do it every time you use it. If you do not remember it, you may not make mistakes immediately, so it may become a hidden risk.
Finally, use TList. But the TList is a pointer, so this is required when adding
Procedure XXX. Add (AIntf: I1)
Begin
InterList. Add (Pointer (AIntf ));
End;
Since it stores pointers, no special processing is required for release.
It seems perfect, but there is still a trap. What if you write such code?
InterList. Add (TC2.Create );
Or
Obj2: = TC2.Create;
InterList. Add (Obj2 );
Error! Because the pointer is saved purely, the address translation referenced by the object to the interface is not performed when it is converted to an interface (it does not know how to do it ), therefore, an Invalid Address Access Error occurs when you call the method declared by the interface. This is the only way to write:
InterList. Add (Pointer (I1 (TC2.Create )));
Despite some troubles, this is the best solution (as I know. If you forget that the transfer made an error during the first call, it is easier to find the error (compared to using array ).
1. Use Tlist to Manage Interface references.
2. When adding an object, you need to convert it into an Interface and then a Pointer. If it is already an Interface reference, you can directly convert it into a Pointer.
3. For interface references that are not managed using Tlist. To reference an interface, manually set it to nil: Pointer (IntfRef): = nil;
In addition, TInterfacedObject also implements three IInterface methods. Therefore, inheritance can save the trouble of implementing these three methods. However, its _ Release is implemented as follows:
Function TInterfacedObject. _ Release: Integer;
Begin
Result: = InterlockedDecrement (FRefCount );
If Result = 0 then
Destroy;
End;
The nil referenced by the interface will call the _ Release of the interface, so it may Release the object. At that time, I was shocked by it, thanks to the fact that I have never used it. That is, the implementation is much more troublesome than inheriting from TComponent. Unless otherwise used, it is not recommended.
All the above discussions about interfaces are limited to common language-level interfaces, but not Com + interfaces. The implementation of these strange interfaces of Delphi is closely related to its development from the Com + interface, that is the result of a compromise due to the heavy burden of history.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.