Entwickler-Ecke

Windows API - Objektfunktion als Callback


Martok - So 04.03.07 19:07
Titel: Objektfunktion als Callback
Hallöle,

Das Problem kennt ihr bestimmt alle, und ich kann mich auch erinnern, dass Jemand mal ne Lösung dazu hatte. Ich weiß bloß nicht mehr, wer... :roll:

Und zwar habe ich eine Callbackfunktion (nicht WinAPI, sondern SDL, aber egal), die in einer Klasse liegt.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
function TGame.glTimer1( interval: UInt32; param: Pointer ): UInt32;
begin
  SDL_WM_SetCaption(pChar( WINDOWS_CAPTION + ' ['+IntToStr(Round(FPSCount * 1000/FPS_INTERVAL))+' FPS]') , nil);
  FPSCount := 0;
  Result := interval;
end;

procedure TGame.Init_User;
begin
  SDL_AddTimer(1000,@glTimer1,nil);
end;

Das geht logischerweise nicht, weil glTimer1 eine function of object ist, der erwarte Parameter aber eine function.

Irgendeinen Trick gabs da, wie war der noch gleich?

Danke,
Sebastian


HelgeLange - So 04.03.07 19:14

einfach nicht als Objekt-Funktion deklarieren. Object-Funktionen benutzen 8 byte statt 4, deswegen können sie nicht Zuweisungs-Kompatibel sein. Und die zusätzlichen 4 Byte braucht er für den Offset der Objekt-Daten :P


Martok - So 04.03.07 19:22

Stimmt ja, da war ja noch der Self-Pointer mit drin...
user profile iconHelgeLange hat folgendes geschrieben:
einfach nicht als Objekt-Funktion deklarieren.

Ja gut, aber wie soll ich denn OOP ohne O(bjekte) machen? :gruebel:

Das Problem ist ja, dass ich mir einen OOP-Wrapper um das SDL bauen möchte, diese Prozedurale gefällt mir nicht wirklich. Wer mich kennt weiß, bei mir wird alles ein Objekt... ;)
OK, im Prinzip habe ich nur eine Instanz dieser Klasse, sie ist die Game-Klasse. Aber trotzdem würde das alles kaputt machen.

Kann es sein, dass der Trick von Luckie war? Ach nee, Mist. Das war das, wie man einen TTimer in einer KonsolenApp benutzen kann. So mit TMethod und so. Also genau andersrum...


Christian S. - So 04.03.07 19:32

Hallo!

Wenn ich das richtig sehe, erhält SDL_AddTimer einen Pointer als dritten Parameter. Damit müsste es doch möglich sein, dass so zu machen (oder so ähnlich, evtl. noch ein bisschen referenzieren und dereferenzieren ;-)):


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
function glTimer1( interval: UInt32; param: Pointer ): UInt32;
var
  selfGame : TGame;

begin
  selfGame := TGame(Param);
  SDL_WM_SetCaption(pChar( WINDOWS_CAPTION + ' ['+IntToStr(Round(FPSCount * 1000/FPS_INTERVAL))+' FPS]') , nil);
  FPSCount := 0;
  Result := interval;
end;

procedure TGame.Init_User;
begin
  SDL_AddTimer(1000,@glTimer1,self);
end;


Grüße
Christian

P.S.: Anstatt einer globalen Prozedur kannst Du ab Delphi 2005 auch eine Klassenmethode nehmen, welche als "static" deklariert wird. Die hat dann keinen versteckten self-Parameter und dürfte als Callback-Methode verwendbar sein.

//edit: Oh, zumindest in 2005 sind klassenstatische Methoden nur in Delphi .NET drin. Vielleicht gibt's die in Delphi 2006 auch bei Win32?


HelgeLange - So 04.03.07 19:38

ok, dann erkläre ich es mal für Objekt-Bastler, die einfach die Funktionsweise nicht kennen :

Wenn man eine Instanz von einem Objekt in Delphi erzeugt, wird nicht alles vom Objekt kopiert und basta, sondern es wird nur Speicher für die Daten des Objekts und seiner Vorfahren erzeugt und der Objekt-Zeiger ist ein Zeiger auf den Anfang dieser Daten. Funktionen und Proceduren werden nicht innerhalb dieses Speichers zu finden sein, diese liegen nur 1x im Speicher, ruft man sie auf, werden entsprechend neben den "normalen" parametern die objekt-Instanz mitgegeben (also der Zeiger auf den Start der Daten). Dadurch weiss das Objekt, auf welchen Datenbereich er zugreifen muss, damit er die Daten für das augenblickliche Objekt auch lesen/schreiben kann.

Technisch gesehen kannst Du daher folgendes machen in Delphi :


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
type TMyClass = class(TComponent)
       FHallo: String;
     public
       procedure HalloWelt;
     end;

procedure ZeigeHalloWelt;
begin 
  TMyClass.HalloWelt;
end;

procedure TMyClass.HalloWelt;
begin
  ShowMessage('Hallo Welt');
end;


Und das liegt daran, dass Du nicht auf Daten des Objects zugreifst.
Was aber nicht geht :


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
type TMyClass = class(TComponent)
       FHallo: String;
     public
       procedure HalloWelt;
     end;

procedure ZeigeHalloWelt;
begin 
  TMyClass.HalloWelt;
end;

procedure TMyClass.HalloWelt;
begin
  FHallo := 'Hallo Welt';
  ShowMessage(FHallo);
end;



So, zu Deinem Problem.. entweder findest Du eine Möglichkeit, die Object-Instanz mit zugeben oder Pech gehabt :P


Martok - So 04.03.07 19:45

Danke für die Erklärungen, Helge ;)
Dein letzter Satz ist gut ;)

@Christian: von Wäldern und Bäumen.....
Ganz genau so gehts. Könnte sogar sein, dass dieser Parameter für sowas gedacht ist...
Funktioniert jedenfalls 1a mit Übergabe über param.

Schon die 2. Community in den Danksagungen...


HelgeLange - So 04.03.07 19:51

Ich hatte mir kurzzeitig überlegt, ob ich Dir das mit dem parameter verticke, den Du im Ursprung als nil mitgegeben hast, aber ich dachte mir, dass Du nach meiner Erklärung auch selber drauf kommen kannst. über selbst gefundene Sachen freut man sich einfach mehr und vergisst sie ned so schnell :P


Martok - Mo 05.03.07 00:07

Also jetzt wo ich weiß, was du damit erreichen wolltest, sehe ich die Hinweise auch... :zwinker:

Selbst nicht gefundene Sachen vergisst man aber auch nicht so schnell, jedenfalls sowas einfaches...
Wo ich doch das sogar vor ein paar Monaten mit irgend einer EnumWindows oder ähnlichem gemacht habe. :autsch: