Callback-Funktionen


Eine Callback-Funktion ist eine Funktion, die als Prototyp von einer anderen Funktion definiert wird. Diese andere Funktion kann man dann mit einem Funktionszeiger aufrufen, der auf eine Funktion zeigt, die diesem Prototypen entspricht und dann unsere Funktion aufruft. Das klingt etwas verwirrend, ist es aber eigentlich nicht, wenn man das Prinzip einmal verstanden hat. Am besten wird das in einem Beispiel deutlich:

type
  TCallbackProc = function(i: Integer): Boolean; stdcall;

procedure DoSomething(CallbackProc: TCallbackProc); stdcall;
var
  i: Integer;
begin
  if Assigned(CallbackProc) then
  begin
    for i := 0 to 9 do
      if not CallBackProc(i) then
        break;
  end;
end;

Sei DoSomething unsere Funktion, die einen Funktionszeiger als Parameter erwartet. Dort wird eine Variable CallbackProc vom Typ function(i: Integer): Boolean; stdcall; deklariert. Dieser Varaiablen wird dann ein Funktionszeiger auf eine von uns geschriebene Funktion übergeben. In DoSomething wird dann die deklarierte Funktion CallBackProc aufgerufen. Da wir der Variablen vorher den Funktionszeiger von der von uns geschriebenen Funktion zugewiesen haben, zeigt sie auf unsere Funktion, die dann entsprechend aufgerufen und ausgeführt wird. Dabei muss man natürlich beachten, dass die von uns geschriebene Funktion dem Funktionsprototypen enstspricht, der in der Funktion DoSomething deklariert wurde, sonst funktioniert es nicht.

Unsere passende Callback-Funktion könnte jetzt so aussehen:

function CallBack(i: Integer): Boolean; stdcall;
begin
  result := True;
  ShowMessage(IntToStr(i));
  if i = 5 then
    result := False;
end;

Und der passenden Funktionsaufruf von dem ganzen:

procedure TForm1.Button1Click(Sender: TObject);
begin
  DoSomething(@Callback);
end;

Wie man sieht, entspricht sie dem Funktionsprototypen, der in DoSomething definiert wurde. Wenn wir jetzt DoSomething aufrufen, wird unsere Funktion mit dem Parameter i aufgerufen. In unserer Callback-Funktion können wir jetzt i auswerten. In unserem Beispiel gibt Callback False zurück, wenn i den Wert fünf erreicht. In der Funktion DoSomething wird nun der Rückgabewert ausgewertet und entsprechend darauf reagiert. In diesem Fall wird die Schleife frühzeitig verlassen. Das heißt, in einer gewissen Weise können wir mit unserer Callback-Funktion, die aufrufende Funktion steuern, insofern es eben vorgesehen ist.

Wo werden jetzt Callback-Funktionen eingesetzt bzw. in welchen Fällen sind sie nützlich? Es gibt drei konkrete Anwendungsfälle: Wenn ...

  1. gegenseitige Abhängigkeiten in allgemeinen APIs aufgelöst werden müssen
    (DoSomething muss nichts über das benutzende Programm wissen)
  2. der Fortschritt beeinflusst werden kann oder visuelle dargestellt werden soll
    (insbesondere bei Aufzählungen)
  3. man Entwickler mit unnötiger Abstraktion ärgern will (Zitat: Nico Bendlin)

Zum Beispiel haben wir eine Funktion, die alle Benutzerkonten auf einem Rechner ausliest. Die Benutzer könnte man dann am Ende alle zusammen entweder in einem dynamischen Array zurückgeben oder aber man nutzt die Technik der Callback-Funktionen, um jeden Benutzer einzeln zurückzugeben. Die Callback-Funktion wäre hier die für den Anwender flexiblere Lösung, da er sagen kann "Drei Benutzer reichen mir, ich will ich abbrechen, den Rest brauche ich nicht mehr". Bei einem dynamischen Array wäre dies nicht möglich. Allgemein bieten sie sich also bei Aufzählungen an. So werden sie auch zum Beispiel von Windows-Funktionen genutzt. Es sei nur mal als ein Beispiel von vielen EnumWindows [1] aufgeführt. EnumWindows ermittelt alle Fensterhandle und gibt sie mit Hilfe einer Callback-Funktion an den Aufrufer zurück. Oder die API-Funktion CopyFileEx [2], welches eine Callback-Funktion nutzt, um den Fortschritt beim Kopieren einer Datei zurückzugeben bzw. dem Aufrufer die Möglichkeit bietet den Kopiervorgang abzubrechen.

Übrigens, Ereignisse, wie man sie aus der objektorientierten Programmierung kennt, sind im Endeffekt auch nichts anderes als Callback-Funktionen. In Delphi übergibt man die Adresse einer Methode und die VCL bzw. Klasse, welche das Ereignis implementiert, ruft sie dann auf. Eben wie es mit unserer Callback-Funktion geschiet. Eine alte Technik im neuen Gewand sozusagen.

Links

[1] http://msdn.microsoft.com/library/default.asp?url=/librar y/en-us/winui/winui/windowsuserinterface/windowing/windows/windowreference/windowfunctions/enumwindows.asp
[2] http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/copyfileex.asp


2010-12-29T23:44:37 +0100, mail+homepage[at]michael-puff.de