Интерфейсті қалай шешуге болады

Мен интерфейстерді әртүрлі байланыссыз сыныптарға ортақ функционалдылықты беру тәсілі ретінде әрқашан ойлаған болатынмын. Бірақ интерфейстің сипаты - «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;

немесе ескертулерде Дэвид ұсынғандай, refccounting қызметін өшіреді. Бұл өзіңіздің «TInterfacedObject» -ды жариялау және үш ІІІ интерфейстің әдістерін енгізуді білдіреді:

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

Мағынасы _AddRef және _Release үшін -1 қайтару. Дэвиддің айтқанындай: «Коммпинсонның» оны қалай жасайтынын қараңыз. FVCLComObject нөлге тең болғанда ғана жасаңыз.

8
қосылды
мәні сілтемелерді санамайды. Қайтару -1 - сілтеме санауды ажыратады.
қосылды автор Rob Kennedy, көзі
@Wodzu, босатылған нысанның өрістеріне қатынасу бағдарламаның құлауы міндетті емес. Егер жады ОЖ-ға қайтарылмаған болса, ОЖ-ны сіздің жадыңызға тиесілі деп санайды, себебі сіздің процеңіз ешқандай объектіге бермеген болса да, бұл жадты оқуға рұқсатсыз кіру мүмкін емес. Интерфейсті емес, объектінің өзі туралы емес, сөйлесу туралы әңгімелесу нөлдік емес; объект болмаса, онда - интерфейсі жоқ. Интерфейс өзінің жеке деректерін қамтымайды.
қосылды автор Rob Kennedy, көзі
Жоқ, @Marjan, қайтару мәні мүлдем маңызды емес. AddRef-дің қайтаратын мәнінде көрінеді дегенге ешқандай код таба алмайсыз. Сілтеме санауды өшіретіні - бұл AddRef енгізу әрекеті және емес сілтемелердің кез келген санын жаңартатын кез келген кодты жазу. Объектілер өздерінің сілтемелерін қадағалайды. Нысан өз меншікті сілтемесін нөлге дейін азайтқан кезде, ол өзі жояды екенін байқайды. Мәселен, сіз тіпті сілтеме санауды ажыратудың қажеті жоқ; _Release ішінен өздігінен жою қадамын өткізіп алуыңыз керек.
қосылды автор Rob Kennedy, көзі
@Wodzu: мүмкін. Кейде компилятор енгізген уақытша айнымалылар процедура/функция шыққанша реферттің нөлден жоғары деңгейін сақтайды.
қосылды автор Marjan Venema, көзі
#Rob: егер қайтаратын -1 рефcount-ды өшірмесе, онда не бар? О, ойланып көрейін деп ойлаймын. Қайтару -1 - «сілтемелерді нақты санаудың бірден-бір жолы» (және осылайша референт нөлге жетуден аулақ болу ...)
қосылды автор Marjan Venema, көзі
@Rob: Түсіндіру үшін рақмет. Оқуым мен үшін өзіме «Д'О» дегенді жіберді - мен мидың тұмандығы үшін созылмалы мигреньді ... :-)
қосылды автор Marjan Venema, көзі
Жауап Маржанға рахмет, бірақ мен бірдеңе алмаймын. RefCount 0-ге түссе, MyObject автоматты түрде босатылды. Бірақ сіз MyObject.RefCount жасап, сіз AccesViolation аласыз, қалай келеді? Компилятор сіз өзіңіздің интерфейсіңізге емес, объектіге емес, репербереніңізді біледі?
қосылды автор Wodzu, көзі
@Rob Сонымен, Marjan мысалындағы соңғы код қате емес (RefCount интерфейсті жасағаннан кейін көрсетіледі) және бұл тек қана сәйкестік болып табылады?
қосылды автор 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
қосылды
Шындығында өте қарапайым және көптеген жағдайларда жақсы ненасы сіз кейінірек қоңырау шалуды жасасаңыз және сілтеме санағы нөлге жетеді және объект ақыр соңында босатылады және ағып кетпейді.
қосылды автор Warren P, көзі