Як вырашыць інтэрфейс беспарадак

Я заўсёды думаў пра інтэрфейсах як спосаб даць розныя няроднасныя класы агульнай функцыянальнасці. Але ўласцівасць інтэрфейсу - «вызваліць аб'ект, калі RefCount падае да нуля» не дазваляе мне працаваць, як я хачу.

Напрыклад: выкажам здагадку, што ў мяне ёсць два класа: TMyObject і TMyDifferentObject. Яны абодва падтрымліваюць гэты інтэрфейс:

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;

Цяпер я хацеў бы ствараць асобнікі гэтага класа ў маёй праграме, а затым перадаць гэтыя асобнікі да гэтага метаду:

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!

Гэта прыклад. Увогуле, я хачу перадаць асобнік аб'екта ў пэўную працэдуру і праверыць, калі гэты аб'ект падтрымлівае інтэрфейс, калі так, то я хачу, каб выканаць метад гэтага інтэрфейсу. Але я не хачу, каб скончыць працу з гэтым аб'ектам, калі інтэрфейс выходзіць з вобласці бачнасці. Як гэта зрабіць?

З найлепшымі пажаданнямі.

2
Вы можаце адключыць падлік спасылак. Паглядзіце, як IInterface рэалізуецца ў TComponent.
дададзена аўтар David Heffernan, крыніца

3 адказы

Ваша праблема, верагодна, звязана з тым, што вы ствараеце свае аб'екты, выкарыстоўваючы спасылку на аб'ект:

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;

Робячы гэта, як гэта азначае, што RefCount пасля стварэння роўны нулю. Альбо прысвоіць аб'ект інтэрфейснай спасылкі, а так доўга, як вам гэта трэба,

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;

або адключыць refcounting як Давід прапанаваў у каментарах. Які па сутнасці азначае, абвяшчаючы свой уласны «TInterfacedObject» і рэалізацыі трох метадаў IInterface:

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

Сутнасць у тым, каб вярнуцца -1 як для _AddRef і _Release. Як сказаў Давід: зірнуць на тое, як ТСотропепЬ гэта робіць. І проста ўзяць тое, што ён робіць, калі FVCLComObject роўная нулю.

8
дададзена
<Я> сутнасць , каб не лічыць спасылкі. Вяртанне -1 не тое, што адключае лічыльнік спасылак.
дададзена аўтар Rob Kennedy, крыніца
@Wodzu, доступ да палёў вызваленага аб'екта не абавязкова выклікае праграму аварыі. Калі памяць не была вернутая ў АС, гэта немагчыма, каб атрымаць парушэнне правоў доступу ад чытання гэтай памяці, так як аперацыйная сістэма лічыць, што яна па-ранейшаму належыць да працэсу, нават калі працэс не вылучае яго на які-небудзь аб'ект. Гэта нонсэнс казаць аб спасылцы на інтэрфейс, а не сам аб'ект; без аб'екта, існуе не няма інтэрфейсу. Інтэрфейс не ўтрымлівае ніякіх дадзеных пра сябе.
дададзена аўтар Rob Kennedy, крыніца
Не, @Marjan, вяртаецца значэнне зусім не мае значэння. Вы ніколі не знойдзеце код, які нават <я> выглядае у якое вяртаецца значэння AddRef. Што адключае лічыльнік спасылак з'яўляецца актам рэалізуе AddRef, а затым <я> не пісаць код, які абнаўляе любую колькасць спасылак. Аб'екты адсочвання ўласныя спасылкі. Калі аб'ект Декременты свой лічыльніка спасылак на нуль, ён заўважае, што і <я> самоудаляюсь . Такім чынам, вы сапраўды не трэба нават адключыць падлік спасылак; Вам проста трэба прапусціць крок самастойна выдаліць ў _Release .
дададзена аўтар Rob Kennedy, крыніца
@Wodzu: магчыма. Хоць часам часовая пераменная ўведзена кампілятар будзе трымаць RefCount вышэй за нуль і экзэмпляр вакол, пакуль працэдуру/функцыянальныя выхады.
дададзена аўтар Marjan Venema, крыніца
#Rob: калі вяртанне -1 не адключае refcounting, тое, што робіць? О, пачакайце, я думаю, я разумею, што вы маеце на ўвазе. Вяртанне -1 «проста спосаб на самай справе, не лічачы спасылак» (і, такім чынам, пазбягаючы RefCount дасягаючы нуля ...)
дададзена аўтар Marjan Venema, крыніца
@Rob: Дзякуй за тлумачэнне. Чытаючы гэта прымусіла мяне пайсці «D'ой» на сябе - я патрабую млявай мігрэні для foginess майго мозгу ... :-)
дададзена аўтар Marjan Venema, крыніца
Дзякуй за адказ Мар'ян, але я не атрымліваю адну рэч. Калі RefCount ўпаў да 0, то MyObject былі вызваленыя аўтаматычна. Але тады вы робіце MyObject.RefCount і вы не атрымліваеце AccesViolation, як жа? Кампілятар прызнае, што вы ВЕ інтэрфейс, а не сам аб'ект?
дададзена аўтар Wodzu, крыніца
@Rob Такім чынам, апошняя радок кода ў прыкладзе Мар'ян з'яўляецца няправільным (паказваючы RefCount пасля nilling інтэрфейс), і гэта ўсяго толькі супадзенне, што гэта працуе?
дададзена аўтар Wodzu, крыніца

