Bir arayüz karmaşası nasıl çözülür

Her zaman birbiriyle ilişkili olmayan sınıflara ortak bir işlev kazandırmanın bir yolu olarak arayüzleri düşünüyordum. Ama arabirimin özelliği - "RefCOunt sıfıra düştüğünde bir nesne serbest" istediğim gibi çalışmama izin vermiyor.

Örneğin: iki farklı sınıfa sahip olduğumu varsayalım: TMyObject ve TMyDifferentObject. İkisi de bu arayüzü destekliyor:

const
  IID_MyInterface: TGUID = '{4D91C27F-510D-4673-8773-5D0569DFD168}';

type
 IMyInterface = Interface(IInterface)
  ['{4D91C27F-510D-4673-8773-5D0569DFD168}']
  function GetID : Integer;
 end;

type
  TMyObject = class(TInterfacedObject, IMyInterface)
    function GetID: Integer;
  end;

function TMyObject.GetID: Integer;
begin
  Result := 1;
end;


type
  TMyDifferentObject = class(TInterfacedObject, IMyInterface)
    function GetID: Integer;
  end;

function TMyDifferentObject.GetID: Integer;
begin
  Result := 2;
end;

Şimdi, bu sınıfların örneklerini programımda oluşturmak ve bu örnekleri bu yönteme aktarmak istiyorum:

procedure ShowObjectID(AObject: TObject);
var
  MyInterface: IMyInterface;
begin
  if Supports(AObject, IID_MyInterface, MyInterface) then
  begin
    ShowMessage(IntToStr(MyInterface.GetID));
  end;
end;  //Interface goes out of scope and AObject is freed but I still want to work with that object!

Bu bir örnektir. Genelde, bir nesne örneğini bazı yordamlara aktarmak ve bu arabirimin bir arabirimi destekleyip desteklemediğini kontrol etmek istiyorum, evet ise, bu arabirimin yöntemini çalıştırmak istiyorum. Ancak, arabirim kapsam dışına çıktığında bu nesneyle çalışmayı bitirmek istemiyorum. Bu nasıl yapılır?

Saygılarımızla.

2
Referans saymayı devre dışı bırakabilirsiniz. TCmonponent içerisinde IInterface'in nasıl uygulandığına bakın.
katma yazar David Heffernan, kaynak

3 cevap

Sorununuz, bir nesne başvurusu kullanarak nesnelerinizi oluşturduğunuzdan kaynaklanıyor olabilir:

var
  MyObject: TObject;
