Delphi's interface trap
Now I know two major pitfalls:
Trap 1. Interface type conversion trap
a) You cannot cast an object reference to an interface that does not declare the type of this reference, even if the object actually implements this interface (hehe, the advantages are difficult to pronounce). b) When an object variable is assigned to an interface variable and when the interface variable is assigned to the object variable, the address of the object variable has changed, that is, it is no longer the original object, but points to an error 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. Since OBJ1's type TC1 does not declare that I1 is implemented, it cannot be converted to I1, even if OBJ1 does implement I1. Also, if you convert the object to an interface and then back, there will be problems. OBJ2 := TC2.Create;OBJ2.ATT1 := 0;
Intf1 := OBJ2;//Correct. OBJ2 := Intf1; TC2(Intf1).ATT1 := 0; //Illegal address access error during runtime. OBJ2.ATT1 := 0; //Illegal address access error during runtime. That is, after converting from object reference to pointer reference, the address changes, but when the pointer reference is turned back from object reference and then returned to object reference (Delphi's bug?).
Trap 2. Interface lifetime management
Based on my common sense (here is programming common sense, not Delphi usage common sense), I think interfaces do not require lifetime management because interfaces cannot generate real objects at all. But Delphi once again hit my common sense (Huh, why do you say "renew"?), its interface has a lifetime, and it 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 more troublesome to implement these three methods every time, and more importantly, I don't know when and how to use Delphi These three methods? So I don't know how to implement these three methods. J If you don't want to implement these three methods yourself, you can use TComponent. Because TComponent has implemented these three methods, it can be inherited from it, so there is no need to implement these three methods. Can you use it with confidence? The answer is no. Because Delphi secretly (because it was beyond my expectations) when you set the interface variable to nil. function _IntfClear(var Dest: IInterface): Pointer;var P: Pointer; began Result := @Dest; if Dest <> nil then begin P := Pointer(Dest); Pointer(Dest) := nil; IInter face(P) ._Release; end;end;and what did you do when _Release? function TComponent._Release: Integer; began if FVCLComObject = nil then Result := -1 // -1 indicates no reference counting is taking place else Result := IVCL ComObject(FVCLComObject)._Release;end;If it is not a Com object, then nothing will be Didn't do it. What we are not working as Com objects, so there is no problem? The answer is still no, consider the following situation: OBJ2 := TC2.Create;tryIntf1 := OBJ2;Intf1.DO; Finally OBJ2.Free; Intf1 := nil;End; What will happen? An illegal address access error will occur. Why? As mentioned above, when the interface reference is set to nil, _IntfClear will be called, and _IntfClear will call the object's _Release. At this time, the object has been released, and there will naturally be an illegal address access error. Some people say it's unnecessary? The interface reference is just an address, and there is no need to manually set it to nil. OBJ2 := TC2.Create;tryIntf1 := OBJ2;Intf1.DO; Finally OBJ2.Free;End;The result may still be unexpected, or the illegal address access error. Why? Because the Delphi compiler is clever, it thinks that you forgot to set this address reference to nil, so you will automatically add it to you. It seems that the Delphi compiler is too clever. How to solve it? Method 1: Set the interface reference to nil first, and then release the object. Intf1 := nil; OBJ2.Free; Method 2, force the interface reference to pointer type and set to nil. Pointer(Intf1) := nil; At this time, it is equivalent to directly clearing the address and will not call _IntfClear. I tend to use the second method so you don't have to think about who to release first. Moreover, in some design patterns, you may only hold interface references, and you don’t know when the referenced object will be released. Method 2 must be used at this time. For example, consider the Composite pattern. TComposite = class(TComponent, I1)PRivate interList: TXContainer;//A container class that stores the interface reference for "leaf". Public Procedure Add (AIntf: I1); function DO: Boolean;End; Should it release its "leaf"? Obviously not, so will the "leaves" definitely be released later than this "synthesis object" object? I don't think so. If this regulation is mandated, a lot of flexibility will be lost. So we definitely think that when these interface references are placed nil, there will be no relationship with the original object, so as to avoid illegal address access errors after the object is released. What container should you consider using? array? TList? TInterfaceList? First of all, I think it must be a TInterfaceList, because what we want to accommodate is the interface. But when Free on him, it puts all the interfaces it accommodates to nil, which is exactly what we don't want. Or we can convert the interface reference it stores into a pointer before Free and then set it to nil. for I := 0 to interList.Count -1 doPointer(interList.Items[i]) := nil; Unfortunately, the compilation error "[Error] XXXX.pas(XX): Left side cannot be assigned to". Then we try the array. interList: Array of I1;Dynamic arrays should not be released. It seems to be very useful, but when the compiler releases it, it will still set each element to nil, and as an interface, there is still a possibility of illegal address access errors. It can be done in this way for i := Low(arr) to High(arr) doPointer(arr[i]) := nil; but this is a violation of coding habit after all, and you must remember to do it every time you use it, and you may not remember to do it or not. There will be an error immediately, so it may become a hidden danger. Finally, use TList. However, there is a pointer in TList, so when Add, you must proceed as follows: XXX.Add(AIntf: I1)Begin InterList.Add(Pointer(AIntf));End; Since it originally saves a pointer, there is no need for special processing when releasing. . It seems to be perfect, but there is still a trap. What happens if you write such code? interList.Add(TC2.Create); or Obj2 := TC2.Create;interList.Add(Obj2); wrong! Because it is a pure pointer, when converted to an interface, the address conversion of the object referenced to the interface reference is not carried out (it doesn't know how to do it), so when calling the method declared by the interface, it is another illegal address access error. This is the only way to write: interList.Add(Pointer(I1(TC2.Create)); Although it is a bit troublesome, this is the best solution (as I know). Because if you forget that the transfer makes an error on the first call, it is easier to find errors (compared to using array). So I suggest: 1. Use Tlist to manage interface references. 2. When adding, you should transform the object into an Interface and then into a Pointer. If it is originally an interface reference, it can be directly converted to a Pointer. 3. For interface references that are not managed using Tlist. For reference to interfaces, you must manually set to nil by the following method: Pointer(IntfRef):= nil; In addition, TInterfacedObject also implements three methods of IInterface. Therefore, inheriting from it can also save yourself the trouble of implementing these three methods. But its _Release is implemented like this: function TInterfacedObject._Release: Integer; begin Result := InterlockedDecrement(FRefCount); if Result = 0 then Destroy;end; As mentioned, the nil referenced interface will call the interface_ Release, so it may release the object. I was shocked by it at that time, thanks to me that I had never used it. This is how it is implemented, it brings more trouble than inheriting from TComponent. Unless otherwise used, it is not recommended. All the discussions on interfaces above are limited to the language-level interfaces used generally, and there is no discussion on Com+ interfaces. The implementation of these strange interfaces of Delphi has a lot to do with it developing from the Com+ interface, which is the result of the compromise made due to the heavy historical burden.