Адзін з падыходаў, каб вырашыць вашу праблему, каб змяніць код так, што вы толькі калі-небудзь спасылацца на аб'ект праз інтэрфейсных спасылку. Іншымі словамі, замест таго,

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

Вы пішаце

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

Гэта можа быць нязручна, так што замест гэтага ён можа быць больш зручным для адключэння аўтаматычнага кіравання часам жыцця. Вы павінны зрабіць гэта для рэалізуе аб'екта:

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;

Вы можаце атрымаць свае класы з гэтага класа.

Існуе адзін вельмі буйны нюанс з гэтым падыходам. Выкажам здагадку, што вы трымаеце ў зменных (лакальныя, глабальныя, член класа) любыя інтэрфейсы, якія рэалізуюцца з дапамогай класа, вытворнага ад TInterfacedObjectWithoutLifetimeManagement . Усе гэтыя зменныя інтэрфейсу павінны быць завершаны <моцнага> перад вы выклікаеце Free на які рэалізуе аб'екце.

Калі вы не будзеце прытрымлівацца гэтага правіла вы ўбачыце, што, калі гэтыя зменныя інтэрфейсу выходзяць з вобласці бачнасці, кампілятар яшчэ генеруе код для выкліку _Release і гэта памылка, каб выклікаць метад аб'екта пасля таго, як ён быў знішчаны. Гэта асабліва непрыемны тып памылкі, таму што ён звычайна не будзе выяўляцца з адмовай падчас выканання, пакуль ваш код не працуе на кампутары вашага самы важнага кліента! Іншымі словамі, такія памылкі могуць быць перарывістага характару.

3
дададзена
Назва Вялікі клас ( TInterfacedObjectWithoutLifetimeManagement ). +1 за тое, што зразумела.
дададзена аўтар Warren P, крыніца

Іншы варыянт ніхто не згадаў да гэтага часу відавочна выклікаць _AddRef на асобнік аб'екта, каб захаваць яго ў жывых да тых часоў, пакуль вам гэта трэба, а затым выклікаць _Release .

3
дададзена
Вельмі просты варыянт, зрэшты, і ў многіх выпадках добра «Nuff, да таго часу, як вы пазней зрабіць _Release выклікаў і лічыльнік спасылак сапраўды дасягае нуля, і аб'ект, у рэшце рэшт вызвалілі, і ня прасачылася.
дададзена аўтар Warren P, крыніца