begin
  MyObject := TMyObject.Create;
  ShowMessage('Before ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
  ShowObjectID(MyObject);
  ShowMessage('After ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
end;

Bunun böyle yapması, yaratıldıktan sonra RefCount'un sıfır olduğu anlamına gelir. İhtiyaç duyduğunuz sürece nesneyi bir arayüz referansına da atayabilirsiniz,

var
  MyObject: TMyObject;
  MyIntf: IMyInterface;
begin
  MyObject := TMyObject.Create;
  MyIntf := MyObject;
  ShowMessage('Before ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
  ShowObjectID(MyObject);
  ShowMessage('After ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
  MyIntf := nil;
  ShowMessage('After nilling the interface MyObject RefCount: ' + IntToStr(MyObject.RefCount));
end;

veya David'in yorumlarda önerdiği gibi refcounting'i devre dışı bırakın. Esasen kendi "TInterfacedObject" inizi bildirmek ve üç adet IInterface yöntemini uygulamak anlamına gelir:

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;

Öz, _AddRef ve _Release için -1 döndürmektir. David'in dediği gibi: TComponent'in nasıl yaptığını gözden geçirin. Ve sadece FVCLComObject nil olduğunda ne yaptığınızı alın.

8
katma
<�İ> Öz referans sayılmaz. Geri dönen -1, referans saymayı engelleyen şey değil.
katma yazar Rob Kennedy, kaynak
@Wodzu, serbest bir nesnenin alanlarına erişim mutlaka bir programın çökmesine neden olmaz. Bellek işletim sistemine geri gönderilmediyse, işleminiz herhangi bir nesneye tahsis edilmemiş olsa bile, işletim sistemi hala işleminize ait olduğunu düşündüğünden, bu belleği okumadan bir erişim ihlali elde etmek imkansızdır. Arayüzün kendisine değil, arayüze atıfta bulunmaktan söz etmek saçmalıktır; nesne olmadan arabirim yoktur. Arayüz kendi veriyi içermiyor.
katma yazar Rob Kennedy, kaynak
Hayır, @Marjan, dönüş değeri tamamen alakasız. AddRef'in dönüş değerinde bile görünen olan hiçbir kodu bulamazsınız. Referans sayımını devre dışı bırakan, AddRef uygulamasının gerçekleştirilmesidir ve daha sonra, herhangi bir referans sayısının güncellenmesiyle ilgili herhangi bir kodun yazılması değil . Nesneler kendi referanslarını takip eder. Bir nesne kendi referans sayımını sıfıra indirdiğinde, bunu fark eder ve kendisini siler . Yani, gerçekten referans sayımını devre dışı bırakmanıza bile gerek yok; sadece _Release 'deki kendi kendini silme adımını atlamanız gerekir.
katma yazar Rob Kennedy, kaynak
@Wodzu: muhtemelen. Zaman zaman derleyici tarafından başlatılan geçici bir değişken, geri sayımı sıfırın üzerinde tutacak ve prosedür/işlev çıkana kadar olayı koruyacaktır.
katma yazar Marjan Venema, kaynak
#Rob: -1 döndürürse refcounting'i devre dışı bırakmazsa, o zaman ne yapar? Oh, bekle, ne demek istediğini anladım. -1 dönüşü "sadece referansları saymamanın bir yoludur" (ve böylece sıfır sayısının sıfır seviyesine ulaşmasını engeller).
katma yazar Marjan Venema, kaynak
@Rob: Açıklama için teşekkürler. Okumak, kendime "D'oh" yapmamı sağladı - beynimin sisli olması için sürekli bir migren olduğunu iddia ediyorum ... :-)
katma yazar Marjan Venema, kaynak
Cevabın Marjan için teşekkürler, ama bir şey alamıyorum. RefCount 0'a düşerse, MyObject otomatik olarak serbest bırakıldı. Ama sonra MyObject.RefCount yapıyorsun ve AccesViolation alamayacaksın, nasıl oldu? Derleyici, nesnenin kendisine değil, arabirime yöneldiğinizin farkındadır?
katma yazar Wodzu, kaynak
@Rob Bu yüzden Marjan'ın örneğindeki son kod satırı yanlıştır (arabirimi doldurduktan sonra RefCount'u gösterir) ve bu sadece bir tesadüf mü?
katma yazar Wodzu, kaynak

Sorununuzu çözmek için bir yaklaşım kodunuzu değiştirmektir, böylece sadece bir arabirim referansı yoluyla nesneye başvurursunuz. Başka bir deyişle yerine

var
  obj: TMyObject;
...
obj := TMyObject.Create;
try
  obj.DoStuff;
  //etc. etc.
finally
  obj.Free;
end;

Sen yaz

var
  obj: IMyObject;//NOTE: interface variable
...
obj := TMyObject.Create;
obj.DoStuff;
//etc. etc.
obj := nil;//or let it go out of scope and release that way

Bu rahatsız edici olabilir, bu yüzden otomatik ömür boyu yönetimi devre dışı bırakmak daha uygun olabilir. Uyguladığınız nesne için bunu yapmanız gerekir:

type
  TInterfacedObjectWithoutLifetimeManagement = class(TObject, IInterface)
  private
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

function TInterfacedObjectWithoutLifetimeManagement.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TInterfacedObjectWithoutLifetimeManagement._AddRef: Integer;
begin
  Result := -1;
end;

function TInterfacedObjectWithoutLifetimeManagement._Release: Integer;
begin
  Result := -1;
end;

Daha sonra sınıflarınızı bu sınıftan türetebilirsiniz.

Bu yaklaşımla çok büyük bir uyarı var. TInterfacedObjectWithoutLifetimeManagement türetilmiş bir sınıf tarafından uygulanan arabirimleri değişkenler (yerel, genel, sınıf üyesi) tuttuğunuzu varsayalım. Bu tür arayüz değişkenlerinin, uygulama nesnesinde olarak adlandırdığınız önce sonlandırılması gerekir.

Bu kuralı uygulamamanız durumunda, bu arayüz değişkenleri kapsam dışında kaldığında, derleyici kod _Release 'i çağırmak için hala kod çıkarır ve bir nesne üzerinde bir yöntemi çağırmak için bir hata olur. yerlebir edilmiş. Bu özellikle kötü bir hata türüdür, çünkü kodunuz en önemli müşterinizin makinesinde çalışıncaya kadar bir çalışma zamanı hatası ile kendini göstermez! Diğer bir deyişle, bu tür hatalar aralıklı doğa olabilir.

3
katma
Büyük sınıf adı ( TInterfacedObjectWithoutLifetimeManagement ). Bunu anlaşılabilir kılmak için +1.
katma yazar Warren P, kaynak

Şimdiye kadar bahsettiğim başka bir seçenek, nesne örneğinde, ihtiyacınız olduğu sürece canlı tutmak için _AddRef 'i açıkça çağırmak, ardından _Release ' i çağırmaktır.

3
katma
Gerçekten çok basit bir seçenek ve çoğu durumda, iyi 'nuff, daha sonra _release çağırır ve referans sayısı sıfıra ulaşır ve nesne sonunda serbest ve sızdırmaz.
katma yazar Warren P, kaynak