Delphi interface traps
Now there are two traps I know:
Trap 1, type conversion traps
a) You cannot convert an object reference to this type of interface that does not have a declaration implementation, even if this object actually implements this interface (huh, advantage).
b) When an object variable is assigned to an interface variable, the address of this object variable has changed, that is, the original object is no longer, but points to an error. address.
E.g:
I1 = interface function do: boolean;
TC1 = Class Att1: integer;
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.
Since the type of OBJ1 TC1 does not declares that I1 is implemented so that I1 cannot be converted to I1, even if OBJ1 does implement I1.
Also, if you turn the object to an interface, there will be a problem.
Obj2: = tc2.create;
Obj2.att1: = 0; intf1: = Obj2; // correct.
Obj2: = intf1;
TC2 (intf1) .att1: = 0; // runtime illegal address access error.
Obj2.att1: = 0; // runtime illegal address access error.
That is, the address changed from the object reference to the pointer reference, but the address did not change when the pointer reference is reproduced back to the object reference (DELPHI's bug?).
Trap 2, the survival management of the interface
Based on my common sense (here is a programming common sense, not Delphi usage knowledge), I think the interface does not need to be managed by the survival period, because the interface is not possible to generate real objects. But Delphi has once again hits my common sense (咦, why do you want to say "again"?), Its interface is survival, and must achieve the following three methods:
Function QueryInterface (Const IID: Tguid; Out Obj): hResult; stdcall;
Function _addref: integer; stdcall;
Function _Release: integer; stdcall;
Every time you have to achieve these three methods is more trouble, and more importantly, I don't know when Delphi uses and how to use these three methods? So I don't know how to achieve these three methods. J
If you don't want to achieve these three methods, you can use Tcomponent. Because Tcomponent has implemented these three methods, you can inherit it from it, you don't have to achieve these three methods.
Can you use it with confidence? the answer is negative. Because Delphi is secret (because I am very unexpected) when you set the interface variable to NIL.
Function_INTFCLEAR (VAR DEST: IINTERFACE): POINTER;
VAR
P: POINTER;
Begin
Result: = @Dest;
IF DEST <> nil dam
Begin
P: = Pointer (DEST);
Pointer: = NIL;
Iinterface (p) ._ release;
END;
And what happened to _RELEASE?
Function Tcomponent._release: integer;
Begin
IF fvclcomobject = nil then
Result: = -1 // -1 Indicates No Reference Counting is Taking Place
Else
Result: = ivcl.comObject (fvclcomobject).
END;
If you are not a COM object, you have nothing. What we do is not a COM object, is there any problem? The answer is still negative, considering the following situation:
Obj2: = tc2.create;
Try
INTF1: = OBJ2;
Intf1.do;
Finally
Obj2.free;
INTF1: = NIL;
END;
What will it? The illegal address access error will be accessed. why? As mentioned above, when the interface is referenced to NIL, _intfclear will be called, and _intfclear will call the _Release, and this object has been released, and naturally the illegal address access is wrong.
Some people say more, the interface reference is just an address, and it is not necessary to have a nil.
Obj2: = tc2.create;
Try
INTF1: = OBJ2;
Intf1.do;
Finally
Obj2.free;
END;
The results may also have your expectation, or illegally address access errors. why? Because the Delphi compiler played a little smart, it thinks you forget to set this address to nil, so you will automatically give you, it seems that the Delphi compiler is smart. J.
How to solve it?
Method 1, first put the interface reference to NIL, then release the object.
INTF1: = NIL;
Obj2.free;
Method 2, the interface reference is forced to convert to the pointer type to NIL.
Pointer: = NIL;
At this time, it is equivalent to clearing the address directly, and _intfclear is not called.
I tend to use the second method so you don't have to consider the problem of who is released first. And some design patterns you might only hold interface reference, and you don't know when the reference object is released, and you must use Method 2.
For example, consider the Composite mode.
Tcomposite = Class (Tcomponent, i1)
Private
Interlist: txcontainer; // A container class, stores the "leaf" interface reference.
Public
Procedure Add (AINTF: I1);
Function do: boolean;
END;
Should it release its "leaf"? Obviously, the "leaf" will be released later than this "synthetic object" object? I don't want it. If it is forced to specify, there is a lot of flexibility. So we affirmed that when these interfaces cited NIL, we won't have any relationship with the original object, so as to prevent objects to be released and illegal address access errors. What container considers it? array? TLIST? TinterFacelist?
First of all, I think of it is TinterFacelist, because we are to accommodate the interface. But when FREE is performed on him, it will place all of its accommodated interfaces as NIL, which is exactly what we don't want. Or we can use the interface reference to the interface to the pointer to NIL before free before Free.
For i: = 0 to interlist.count -1 dopointer (interlist.Items [i]): = nil;
It is a pity that compiling errors "[Error] xxxx.pas (xx): Left side cannot be assigned to".
Then we tried array.
Interlist: Array Of I1;
Dynamic arrays do not release. It seems to be used very well, but the compiler will still be set to nil for each element, and as an interface, there is still an illegal address access error. Can
For i: = low (arr) to high (arr) do
Pointer (arr [i]): = nil;
But this is a violation of the coding habit, and every time you use, you must remember, you may not remember that it may not be wrong immediately, so it is possible to become a hidden danger.
Finally, use TLIST. However, the TLIST is a pointer, so the add must be like this.
Procedure xxx.add (AINTF: I1)
Begin
Interlist.add (Pointer (AINTF));
END;
Since it is a pointer, it does not require special processing when it is released.
It seems to be perfect, but there is still a trap, if you write such a code?
Interlist.add (tc2.create);
or
Obj2: = tc2.create;
Interlist.add (Obj2);
wrong! Since the pure pointer is saved, the object references to the interface references are not performed (it doesn't know how to perform), so it is an illegal address access error when calling the interface declaration. Can only write this:
Interlist.add (Pointer (Tc2.create)));
Although there is a bit of trouble, it is a best solution (I know) compared to this. Because you have forgotten that the transfer is wrong when you call, it is easier to find errors (compared to using array).
So I suggest:
1. Use TLIST to manage interface references.
2. When the increase is increased to Interface and transform into Pointer, if it is the interface referenced, it can be converted to Pointer.
3. The interface references that do not use TLIST to manage. For the reference to the interface, you can use the following method to manually, to NIL: Pointer: = NIL;
In addition, TinterFaceDObject also implements three methods of IINTERFACE. Therefore, from its inheritance, you can save the trouble of achieving these three methods. But its _RELEASE is this:
Function TinterFaceDObject._release: integer;
Begin
Result: = interlockedDecrement (freecount);
if Result = 0 THEN
DESTROY;
END;
We have said before, the interface references will be called _Release, so it is possible to release the object to be released. I was shocked by it, thanks I didn't have used it. That is, the implementation is greater than inheriting from Tcomponent. Unless special use is not recommended.