Problem bei der Übergabe eines Prozedurzeigers

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Kay
Beiträge: 134
Registriert: So 14. Nov 2010, 15:17

Problem bei der Übergabe eines Prozedurzeigers

Beitrag von Kay »

Hallo,

ich habe ein Problem bei der Übergabe eines Zeigers auf eine Prozedur. Das Problem tritt bei mir zwar bei der Einbindung der BASS Audiobibliothek auf, es gibt aber sicherlich auch viele andere Anwendungsfälle. Man muss die Library also nicht kennen, um mein Problem nachvollziehen zu können.
Ich habe zur besseren Verwendung eine TMediaPlayer-Klasse gebaut. Beim Öffnen einer neuen Audiodatei soll unter anderem EndSync gesetzt werden, ein Zeiger auf eine Prozedur, die immer beim Ende der Wiedergabe ausgeführt wird. Ich möchte dann ein eigenes Ereignis auslösen:

Code: Alles auswählen

TMPEndFileEvent = procedure(Sender: TObject) of object;
 
TMediaPlayer = class
private
  FHandle: DWORD;
  FOnEndFile: TMPEndFileEvent;
  procedure EndSync(Handle: HSYNC; Channel, Data: DWORD; User: Pointer); {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
public
  procedure Open;
  property OnEndFile: TMPEndFileEvent read FOnEndFile write FOnEndFile;
end;
 
{...}
 
procedure TMediaPlayer.Open;
begin
  BASS_ChannelSetSync(FHandle, BASS_SYNC_END, 0, @EndSync, nil);
end;
 
procedure TMediaPlayer.EndSync(Handle: HSYNC; Channel, Data: DWORD; User: Pointer); {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
begin
//  if Assigned(FOnEndFile) then FOnEndFile(Self);
end;
Wenn ich das Projekt nun kompiliere, erhalte ich folgende Fehlermeldung:
Error: Incompatible type for arg no. 4: Got "<procedure variable type of procedure(LongWord,LongWord,LongWord,Pointer) of object;StdCall>", expected "<procedure variable type of procedure(LongWord,LongWord,LongWord,Pointer);StdCall>"

Wenn ich die Prozedur nicht als Methode meiner Klasse sondern "alleinstehend" implementiere, dann könnte ich zwar ohne Fehler kompilieren, ich hätte dann aber keinen Zugriff mehr auf Attribute der TMediaPlayer-Instanz. Ich kann das Ereignis OnEndFile also dann nicht auslösen.
Kann man dieses Problem irgendwie beheben? Ich kenne mich bei solchen Zeigergeschichten nicht so gut aus...

Vielen Dank schonmal für eure Hilfe!

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1617
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Problem bei der Übergabe eines Prozedurzeigers

Beitrag von corpsman »

Das Problem ist das " of object;"

=> Zieh deine Procedure aus TMediaPlayer raus, dann müsste es gehn.
--
Just try it

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Problem bei der Übergabe eines Prozedurzeigers

Beitrag von mse »

Code: Alles auswählen

TMPEndFileEvent = procedure(Sender: TObject) of object;
 
TMediaPlayer = class
private
  FHandle: DWORD;
  FOnEndFile: TMPEndFileEvent;
  procedure EndSync(Handle: HSYNC; Channel, Data: DWORD); 
public
  procedure Open;
  property OnEndFile: TMPEndFileEvent read FOnEndFile write FOnEndFile;
end;
 
{...}
 
procedure callendsync((Handle: HSYNC; Channel, Data: DWORD; User: Pointer); 
                            {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
begin
 tmediaplayer(user).endsync(handle,channel,data);
end;
 
procedure TMediaPlayer.Open;
begin
  BASS_ChannelSetSync(FHandle, BASS_SYNC_END, 0, @callendsync, self);
end;
 
procedure TMediaPlayer.EndSync(Handle: HSYNC; Channel, Data: DWORD); 
begin
//  if Assigned(FOnEndFile) then FOnEndFile(Self);
end;
Ungeprüft! ;-)
Edit: tmediaplayer.endsync() Aufruf korrigiert.
Zuletzt geändert von mse am Mi 9. Apr 2014, 07:30, insgesamt 2-mal geändert.

Kay
Beiträge: 134
Registriert: So 14. Nov 2010, 15:17

Re: Problem bei der Übergabe eines Prozedurzeigers

Beitrag von Kay »

Hallo zusammen,

erstmal vielen Dank für eure Antworten.

@corpsman:
Naja, wie ich ja schon schrieb, kannst du zwar die Methode aus der Klasse ziehen, dann hast du aber logischerweise keinen Zugriff mehr auf deren Eigenschaften.

@mse:
Deine Lösung finde ich echt genial. Genauso hab ich mir das vorgestellt.
Es gibt nur ein kleines Problem: Die Methode CallEndSync wird ordnungsgemäß ausgeführt, der Zeiger gecastet und dann das Ereignis ausgelöst. Gleich danach tritt aber eine Accdess Violation auf und das Programm beendet sich komplett.
Woran kann das liegen? Muss ich vielleicht den Pointer noch irgendwie freigeben oder sowas?

VG, Kay

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Problem bei der Übergabe eines Prozedurzeigers

Beitrag von mse »

Wie sieht der stack trace zum Zeitpunkt der exception aus? Kompiliere doch mal mit -gh (heaptrace) um mehr Informationen über den Speicher zu erhalten. Ist die tmediaplayer Instanz zum Zeitpunkt des Aufrufes noch am Leben? Machst du in der aufgerufenen Methode etwas mir der BASS-Bibliothek?

Kay
Beiträge: 134
Registriert: So 14. Nov 2010, 15:17

Re: Problem bei der Übergabe eines Prozedurzeigers

Beitrag von Kay »

mse hat geschrieben:Ist die tmediaplayer Instanz zum Zeitpunkt des Aufrufes noch am Leben?
Ja, ich erzeuge sie in FormCreate und gebe sie erst bei FormDestroy wieder frei.
mse hat geschrieben:Machst du in der aufgerufenen Methode etwas mir der BASS-Bibliothek?
Nein, ich hab im Wesentlichen deinen Vorschlag übernommen. Eine kleinere Modifikation hatte ich vorgenommen, damit die Syntax stimmt, aber das dürfte eigentlich nicht ausschlaggebend sein.

Hier der aktuelle Schnipsel:

Code: Alles auswählen

type
  DWORD = LongWord;
  HSYNC = DWORD;
 
  TMediaPlayer = class
  private
    procedure EndSync(Handle: HSYNC; Channel, Data: DWORD);
  end;
 
{...}
 
procedure CallEndSync(Handle: HSYNC; Channel, Data: DWORD; User: Pointer); {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
begin
  TMediaPlayer(User).EndSync(Handle, Channel, Data);
end;
 
procedure TMediaPlayer.EndSync(Handle: HSYNC; Channel, Data: DWORD);
begin
  if Assigned(FOnEndFile) then FOnEndFile(Self);
end;
Ansonsten habe ich die Option -gh aktiviert, beim Programmende wird Folgendes ausgegeben:

Code: Alles auswählen

Heap dump by heaptrc unit
1892 memory blocks allocated : 1589265/1593480
1892 memory blocks freed     : 1589265/1593480
0 unfreed memory blocks      : 0
True heap size               : 688128 (80 used in System startup)
True free heap               : 688048
Ich gestehe allerdings gleich, dass mir das nicht wirklich viel sagt...

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Problem bei der Übergabe eines Prozedurzeigers

Beitrag von mse »

Wie sieht der stack trace zum Zeitpunkt der exception aus? In MSEide bekämst du eine Kopie in die Zwischenablage mit 'View'-'Stack'-RightClick-'Copy to Clipboard'. Was passiert wenn du " if Assigned(FOnEndFile) then FOnEndFile(Self);" auskommentierst? Was passiert wenn du " TMediaPlayer(User).EndSync(Handle, Channel, Data);" auskommentierst?

Kay
Beiträge: 134
Registriert: So 14. Nov 2010, 15:17

Re: Problem bei der Übergabe eines Prozedurzeigers

Beitrag von Kay »

Hallo mse,

hier der Stack Trace:

Code: Alles auswählen

  $00430F0E  TMEDIAPLAYER__ENDSYNC,  line 1184 of uplayer.pas
  $00430ED4  CALLENDSYNC,  line 1178 of uplayer.pas
  $11020676
  $7C80B713
In beiden Fällen des Auskommentierens wird das Ereignis OnEndFile natürlich nicht ausgelöst, es tritt aber auch kein Fehler auf, d.h. das Programm läuft dann nachwievor und ich kann weitere Buttons auf der Oberfläche klicken.

Ich weiß nicht, ob dir das irgendwie weiterhilft, aber hier ist mal noch der Code zum Setzen bzw. Behandeln des Ereignisses:

Code: Alles auswählen

type
  TForm1 = class(TForm)
    Memo1: TMemo;
  private
    MediaPlayer1: TMediaPlayer;
    procedure MediaPlayer1EndFile(Sender: TObject);
  end;
 
{...}
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  MediaPlayer1 := TMediaPlayer.Create;
  MediaPlayer1.OnEndFile := @MediaPlayer1EndFile;
end;
 
procedure TForm1.MediaPlayer1EndFile(Sender: TObject);
begin
  Memo1.Text := 'OnEndFile';
end;
Vielen Dank für deine Hilfe.

VG, Kay

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Problem bei der Übergabe eines Prozedurzeigers

Beitrag von mse »

Kay hat geschrieben:

Code: Alles auswählen

  $00430F0E  TMEDIAPLAYER__ENDSYNC,  line 1184 of uplayer.pas
  $00430ED4  CALLENDSYNC,  line 1178 of uplayer.pas
  $11020676
  $7C80B713
Daraus sehen wir, dass tmediaplayer.endsync() tatsächlich aufgerufen wird und der Fehler in Zeile 1184 ausgelöst wird. Wie lautet denn die exakte Fehlermeldung? Zeile 1184 ist vermutlich " if Assigned(FOnEndFile) then FOnEndFile(Self);"? Setze doch mal einen Haltepunkt auf uplayer.pas:1178 und versuche mit F7 in fonendfile hineinzusteppen.
In beiden Fällen des Auskommentierens wird das Ereignis OnEndFile natürlich nicht ausgelöst, es tritt aber auch kein Fehler auf, d.h. das Programm läuft dann nachwievor und ich kann weitere Buttons auf der Oberfläche klicken.
Das Konstrukt scheint also bis zum Aufruf von fonendfile grundsätzlich zu funktionieren.

Code: Alles auswählen

type
  TForm1 = class(TForm)
    Memo1: TMemo;
  private
    MediaPlayer1: TMediaPlayer;
    procedure MediaPlayer1EndFile(Sender: TObject);
  end;
 
{...}
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  MediaPlayer1 := TMediaPlayer.Create;
  MediaPlayer1.OnEndFile := @MediaPlayer1EndFile;
end;
 
procedure TForm1.MediaPlayer1EndFile(Sender: TObject);
begin
  Memo1.Text := 'OnEndFile';
end;
Versuch: Kommentiere "Memo1.Text := 'OnEndFile';" aus wenn es danach keinen Absturz mehr gibt, klappt auch der Aufruf des eventhandlers. Der stack trace zeigt den Aufruf von MediaPlayer1EndFile zwar nicht, der stack trace ist aber nicht immer 100% zuverlässig. Wenn's in " Memo1.Text := 'OnEndFile';" knallt, gibt es wieder mehrere Möglichkeiten. Auf Spekulationen lasse ich mich erst ein, wenn die Bestätigung vorliegt. ;-)

Kay
Beiträge: 134
Registriert: So 14. Nov 2010, 15:17

Re: Problem bei der Übergabe eines Prozedurzeigers

Beitrag von Kay »

Hallo mse,

ja genau, die Bedingung " if Assigned(...) " ist die Zeile 1184.

Ich wollte gerade wie von dir vorgeschlagen den Haltepunkt auf 1178 " TMediaPlayer(User).EndSync " setzen.
Aber was müsste ich da als Bedingung angeben? Wenn ich das Feld frei lasse, dann erhalte ich beim Steppen lediglich den Hinweis " Warning: Cannot insert a breakpoint -243. "

Ansonsten bin ich jetzt echt komplett verwirrt. Ich habe Memo1.Text auskommentiert und es läuft alles absolut fehlerfrei.
Sobald ich die Zeile wieder aktiviere, tritt die Exception auf. Leider wird mir hier nur " Access Violation " mitgeteilt, sonst keine weitere Meldung. Wenn ich OK klicke, dann schließt sich das Programm einfach.

VG, Kay

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Problem bei der Übergabe eines Prozedurzeigers

Beitrag von mse »

Kay hat geschrieben: Ich wollte gerade wie von dir vorgeschlagen den Haltepunkt auf 1178 " TMediaPlayer(User).EndSync " setzen.
Aber was müsste ich da als Bedingung angeben? Wenn ich das Feld frei lasse, dann erhalte ich beim Steppen lediglich den Hinweis " Warning: Cannot insert a breakpoint -243. "
Da müssen wir Martin Friebe fragen. Martin?
Ansonsten bin ich jetzt echt komplett verwirrt. Ich habe Memo1.Text auskommentiert und es läuft alles absolut fehlerfrei.
Das ist die Bestätigung, dass der Aufrufmechanismus grundsätzlich funktioniert, siehe unten.
Sobald ich die Zeile wieder aktiviere, tritt die Exception auf. Leider wird mir hier nur " Access Violation " mitgeteilt, sonst keine weitere Meldung. Wenn ich OK klicke, dann schließt sich das Programm einfach.
In der Lazarus-IDE solltest du doch den stack trace bei der Exception anschauen können? In MSEide ginge das durch Aktivierung von 'Projekt'-'Options'-'Debugger'-'Stop on Exception'.

Zur Erklärung des Absturzes in "Memo1.Text := 'OnEndFile';" gibt es verschiedene Möglichkeiten. Ist memo1 zum Absturzzeitpunkt noch am Leben? Setze einen breakpoint in "Memo1.Text := 'OnEndFile';" und kompiliere mit -gh (heaptrace), wenn das Programm am Haltepunkt steht, schau dir im watches-Fenster memo1 und seine Eigenschaften an, Sehen sie OK aus oder gibt es "0xdeadbeef" oder "0xf0f0f0f0" Bereiche? Das wäre ein Hinweis darauf, dass memo1 gestorben ist.
Eine Andere Möglichkeit ist, dass das Ereignis nicht aus dem Haupt-thread heraus aufgerufen wird. Schau mal beim Stopp am Haltepunkt in das threads-Fenster, gibt es dort mehrere threads? Wenn es mehrere sind, schalte zwischen den threads um und schau den stack an, versuche herauszufinden, ob der Aufruf aus einem worker thread stattfindet. Vermutlich ja schon. In MSEgui würde man in so einem Fall schreiben:

Code: Alles auswählen

 
application.lock;
Memo1.Text := 'OnEndFile'
application.unlock;
 
oder endsync() ändern zu

Code: Alles auswählen

 
procedure TMediaPlayer.EndSync(Handle: HSYNC; Channel, Data: DWORD);
begin
  application.lock;
  try
    if Assigned(FOnEndFile) then begin
      FOnEndFile(Self);
    end;
  finally
    application.unlock;
  end;
end;
 
in Lazarus müsstest du vermutlich stattdessen synchronize() oder queueasynccall() benutzen.

Kay
Beiträge: 134
Registriert: So 14. Nov 2010, 15:17

Re: Problem bei der Übergabe eines Prozedurzeigers

Beitrag von Kay »

Hallo mse,

hat jetzt etwas länger gedauert, da ich erstmal die Zeit zum Programmieren finden musste.
Ich hab das Problem mittlerweile gelöst. So geht's:

Code: Alles auswählen

type
  TMediaPlayer = class
  private
    FHandle: LongWord;
    FOnEndFile: TMPEndFileEvent;
    procedure EndSync;
  public
    procedure Open;
    property OnEndFile: TMPEndFileEvent read FOnEndFile write FOnEndFile;
  end;
 
{...}
 
procedure CallEndSync(Handle: HSYNC; Channel, Data: DWORD; User: Pointer); {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
begin
  TMediaPlayer(User).EndSync;
end;
 
procedure TMediaPlayer.EndSync;
begin
  if Assigned(FOnEndFile) then FOnEndFile(Self);
end;
 
procedure TMediaPlayer.Open;
begin
  BASS_ChannelSetSync(FHandle, BASS_SYNC_END, 0, @CallEndSync, Self);
end;
Ich hab allerdings keine Ahnung, wieso diese Variante funktioniert, während die andere einen Fehler verursacht. Ich hab's ehrlich gesagt auch nur durch rumprobieren rausgefunden. Wahrscheinlich haut irgendwas bei der Parameterübergabe nicht hin. Auf jeden Fall funktioniert das Ganze aber jetzt einwandfrei.

Ich dank dir nochmal für deine Hilfe, dein Vorschlag war ja quasi die Basis!

VG, Kay

Antworten