2017-02-18 1 views
4

Je testais un bloc de code pour un commentaire ici sur StackOverflow et j'ai rencontré une situation où la variable d'interface implicite l'a élevé. Ce que je ne peux pas voir, c'est ce qui le cause dans ce cas. J'ai une mini usine qui retourne une interface pour un objet nouvellement créé. Si j'appelle la méthode dans un bloc de procédure alors j'obtiens seulement un nombre de référence de 1. Si je l'appelle du bloc de programme principal alors j'obtiens un compte de référence de 2.Quand le compilateur Delphi crée-t-il une variable d'interface implicite?

J'ai testé ceci avec Delphi 10 Seattle. Y at-il une ressource qui contient les règles pour la création d'interface implicite et est le modèle où une interface est retournée par une usine pas un modèle fiable?

program TestRefCount; 

{$APPTYPE CONSOLE} 

{$R *.res} 

uses 
    System.SysUtils, 
    Vcl.Dialogs; 

type 
    IMyInterface = interface(IInterface) 
    ['{62EB2C46-9B8A-47CE-A881-DB96E6F6437D}'] 
    procedure DoSomething; 
    function GetRefCount: Integer; 
    end; 

    TMyObject = class(TInterfacedObject, IMyInterface) 
    strict private 
    FMyValue: Integer; 
    public 
    procedure Init; 
    procedure DoSomething; 
    function GetRefCount: Integer; 
    end; 

    TMyFactory = class(TObject) 
    private 
    function CreateMyInt: IMyInterface; 
    end; 

procedure TMyObject.DoSomething; 
begin 
    MessageDlg(IntToStr(FMyValue), mtInformation, [mbok], 0); 
end; 

function TMyObject.GetRefCount: Integer; 
begin 
    Result := FRefCount; 
end; 

procedure TMyObject.Init; 
begin 
    FMyValue := 100; 
end; 

function TMyFactory.CreateMyInt: IMyInterface; 
var 
    myObject: TMyObject; 
begin 
    myObject := TMyObject.Create; 
    Assert(myObject.GetRefCount = 0); 
    myObject.Init; 
    Assert(myObject.GetRefCount = 0); 
    Result := myObject; 
    Assert(myObject.GetRefCount = 1); 
    Assert(Result.GetRefCount = 1); 
end; 

procedure WorkWithIntf; 
var 
    myFactory: TMyFactory; 
    myInt: IMyInterface; 
begin 
    myFactory := TMyFactory.Create; 
    try 
    myInt := myFactory.CreateMyInt; 
    Assert(myInt.GetRefCount = 1); 
    myInt.DoSomething; 
    Assert(myInt.GetRefCount = 1); 
    finally 
    myFactory.Free; 
    end; 
end; 

var 
    myFactory: TMyFactory; 
    myInt: IMyInterface; 
begin 
    try 
    // This case doesn't have an implicit interface variable 
    WorkWithIntf; 
    // This case does have an implicit interface variable 
    myFactory := TMyFactory.Create; 
    try 
     myInt := myFactory.CreateMyInt; 
     Assert(myInt.GetRefCount = 1); // This fails because the refcount is 2 
     myInt.DoSomething; 
     Assert(myInt.GetRefCount = 1); 
    finally 
     myFactory.Free; 
    end; 
    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 
end. 

Voici les premiers blocs où il n'y a pas de variable d'interface implict:

TestRefCount.dpr.67: myInt := myFactory.CreateMyInt; 
005C6A5A 8D55F8   lea edx,[ebp-$08] 
005C6A5D 8B45FC   mov eax,[ebp-$04] 
005C6A60 E83BFEFFFF  call TMyFactory.CreateMyInt 
TestRefCount.dpr.68: Assert(myInt.GetRefCount = 1); 
005C6A65 8B45F8   mov eax,[ebp-$08] 

Voici le deuxième bloc où l'on peut voir la variable d'interface implicite:

TestRefCount.dpr.86: myInt := myFactory.CreateMyInt; 
005CF513 8D55EC   lea edx,[ebp-$14] 
005CF516 A19CB75D00  mov eax,[$005db79c] 
005CF51B E88073FFFF  call TMyFactory.CreateMyInt 
005CF520 8B55EC   mov edx,[ebp-$14] 
005CF523 B8A0B75D00  mov eax,$005db7a0 
005CF528 E8C7E3E3FF  call @IntfCopy 
TestRefCount.dpr.87: Assert(myInt.GetRefCount = 1); // This fails because the refcount is 2 
005CF52D A1A0B75D00  mov eax,[$005db7a0] 

Répondre

0

Comme je l'ai le comprendre, et il n'est écrit dans aucune documentation, le compilateur traite différemment les variables locales et globales. Avec un local, il espère que l'affectation au local réussira. Par conséquent, aucun local implicite n'est nécessaire. C'est votre premier cas.

Pour une variable globale, le compilateur est plus circonspect. Il regarde les variables globales avec suspicion. Il émet un code défensif en cas d'échec de l'affectation à la variable. Le compilateur attribue d'abord à un local implicite, une assignation dont il est certain qu'elle réussira. Par conséquent, l'interface aura son compte de référence incrémenté. Ensuite, il affecte au global. Si cela échoue alors au moins le local implicite a une référence et est capable de le décrémenter et de libérer l'interface correctement.

Vous pourriez vous demander pourquoi le compilateur est nerveux à propos de l'assignation à votre variable globale. Vous savez qu'il est sûr, et ne peut pas échouer, de quoi le compilateur a-t-il peur? Son concept de variable globale est plus large. Il considère qu'un pointeur sur une référence d'interface, par exemple, est global. Le compilateur essaie de se défendre contre l'invalidation de ce pointeur, l'échec de l'assignation, et personne ne prend de référence à l'interface. Le compilateur ne considère que deux cas: local et global. Il confie l'affectation aux variables locales et tout le reste est associé à des variables globales potentiellement risquées. Y compris votre global parfaitement sûr.

À mon avis le compilateur est trop prudent. Si le programmeur a dit que la variable peut être affectée à, je ne pense pas que c'est la place du compilateur pour en douter. Si le programmeur a fait une erreur, alors le programmeur devrait sûrement être prêt à accepter les conséquences. Qu'il s'agisse de fuites ou d'échecs d'accès à la mémoire d'exécution. Mais les concepteurs ont adopté une approche différente, plus conservatrice. Un autre scénario dans lequel vous voyez une variable locale implicite est lorsque vous utilisez l'opérateur as sur une valeur de retour de fonction un autre scénario où 012. Par exemple:

Foo := GetBar as IFoo; 

Plus sur cette ici: The mysterious case of the unexpected implicit interface variable.

Ce cas est clair cependant. La variable locale implicite est essentielle car il est parfaitement raisonnable que as déclenche une exception.