Interfejsy

Adam Boduch

Interfejsy, podobnie jak klasy, służą do przechowywania funkcji i procedur. W odróżnieniu od klasy, interfejsy jedynie definiują zbiór funkcji i procedur, nie zawierają części implementacyjnej.

Interfejsy deklarowane są z użyciem słowa kluczowego interface:

type
  IFoo = interface
    procedure Bar();
  end;

Podobnie jak wszystkie klasy VCL, dziedziczą po TObject, wszystkie interfejsy dziedziczą po interfejsie IUnknown. Sama deklaracja interfejsu jest prosta, jednak to klasa musi implementować funkcje i procedury ów interfejsu:

type
  TFoo = class(TObject, IFoo)
  public
    procedure Bar();
  end;

procedure TFoo.Bar();
begin
  WriteLn('Hello World!');
end;

Język Delphi nie umożliwia dziedziczenia po wielu klasach. Możliwe jest jednak implementowanie wielu interfejsów, których nazwy oddzielone są od siebie znakiem przecinka. Innymi słowy, klasa TFoo użyta w przykładzie musi zawierać procedurę Bar, która zadeklarowana została w interfejsie IFoo.

Ogólnie przyjęte zostało, że nazwy interfejsów poprzedza litera I.

Istnieje kilka istotnych reguł dotyczących interfejsów, które powinieneś znać:

  • Interfejsy mogą zawierać jedynie metody lub właściwości. pola są zabronione.
  • Jako że interfejsy nie mogą zawierać pól, słowa kluczowe read i write muszą wskazywać na metody.
  • Wszystkie metody interfejsu są domyślnie publiczne. W interfejsach nie istnieje coś takiego jak dostępność klas.
  • Interfejsy nie mają konstruktorów ani destruktorów.
  • Metody interfejsu nie mogą być opatrzone dyrektywami virtual, dynamic, abstract lub override.

Spójrz jeszcze raz na powyższy kod Delphi. Próba kompilacji takiego kodu może zakończyć się komunikatem błędu: Undeclared identifier: 'QueryInterface', Undeclared identifier: '_AddRef', Undeclared identifier: '_Release'. Wymusza to na nas zaimplementowanie w interfejsie wymienionych metod.

Umieść kursor w dowolnym miejscu wewnątrz klasy i naciśnij kombinację klawiszy Ctrl + Spacja, aby wywołać mechanizm automatycznego wykańczania kodu. W wyświetlonym oknie wykańczania kodu zostaną na czerwono wyświetlone niezaimplementowane jeszcze metody. Zaznacz na liście wszystkie metody oznaczone kolorem czerwonym ? przytrzymując wciśnięty klawisz Shift użyj klawiszy strzałek lub myszki. Naciśnij klawisz Enter ? metody interfejsu zostaną automatycznie dodane do definicji klasy. Naciśnij kombinację klawiszy Ctrl + Shift + C, aby wykończyć część implementacyjną dla nowo dodanych metod:

type
  TFoo = class(TObject, IFoo)
  public
    procedure Bar();
  public
    function _AddRef(): Integer; stdcall;
    function _Release(): Integer; stdcall;
    function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
  end;

Jeśli nasza klasa implementuje wiele interfejsów, które zawierają metody oznaczone takimi samymi sygnaturami, musimy dla tych metod stworzyć odpowiednie aliasy. Spójrz na poniższy przykład:

type
  IFoo = Interface
    procedure Bar();
  end;

  IBar = interface
    procedure Bar();
  end;

  TFooBar = class(TObject, IFoo, IBar)
  public
    // aliasy
    procedure IFoo.Bar = IFooBar;
    procedure IBar.Bar = IBarBar;
  public
    // metody
    procedure IFooBar();
    procedure IBarBar();
  public
    function _AddRef(): Integer; stdcall;
    function _Release(): Integer; stdcall;
  public
    function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
  end;

Zobacz też:

5 komentarzy

co prawda 3 lata po komentarzu samueldama ale lepiej późno niż wcale.
Załóżmy, że mamy jaką rodzinę klas pochodzącychod TKapsalon.. Mają pierdyliony metod w tym metodę NapiszIleProcentJużWykonano. Na dodatek najbardziej pierwotny ma najbardziej istotną dla rodziny proceduręUstalSkładProcentoWyMięsaDoFrytek. Bez tego nie ma kapsalonów.
I mamy inną rodzinę TKebab. Mają pierdyliony innych procedur, ale także mają NapiszIleProcentJużWykonano. Oczywiście procedura najbardziej istotna jest WybierzJakieDaćMięsko. Bez tego nie ma kebaba.
W żaden sensowny sposób nie da się wtłoczyć Kapsalona do Kebaba i na odwrót.
Ale chcemy mieć procedurę, która dla wszystkich dań wyświetli stan prac.
Dajemy interface zawierający NapiszIleProcentJużWykonano i możemy pracowańć i na kapsalonach i na kebabach.
A jak stworzymy rodzinę klas pochodzących od TRodzajKsięgowania i damy ten interface to o dziwo wszsytkie procedurey dla kaspalona i kebaba wyświetlające stan prac będą mogły być zastosowane przez program księgowy...

Mam to samo zdanie, art jest bezużyteczny, mój poprzednik nie napisał dlaczego, ja napiszę, otóż artykuł opisuje podstawy użycia interfejsów w delphi, jednakże po tym gdy go przeczytałem, zadałem sobie pytanie, po co używać interfejsów? Skoro klasy dają mi to samo, a nawet więcej? No i właśnie brakuje mi tu tej istotnej informacji.

Bezużyteczny ten artykuł....

Funkcje te słuzą zliczaniu referencji do obiektów. Chodzi o to by wiedziec czy gdzies trzymasz obiekt przypisany do interfejsu. np

var
i : IFoo;
Foobar: TFooBar
begin
....
i:=Foobar; // Powoduje samoistne wywolanie _AddRef.
i:=nil; // Powoduje samoistne wywolanie _Release. To samo osiagnie sie przy wyjsciu z procedury (gdy zmienna i bedzie usuwana) !

Generalnie ma to sens gdy masz liste interfejsów na ktorej przechowyjesz obiekty implementujace dany interfejs. Wtedy jak skasujesz wszystkie referencje do danego obiektu to on znika. Przyjrzyj sie pliką nagłowkowym do blibiotek DLL np do DirectX. Obiekt jest tworzony rpzez funkcje lub metode, a Ty dostajesz do niego interface, a jak chcesz goz wolnic to poprostu przypisujesz nil. ;) To użyteczne i dość wygodne rozwiazanie :)

A jezeli chodzi o implemetacje tych metod to standardowo wkleja sie taki kod :
function TParent.QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
begin
if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE;
end;

function TParent._AddRef: Integer; stdcall;
begin
Result := InterlockedIncrement(FRefCount);
end;

function TParent._Release: Integer; stdcall;
begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then Destroy;
end;

lub za klase bazowa w hierarchi dziedziczenia przyjmuje TInterfacedObject.

Widzialem ze czesc osob nieco przerabia ta idee dodajac do constructora _addRef, a zamiast destructora uzywajac _Release dzieki czemu obiekt znika dopiero na rzadanie, a nie po usunieciu ostatniej referencji.

mam takie pytanie:
czemu sluza funkcje:
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
i co powinienem w nich napisac?